4. 捷联惯导解算(INS):姿态更新算法(四元数微分方程)、速度更新算法(比力方程)、位置更新算法、圆锥效应与划船效应补偿

各位同学,欢迎来到实战案例的第四讲。今天我们要啃的这块骨头,是INS解算的核心——姿态、速度、位置的更新。说白了,就是怎么用IMU测出来的角速度和加速度,算出载体每一刻到底在哪儿、面朝哪边、跑得多快。

我个人习惯把INS解算比作「积分游戏」。你想想看,陀螺给角速度,积分得角度;加速度计给比力,积分得速度,再积分得位置。听起来简单?嗯,这里要注意,实际工程里全是坑。我当年第一次调车规级IMU,就因为在积分算法上偷了个懒,结果车在模拟器上原地转圈,定位误差直接飞了。

4.1 姿态更新算法:四元数微分方程

姿态更新是INS的「脊梁骨」。姿态算错了,速度和位置全完蛋。目前工程界最常用的姿态表达方式就是四元数。为什么不用欧拉角?因为欧拉角有万向锁问题,而且三角函数计算量大。四元数呢?紧凑、无奇点、适合计算机迭代。

四元数微分方程长这样:

dq/dt = 0.5 * q ⊗ ω

其中ω是载体坐标系下的角速度,q是当前姿态四元数。⊗表示四元数乘法。

实际工程中,我们没法做连续积分,只能用离散化方法。最常用的就是毕卡逼近法(一阶或二阶)。我建议初学者先掌握一阶毕卡法,代码简单,理解起来也直观:

// 一阶毕卡法姿态更新
void attitudeUpdate(Quaternion *q, const Vector3 *gyro, float dt) {
    float wx = gyro->x * dt * 0.5f;
    float wy = gyro->y * dt * 0.5f;
    float wz = gyro->z * dt * 0.5f;
    
    Quaternion dq = {1.0f, wx, wy, wz};
    *q = quaternionMultiply(*q, dq);
    quaternionNormalize(q);  // 归一化,防止误差累积
}

我的经验:归一化这一步千万别省!我曾经在一个项目里为了省几个CPU周期跳过了归一化,结果跑了10分钟后四元数模长漂到了1.05,姿态误差直接导致定位失效。从那以后,我每次更新完姿态必做归一化,哪怕用快速近似算法也要做。

4.2 速度更新算法:比力方程

速度更新依赖的是比力方程。加速度计测的是「比力」,不是真正的加速度。比力包含了重力加速度和运动加速度。所以我们要把重力分量去掉,才能得到真实的运动加速度。

比力方程在导航坐标系下的形式:

dV/dt = C_b^n * f_b - (2ω_ie + ω_en) × V + g_n

看着复杂?我拆开给你看:

  • C_b^n * f_b:把加速度计测的比力从载体坐标系转到导航坐标系
  • -(2ω_ie + ω_en) × V:哥里奥利力和向心力的补偿项
  • g_n:重力加速度(已知的模型值)

实际代码实现时,我习惯用中值法做积分。也就是取当前时刻和上一时刻的加速度平均值来更新速度:

void velocityUpdate(Vector3 *vel, const Vector3 *acc_n, float dt) {
    // acc_n 已经是导航坐标系下的比力(已扣除重力)
    vel->x += (acc_n->x + acc_n_prev->x) * 0.5f * dt;
    vel->y += (acc_n->y + acc_n_prev->y) * 0.5f * dt;
    vel->z += (acc_n->z + acc_n_prev->z) * 0.5f * dt;
    
    // 保存当前值供下一帧使用
    *acc_n_prev = *acc_n;
}

注意:中值法虽然比欧拉法精度高,但在高动态场景下仍然不够。如果你做的是赛车或无人机导航,建议用更高阶的积分方法,比如龙格-库塔法。

4.3 位置更新算法

