首页 > 其他分享 >VINS中的IMU因子(一)

VINS中的IMU因子(一)

时间:2023-09-23 13:55:42浏览次数:38  
标签:acc Eigen parameters Vector3d 因子 IMU VINS dt

VINS中的IMU因子(一)

在这篇文章中我们分析一些VINS中对于IMU因子的处理和构建方式。首先来看一下再 estimator类中关于预积分因子的几个重要成员变量。pre_integrations 存储了滑动窗口中相邻两帧之间的预积分增量。acc_0,gyr_0则保存了当前时刻的角速度和加速度值。

IntegrationBase *pre_integrations[(WINDOW_SIZE + 1)];
Vector3d acc_0, gyr_0;

在processMeasurements()线程中,当图像到来的时候,会处理一次堆积的IMU数据,遍历所有的IMU数据,调用processIMU()函数进行处理,下面来看下该函数具体做了什么事?

void Estimator::processIMU(double t, double dt, const Vector3d &linear_acceleration, const Vector3d &angular_velocity);

首先看下该函数的入参

  • t : 为当前数据的时间戳
  • dt : 为相邻两个IMU数据之间的时间间隔
  • linear_acceleration : 加速度
  • angular_velocity : 角速度

if (!first_imu)
{
    first_imu = true;
    acc_0 = linear_acceleration;
    gyr_0 = angular_velocity;
}

首先判断是否是第一帧IMU数据,如果是,则设置标志位,同时对前最新时刻的加速度和角速度数据进行赋值。


if (!pre_integrations[frame_count])
{
    pre_integrations[frame_count] = new IntegrationBase{acc_0, gyr_0, 		 Bas[frame_count], Bgs[frame_count]};
}

如果滑窗内的当前帧还没有IMU预积分,则为其创建并分配内存。这种情况会发生在处理每组IMU数据中的第一个的时候发生。


if (frame_count != 0)
{
    pre_integrations[frame_count]->push_back(dt, linear_acceleration, angular_velocity);
 
    tmp_pre_integration->push_back(dt, linear_acceleration, angular_velocity);
}

如果不是第一帧图像,才进行积分操作,这是因为在vio中我们以第一帧图像和其前面的IMU数据是无法构成约束的。这里进行了两个操作,分别对pre_integrations[frame_count]和tmp_pre_integration进行了push_back操作,这个可不是vector的push_back操作,这里其实是对IMU进行预积分,预积分主要干了三件事,第一就是预积分增量的递推,第二则是计算雅可比,第三则是更新噪声协方差矩阵


dt_buf[frame_count].push_back(dt);
linear_acceleration_buf[frame_count].push_back(linear_acceleration);
angular_velocity_buf[frame_count].push_back(angular_velocity);

同时存储相关变量。这里的push_back才是vector的。


int j = frame_count;
/// 此时的 RS[j] 还表示的是上一个时刻的旋转,还没有更新,注意这里的 j 表示的图像,不是IMU数据下标,我们还假定两个图像之间的IMU零偏是固定的
Vector3d un_acc_0 = Rs[j] * (acc_0 - Bas[j]) - g;   /// a^' = R^T * (a-g)
Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - Bgs[j];
/// 这里已经通过积分对位姿进行了预测,作为优化的初始值
Rs[j] *= Utility::deltaQ(un_gyr * dt).toRotationMatrix(); /// 右乘进行更新
Vector3d un_acc_1 = Rs[j] * (linear_acceleration - Bas[j]) - g; /// 这里的减g是因为vins建模的时候g为正数
Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
Ps[j] += dt * Vs[j] + 0.5 * dt * dt * un_acc;
Vs[j] += dt * un_acc;
  1. 根据上一时刻的加速度测量值计算出上一时刻的加速度 un_acc_0。
  2. 利用上一时刻和当前时刻的角平均作为当前时刻的角速度。
  3. 利用角速度更新滑窗中当前时刻的姿态
  4. 计算出当前时刻的加速度 un_acc_1。
  5. 利用当前时刻和上一时刻的加速度平均作为当前时刻纠正后的加速度。
  6. 更新滑窗中当前时刻的位置和速度

acc_0 = linear_acceleration;
gyr_0 = angular_velocity; 

记录加速度和角速度的测量值。


注意到在进入非线性优化阶段求解VIO的时候,有这样一个判断

  if(!USE_IMU)
  	f_manager.initFramePoseByPnP(frame_count, Ps, Rs, tic, ric);
  f_manager.triangulate(frame_count, Ps, Rs, tic, ric);

如果是使用IMU的情况下,则会使用IMU的递推状态作为优化的初始值,否则,也就是在纯双目模式在则会使用PNP解算的位姿作为初始值。当然,这里你也可以注释掉这句话,此时就是在双目+IMU的模式下使用PNP作为优化的初始值,那么上面的IMU递推部分则可以注释掉了。

