VINS-Mono后端知识点汇总

processImage:每帧都干了什么

谁是Featureanager:维护路标点与图像

后端干了啥:详解因子图(视觉的因子图、IMU的因子图、因子图和Hessian矩阵的关系)

optimization:优化了谁?(参与到优化的约束有IMU的约束、重投影的约束、边缘化约束(先验约束))

IMU约束、重投影约束:谁长什么样?

麻烦的边缘化

0、后端预处理

        后端node拿到imu和图像后,做了两件事情:

  • IMU预积分:processIMU()
  • 将图像往后端优化:processImage()

 0.1 后端优化的关键类estimator

      estimator类在

问题:为什么使用逆深度? 

1、processImage:对每帧都干了什么?

        

可以看到处理过程基本如下:

        1. f_manager.addFeatureCheckParallax(frame_count, image, td)

本函数有两个作用:①将当前帧看到的特征点image放到特征点管理器f_manager中。

② 同时根据视差进行关键帧的选择,假如当前帧是关键帧,则滑窗内最老的一帧被替换,假如当前帧不是关键帧,则替换滑窗内的次新帧。
        

        image表示当前帧跟踪到上一帧的特征点集合,也就是当前帧观测到的所有的路标点(不包括在当前帧新提取的点,因为当前帧新提取的点的cnt=1,无法完成三角化)。image的数据结构如下:

2. 滑窗优化 solveOdometry()滑窗优化

        对于滑窗优化部分,solveOdometry()函数主要做了两部分工作,一个是对新加入的没有三角化的点进行三角化,另一个是进行真正的滑窗优化optimization()。

2.1 f_manager.triangulate(Ps, tic, ric) 三角化

2.2 optimization()滑窗优化

        在弄清optimization函数如何进行优化时,按照BA优化三要素(误差项、优化变量、协方差矩阵)进行思考,首先明白优化向量是谁:

        优化向量滑窗内的所有相机状态PVQB所有3D点的逆深度(以及Camera到IMU的外参等)。

2.2.1 重投影误差:长什么模样

2.2.2 IMU约束:长什么模样

2.2.3 先验约束

        已加入完成视觉约束和IMU约束,剩下的先验约束如何加?

3. solveOdometry()滑窗优化中的边缘化操作

        最老帧需要扔掉M0(V/Ba/Bg),T0(P/Q),以及观测到的路标点也全部断开n×1(λ),n是观测到的路标点的个数。

optimization()函数中的边缘化操作

3.1 边缘化结果

        在添加先验约束时,需要注意先看optimization()函数是如何添加先验约束的,代码在estimator.cpp中的optimization()函数中,代码如下:

// Step 4 边缘化
// 科普一下舒尔补
TicToc t_whole_marginalization;
if (marginalization_flag == MARGIN_OLD)
{
    // 一个用来边缘化操作的对象
    MarginalizationInfo *marginalization_info = new MarginalizationInfo();
    // 这里类似手写高斯牛顿,因此也需要都转成double数组
    vector2double();

    // 关于边缘化有几点注意的地方
    // 1、找到需要边缘化的参数块,这里是地图点,第0帧位姿,第0帧速度零偏
    // 2、找到构造高斯牛顿下降时跟这些待边缘化相关的参数块有关的残差约束,那就是预积分约束,重投影约束,以及上一次边缘化约束
    // 3、这些约束连接的参数块中,不需要被边缘化的参数块,就是被提供先验约束的部分,也就是滑窗中剩下的位姿和速度零偏

    // 上一次的边缘化结果
    if (last_marginalization_info)
    {
        vector<int> drop_set;
        // last_marginalization_parameter_blocks是上一次边缘化对哪些当前参数块有约束
        for (int i = 0; i < static_cast<int>(last_marginalization_parameter_blocks.size()); i++)
        {
            // 涉及到的待边缘化的上一次边缘化留下来的当前参数块只有位姿和速度零偏
            if (last_marginalization_parameter_blocks[i] == para_Pose[0] ||
                last_marginalization_parameter_blocks[i] == para_SpeedBias[0])
                drop_set.push_back(i);
        }
        // 处理方式和其他残差块相同
        // construct new marginlization_factor
        MarginalizationFactor *marginalization_factor = new MarginalizationFactor(last_marginalization_info);
        ResidualBlockInfo *residual_block_info = new ResidualBlockInfo(marginalization_factor, NULL, last_marginalization_parameter_blocks, drop_set);

        marginalization_info->addResidualBlockInfo(residual_block_info);
    }
... ...
};

