当前位置:   article > 正文

LVI-SAM imuPreintegration代码解析_imupreintegrationresetid

imupreintegrationresetid

写在前面:这个部分imu预积分,具体是啥,大家可以自行网络搜索,我的理解就是在进行优化的时候为了方便计算,不至于从头开始计算,于是提出了预积分的概念,然后相关公式大家可以在网上自行查阅。还有预积分的话,就是imu获取数据的频率是很高的,但是雷达有些慢。比如雷达获取1帧的数据,此时imu已经有了10帧数据,因此预积分也可以将这10帧imu数据变成1帧,然后与雷达的数据帧数对齐。下面是代码解析。

头文件

  1. // 开头 包含很多库,然后这个gtsam是关于解决slam的一个库,这个暂时还没仔细看里面的内容
  2. #include "utility.h"
  3. #include <gtsam/geometry/Rot3.h>
  4. #include <gtsam/geometry/Pose3.h>
  5. #include <gtsam/slam/PriorFactor.h>
  6. #include <gtsam/slam/BetweenFactor.h>
  7. #include <gtsam/navigation/GPSFactor.h>
  8. #include <gtsam/navigation/ImuFactor.h>
  9. #include <gtsam/navigation/CombinedImuFactor.h>
  10. #include <gtsam/nonlinear/NonlinearFactorGraph.h>
  11. #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
  12. #include <gtsam/nonlinear/Marginals.h>
  13. #include <gtsam/nonlinear/Values.h>
  14. #include <gtsam/inference/Symbol.h>
  15. #include <gtsam/nonlinear/ISAM2.h>
  16. #include <gtsam_unstable/nonlinear/IncrementalFixedLagSmoother.h>

头文件是一些关于gtsam的文件,这个库是专门来解决slam或者sfm问题的。然后其中的具体内容没看,但是那个ISAM2是用来解决优化问题的,可以理解成来求解的。

变量

  1. // 这里是一个变量的使用声明
  2. using gtsam::symbol_shorthand::X; // Pose3 (x,y,z,r,p,y) 姿态
  3. using gtsam::symbol_shorthand::V; // Vel (xdot,ydot,zdot) 速度 这个dot表示导数的意思
  4. using gtsam::symbol_shorthand::B; // Bias (ax,ay,az,gx,gy,gz) 偏差
  5. // 下面这4个都是在ros::里面的,说明下面的变量可能最终会在rviz里面进行展示
  6. // 输入
  7. ros::Subscriber subImu; // 订阅器
  8. ros::Subscriber subOdometry;
  9. // 输出
  10. ros::Publisher pubImuOdometry; // 发布器
  11. ros::Publisher pubImuPath;
  12. // map -> odom 转换关系,前面所示,不多讲
  13. tf::Transform map_to_odom;
  14. tf::TransformBroadcaster tfMap2Odom;
  15. // odom -> base_link
  16. tf::TransformBroadcaster tfOdom2BaseLink;
  17. bool systemInitialized = false; // 这个是系统初始化
  18. // 噪声协方差
  19. gtsam::noiseModel::Diagonal::shared_ptr priorPoseNoise; // Diagonal对角线 先验位姿噪声
  20. gtsam::noiseModel::Diagonal::shared_ptr priorVelNoise; // 先验速度噪声
  21. gtsam::noiseModel::Diagonal::shared_ptr priorBiasNoise; // 先验偏差噪声
  22. gtsam::noiseModel::Diagonal::shared_ptr correctionNoise; // 修正噪声
  23. gtsam::Vector noiseModelBetweenBias;
  24. // 预积分
  25. // 负责预积分两个激光里程计之间的imu数据,作为约束加入因子图,并且优化出bias
  26. // 这个和优化有关
  27. gtsam::PreintegratedImuMeasurements *imuIntegratorOpt_;
  28. // 用来根据新的激光里程计到达后已经优化好的bias,预测从当前帧开始,下一帧激光里程计到达之前的imu里程计增量
  29. // 这个和imu预测有关
  30. gtsam::PreintegratedImuMeasurements *imuIntegratorImu_;
  31. // 下面这两个双端队列,用来存储上面的两个指针(或者叫数据来源)
  32. // 给imuIntegratorOpt_提供数据来源,不要的就弹出(从队头开始出发,比当前激光里程计数据早的imu通通积分,用一个仍一个)
  33. std::deque<sensor_msgs::Imu> imuQueOpt;
  34. // 给imuIntegratorImu_提供数据来源,不要的就弹出(弹出当前激光里程计之前的imu数据,预计分用完一个弹一个)
  35. std::deque<sensor_msgs::Imu> imuQueImu;
  36. // imu因子图优化过程中的状态变量
  37. gtsam::Pose3 prevPose_;
  38. gtsam::Vector3 prevVel_;
  39. gtsam::NavState prevState_;
  40. gtsam::imuBias::ConstantBias prevBias_;
  41. // imu状态
  42. gtsam::NavState prevStateOdom;
  43. gtsam::imuBias::ConstantBias prevBiasOdom;
  44. bool doneFirstOpt = false; // 这个应该是判断是否是第一次来优化的
  45. double lastImuT_imu = -1;
  46. double lastImuT_opt = -1;
  47. // iSAM2优化器
  48. gtsam::ISAM2 optimizer;
  49. gtsam::NonlinearFactorGraph graphFactors; // 总的因子图模型
  50. gtsam::Values graphValues; // 因子图模型中的值
  51. const double delta_t = 0;
  52. int key = 1;
  53. int imuPreintegrationResetId = 0; // 这个应该和reset有关系
  54. // 这里是imu-lidar位姿变换
  55. // 这里不可以理解为把imu数据转到lidar下的变换矩阵
  56. // 作者把imu数据先用imuConverter旋转到雷达系下(但其实还差个平移)
  57. // 通过lidar2Imu将雷达数据反向平移了一下,和转换以后差了个平移的imu数据在“中间系”对齐
  58. // 之后算完又从中间系通过imu2lidar挪回了雷达系进行publish
  59. gtsam::Pose3 imu2Lidar = gtsam::Pose3(gtsam::Rot3(1, 0, 0, 0), gtsam::Point3(-extTrans.x(), -extTrans.y(), -extTrans.z()));
  60. gtsam::Pose3 lidar2Imu = gtsam::Pose3(gtsam::Rot3(1, 0, 0, 0), gtsam::Point3(extTrans.x(), extTrans.y(), extTrans.z()));;