接下来则要看一些 IMUFactor 类的实现细节。

class IMUFactor : public ceres::SizedCostFunction<15, 7, 9, 7, 9>

可以看到IMU残差块继承自 Ceres,这里的后面的参数代表什么意思呢,以这里的IMU因子为例:

  • 15 代表的是 IMU 残差的维度,分别是 P,V,Q,Bg,Ba。
  • 7 代表的是两帧之间前一帧的位置和姿态,只不过这里姿态用了四元数,所以是 3 + 4 = 7
  • 9 代表的是两帧之间前一帧的 V,Bg,Ba,所以是 3 * 3 = 9 维
  • 7 代表的是两帧之间后一帧的位置和姿态,只不过这里姿态用了四元数,所以是 3 + 4 = 7
  • 9 代表的是两帧之间后一帧的 V,Bg,Ba,所以是 3 * 3 = 9 维

如果想要使用 Ceres 则要重载其 Evaluate 函数,用于计算残差和雅可比,下面我们来详细分析一下其实现。

virtual bool Evaluate(double const *const *parameters, double *residuals, double **jacobians) const;
  • double const *const *parameters : 存储的是个二维数组,一共有四个数组,每个数组有 7, 9, 7, 9 个元素,和前面的 ceres::SizedCostFunction<15, 7, 9, 7, 9> 参数是对应的。
  • double *residuals : 计算得到的残差。
  • double **jacobians : jacobian 矩阵。

第一个参数为入参,第二三个函数为出参。

Eigen::Vector3d Pi(parameters[0][0], parameters[0][1], parameters[0][2]);
Eigen::Quaterniond Qi(parameters[0][6], parameters[0][3], parameters[0][4], parameters[0][5]);

Eigen::Vector3d Vi(parameters[1][0], parameters[1][1], parameters[1][2]);
Eigen::Vector3d Bai(parameters[1][3], parameters[1][4], parameters[1][5]);
Eigen::Vector3d Bgi(parameters[1][6], parameters[1][7], parameters[1][8]);

Eigen::Vector3d Pj(parameters[2][0], parameters[2][1], parameters[2][2]);
Eigen::Quaterniond Qj(parameters[2][6], parameters[2][3], parameters[2][4], parameters[2][5]);

Eigen::Vector3d Vj(parameters[3][0], parameters[3][1], parameters[3][2]);
Eigen::Vector3d Baj(parameters[3][3], parameters[3][4], parameters[3][5]);
Eigen::Vector3d Bgj(parameters[3][6], parameters[3][7], parameters[3][8]);

首先获得 i 和 j 时刻的状态变量。


然后调用预积分类的 evaluate 函数计算残差:

Eigen::Map<Eigen::Matrix<double, 15, 1>> residual(residuals);
residual = pre_integration->evaluate(Pi, Qi, Vi, Bai, Bgi,
                                     Pj, Qj, Vj, Baj, Bgj);

evalueate 的函数体的内容为:

Eigen::Matrix<double, 15, 1> residuals;
/// 分别获得当前时刻 P, V, Q 对 ba,bg 的导数
Eigen::Matrix3d dp_dba = jacobian.block<3, 3>(O_P, O_BA);
Eigen::Matrix3d dp_dbg = jacobian.block<3, 3>(O_P, O_BG);

/// Q 只和 bg 有关
Eigen::Matrix3d dq_dbg = jacobian.block<3, 3>(O_R, O_BG);

Eigen::Matrix3d dv_dba = jacobian.block<3, 3>(O_V, O_BA);
Eigen::Matrix3d dv_dbg = jacobian.block<3, 3>(O_V, O_BG);

/// 得到 ba 优化前后的变化量,Bai 为上一时刻优化后的加速度零偏
Eigen::Vector3d dba = Bai - linearized_ba;
/// 得到 bg 优化前后的变化量,Bgi 为上一时刻优化后的加速度零偏
Eigen::Vector3d dbg = Bgi - linearized_bg;

/// 根据变化量和对每个状态量的雅可比矩阵更新预积分(预积分和状态变量PVQ无关,但是和 ba,bg 有关)
Eigen::Quaterniond corrected_delta_q = delta_q * Utility::deltaQ(dq_dbg * dbg);
Eigen::Vector3d corrected_delta_v = delta_v + dv_dba * dba + dv_dbg * dbg;
Eigen::Vector3d corrected_delta_p = delta_p + dp_dba * dba + dp_dbg * dbg;

/// 计算残差
residuals.block<3, 1>(O_P, 0) = Qi.inverse() * (0.5 * G * sum_dt * sum_dt + Pj - Pi - Vi * sum_dt) - corrected_delta_p;
residuals.block<3, 1>(O_R, 0) = 2 * (corrected_delta_q.inverse() * (Qi.inverse() * Qj)).vec();
residuals.block<3, 1>(O_V, 0) = Qi.inverse() * (G * sum_dt + Vj - Vi) - corrected_delta_v;
residuals.block<3, 1>(O_BA, 0) = Baj - Bai;
residuals.block<3, 1>(O_BG, 0) = Bgj - Bgi;
return residuals;

