四元数运动学方程:从微分到工程实现

好,我们进入第四章。这一章,说白了就是回答一个问题:陀螺仪测出了角速度,怎么用它来更新四元数?

我在做第一个飞控项目时,就卡在这里。角速度数据哗哗地来,但姿态就是飘得离谱。后来才发现,是四元数更新的算法没选对。嗯,今天我们就把它彻底讲透。

4.1 四元数微分方程的推导

先看数学基础。四元数描述的是旋转,而旋转是随时间变化的。我们要找的就是这个变化率——四元数对时间的导数

假设一个刚体在旋转,它的姿态用四元数 q 表示。旋转角速度是 ω。那么,四元数随时间的变化满足:

dq/dt = 0.5 * q ⊗ ω

这里的 ⊗ 是四元数乘法。ω 要写成纯四元数形式:ω = [0, ωx, ωy, ωz]。

为什么会这样?我简单推导一下。

四元数旋转公式是:q(t) = q(0) ⊗ exp(0.5 * ω * t)。对时间求导,自然就得到上面的微分方程。你想想看,这和复数的指数旋转其实是一个道理,只是从二维扩展到了四维。

核心公式:四元数运动学方程

dq/dt = 0.5 * Ω(ω) * q

其中 Ω(ω) 是角速度的矩阵形式:

Ω(ω) = [ 0   -ωx  -ωy  -ωz
          ωx   0    ωz  -ωy
          ωy  -ωz   0    ωx
          ωz   ωy  -ωx   0  ]

4.2 角速度与四元数更新的关系

有了微分方程,下一步就是离散化。陀螺仪输出的是离散的角速度采样,我们要用这些采样来更新四元数。

这里有个关键点:角速度是瞬时量,而四元数更新是积分过程。说白了,就是用一小段时间内的角速度,去推算姿态的变化量。

我习惯把这个问题分成两步看:

  1. 角速度积分:把角速度乘上时间步长 Δt,得到角度增量 Δθ
  2. 四元数更新:用角度增量去旋转当前的四元数

数学上,离散更新公式是:

q_{k+1} = q_k ⊗ Δq(Δθ)

其中 Δq(Δθ) 是角度增量对应的四元数:

Δq = [cos(|Δθ|/2), (Δθ/|Δθ|) * sin(|Δθ|/2)]

我的经验:在实际项目中,Δt 通常取陀螺仪的采样周期。比如 100Hz 的陀螺仪,Δt = 0.01s。但要注意,如果角速度变化剧烈,这个 Δt 可能不够小,需要做插值或使用更高阶的积分方法。

4.3 毕卡逼近法求解

直接解四元数微分方程,其实有解析解。但工程上我们更常用毕卡逼近法(Picard iteration)。

毕卡法的思路很简单:把微分方程写成积分形式,然后迭代逼近。对于四元数微分方程:

q(t) = q(0) + ∫₀ᵗ 0.5 * Ω(ω) * q(τ) dτ

第一次近似:q₁(t) = q(0) + 0.5 * Ω(ω) * q(0) * t

第二次近似:q₂(t) = q(0) + 0.5 * Ω(ω) * q₁(t) * t

... 以此类推

在实际工程中,我们通常只取前两项或前三项。为什么?因为高阶项对精度提升有限,但计算量翻倍。我做过测试,对于大多数无人机应用,二阶毕卡逼近已经足够

二阶毕卡逼近公式

q_{k+1} = [I * (1 - |Δθ|²/8) + 0.5 * Ω(Δθ)] * q_k

其中 Δθ = ω * Δt,I 是 4x4 单位矩阵。

4.4 工程实现中的坑与对策

讲完理论,说说我踩过的坑。

我曾经犯过的错

  • 忘记归一化:四元数更新后,由于数值误差,模长会偏离1。如果不做归一化,姿态会慢慢漂移。我有个项目因此飞了30秒就翻了。
  • 角速度单位搞错:陀螺仪输出通常是 deg/s,但公式里要用 rad/s。这个坑我至少见过三次。
  • 时间步长不匹配:如果陀螺仪采样不稳定,直接用固定 Δt 会引入误差。建议用硬件定时器或时间戳。

下面是我常用的 C 代码实现,你可以参考:

// 四元数更新函数(二阶毕卡法)
void quaternion_update(quat_t *q, float gx, float gy, float gz, float dt) {
    // 角速度转弧度,计算角度增量
    float dtheta_x = gx * DEG2RAD * dt;
    float dtheta_y = gy * DEG2RAD * dt;
    float dtheta_z = gz * DEG2RAD * dt;
    
    float dtheta_sq = dtheta_x*dtheta_x + dtheta_y*dtheta_y + dtheta_z*dtheta_z;
    
    // 二阶毕卡逼近
    float q0 = q->w;
    float q1 = q->x;
    float q2 = q->y;
    float q3 = q->z;
    
    float half_dt = 0.5f * dt;
    float one_minus_theta_sq_8 = 1.0f - dtheta_sq / 8.0f;
    
    q->w = one_minus_theta_sq_8 * q0 + (-dtheta_x*q1 - dtheta_y*q2 - dtheta_z*q3) * half_dt;
    q->x = one_minus_theta_sq_8 * q1 + ( dtheta_x*q0 - dtheta_y*q3 + dtheta_z*q2) * half_dt;
    q->y = one_minus_theta_sq_8 * q2 + ( dtheta_y*q0 + dtheta_x*q3 - dtheta_z*q1) * half_dt;
    q->z = one_minus_theta_sq_8 * q3 + ( dtheta_z*q0 - dtheta_x*q2 + dtheta_y*q1) * half_dt;
    
    // 归一化
    float norm = sqrt(q->w*q->w + q->x*q->x + q->y*q->y + q->z*q->z);
    q->w /= norm;
    q->x /= norm;
    q->y /= norm;
    q->z /= norm;
}

我个人的建议:在嵌入式平台上,如果计算资源紧张,可以用一阶毕卡法(只取第一项)。虽然精度稍差,但胜在速度快。对于大多数消费级产品,一阶已经够用。

4.5 本章知识体系

下面这张图,是我梳理的本章核心逻辑。你看一遍,应该就能把整个流程串起来。

四元数运动学方程知识体系 陀螺仪角速度 ω 四元数微分方程 dq/dt = 0.5·q⊗ω 离散化更新 q_{k+1} = q_k ⊗ Δq 求解方法:毕卡逼近法 一阶毕卡(快速) 二阶毕卡(推荐) 高阶毕卡(高精度) 输出:更新后的四元数 q_{k+1}

嗯,这一章的内容就到这里。四元数微分方程是姿态解算的发动机,毕卡逼近法就是那个点火开关。你把这个逻辑理清了,后面的卡尔曼滤波、互补滤波学起来会轻松很多。


公众号:蓝海资料掘金营,微信deep3321