一些变量,这里没啥好解释的,所以就直接看里面的内容就可以。

  1. IMUPreintegration()
  2. {
  3. // nh == nodehandle 是个函数句柄
  4. // 订阅imu原始数据,用下面因子图优化的结果,施加两帧之间的imu预积分量,预测每一时刻(imu频率)的imu里程计
  5. subImu = nh.subscribe<sensor_msgs::Imu> (imuTopic, 2000, &IMUPreintegration::imuHandler, this, ros::TransportHints().tcpNoDelay()); // 最后这个参数是ros里面信息的传输方式,这里用的是tcp传输,而且是无延迟的
  6. //订阅激光里程计,来自mapOptimization,用两帧之间的imu预积分量构建因子图,优化当前帧位姿(这个位姿仅用于更新每时刻的imu里程计,以及下一次因子图优化)
  7. subOdometry = nh.subscribe<nav_msgs::Odometry>(PROJECT_NAME + "/lidar/mapping/odometry", 5, &IMUPreintegration::odometryHandler, this, ros::TransportHints().tcpNoDelay());
  8. // 发布imu里程计
  9. pubImuOdometry = nh.advertise<nav_msgs::Odometry> ("odometry/imu", 2000);
  10. pubImuPath = nh.advertise<nav_msgs::Path> (PROJECT_NAME + "/lidar/imu/path", 1);
  11. map_to_odom = tf::Transform(tf::createQuaternionFromRPY(0, 0, 0), tf::Vector3(0, 0, 0));
  12. // imu预积分的噪声协方差
  13. boost::shared_ptr<gtsam::PreintegrationParams> p = gtsam::PreintegrationParams::MakeSharedU(imuGravity);
  14. // 加速计的 pow(x,y)是求x的y次方
  15. p->accelerometerCovariance = gtsam::Matrix33::Identity(3,3) * pow(imuAccNoise, 2); // acc white noise in continuous 加速计的连续白噪声
  16. // 陀螺仪的
  17. p->gyroscopeCovariance = gtsam::Matrix33::Identity(3,3) * pow(imuGyrNoise, 2); // gyro white noise in continuous
  18. // 积分的
  19. p->integrationCovariance = gtsam::Matrix33::Identity(3,3) * pow(1e-4, 2); // error committed in integrating position from velocities
  20. gtsam::imuBias::ConstantBias prior_imu_bias((gtsam::Vector(6) << 0, 0, 0, 0, 0, 0).finished());; // assume zero initial bias 假设零初始偏差
  21. // 噪声先验
  22. // 这里是噪声,猜测后面的sigma函数是有关这个噪声分布的,然后注释后面跟着每个数值的单位
  23. priorPoseNoise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 1e-2, 1e-2, 1e-2, 1e-2, 1e-2, 1e-2).finished()); // rad,rad,rad,m, m, m
  24. priorVelNoise = gtsam::noiseModel::Isotropic::Sigma(3, 1e2); // m/s
  25. priorBiasNoise = gtsam::noiseModel::Isotropic::Sigma(6, 1e-3); // 1e-2 ~ 1e-3 seems to be good
  26. // 激光里程计scan-to-map优化过程中发生退化,则选择一个较大的协方差
  27. correctionNoise = gtsam::noiseModel::Isotropic::Sigma(6, 1e-2); // meter
  28. noiseModelBetweenBias = (gtsam::Vector(6) << imuAccBiasN, imuAccBiasN, imuAccBiasN, imuGyrBiasN, imuGyrBiasN, imuGyrBiasN).finished();
  29. // imu预积分器,用于预测每一时刻(imu频率)的imu里程计(转到lidar系,与激光里程计同一个系)
  30. imuIntegratorImu_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for IMU message thread
  31. // imu预积分器,用于因子图优化 这里的Opt是optimization的缩写
  32. imuIntegratorOpt_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for optimization
  33. }