当然还有一些关于预积分因子的雅可比的推导,我们放到下一节进行讨论。

标签:acc,Eigen,parameters,Vector3d,因子,IMU,VINS,dt
From: https://www.cnblogs.com/weihao-ysgs/p/vins-imu-factor1.html

相关文章

  • SCI期刊影响因子(Journal Impact Factor,简称JIF)
    SCI期刊影响因子(JournalImpactFactor,简称JIF)是指某一期刊在特定年份内被引用的次数(包括引用期刊内的文章和其他期刊对该期刊的引用),除以该期刊在同一年发表的总论文数目。该值反映了该期刊上一年发表的论文平均被引用的频率。   全球公认的期刊学术影响力评价指标......
  • hadoop中mapred.tasktracker.map.tasks.maximum的设置
    目前,我们邮件的一部分log已经迁移到Hadoop集群上并由Hive来执行相关的查询hadoop中默认的mapred.tasktracker.map.tasks.maximum设置是2也即:每一个tasktracker同时运行的map任务数为2照此默认设置,查询80天某用户的操作日志,耗时5mins,45sec经过测试,发现将mapred.tasktracker.map.ta......
  • LeetCode-Java题解 209. Minimum Size Subarray Sum
    题目地址:209.MinimumSizeSubarraySum解题思路:    看到这道题,心里本身是有双指针这个概念的,但是不知道怎么用,脑子里第一反应就是暴力解法,双for一把梭,然后时间就超时了...看了题解才知道滑动窗口这个解法,不禁直呼妙啊!感觉和双指针非常类似,其核心点在于避免了暴力算法的枚......
  • 4、索引如何设置填充因子
    理解填充因子重建索引固然可以解决碎片的问题.但是重建索引的代价不仅仅是麻烦,还会造成阻塞。影响使用.而对于数据比较少的情况下,重建索引代价并不大。而当索引本身超过百兆的时候。重建索引的时间将会很让人蛋疼.填充因子的作用正是如此。对于默认值来说,填充因子为0(0和100表示......
  • 生态保护红线划定与评估中气象因子计算方法 学习记录
    声明本文是学习GB-T42960-2023生态保护红线划定与评估中气象因子计算方法.而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们1范围本文件规定了生态保护红线划定与评估中气象资料选取的要求,描述了气象因子计算的方法。本文件适用于生态功能的调查和生态保护......
  • 洛谷 AT_maximum_cup_2018_a フィギュアスケート界の貴公子埼大選手 の 题解
    这道题是一道水题,所以你的代码很可能与我相似或相同,如果你的代码出现了问题,你很可能在我的题解里找出答案。这道题大概思路是一旦$10$秒后运动员会接触到毛绒玩具,那么就加上在这个点上毛绒玩具的数量。但是!这道题有一道巨坑的点!由于这道题比较远古,所以说你即使是正解,你也要在......
  • simulink模块执行顺序
    simulink模块执行顺序Simulink模块的执行顺序都是序贯进行的,也就是沿着信号的流向进行。没有输入的模块先进行计算,更新状态量与输出,需要输入信号的模块等到输入信号准备ready之后进行状态量和输出的更新。两个模块A/B,如果B模块的输入依赖于A模块的输出,那么先执行A模块再执行B模......
  • [ABC319D] Minimum Width 题解
    [ABC319D]MinimumWidth题解题意分析  给定\(n\)个单词,现在想像“记事本”一样把它们依次地一行一行显示出来。每个字母宽度为一,单词之间需要有空格,宽度也为一。一个单词不可以成两部分显示在两行。如果单词最后一个字母来到行末,直接换行,不用空格。  给定窗口最大高度......
  • All Pairs Maximum Flow题解
    前置知识:1.P3376【模板】网络最大流2.P4897【模板】最小割树(Gomory-HuTree)Ebola有一句很著名的话如果你乱搞过了我请你抽烟那么这道题肯定不能普通的dinic直接水过去,不然就不是紫题了,那么直接祭出最小割树,复杂度\(O(Tn^3m)\),但是因为dinic跑不满,所以是可以过的。......
  • 【ROS2机器人入门到实战】使用开源库驱动IMU
    2.使用开源库驱动IMU写在前面当前平台文章汇总地址:ROS2机器人从入门到实战获取完整教程及配套资料代码,请关注公众号<鱼香ROS>获取教程配套机器人开发平台:两驱版|四驱版为方便交流,搭建了机器人技术问答社区:地址fishros.org.cn你好,我是爱吃鱼香ROS的小鱼。上一节我们安装好了MPU605......