4. IMU运动学模型:离散运动学方程与PVQ积分

各位同学,今天我们来聊聊IMU运动学模型的核心——离散运动学方程,以及位置、速度、姿态的递推更新,也就是业内常说的PVQ积分。

说实话,这部分内容我刚开始接触时也觉得有点绕。但后来在实际项目中吃过亏,才真正理解它的重要性。你想想看,IMU的数据是离散的,而我们需要的却是连续的运动估计。怎么把这两者桥接起来?这就是我们今天要解决的问题。

4.1 连续时间下的IMU运动学模型

先回顾一下连续时间下的模型。IMU测量的是加速度和角速度,但注意,它测量的是本体坐标系下的值。我们需要把这些测量值转换到世界坐标系下,才能进行积分。

我个人习惯把IMU的运动学模型写成下面这样:

// 连续时间下的IMU运动学方程
// p: 位置 (世界坐标系)
// v: 速度 (世界坐标系)  
// q: 姿态 (四元数,世界到本体的旋转)
// a_m: 加速度计测量值 (本体坐标系)
// w_m: 陀螺仪测量值 (本体坐标系)
// g: 重力加速度 (世界坐标系)

p_dot = v
v_dot = R(q) * (a_m - b_a - n_a) + g
q_dot = 0.5 * q ⊗ (w_m - b_g - n_g)

这里有几个关键点:

  • R(q) 是从本体到世界的旋转矩阵,由四元数q转换而来
  • b_a, b_g 是加速度计和陀螺仪的偏置,需要在线估计
  • n_a, n_g 是高斯白噪声
  • ⊗ 表示四元数乘法
⚠️ 注意: 这里的加速度测量值包含了重力分量。很多新手会忘记减去重力,导致积分出来的位置飞上天。我曾经在一个项目中调试了整整两天,最后发现是重力补偿没做对。

4.2 离散化:从连续到离散

实际IMU数据是离散采样的,频率通常在100Hz到1000Hz之间。我们需要把上面的连续方程离散化。

常用的离散化方法有两种:

  1. 欧拉法:简单粗暴,但精度一般
  2. 中值法:精度更高,我推荐使用

嗯,这里要注意,欧拉法虽然简单,但在高动态场景下误差会累积得很快。我建议至少用中值法。

4.2.1 欧拉法离散

// 欧拉法离散化
// dt: 采样间隔
// 下标 k 表示当前时刻,k+1 表示下一时刻

p_{k+1} = p_k + v_k * dt + 0.5 * R(q_k) * (a_m_k - b_a) * dt^2 + 0.5 * g * dt^2
v_{k+1} = v_k + (R(q_k) * (a_m_k - b_a) + g) * dt
q_{k+1} = q_k ⊗ exp(0.5 * (w_m_k - b_g) * dt)

说白了,欧拉法就是用当前时刻的测量值来近似整个时间间隔内的运动。这在IMU频率足够高时勉强可用,但如果你做的是高精度定位,我建议别用。

4.2.2 中值法离散(推荐)

// 中值法离散化
// 使用 k 和 k+1 时刻测量值的平均值

// 角速度中值
w_mid = 0.5 * (w_m_k + w_m_{k+1}) - b_g

// 加速度中值
a_mid = 0.5 * (a_m_k + a_m_{k+1}) - b_a

// 姿态更新(先更新姿态,因为位置和速度需要用到)
q_{k+1} = q_k ⊗ exp(0.5 * w_mid * dt)

// 位置和速度更新
p_{k+1} = p_k + v_k * dt + 0.5 * R(q_mid) * a_mid * dt^2 + 0.5 * g * dt^2
v_{k+1} = v_k + (R(q_mid) * a_mid + g) * dt

中值法的好处是精度更高,因为用了两个时刻的平均值来近似。我在做自动驾驶的IMU预积分时,一直用的就是中值法。

💡 小技巧: 实际工程中,如果IMU频率很高(比如400Hz以上),欧拉法和中值法的差异不大。但如果频率低于200Hz,一定要用中值法。

4.3 PVQ积分的完整流程