构造函数,里面用了下面的odometryhandler和imuhandler,其他的话没啥好解释的,就看里面的内容就可以。然后这个sigma()函数的话,个人认为是和分布有关的函数。

  1. void resetOptimization()
  2. {
  3. //ISAM2的参数类 重新赋值
  4. gtsam::ISAM2Params optParameters;
  5. optParameters.relinearizeThreshold = 0.1; // relinearize 重新线性化
  6. optParameters.relinearizeSkip = 1;
  7. //优化器 根据参数重新生成优化器
  8. optimizer = gtsam::ISAM2(optParameters);
  9. gtsam::NonlinearFactorGraph newGraphFactors;
  10. graphFactors = newGraphFactors;
  11. gtsam::Values NewGraphValues;
  12. graphValues = NewGraphValues;
  13. }

重新设置图优化的参数,这里的话就关注一下图优化里面的内容,就是一个graphFactors graphValues的值就可以。

  1. void resetParams()
  2. {
  3. //将上一帧imu数据的时间戳更新成-1(代表还没有imu数据进来)
  4. lastImuT_imu = -1;
  5. //false代表需要重新进行一次odom优化(跟imuHandler联系起来)
  6. doneFirstOpt = false;
  7. //系统关闭
  8. systemInitialized = false;
  9. }