位置更新就相对简单了——对速度做积分。但这里有个细节:地球是椭球体,经纬度更新时需要考虑曲率半径。

位置更新的离散形式:

// 经纬高更新(简化版)
void positionUpdate(Position *pos, const Vector3 *vel_n, float dt) {
    float lat = pos->latitude;
    float alt = pos->altitude;
    
    // 计算曲率半径
    float Rn = 6378137.0f / sqrt(1.0f - (0.006694379f * sin(lat) * sin(lat)));
    float Re = Rn * (1.0f - 0.006694379f) / (1.0f - 0.006694379f * sin(lat) * sin(lat));
    
    // 位置更新
    pos->latitude  += vel_n->x * dt / (Rn + alt);
    pos->longitude += vel_n->y * dt / ((Re + alt) * cos(lat));
    pos->altitude  += vel_n->z * dt;
}

嗯,这里要注意,纬度更新后会影响经度更新的分母,所以最好先更新纬度,再用新纬度算经度。

4.4 圆锥效应与划船效应补偿

这两个效应是INS解算里的「隐形杀手」。很多初学者不知道它们的存在,直到定位精度怎么都提不上去。

圆锥效应:当载体做圆锥运动时(比如直升机旋翼的振动),陀螺输出的角速度积分后会产生虚假的姿态漂移。说白了,就是角速度的「整流」效应。

划船效应:类似地,当载体同时有平动和转动时,加速度计积分会产生虚假的速度增量。

补偿方法有很多,我推荐工程中最实用的三子样算法:

// 圆锥效应补偿(三子样算法)
Vector3 coningCompensation(const Vector3 *gyro, float dt) {
    float h = dt / 3.0f;  // 子样间隔
    
    // 三个子样
    Vector3 w1 = gyro[0];
    Vector3 w2 = gyro[1];
    Vector3 w3 = gyro[2];
    
    // 圆锥补偿项
    Vector3 alpha = vectorAdd(w1, w2);
    alpha = vectorAdd(alpha, w3);
    alpha = vectorScale(alpha, h);
    
    // 补偿量
    Vector3 delta = vectorCross(w1, w2);
    delta = vectorAdd(delta, vectorScale(vectorCross(w2, w3), 3.0f));
    delta = vectorAdd(delta, vectorScale(vectorCross(w1, w3), 0.45f));
    delta = vectorScale(delta, h * h);
    
    return vectorAdd(alpha, delta);
}

避坑指南:我曾经在一个车载项目中忽略了划船效应补偿,结果车辆在颠簸路面行驶时,速度误差以每分钟0.5m/s的速度增长。加了补偿后,误差降到了0.05m/s以内。所以,如果你的IMU更新率低于200Hz,或者车辆经常在颠簸路面行驶,这两个补偿绝对不能省。

4.5 知识体系总览

为了让你更直观地理解INS解算的完整流程,我画了一张图:

INS捷联惯导解算流程 陀螺仪 (角速度) 加速度计 (比力) 圆锥效应补偿 划船效应补偿 姿态更新 四元数微分方程 坐标转换 (C_b^n) 速度更新 (比力方程) 位置更新 输出: 姿态/速度/位置

这张图把整个流程串起来了。你从左边看,陀螺数据经过圆锥补偿后进入姿态更新,姿态更新结果用于坐标转换。右边加速度计数据经过划船补偿后,结合坐标转换结果,进入速度更新,最后得到位置。每一步都环环相扣。

好了,这一章的内容就到这里。INS解算的这三个核心算法——姿态、速度、位置更新,加上两个补偿算法,是车载定位系统的基石。我建议你动手把代码写一遍,跑几个仿真数据看看效果。只有亲手调过,才能真正理解这些算法背后的物理意义。

我的建议:初学者可以先忽略圆锥和划船补偿,先把基本的三更新跑通。等精度遇到瓶颈了,再回头加补偿。这样调试起来思路更清晰。

可可精品资料库