其中添加的last_marginalization的类型在Estimator类中有定义,定义如下:

MarginalizationInfo *last_marginalization_info;

MarginalizationInfo是vins_estimator/src/factor/marginalization.h文件中定义的类,是边缘化的大管家,代码如下:

class MarginalizationInfo
{
  public:
    ~MarginalizationInfo();
    int localSize(int size) const;
    int globalSize(int size) const;
    void addResidualBlockInfo(ResidualBlockInfo *residual_block_info);
    void preMarginalize();
    void marginalize();
    std::vector<double *> getParameterBlocks(std::unordered_map<long, double *> &addr_shift);

    std::vector<ResidualBlockInfo *> factors;
    int m, n;
    std::unordered_map<long, int> parameter_block_size; //global size   // 地址->global size
    int sum_block_size;
    std::unordered_map<long, int> parameter_block_idx; //local size // 地址->参数排列的顺序idx
    std::unordered_map<long, double *> parameter_block_data;    // 地址->参数块实际内容的地址

    std::vector<int> keep_block_size; //global size
    std::vector<int> keep_block_idx;  //local size
    std::vector<double *> keep_block_data;

    Eigen::MatrixXd linearized_jacobians;
    Eigen::VectorXd linearized_residuals;
    const double eps = 1e-8;
};

其中的残差块添加函数addResidualBlockInfo函数中出现残差项因子ResidualBlockInfo,具体来看边缘化的残差因子定义:

        需要解释的是,待marg变量的序号就是滑窗内的编号,比如要边缘化最老帧关键帧,那么就是_drop_set就是0和1。

        回到MarginalizationInfo类,前面说过,这个类是边缘化操作的大管家,它主要包含以下三个函数

void addResidualBlockInfo() // 将所有参与边缘化的因子加进来
void preMarginalize() // 做预处理
void marginalize() // 边缘化操作

五个变量

parameter_block_size:每个变量的维度
parameter_block_data:每个变量的数据
parameter_block_idx:每个变量在H矩阵中的索引
m:需要marg掉的变量的总维度
n:需要保留的变量的总维度

3.1.1 5个变量

        单说关系或者数字,可能不太明白,下面举个例子来说,先搞明白5个变量是怎么来的:

 那么对应到上面MarginalizationInfo中的几个变量,关系如下:

 这三个参数涉及的维度也好,数据也好,索引也好,全部是围绕这52个优化变量展开的。计算以下所有变量的Local维度总和,即为H矩阵的维度:

2(M0(V,Ba,Bg),M1)×3×3 + 11(P(P/Q))×2×3 + 1(Tbc)×6 + 42(λ)×1 = 132,

H矩阵的大小就为132×132

         那么MarginalizationInfo中的m和n也就不难计算:

        进一步,parameter_block_size、parameter_block_data和parameter_block_idx
的具体内容如下:

前边44个优化变量,维度是57,需要marg掉。

后边12个优化变量,维度是75,需要保存。

3.1.2 3个函数之addResidualBlockInfo()

再说回3个函数

1. 首先来看添加视觉因子(残差)的函数:

 

        这里就会明白添加的{0,3}是什么意思,代表[Ti,Tj,Tbc,λ]数组中的第0个Ti和第3个λ。

2. 再添加IMU因子:

3.1.3 3个函数之preMarginalize()

        

 

 

3.1.4 3个函数之marginalize()

        

 

 

3.1.2 IMU因子

        IMU因子优化变量:第0帧、第1帧的PVQB。