重新设置参数,这个没啥好解释的,然后里面的值的话用在下面函数的if判断条件里面,逻辑搞清楚即可。

  1. /*
  2. 里程计回调函数
  3. 每隔100帧激光里程计,重置iSAM2优化器,添加里程计、速度、偏置先验因子,执行优化
  4. 计算前一帧激光里程计与当前帧激光里程计之间的imu预积分量,用前一帧状态施加预积分量得到当前帧初始状态估计,添加来自mapOptimization的
  5. 当前位姿,进行因子图优化,更新当前帧状态。
  6. 优化之后,获得imu真实的bias,用来计算当前时刻之后的imu预积分。
  7. */
  8. void odometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
  9. {
  10. // 当前帧激光里程计时间戳 就是获取时间的
  11. double currentCorrectionTime = ROS_TIME(odomMsg);
  12. // make sure we have imu data to integrate
  13. // 确保imu优化队列中有imu数据进行预积分
  14. // 在imuHander函数中,会将imu数据push到队列中,数据是imu原始数据经过旋转但没平移到雷达坐标系下的数据
  15. if (imuQueOpt.empty())
  16. return;
  17. // 当前帧激光位姿,来自scan-to-map匹配,因子图优化后的位姿
  18. float p_x = odomMsg->pose.pose.position.x;
  19. float p_y = odomMsg->pose.pose.position.y;
  20. float p_z = odomMsg->pose.pose.position.z;
  21. // 这个就是四元数的那四个值
  22. float r_x = odomMsg->pose.pose.orientation.x;
  23. float r_y = odomMsg->pose.pose.orientation.y;
  24. float r_z = odomMsg->pose.pose.orientation.z;
  25. float r_w = odomMsg->pose.pose.orientation.w;
  26. // 四舍五入 这个resetid一直没太看懂,后面是四舍五入了方差的第一个元素
  27. int currentResetId = round(odomMsg->pose.covariance[0]);
  28. // 用订阅的激光雷达里程计消息,初始化一个lidarpose 包括一个四元数和points
  29. gtsam::Pose3 lidarPose = gtsam::Pose3(gtsam::Rot3::Quaternion(r_w, r_x, r_y, r_z), gtsam::Point3(p_x, p_y, p_z));
  30. // correction pose jumped, reset imu pre-integration
  31. //比较当前里程计id 和 imu预积分的id 来判断是否属于同一个时间段内,雷达的id 可以连续几帧都是一个id
  32. if (currentResetId != imuPreintegrationResetId)
  33. {
  34. resetParams();
  35. imuPreintegrationResetId = currentResetId;
  36. return;
  37. }
  38. // 0. initialize system
  39. // 系统初始化,第一帧
  40. if (systemInitialized == false)
  41. {
  42. resetOptimization(); // 具体可以转到这个函数的声明,没啥难度 先重新设置参数
  43. // pop old IMU message
  44. // 从imu优化队列中删除当前帧激光里程计时刻之前的imu数据
  45. while (!imuQueOpt.empty())
  46. {
  47. // 最终得到了离currentCorrectionTime - delta_t 最近的那个imu时间
  48. if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t)
  49. {
  50. // 这里判断条件中 delta_t的值为0
  51. //将队首元素的时间戳记录下来,更新lastImuT_opt,再将元素弹出
  52. lastImuT_opt = ROS_TIME(&imuQueOpt.front());
  53. imuQueOpt.pop_front();
  54. }
  55. else
  56. break;
  57. }
  58. // initial pose
  59. // 添加里程计位姿先验因子
  60. // 将雷达位置转换到Imu坐标系下
  61. prevPose_ = lidarPose.compose(lidar2Imu);
  62. gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, priorPoseNoise);
  63. // 先验因子中的参数:位姿信息和噪声,在构造函数中初始化过,是一个常量
  64. // 上面priorPose里面的第一个参数X ,参见:using gtsam::symbol_shorthand::X; // Pose3 (x,y,z,r,p,y) 姿态
  65. graphFactors.add(priorPose); // 添加先验位姿 先验是计算得到的,后验是观测得到的
  66. // initial velocity
  67. // 添加里程计速度先验因子
  68. prevVel_ = gtsam::Vector3(0, 0, 0);
  69. gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, priorVelNoise); // 这个没啥好说的 和上面是一个模式
  70. // 类似于位姿先验因子的添加
  71. graphFactors.add(priorVel);
  72. // initial bias
  73. // 添加imu偏差先验因子
  74. prevBias_ = gtsam::imuBias::ConstantBias();
  75. gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, priorBiasNoise);
  76. graphFactors.add(priorBias);
  77. // add values
  78. // 变量节点赋初值,没啥好说的,这里赋值为0
  79. graphValues.insert(X(0), prevPose_);
  80. graphValues.insert(V(0), prevVel_);
  81. graphValues.insert(B(0), prevBias_);
  82. // optimize once
  83. // 假定起始为0速度,进行一次优化,用于刚开始的scan-matching
  84. // 对于速度小于10m/s 角速度小于180/s 效果都非常好
  85. // 但是这总归是基于0起始速度估计的,起始估计并不是实际情况,因此消除图优化中内容
  86. optimizer.update(graphFactors, graphValues);
  87. // 下面清空操作的意义:但是这总归是基于0起始速度估计的,起始估计并不是实际情况,因此消除图优化中内容
  88. graphFactors.resize(0);
  89. graphValues.clear();
  90. //重置预积分器
  91. imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_);
  92. imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
  93. //key = 1 表示有一帧
  94. //系统初始化成功
  95. key = 1;
  96. systemInitialized = true;
  97. return;
  98. }
  99. // reset graph for speed
  100. if (key == 100)
  101. {
  102. // get updated noise before reset
  103. // 下面三行赋值操作
  104. gtsam::noiseModel::Gaussian::shared_ptr updatedPoseNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(X(key-1)));
  105. gtsam::noiseModel::Gaussian::shared_ptr updatedVelNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(V(key-1)));
  106. gtsam::noiseModel::Gaussian::shared_ptr updatedBiasNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(B(key-1)));
  107. // reset graph
  108. resetOptimization();
  109. // 下面就是一些和上面初始化相同的操作,就是初始化个值为0的容器,然后加入到因子图中,然后因子图的值也赋为0
  110. // add pose
  111. gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, updatedPoseNoise);
  112. graphFactors.add(priorPose);
  113. // add velocity
  114. gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, updatedVelNoise);
  115. graphFactors.add(priorVel);
  116. // add bias
  117. gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, updatedBiasNoise);
  118. graphFactors.add(priorBias);
  119. // add values
  120. graphValues.insert(X(0), prevPose_);
  121. graphValues.insert(V(0), prevVel_);
  122. graphValues.insert(B(0), prevBias_);
  123. // optimize once
  124. // 假定起始为0速度,进行一次优化,用于刚开始的scan-matching
  125. // 对于速度小于10m/s 角速度小于180/s 效果都非常好
  126. // 但是这总归是基于0起始速度估计的,起始估计并不是实际情况,因此消除图优化中内容
  127. optimizer.update(graphFactors, graphValues);
  128. graphFactors.resize(0);
  129. graphValues.clear();
  130. key = 1;
  131. }
  132. // 1. integrate imu data and optimize
  133. // 初始化完成后就可以估计IMU偏差,机器人位姿,速度
  134. // 计算前一帧与当前帧之间的imu预积分量,用前一帧状态施加预积分量得到当前帧初始状态估计
  135. // 添加来自mapOptimization的当前帧位姿,进行因子图优化,更新当前帧状态
  136. while (!imuQueOpt.empty())
  137. {
  138. // pop and integrate imu data that is between two optimizations
  139. // 提取前一帧与当前帧之间的imu数据,计算预积分
  140. sensor_msgs::Imu *thisImu = &imuQueOpt.front();
  141. double imuTime = ROS_TIME(thisImu);
  142. // 判断这一帧是否已经超过了当前激光雷达帧的时间戳
  143. if (imuTime < currentCorrectionTime - delta_t)
  144. {
  145. double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt);
  146. // 此处积分只用到了线加速度和角速度
  147. // 加入的是用来因子图优化的预积分器imuIntegratorOpt_.注意加入了上一步计算出的dt
  148. // 作者要求的9轴imu数据中欧拉角在本程序中没有任何用到,全在地图优化里用到的
  149. imuIntegratorOpt_->integrateMeasurement(
  150. gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
  151. gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt);
  152. lastImuT_opt = imuTime;
  153. //从队列中删除已经处理的imu数据
  154. imuQueOpt.pop_front();
  155. }
  156. else
  157. break;
  158. }
  159. // add imu factor to graph
  160. // 加imu预积分因子,利用两帧之间的IMU数据完成了预积分后增加imu因子到因子图中
  161. const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);
  162. //函数参数:前一帧位姿,前一帧速度,当前帧位姿,当前帧速度,前一帧偏差,预积分量
  163. gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
  164. graphFactors.add(imu_factor);
  165. // add imu bias between factor
  166. graphFactors.add(gtsam::BetweenFactor<gtsam::imuBias::ConstantBias>(B(key - 1), B(key), gtsam::imuBias::ConstantBias(),
  167. gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias)));
  168. // add pose factor
  169. gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu);
  170. gtsam::PriorFactor<gtsam::Pose3> pose_factor(X(key), curPose, correctionNoise);
  171. //添加位姿因子
  172. graphFactors.add(pose_factor);
  173. // insert predicted values
  174. //用前一帧的状态、偏差、施加imu预积分量来得到当前帧的状态
  175. gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);
  176. graphValues.insert(X(key), propState_.pose());
  177. graphValues.insert(V(key), propState_.v());
  178. graphValues.insert(B(key), prevBias_);
  179. // optimize 这里的优化为什么也清零了?? 可能每个优化都会清零,也有可能是这还在初始阶段,优化结果不准确
  180. optimizer.update(graphFactors, graphValues);
  181. optimizer.update();
  182. graphFactors.resize(0);
  183. graphValues.clear();
  184. // Overwrite the beginning of the preintegration for the next step.
  185. // 优化结果
  186. gtsam::Values result = optimizer.calculateEstimate();
  187. // 更新当前帧位姿、速度 --> 上一帧 这步就是当前帧的变成了上一帧的
  188. prevPose_ = result.at<gtsam::Pose3>(X(key));
  189. prevVel_ = result.at<gtsam::Vector3>(V(key));
  190. // 更新当前帧状态 --> 上一帧
  191. prevState_ = gtsam::NavState(prevPose_, prevVel_);
  192. // 更新当前帧imu偏差
  193. prevBias_ = result.at<gtsam::imuBias::ConstantBias>(B(key));
  194. // Reset the optimization preintegration object.
  195. // 重置预积分器,设置新的偏差,这样下一帧激光里程计进来的时候,预积分量就是两帧之间的增量
  196. imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
  197. // check optimization
  198. // 检查是否失效 对速度 和 偏差进行检查
  199. // 这个判断里面的failureDetection很easy
  200. if (failureDetection(prevVel_, prevBias_))
  201. {
  202. resetParams();
  203. return;
  204. }
  205. // 2. after optiization, re-propagate imu odometry preintegration
  206. // re-propagate:重新传播
  207. prevStateOdom = prevState_;
  208. prevBiasOdom = prevBias_;
  209. // first pop imu message older than current correction data
  210. double lastImuQT = -1;
  211. // 从imu队列中删除当前激光里程计时刻之前的imu数据
  212. while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t)
  213. {
  214. // 弹出
  215. lastImuQT = ROS_TIME(&imuQueImu.front());
  216. imuQueImu.pop_front();
  217. }
  218. // repropogate
  219. // 理由:对剩余的imu数据计算预积分,因为bias改变了
  220. if (!imuQueImu.empty())
  221. {
  222. // reset bias use the newly optimized bias
  223. // 重置预积分器和最新的偏置,使用最新的偏差更新bias值
  224. imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);
  225. // integrate imu message from the beginning of this optimization
  226. for (int i = 0; i < (int)imuQueImu.size(); ++i)
  227. {
  228. //重新进行预积分
  229. //与imuHandler中的预积分过程相同
  230. sensor_msgs::Imu *thisImu = &imuQueImu[i];
  231. double imuTime = ROS_TIME(thisImu);
  232. double dt = (lastImuQT < 0) ? (1.0 / 500.0) :(imuTime - lastImuQT);
  233. imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
  234. gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt);
  235. lastImuQT = imuTime;
  236. }
  237. }
  238. //记录帧数
  239. ++key;
  240. //优化器开启
  241. doneFirstOpt = true;
  242. }