PVQ积分,说白了就是位置(Position)、速度(Velocity)、姿态(Quaternion)的递推更新。整个过程可以用下面的流程图来概括:

PVQ积分流程图 IMU测量值 (a_m, w_m) 偏置补偿: a = a_m - b_a, w = w_m - b_g 离散化 方法选择 欧拉法(低精度) 中值法(推荐) 输出: p_{k+1}, v_{k+1}, q_{k+1}

整个PVQ积分的核心步骤可以总结为:

  1. 读取IMU测量值:获取当前时刻的加速度和角速度
  2. 偏置补偿:减去估计的偏置值
  3. 离散化积分:选择欧拉法或中值法进行递推
  4. 更新状态:得到新的位置、速度、姿态

4.4 实际工程中的注意事项

在实际项目中,PVQ积分有几个坑需要特别注意:

🔑 关键点:
  • 四元数归一化:每次更新后一定要对四元数做归一化,否则数值误差会累积,导致姿态发散。我见过有人忘了这步,结果姿态越转越歪。
  • 重力对齐:初始时刻需要准确估计重力方向,否则位置积分会漂移。
  • 时间戳同步:IMU和激光雷达的时间戳必须严格对齐,否则融合效果会大打折扣。
⚠️ 避坑指南: 我曾经在一个项目中,IMU的采样频率是200Hz,激光雷达是10Hz。我直接用原始时间戳做积分,结果发现位置漂移得厉害。后来排查发现,是IMU和激光雷达的时钟不同步导致的。解决方案是用硬件同步信号或者做时间戳插值。

4.5 代码实现示例

下面是一个简单的PVQ积分实现,我用的是中值法:

// PVQ积分 - 中值法实现
// 输入: 上一时刻状态 (p_k, v_k, q_k), IMU测量值 (a_k, w_k, a_{k+1}, w_{k+1}), 偏置 (b_a, b_g), 时间间隔 dt
// 输出: 当前时刻状态 (p_{k+1}, v_{k+1}, q_{k+1})

void pvq_integration(
    const Vector3d& p_k, const Vector3d& v_k, const Quaterniond& q_k,
    const Vector3d& a_k, const Vector3d& w_k,
    const Vector3d& a_{k+1}, const Vector3d& w_{k+1},
    const Vector3d& b_a, const Vector3d& b_g, double dt,
    Vector3d& p_{k+1}, Vector3d& v_{k+1}, Quaterniond& q_{k+1})
{
    // 1. 偏置补偿
    Vector3d a_mid = 0.5 * (a_k + a_{k+1}) - b_a;
    Vector3d w_mid = 0.5 * (w_k + w_{k+1}) - b_g;
    
    // 2. 姿态更新 (使用中值角速度)
    double theta = w_mid.norm() * dt;
    Vector3d axis = w_mid.normalized();
    Quaterniond dq(cos(theta/2), sin(theta/2)*axis.x(), sin(theta/2)*axis.y(), sin(theta/2)*axis.z());
    q_{k+1} = (q_k * dq).normalized();  // 别忘了归一化!
    
    // 3. 速度更新
    Matrix3d R_mid = q_k.toRotationMatrix();  // 这里简化了,实际应该用中值姿态
    v_{k+1} = v_k + (R_mid * a_mid + g) * dt;
    
    // 4. 位置更新
    p_{k+1} = p_k + v_k * dt + 0.5 * (R_mid * a_mid + g) * dt * dt;
}
💡 个人经验: 上面的代码中,我用了q_k的旋转矩阵来近似中值姿态。严格来说应该用q_k和q_{k+1}的平均值,但这样实现简单,精度也够用。如果你追求极致精度,可以迭代一次。

4.6 总结

好了,今天的内容就到这里。PVQ积分是IMU运动学模型的核心,也是后续IMU预积分、IMU与激光雷达融合的基础。

记住几个要点:

  • 离散化方法选中值法,别偷懒用欧拉法
  • 四元数必须归一化,这是血的教训
  • 注意重力补偿和时间同步

下一章我们会深入IMU预积分的内容,到时候你会看到今天讲的PVQ积分是如何被巧妙地重用的。

可可精品资料库