从代码到理论:拆解VINS-MONO的IntegrationBase类,搞懂预积分状态管理

张开发
2026/4/18 18:10:21 15 分钟阅读

分享文章

从代码到理论:拆解VINS-MONO的IntegrationBase类,搞懂预积分状态管理
从代码到理论拆解VINS-MONO的IntegrationBase类搞懂预积分状态管理在视觉惯性里程计VIO系统中IMU数据的处理一直是核心难点之一。VINS-MONO作为开源VIO方案的标杆其预积分实现被众多开发者视为学习范本。今天我们就深入代码层面解剖IntegrationBase这个关键类看看它如何优雅地管理预积分状态。1. 预积分的基本概念与价值IMU传感器以数百赫兹的频率输出角速度和加速度数据而相机帧率通常只有几十赫兹。这种频率差异导致相邻图像帧之间可能包含数十个IMU测量值。直接对这些高频IMU数据进行积分计算位姿会在优化过程中因状态更新而需要反复重新积分带来巨大的计算开销。预积分技术的核心思想是将IMU测量值从世界坐标系转换到局部坐标系进行积分。这样得到的相对运动量位移增量、速度增量和旋转增量就与世界坐标系解耦在后续优化过程中无需重复计算。VINS-MONO中IntegrationBase类正是这一思想的工程实现。预积分带来的三大优势计算效率避免优化迭代中的重复积分数值稳定性局部坐标系下的增量计算更稳定模块化设计将IMU处理封装为独立模块2. IntegrationBase的成员变量解析打开integration_base.h首先映入眼帘的是这个类的成员变量。理解这些变量的含义是掌握预积分的关键// 当前时刻的IMU测量值 Eigen::Vector3d acc_0, gyr_0; // 线性化点的IMU测量值对应关键帧时刻 Eigen::Vector3d linearized_acc, linearized_gyr; // 偏置相关变量 Eigen::Vector3d linearized_ba, linearized_bg; // 预积分结果 Eigen::Vector3d delta_p; // 位置增量(α) Eigen::Quaterniond delta_q; // 旋转增量(γ) Eigen::Vector3d delta_v; // 速度增量(β) // 误差传播矩阵 Eigen::Matrixdouble, 15, 15 jacobian, covariance; // 噪声矩阵 Eigen::Matrixdouble, 18, 18 noise;特别需要注意acc_0/gyr_0与linearized_acc/linearized_gyr的区别前者是当前最新IMU测量值后者是关键帧时刻的IMU测量值线性化点3. 核心方法propagate的实现原理propagate方法是预积分的核心它采用中值积分策略处理两个连续IMU测量值之间的状态传播void propagate(double _dt, const Eigen::Vector3d _acc_1, const Eigen::Vector3d _gyr_1) { dt _dt; acc_1 _acc_1; // tdt时刻的加速度 gyr_1 _gyr_1; // tdt时刻的角速度 Vector3d result_delta_p; Quaterniond result_delta_q; Vector3d result_delta_v; midPointIntegration(_dt, acc_0, gyr_0, _acc_1, _gyr_1, delta_p, delta_q, delta_v, linearized_ba, linearized_bg, result_delta_p, result_delta_q, result_delta_v, /*...*/ true); // 更新预积分状态 delta_p result_delta_p; delta_q result_delta_q; delta_v result_delta_v; delta_q.normalize(); sum_dt dt; acc_0 acc_1; // 准备下一次传播 gyr_0 gyr_1; }中值积分的数学本质是对两个时刻的测量值取平均其离散形式为$$ \begin{aligned} \omega \frac{1}{2}(\omega_{k} \omega_{k1}) - b_g \ a \frac{1}{2}(q_k(a_k-b_a) q_{k1}(a_{k1}-b_a)) \end{aligned} $$4. 误差传播与协方差更新预积分不仅要计算状态量还需要维护误差传播的协方差矩阵。这部分代码在midPointIntegration的后半段if(update_jacobian) { // 构建误差状态转移矩阵F MatrixXd F MatrixXd::Zero(15, 15); F.block3, 3(0, 0) Matrix3d::Identity(); F.block3, 3(0, 3) -0.25 * delta_q.matrix() * R_a_0_x * dt * dt -0.25 * result_delta_q.matrix() * R_a_1_x * (Matrix3d::Identity() - R_w_x * dt) * dt * dt; // ... 其他F矩阵块赋值 // 构建噪声传播矩阵V MatrixXd V MatrixXd::Zero(15,18); V.block3, 3(0, 0) 0.25 * delta_q.matrix() * dt * dt; // ... 其他V矩阵块赋值 // 更新Jacobian和协方差 jacobian F * jacobian; covariance F * covariance * F.transpose() V * noise * V.transpose(); }误差状态转移矩阵F的维度是15×15对应位置、速度、旋转误差各3维加速度计偏置和陀螺仪偏置误差各3维5. repropagate的特殊作用当偏置估计发生较大变化时需要重新计算预积分量。这就是repropagate方法的职责void repropagate(const Eigen::Vector3d _linearized_ba, const Eigen::Vector3d _linearized_bg) { // 重置状态 sum_dt 0.0; acc_0 linearized_acc; gyr_0 linearized_gyr; delta_p.setZero(); delta_q.setIdentity(); delta_v.setZero(); // 更新偏置 linearized_ba _linearized_ba; linearized_bg _linearized_bg; // 重置误差传播矩阵 jacobian.setIdentity(); covariance.setZero(); // 重新传播所有IMU数据 for (int i 0; i static_castint(dt_buf.size()); i) propagate(dt_buf[i], acc_buf[i], gyr_buf[i]); }关键点在于重置所有状态量为初始值更新线性化点的偏置估计重新执行所有IMU测量值的传播6. 预积分在实际系统中的应用理解IntegrationBase的最好方式是在完整VIO流程中观察它的作用。在VINS-MONO中预积分主要在两个环节发挥作用前端跟踪为帧间位姿估计提供初始值后端优化构建IMU残差项以优化环节为例IMU残差的计算方式为residuals.block3, 1(O_P, 0) Qi.inverse() * (Pj - Pi - Vi * sum_dt 0.5 * g * sum_dt2) - corrected_delta_p; residuals.block3, 1(O_R, 0) 2 * (corrected_delta_q.inverse() * (Qi.inverse() * Qj)).vec(); residuals.block3, 1(O_V, 0) Qi.inverse() * (Vj - Vi g * sum_dt) - corrected_delta_v;其中corrected_delta_*就是经过偏置修正的预积分量它们与状态估计值之间的差异构成了IMU残差。7. 性能优化与实现细节在实际编码实现中IntegrationBase有几个值得注意的优化点四元数归一化每次更新旋转增量后都执行delta_q.normalize()矩阵块操作使用Eigen的block操作高效更新大矩阵内存预分配所有矩阵在构造函数中初始化完成条件更新通过update_jacobian参数控制是否更新Jacobian一个常见的陷阱是忽略偏置的变化对预积分量的影响。当偏置变化较大时必须调用repropagate而不是继续使用旧的预积分结果。

更多文章