里程计handler函数,可以说句柄函数,也可以说是回调函数。然后这里的话就先判断各种条件,然后初始化系统,然后判断帧是否到了100,如果到了100就重新设置图。个人认为到了100帧有可能累计误差太大的原因,会导致图优化不准确,因此需要重置一下。然后重置以后,添加图因子,然后插入graphValues的值,然后用optimizer进行计算更新,最后的话对graphFactors和graphValues的值进行清空和重新赋值空间,这步操作在多处出现,应该是计算优化的一种套路。然后系统初始化以后开始积分imu的数据和优化,优化过后,re-propagate imu odometry preintegration。最后记录帧数 and doneFirstOpt的值进行更新一下就可以。这里的话,图优化的部分不是理解很深刻,就知道了一些基本的添加因子,计算的操作。对于到底是怎么计算的,还不是很清楚明白。

这里再整理一下这步的思路:

0. initialize system

1. integrate imu data and optimize

2. after optiization, re-propagate imu odometry preintegration

就是以上三步,完成了odomHandler里面的主要内容。

  1. //速度大于30或者偏差大于0.1,则返回失效,否则没有失效
  2. bool failureDetection(const gtsam::Vector3& velCur, const gtsam::imuBias::ConstantBias& biasCur)
  3. {
  4. Eigen::Vector3f vel(velCur.x(), velCur.y(), velCur.z());
  5. if (vel.norm() > 30)
  6. {
  7. ROS_WARN("Large velocity, reset IMU-preintegration!");
  8. return true;
  9. }
  10. Eigen::Vector3f ba(biasCur.accelerometer().x(), biasCur.accelerometer().y(), biasCur.accelerometer().z());
  11. Eigen::Vector3f bg(biasCur.gyroscope().x(), biasCur.gyroscope().y(), biasCur.gyroscope().z());
  12. if (ba.norm() > 0.1 || bg.norm() > 0.1)
  13. {
  14. ROS_WARN("Large bias, reset IMU-preintegration!");
  15. return true;
  16. }
  17. return false;
  18. }