// 跟构建ceres约束问题一样,这里也需要得到残差和雅克比    IMU因子:第0帧、第1帧的PVQB
IMUFactor* imu_factor = new IMUFactor(pre_integrations[1]);
ResidualBlockInfo *residual_block_info = new ResidualBlockInfo(imu_factor, NULL, vector<double *>{para_Pose[0], para_SpeedBias[0], para_Pose[1], para_SpeedBias[1]}, vector<int>{0, 1});
// 这里就是第0和1个参数块是需要被边缘化的
marginalization_info->addResidualBlockInfo(residual_block_info);

3.1.3 视觉因子

        优化变量:第0帧和共视帧的PQ、外参、逆深度。

ProjectionFactor *f = new ProjectionFactor(pts_i, pts_j);
ResidualBlockInfo *residual_block_info = new ResidualBlockInfo(f, loss_function,
                                   vector<double *>{para_Pose[imu_i], para_Pose[imu_j], 
                                   para_Ex_Pose[0], para_Feature[feature_index]},
                                   vector<int>{0, 3});  // 这里第0帧和地图点被margin

marginalization_info->addResidualBlockInfo(residual_block_info);

4. 根据删除最老帧还是次新帧,对滑窗进行更新:slideWindow() 

### VINS-Mono 后端滑窗优化的实现方式与原理 VINS-Mono后端采用了一种基于滑动窗口的非线性优化方法来提高定位精度和鲁棒性。具体来说,该算法通过构建并维护一个固定大小的时间窗口内的观测数据来进行联合优化。 #### 滑窗优化的核心概念 1. **时间窗口的选择** 时间窗口内包含了若干帧图像及其对应的惯性测量单元(IMU)数据。这些数据共同构成了待优化的状态向量,其中包括相机位姿、速度、偏置以及特征点位置等参数[^2]。 2. **状态变量定义** 对于每一时刻 \( t \),系统会跟踪一系列状态变量,包括但不限于: - 相机姿态 \( R_t, p_t \) - IMU 偏置 \( b_g(t), b_a(t) \) - 特征点世界坐标 \( P_i \) 3. **误差项建模** 为了评估当前估计值的好坏程度,引入了多种类型的残差函数用于描述不同传感器之间的约束关系。主要分为两类: - **视觉重投影误差**:衡量重建三维点投影到二维平面后的偏差; ```cpp double reprojectionError(const Vector3d &point_world, const SE3 &pose_cam, const Vector2d &observed_point); ``` - **IMU 预积分误差**:反映相邻两帧之间imu预积分数值预测值同实际观测间的差异; 4. **目标函数构造** 将上述所有残差平方求和作为最终的目标损失函数,并利用最小二乘法对其进行极小化处理。即寻找一组最优解使得总成本达到最低。 5. **增量更新机制** 当新一帧加入时,旧有的超出窗口范围的数据会被移除,同时调整剩余部分权重系数以保持整体稳定性。此过程涉及到雅克比矩阵计算及稀疏QR分解操作完成高效迭代求解。 6. **闭合回环检测(Loop Closure Detection)** 虽然严格意义上不属于传统意义上的“滑窗”,但在某些复杂场景下,VINS-Mono也会尝试识别历史轨迹中的相似区域从而修正累积漂移问题[^1]。 7. **代码层面的具体体现** 在 `backend` 文件夹下的源码实现了以上提到的功能模块。特别是以下几个文件值得关注: - `estimator.cpp`: 定义了整个估计算法框架,负责初始化、状态管理等功能; - `msckf_vio.cpp`: 实现多态扩展卡尔曼滤波器(MSCKF),为前端提供初步的姿态估算结果; - `optimization_backend.cpp`: 承担具体的最优化任务,调用了诸如 Ceres Solver 进行数值运算; 8. **手写版本辅助理解** 此外,存在一个由贺一家开发的手写版后端程序[VINS-Course](https://github.com/HKUST-Aerial-Robotics/VINS-Course) ,它去除了依赖第三方库的要求(Ceres/ROS),非常适合初学者深入学习内部细节而不受外部因素干扰。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

家门Jm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值