3. 惯性导航基础(下):姿态表示与捷联惯导更新算法

各位同学,咱们接着聊惯性导航。上一节我们把陀螺和加速度计的原理讲透了,这一节重点说说姿态怎么表示、怎么算。说白了,惯导系统最核心的问题就两个:我朝哪看?我往哪走?前者是姿态,后者是位置。今天先啃姿态这块硬骨头。

3.1 姿态表示:三种主流方法

姿态表示,说白了就是描述一个坐标系相对于另一个坐标系的旋转关系。工程上常用的有三种:欧拉角、旋转矩阵、四元数。我当年刚入行时,觉得这三种方法随便选一个就行,后来踩了坑才明白——选错了表示方法,代码里全是坑

3.1.1 欧拉角

欧拉角最直观。它用三个角度描述旋转:横滚角(Roll)、俯仰角(Pitch)、航向角(Yaw)。你想想看,飞机转弯时先转航向,再抬头,最后侧倾——这就是欧拉角的物理含义。

定义:

  • 横滚角 φ:绕X轴旋转
  • 俯仰角 θ:绕Y轴旋转
  • 航向角 ψ:绕Z轴旋转

旋转顺序:Z→Y→X(工程中最常用)

欧拉角有个致命问题——万向锁。当俯仰角接近±90°时,横滚和航向会耦合,丢失一个自由度。我在做车载惯导时遇到过这问题:车辆在陡坡上,俯仰角接近90°,姿态解算直接炸了。嗯,从那以后我再也不敢在车载场景里只用欧拉角了。

3.1.2 旋转矩阵

旋转矩阵用3×3的正交矩阵表示旋转。它没有万向锁问题,但参数多(9个),计算量大。

// 旋转矩阵示例:绕Z轴旋转ψ角
R_z(ψ) = [cosψ  -sinψ  0]
         [sinψ   cosψ  0]
         [0      0     1]

// 完整旋转矩阵(Z→Y→X顺序)
R = R_x(φ) * R_y(θ) * R_z(ψ)

旋转矩阵有个好处:可以链式相乘。你连续旋转两次,直接把两个矩阵乘起来就行。但要注意,矩阵乘法不满足交换律——先转航向再转俯仰,和先转俯仰再转航向,结果完全不同。

注意:旋转矩阵必须是正交矩阵,且行列式为+1。如果数值误差导致矩阵不正交,必须做正交化处理。我见过有人直接拿带误差的矩阵算,结果姿态越偏越远。

3.1.3 四元数

四元数是我个人最推荐的方法。它用四个参数表示旋转:一个实部加三个虚部。没有万向锁,计算量小,插值平滑。

// 四元数定义
q = [q0, q1, q2, q3]^T
其中 q0 = cos(θ/2)
     q1 = e_x * sin(θ/2)
     q2 = e_y * sin(θ/2)
     q3 = e_z * sin(θ/2)
// e = [e_x, e_y, e_z]^T 是旋转轴单位向量
// θ 是旋转角度

四元数更新公式也很简洁:

// 四元数微分方程
dq/dt = 0.5 * q ⊗ ω
// 其中 ω 是角速度,⊗ 表示四元数乘法

// 离散更新(一阶近似)
q_{k+1} = q_k + dq/dt * Δt

我的习惯:车载项目里统一用四元数做姿态解算,只在输出给上层应用时转成欧拉角。这样既避免了万向锁,又方便调试人员理解。

3.2 三种方法的对比

特性 欧拉角 旋转矩阵 四元数
参数数量 3 9 4
万向锁
计算量
插值平滑性
工程推荐度 低(仅限小角度)

3.3 捷联惯导更新算法

捷联惯导的核心思想:陀螺测角速度,加速度计测比力,然后积分得到姿态、速度、位置。说白了就是不断累加。

3.3.1 姿态更新

姿态更新是惯导里最关键的环节。我用四元数法给大家演示:

// 姿态更新伪代码
void updateAttitude(Quaternion &q, Vector3 gyro, float dt) {
    // 1. 计算角增量
    Vector3 delta_theta = gyro * dt;
    
    // 2. 构造四元数增量
    float theta = norm(delta_theta);
    if (theta < 1e-10) return;  // 无旋转
    
    Vector3 axis = delta_theta / theta;
    Quaternion dq;
    dq.w = cos(theta / 2);
    dq.x = axis.x * sin(theta / 2);
    dq.y = axis.y * sin(theta / 2);
    dq.z = axis.z * sin(theta / 2);
    
    // 3. 更新四元数
    q = q * dq;
    
    // 4. 归一化(防止数值误差)
    q = normalize(q);
}

关键点:

  • 每次更新后必须归一化四元数
  • 角增量法比直接积分角速度更精确
  • 高动态场景要用二阶或四阶龙格-库塔法

3.3.2 速度更新

速度更新需要扣除重力:

// 速度更新
Vector3 updateVelocity(Vector3 vel, Vector3 acc, Quaternion q, float dt) {
    // 1. 将加速度从载体坐标系转到导航坐标系
    Vector3 acc_nav = q.rotate(acc);
    
    // 2. 扣除重力
    acc_nav.z -= GRAVITY;
    
    // 3. 积分
    vel += acc_nav * dt;
    
    return vel;
}

这里有个坑:重力补偿必须在导航坐标系下做。我见过有人直接在载体坐标系里减重力,结果水平加速度全错了。

3.3.3 位置更新

位置更新最简单,直接对速度积分:

// 位置更新
Vector3 updatePosition(Vector3 pos, Vector3 vel, float dt) {
    pos += vel * dt;
    return pos;
}

但要注意,如果车辆在转弯,速度方向变化很快,用一阶积分误差会很大。这时候建议用梯形法或龙格-库塔法。

3.4 知识体系总览

下面这张图总结了本章的核心逻辑:

捷联惯导姿态解算知识体系 陀螺仪 (角速度 ω) 加速度计 (比力 f) 姿态表示方法 欧拉角 (Roll/Pitch/Yaw) 旋转矩阵 (3×3) 四元数 (q0,q1,q2,q3) 捷联惯导更新算法 姿态更新 (四元数微分) 速度更新 (比力积分) 位置更新 (速度积分) 核心:陀螺测姿态变化,加速度计测速度变化,积分得到位置

3.5 工程实践中的避坑指南

最后分享几个我踩过的坑:

我曾经犯过的错:

  • 四元数忘记归一化:跑了几分钟,姿态漂移了10度。后来加了归一化,问题解决。
  • 欧拉角直接做插值:车辆转弯时,航向角从359°变到0°,插值结果跑了180°。改用四元数球面插值(Slerp)后正常。
  • 重力补偿时机不对:在载体坐标系减重力,导致水平加速度有偏差。必须在导航坐标系下补偿。

我的建议:

  • 新项目起步时,先用四元数做姿态解算
  • 调试阶段,把四元数转成欧拉角打印出来看
  • 高动态场景(急转弯、急加速),用二阶龙格-库塔法更新
  • 定期做零速修正(ZUPT),抑制积分漂移

好了,姿态表示和捷联惯导更新算法就讲到这里。这些内容是惯导的基石,理解透了后面才能玩转组合导航。大家动手写代码时,建议先从四元数更新开始,跑通后再加加速度和位置更新。


专注资料整理