一个失败检测的小小函数,看看到底是怎么写的

  1. // header中包含了三个参数,方向协方差,角速度协方差,加速度协方差
  2. // 这个叫做回调函数
  3. // 接受从imu得到的原始数据进行处理,利用时间戳信息在imu与积分器中加入该帧
  4. // 然后利用上一帧中的激光里程计时刻对应的状态和偏差,加入当前帧的预测,得到当前时刻的状态。
  5. void imuHandler(const sensor_msgs::Imu::ConstPtr& imuMsg)
  6. {
  7. // 这里的imuConverter函数只有旋转,没有平移
  8. sensor_msgs::Imu thisImu = imuConverter(*imuMsg);
  9. //后续与thisImu有关的都是在差一个平移的雷达坐标系中
  10. // publish static tf
  11. tfMap2Odom.sendTransform(tf::StampedTransform(map_to_odom, thisImu.header.stamp, "map", "odom"));
  12. imuQueOpt.push_back(thisImu);
  13. imuQueImu.push_back(thisImu);
  14. // 这里要求上一次imu因子图优化执行成功,确保更新了上一帧的状态、bias、预积分的信息
  15. // 如果没有执行过odom优化,或者上一次优化失败导致系统重置,则等待一次odom的优化再继续函数流程。
  16. if (doneFirstOpt == false)
  17. return;
  18. // 获取当前imu因子图的时间戳
  19. double imuTime = ROS_TIME(&thisImu);
  20. // double imuTime = ROS_TIME(&thisImu);
  21. // lastImuT_imu初始值为-1
  22. // 如果首次优化,则定义初始时间为1/500秒,否则与上一帧作差
  23. double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu);
  24. // 更新上一帧imu因子图的时间戳
  25. lastImuT_imu = imuTime;
  26. // integrate this single imu message
  27. // imu预积分器添加一帧imu数据
  28. imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y, thisImu.linear_acceleration.z),
  29. gtsam::Vector3(thisImu.angular_velocity.x, thisImu.angular_velocity.y, thisImu.angular_velocity.z), dt);
  30. // predict odometry
  31. // 用上一帧激光里程计时刻对应的信息,加上imu预积分量,得到当前时刻的状态。
  32. gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);
  33. // publish odometry
  34. // 发布imu里程计(转到lidar系,与激光里程计同一个系)
  35. // 这里出现header的第二个内容frame_id 帧的名称
  36. nav_msgs::Odometry odometry;
  37. odometry.header.stamp = thisImu.header.stamp;
  38. odometry.header.frame_id = "odom";
  39. odometry.child_frame_id = "odom_imu";
  40. // transform imu pose to ldiar
  41. // 预测值curretnState获得imu位姿,再由imu到雷达变换,获得雷达位姿
  42. gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position());
  43. gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar);
  44. // pose表示位置的point 和 quaternion,以及对应的协方差矩阵
  45. // twist表示速度的linear 和 angular,以及对应的协方差矩阵
  46. // 第一个pose:geometry_msgs/PoseWithCovariance
  47. // 第二个pose:geometry_msgs/Pose,包含一个point 和 一个quaternion
  48. odometry.pose.pose.position.x = lidarPose.translation().x();
  49. odometry.pose.pose.position.y = lidarPose.translation().y();
  50. odometry.pose.pose.position.z = lidarPose.translation().z();
  51. odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x();
  52. odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y();
  53. odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z();
  54. odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w();
  55. odometry.twist.twist.linear.x = currentState.velocity().x();
  56. odometry.twist.twist.linear.y = currentState.velocity().y();
  57. odometry.twist.twist.linear.z = currentState.velocity().z();
  58. odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x();
  59. odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y();
  60. odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z();
  61. // information for VINS initialization
  62. //8个是什么要清楚,但是第一个好像没看懂,有点套娃的感觉
  63. odometry.pose.covariance[0] = double(imuPreintegrationResetId); // 第一个元素是imuPreintegrationResetId 这个地方先存疑
  64. odometry.pose.covariance[1] = prevBiasOdom.accelerometer().x();
  65. odometry.pose.covariance[2] = prevBiasOdom.accelerometer().y();
  66. odometry.pose.covariance[3] = prevBiasOdom.accelerometer().z();
  67. odometry.pose.covariance[4] = prevBiasOdom.gyroscope().x();
  68. odometry.pose.covariance[5] = prevBiasOdom.gyroscope().y();
  69. odometry.pose.covariance[6] = prevBiasOdom.gyroscope().z();
  70. odometry.pose.covariance[7] = imuGravity;
  71. //发布里程计信息
  72. pubImuOdometry.publish(odometry);
  73. // publish imu path
  74. static nav_msgs::Path imuPath;
  75. static double last_path_time = -1;
  76. //当前imu因子图的时间戳
  77. if (imuTime - last_path_time > 0.1)
  78. {
  79. last_path_time = imuTime;
  80. //定义一个带有时间戳的pose的信息
  81. geometry_msgs::PoseStamped pose_stamped;
  82. /*
  83. 把这一帧imu的时间戳、关联的坐标系id、位置相关信息赋值给pose_stamped
  84. thisImu是在原始的imu数据旋转到雷达坐标系后的数据
  85. */
  86. pose_stamped.header.stamp = thisImu.header.stamp;
  87. pose_stamped.header.frame_id = "odom";
  88. pose_stamped.pose = odometry.pose.pose;
  89. //加入path
  90. imuPath.poses.push_back(pose_stamped);
  91. while(!imuPath.poses.empty() && abs(imuPath.poses.front().header.stamp.toSec() - imuPath.poses.back().header.stamp.toSec()) > 3.0)
  92. imuPath.poses.erase(imuPath.poses.begin());
  93. if (pubImuPath.getNumSubscribers() != 0)
  94. {
  95. //给imuPath添加时间戳stamp 和 坐标系id frame_id
  96. imuPath.header.stamp = thisImu.header.stamp;
  97. imuPath.header.frame_id = "odom";
  98. //发布消息
  99. pubImuPath.publish(imuPath);
  100. }
  101. }
  102. // publish transformation
  103. // 发布odom到base_link的变换,其实也就是到imu的变换
  104. tf::Transform tCur;
  105. // pose.pose 是imu里程计的pose信息(位置和方向)
  106. tf::poseMsgToTF(odometry.pose.pose, tCur);
  107. tf::StampedTransform odom_2_baselink = tf::StampedTransform(tCur, thisImu.header.stamp, "odom", "base_link");
  108. tfOdom2BaseLink.sendTransform(odom_2_baselink);
  109. }
  110. };

这里的话,就涉及到了坐标系的转换,一定要弄清楚这里面的关系。这一步的话,没看出来有啥特别的,就是再不同坐标系更新数据,然后进行发布。这里我没看来什么特别的地方。

  1. int main(int argc, char** argv)
  2. {
  3. ros::init(argc, argv, "lidar");
  4. IMUPreintegration ImuP;
  5. ROS_INFO("\033[1;32m----> Lidar IMU Preintegration Started.\033[0m");
  6. ros::spin();
  7. return 0;
  8. }

然后是主函数,先ros::init,然后类的实体,然后spin()在监控回调函数,这一部分基本ok。

这部分的话就是预积分的内容,虽然代码看过感觉没问题,但是里面的具体的计算还是没有进行,这部分还是要继续看。

问题:

odometry.pose.covariance[0] = double(imuPreintegrationResetId); // 第一个元素是imuPreintegrationResetId 这个地方先存疑

这个重新set的值到底来自哪里,并且它的数据记录都是啥样的没看懂,我看了一下上下文,一种套娃的感觉。

这部分的话,就到这里。然后现在知道了header里面不仅有stamp 还有 frame_id这个东西。这个header还是要多加关注,而且那个covariance[0]的那个内容也得多加关注!

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号