• LIO-SAM 详读代码笔记--2.imuPreintegration


    先把系统topic 数据流图搬过来,能更加清晰的理解这个模块。

     

    图2.ros topic 发布与订阅关系图关系图(还是叫图2,因为是同一个图)

    这个cpp文件主要有两个类,一个叫IMUPreintegration类,一个叫TransformFusion类。从数据流向和依赖程度的角度分析,应该先分析IMUPreintegration类,但是TransformFusion类写在该模块的前头,且这两个模块的数据流相互依赖,这里先分析TransformFusion类。

    TransformFusion类

    讲解之前,二话不说,先上该类的成员变量列表。

    变量名类型注释备注
    mtxstd::mutex因为ros回调函数是异步运行,在回调函数操作成员变量或者队列时,要先上锁-
    subImuOdometryros::Subscriber订阅"odometry/imu_incremental" 虽然名字上有incremental ,但里程计,他就是里程计,代表着该时刻固定的frame 的位姿信息,由下面的imuPreintegration 发出, 虽然 由 gtsam 的预积分器算出来,但完全可以理解为不与预积分有关系,单纯文imu 数据的位姿积分解算 在这个类中 其中一个主要的目的就是 在优化过后的lidar 里程计基础上,叠加上lidar时刻到至今时刻的位姿差,以发出更高频率的里程计定位数据-
    subLaserOdometryros::Subscriber订阅"lio_sam/mapping/odometry" 就是经过MapOptimization优化后的里程计信息,接收周期比慢一个量级,与 "odometry/imu_incremental" 的 frameid 应该是一致的,都是 odomframe,child_frame_id 都是odom_lidar 注意,这里/mapping/odometry和/mapping/odometry_incremental的区别,前者是激光里程计部分直接优化得到的激光位姿,后者相当于激光的位姿经过旋转约束和z轴约束的限制以后,又和原始imu信息里面的角度做了一个加权以后的位姿。-
    pubImuOdometryros::Publisher发布imu 里程计 "odometry/imu" 结合了lidar 里程计的IMU 里程计# IMU pre-preintegration odometry, same frequency as IMU-
    pubImuPathros::Publisher消息名为 "lio_sam/imu/path" ,在图2 中由 rviz 订阅,其发布imu 里程计 "odometry/imu" 对应的轨迹,发布频率是10hz,每次发布都只是一小段,即lidar时刻到当前imu 时刻的位姿数组-
    lidarOdomAffineEigen::Affine3f就是把"lio_sam/mapping/odometry" 的消息转化为位姿变换矩阵,代表但前lidar时刻 (机器人)的位姿-
    lidarOdomAffineEigen::Affine3f就是把"lio_sam/mapping/odometry" 的消息转化为位姿变换矩阵,代表但前lidar时刻 的位姿-
    imuOdomAffineFrontEigen::Affine3f在loam 系列代码里看到这一对 front /back 的变换矩阵,就应该想到作差 相对位姿变换,这个变换矩阵是由lidar 时刻的最近的imu时刻的"odometry/imu_incremental" 的里程计消息转化而来,代表那个时刻的位姿-
    imuOdomAffineBackEigen::Affine3f这个变换矩阵是由当前时刻imu时刻的"odometry/imu_incremental" 的里程计消息转化而来,代表那个时刻的位姿,作用是 与imuOdomAffineFront 作差后,附加到lidarOdomAffine变换上,得到结合地图优化的位姿的更高频率的位姿信息-
    tfListenertf::TransformListener从tf 树中得到 lidar2Baselink 的外参关系-
    lidar2Baselinktf::StampedTransform如果imu里程计与lidar 里程计的坐标系不是同一个坐标系,需要转化到同一个坐标系两个里程计数据才能融合-
    lidarOdomTimefloatlidar时刻的时间戳,每次lidar 里程计来了都更换一次,imu 里程计队列imuOdomQueue的数据,都保留这个时刻之后的数据-
    imuOdomQueuedequeimu 里程计队列,都保留lidar时刻开始之后的数据,便于寻找lidar时刻imu的里程计,和当前时刻里程计,作差,附加到lidar 里程计中去-

    纵观TransformFusion类,该类只干一件事,就是融合lidar里程计(经过mapOptimization 优化过的)和imu 积分的里程计,以比lidar里程计更高的频率分布出去,一个是odometry/imu,一个是odom 到 baselink 的tf 关系,这个就是整个系统的主要输出。

    主要的处理逻辑在imu 里程计的回调函数中,imuOdometryHandler,在函数里面监听的是"odometry/imu_incremental"(在下面imuPreintegration中发出,是imu 独立解算的里程计),把imu里程计放到imuodomQueue里面保存,然后把lidarOdomTime之前的数据扔掉,之后用最新和最旧时刻的mu里程计的计算差异,如图3所示,再加上lidarOdomAffine的基础进行发布。

     

    另外还会发布一个path,用来rviz显示,名字叫lio-sam/imu/path。

    1. /**
    2. * 激光雷达里程计就保存当前的雷达里程计的数据到lidarOdomAffine里面,
    3. * 把时间戳存到lidarOdomTime里面去,没干别的,
    4. * 具体的主要逻辑在imu 里程计的回调函数中处理。
    5. *
    6. * */
    7. void lidarOdometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
    8. {
    9. std::lock_guard lock(mtx);
    10. lidarOdomAffine = odom2affine(*odomMsg);
    11. lidarOdomTime = odomMsg->header.stamp.toSec();
    12. }
    13. /**
    14. * 主要的处理逻辑在imu 里程计的回调函数中,`imuOdometryHandler`,
    15. * 在函数里面监听的是/imu/odometry_incremental(在下面imuPreintegration中发出,
    16. * 是imu 独立解算的里程计),把imu里程计放到imuodomQueue里面保存,
    17. * 然后把lidarOdomTime之前的数据扔掉,之后用最新和最旧时刻的mu里程计的计算差异,
    18. * 再加上lidarOdomAffine的基础进行发布。
    19. * */
    20. void imuOdometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
    21. {
    22. // static tf 这个地方究竟是在干什么?
    23. static tf::TransformBroadcaster tfMap2Odom;
    24. static tf::Transform map_to_odom = tf::Transform(tf::createQuaternionFromRPY(0, 0, 0), tf::Vector3(0, 0, 0));
    25. tfMap2Odom.sendTransform(tf::StampedTransform(map_to_odom, odomMsg->header.stamp, mapFrame, odometryFrame));
    26. std::lock_guard lock(mtx);
    27. imuOdomQueue.push_back(*odomMsg);
    28. // get latest odometry (at current IMU stamp)
    29. if (lidarOdomTime == -1)
    30. return;
    31. while (!imuOdomQueue.empty())
    32. {
    33. if (imuOdomQueue.front().header.stamp.toSec() <= lidarOdomTime)
    34. imuOdomQueue.pop_front();
    35. else
    36. break;
    37. }
    38. Eigen::Affine3f imuOdomAffineFront = odom2affine(imuOdomQueue.front());//前
    39. Eigen::Affine3f imuOdomAffineBack = odom2affine(imuOdomQueue.back());// 后
    40. Eigen::Affine3f imuOdomAffineIncre = imuOdomAffineFront.inverse() * imuOdomAffineBack;
    41. Eigen::Affine3f imuOdomAffineLast = lidarOdomAffine * imuOdomAffineIncre;
    42. float x, y, z, roll, pitch, yaw;
    43. pcl::getTranslationAndEulerAngles(imuOdomAffineLast, x, y, z, roll, pitch, yaw);
    44. // publish latest odometry
    45. nav_msgs::Odometry laserOdometry = imuOdomQueue.back();
    46. laserOdometry.pose.pose.position.x = x;
    47. laserOdometry.pose.pose.position.y = y;
    48. laserOdometry.pose.pose.position.z = z;
    49. laserOdometry.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw);
    50. pubImuOdometry.publish(laserOdometry);
    51. // publish tf
    52. static tf::TransformBroadcaster tfOdom2BaseLink;
    53. tf::Transform tCur;
    54. tf::poseMsgToTF(laserOdometry.pose.pose, tCur);
    55. if(lidarFrame != baselinkFrame)
    56. tCur = tCur * lidar2Baselink;
    57. tf::StampedTransform odom_2_baselink = tf::StampedTransform(tCur, odomMsg->header.stamp, odometryFrame, baselinkFrame);
    58. // 发布 odom 到 baselink 的tf 关系
    59. tfOdom2BaseLink.sendTransform(odom_2_baselink);
    60. // publish IMU path
    61. static nav_msgs::Path imuPath; // 静态数据,Path 实际是pose数组,
    62. static double last_path_time = -1;
    63. double imuTime = imuOdomQueue.back().header.stamp.toSec();
    64. // 控制发送频率 10HZ
    65. if (imuTime - last_path_time > 0.1)
    66. {
    67. last_path_time = imuTime;
    68. geometry_msgs::PoseStamped pose_stamped;
    69. pose_stamped.header.stamp = imuOdomQueue.back().header.stamp;
    70. pose_stamped.header.frame_id = odometryFrame;
    71. pose_stamped.pose = laserOdometry.pose.pose;
    72. imuPath.poses.push_back(pose_stamped);
    73. // lidar 时间戳1秒钟以内的数据,以前的数据删掉
    74. while(!imuPath.poses.empty() && imuPath.poses.front().header.stamp.toSec() < lidarOdomTime - 1.0)
    75. imuPath.poses.erase(imuPath.poses.begin());
    76. if (pubImuPath.getNumSubscribers() != 0)
    77. {
    78. imuPath.header.stamp = imuOdomQueue.back().header.stamp;
    79. imuPath.header.frame_id = odometryFrame;
    80. pubImuPath.publish(imuPath);
    81. }
    82. }
    83. }

     

    IMUPreintegration 类

    成员变量列表:

    变量名类型注释备注
    mtxstd::mutex因为ros回调函数是异步运行,在回调函数操作成员变量或者队列时,要先上锁-
    subImuros::Subscriber//订阅"imu_raw",imu源数据-
    subOdometryros::Subscriber订阅 "lio_sam/mapping/odometry_incremental" 这个消息是从MapOptimization 发布,经过地图优化且经过z_torellen和imu 阈值修正-
    pubImuOdometryros::Publisher发布 "odometry/imu_incremental" 实际 TransformFusion是其中一个订阅者,在一个订阅者是imageProjection 虽然 frame_id = odometryFrame;child_frame_id = "odom_imu"; 但代码里实现应该是“odom_lidar”,因为IMU的数据转换成与Lidar坐标系同方向的数据,最后又右乘一个变换(imu2Lidar 实际不是) ;即T_ol = T_oi*T_il-
    systemInitializedbool初始化标志,因子图优化器初始化完成后,就设置为true,或者积分出来的数据超过阈值,需要重新因子图优化器初始化-
    预积分因子模型噪声模型参数-
    priorPoseNoisegtsam::noiseModel::Diagonal::shared_ptr位姿噪声模型-
    priorVelNoisegtsam::noiseModel::Diagonal::shared_ptr速度噪声模型-
    priorBiasNoisegtsam::noiseModel::Diagonal::shared_ptr偏差噪声模型-
    correctionNoisegtsam::noiseModel::Diagonal::shared_ptr激光雷达位姿噪声模型-
    correctionNoise2gtsam::noiseModel::Diagonal::shared_ptr激光雷达位姿噪声模型,协方差是否为单位阵,决定使用哪个噪声模型-
    noiseModelBetweenBiasgtsam::Vector两帧的bias 形成一个betweenFactor, 也需要提供一个噪声模型-
    *imuIntegratorOpt_gtsam::PreintegratedImuMeasurements预积分器,这个预积分提供预积分数据给因子图优化器-
    *imuIntegratorImu_gtsam::PreintegratedImuMeasurements预积分器,这个预积分提供预积分数据给里程计累加用-
    imuQueOptstd::deque预积分器对应的imu积分数据队列-
    imuQueImustd::deque预积分器对应的imu积分数据队列-
    prevPose_gtsam::Pose3上一帧lidar数据到来时因子图优化后的位姿-
    prevVel_gtsam::Vector3上一帧lidar数据到来时因子图优化后的速度-
    prevState_gtsam::NavState上一帧lidar数据到来时因子图优化后的状态,包含pose、vel-
    prevBias_gtsam::imuBias::ConstantBias上一帧lidar数据到来时因子图优化后的IMU偏差-
    prevStateOdomgtsam::NavState上一帧lidar数据到来时因子图优化后的状态,包含pose、vel;从prevState_ 赋值-
    prevBiasOdomgtsam::imuBias::ConstantBias上一帧lidar数据到来时因子图优化后的IMU偏差;从prevBias_ 赋值-
    doneFirstOptbool只有经过因子图优化后,才进行imuIntegratorImu_预积分累积里程计-
    lastImuT_imudouble上一次的imu 的时间戳,当前帧imu 来时,用当前imu时间戳 减去 这个值,得到dt,imu积分时需要这个值-
    lastImuT_optdouble意思同上,就是对应不同的预积分器而已-
    optimizergtsam::ISAM2因子图优化器,这里用ISAM2作为优化器的后端-
    graphFactorsgtsam::NonlinearFactorGraph因子图容器,用于添加各种因子-
    graphValuesgtsam::Values因子图中各个节点,也就是待优化的变量-
    delta_tconst double常量,距离lidar 时间戳的距离,这里固定为0-
    keyint计数器,每100次 重新初始化 因子图优化器,这个跟gtsam的实现有关系,每次优化保留一些东西在贝叶斯树里头,多了会影响速度-
    keyint计数器,每100次 重新初始化 因子图优化器,这个跟gtsam的实现有关系,每次优化保留一些东西在贝叶斯树里头,多了会影响速度-
    imu2Lidargtsam::Pose3原文注释:T_bl: tramsform points from lidar frame to imu frame 这个注释跟命名感觉不对劲,应该表示成Lidar2Imu ;在params.yaml 文件中 # Extrinsics: T_lb (lidar -> imu) 个人觉得也有问题 应该表示成T_lb (imu -> lidar) 但是 包括下边的两个变量的命名 应该是反着来的-
    lidar2Imugtsam::Pose3原文注释:T_bl: T_lb: tramsform points from imu frame to lidar frame 这个注释跟命名感觉不对劲,应该表示成imu2Lidar ,这个注释没有说到点子上,在整个系统中这两个变量只有在里程计变换中用到,而里程计变换的计算跟坐标系上的点坐标变换是有区别,具体看代码处的注释-

    关于IMU原始数据,送入imuhandle中:

    1.1 imuhandle:

    1.1.1 imu原始数据,会先被坐标系转换,通过调用头文件里的imuConverter函数,转换到一个“雷达中间系”中,其实和真正的雷达系差了一个平移。这里的操作就像对IMU 的数据进行修正,让IMU的坐标系与Lidar 坐标系的方向一致,然后 IMU 与 Lidar外参就只有位移关系了。乍一看,感觉作者故意写复杂了,但是个人猜想,1.由于IMU数据里面没有位移数据,不能进行直接变换到Lidar坐标系;2.速度变化很小,进行位移变换后是否影响积分精度。

    1.1.2 转换后,会存入两个队列,一个imuQueOpt队列,在另一个回调函数odometryHandler会被处理,用于预积分因子源数据,用来优化系统状态(R,P,V,B)和imu的bias。一个imuQueImu队列,imuQueImu是这里真正要用的数据,每到一个imu数据,就用imuIntegratorImu_这个预积分器,把这一帧imu数据累积进去,然后预测当前时刻的状态:currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom); 其中prevStateOdom就是之前系统所保持的状态。

    1.1.3 把currentState,通过imu2Lidar(其实是Lidar2Imu T_il)T_ol = T_oi * T_il,把IMU 积分得到的里程计转成真正的雷达里程计,然后发布出去。发布的话题就叫odometry/imu_incremental(实际是lidar 里程计),这也就是imageProjection.cpp的总结部分的第2点部分提到的“imu”里程计。虽然作者的变量命名很疑惑,但是代码实现得还是很老实的。

    1. void imuHandler(const sensor_msgs::Imu::ConstPtr& imu_raw)
    2. {
    3. std::lock_guard lock(mtx);
    4. // 这个函数这个地方写的非常绕,非值得注意的是,这里是把imu的数据转换到lidar坐标系下,
    5. // 但是又不完全转,只是保持与lidar坐标系的方向一致,差一个位移
    6. // 这里的操作就像对IMU 的数据进行修正,让IMU的坐标系与Lidar 坐标系的方向一致,
    7. // 然后 IMU 与 Lidar外参就只有位移关系了。
    8. sensor_msgs::Imu thisImu = imuConverter(*imu_raw);
    9. // 转换后的数据压入队列中
    10. imuQueOpt.push_back(thisImu);
    11. imuQueImu.push_back(thisImu);
    12. // 如果因子图还没有优化过,就直接返回了
    13. if (doneFirstOpt == false)
    14. return;
    15. // 获取该IMU数据的时间戳
    16. double imuTime = ROS_TIME(&thisImu);
    17. // 与上一帧IMU 数据时间戳作差,
    18. double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu);
    19. lastImuT_imu = imuTime;
    20. // 把当前帧的IMU 数据添加入预积分器
    21. // integrate this single imu message
    22. imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y, thisImu.linear_acceleration.z),
    23. gtsam::Vector3(thisImu.angular_velocity.x, thisImu.angular_velocity.y, thisImu.angular_velocity.z), dt);
    24. // 以上一帧lidar时刻因子图优化出来的状态为基准,预积分器预测出当前IMU时刻的位姿状态
    25. // predict odometry
    26. gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);
    27. // publish odometry
    28. nav_msgs::Odometry odometry;
    29. odometry.header.stamp = thisImu.header.stamp;
    30. odometry.header.frame_id = odometryFrame;
    31. // 这里实际应该是“odom_lidar”
    32. odometry.child_frame_id = "odom_imu";
    33. // transform imu pose to ldiar
    34. gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position());
    35. // imu2Lidar 变量的值给得应该表示成T_il,整个式子 则为 T_ol = T_oi*T_il
    36. gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar);
    37. odometry.pose.pose.position.x = lidarPose.translation().x();
    38. odometry.pose.pose.position.y = lidarPose.translation().y();
    39. odometry.pose.pose.position.z = lidarPose.translation().z();
    40. odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x();
    41. odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y();
    42. odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z();
    43. odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w();
    44. odometry.twist.twist.linear.x = currentState.velocity().x();
    45. odometry.twist.twist.linear.y = currentState.velocity().y();
    46. odometry.twist.twist.linear.z = currentState.velocity().z();
    47. odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x();
    48. odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y();
    49. odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z();
    50. pubImuOdometry.publish(odometry);
    51. }

    1.2. odomHandle:这部分订阅的是/lio_sam/mapping/odometry_incremental,这个话题是由mapOptmization.cpp发布的,就是经过了地图匹配优化后的位姿信息,里程计就是从odom到lidar的变换关系而不是两帧激光之间的变换,它和imu里程计性质类似,就是相对世界坐标系的位姿。

    1.2.1 初始化系统:

    从imuQueOpt队列里,删掉当前这帧激光里程计时间上之前的数据,取两个Lidar时刻之间的IMU 数据进行预积分累积,然后把雷达的pose变换到IMU “坐标系”(这里的IMU坐标经过了修正,方向与Lidar坐标系保持一致,外参只差一个位移),然后初始化因子图优化器,相关优化变量prevPose等。因为预积分器需要bias ,而bias每次优化都会发生变化,所以需要重新设置,所以把两个预积分器imuIntegratorImu_,imuIntegratorOpt_给reset一下。(之后简称imu积分器和opt积分器)

    1.2.2 清理缓存:

    100帧激光里程计数据了,就把优化器给reset一下(用前面的先验协方差给reset一下)。每100 次 重新设置优化器,这个是gtsam 的实现要求的,在每次优化的过程中, gtsam 会缓存一些数据在optimizer贝叶斯树中,随着优化次数增大,该树会越来越大,执行效率就会变慢,所以要周期性清理。 在清理之前,需要把优化变量的协方差给保存起来,给新的优化器使用,在最开始的时候 每个因子的噪声模型都是人工经验值,这里需要把协方差矩阵传递下去,给新的优化器

    1.2.3 正式处理:

    1.2.3.1 把两个Lidar 时刻的IMU数据拿出来,送入opt积分器。这样得到两个时刻之间的IMU预积分,构建imu因子。

    1.2.3.2 然后把关联Xkey-1 和Xkey的IMU 因子加入因子图 以及 激光里程计提供的pose先验因子,还有Bias BetweenFactor,整体做一个优化。优化的结果就是当前Lidar时刻的状态量(R,P,V,B)。

    1.2.3.3 把优化的结果(主要是bias),重置opt积分器和imu积分器。由于imuIntegratorImu_被重置了BIAS ,需要对之前的IMU 数据进行重新积分,并删掉使用过的数据,这就是在imuHandler 中积分却不删数据的原因。

    1. /**
    2. * @brief 激光雷达里程计的回调函数
    3. *
    4. * @param odomMsg
    5. */
    6. void odometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
    7. {
    8. std::lock_guard lock(mtx);
    9. // 获取lidar 时间戳
    10. double currentCorrectionTime = ROS_TIME(odomMsg);
    11. // make sure we have imu data to integrate
    12. if (imuQueOpt.empty())
    13. return;
    14. float p_x = odomMsg->pose.pose.position.x;
    15. float p_y = odomMsg->pose.pose.position.y;
    16. float p_z = odomMsg->pose.pose.position.z;
    17. float r_x = odomMsg->pose.pose.orientation.x;
    18. float r_y = odomMsg->pose.pose.orientation.y;
    19. float r_z = odomMsg->pose.pose.orientation.z;
    20. float r_w = odomMsg->pose.pose.orientation.w;
    21. // 是否退化,什么情况下会退化? 初始情况下odomMsg->pose.covariance[0] == 1
    22. bool degenerate = (int)odomMsg->pose.covariance[0] == 1 ? true : false;
    23. // 这个位姿 是直接转化的里程计消息数据
    24. gtsam::Pose3 lidarPose = gtsam::Pose3(gtsam::Rot3::Quaternion(r_w, r_x, r_y, r_z), gtsam::Point3(p_x, p_y, p_z));
    25. // 初始化
    26. // 0. initialize system
    27. if (systemInitialized == false)
    28. {
    29. // 重置Isam2 优化器
    30. 积分
    31. // 优化器
    32. // optimizer = gtsam::ISAM2(optParameters);
    33. // 因子图容器
    34. // graphFactors = newGraphFactors;
    35. // 待优化的变量
    36. // graphValues = NewGraphValues;
    37. resetOptimization();
    38. // pop old IMU message
    39. while (!imuQueOpt.empty())
    40. {
    41. // imuQueOpt数据中小于lidar时刻的数据全部pop 出去
    42. if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t)
    43. {
    44. lastImuT_opt = ROS_TIME(&imuQueOpt.front());
    45. imuQueOpt.pop_front();
    46. }
    47. else
    48. break;
    49. }
    50. // 因子图是贝叶斯公式推导过来的,P(x|z) = a*P(z|x)P(x) 其中a 为系数,P(z|x)为似然函数,P(x) 则为先验
    51. // 所以 因子图里都要添加进 先验P(x)
    52. // 这里把 因子图中第一个优化变量设为先验
    53. // initial pose
    54. prevPose_ = lidarPose.compose(lidar2Imu);// compose 是什么运算??转到IMU 系 todo:T_oi = T_ol * T_li
    55. gtsam::PriorFactor priorPose(X(0), prevPose_, priorPoseNoise);
    56. graphFactors.add(priorPose);
    57. // initial velocity
    58. prevVel_ = gtsam::Vector3(0, 0, 0);
    59. gtsam::PriorFactor priorVel(V(0), prevVel_, priorVelNoise);
    60. graphFactors.add(priorVel);
    61. // initial bias
    62. prevBias_ = gtsam::imuBias::ConstantBias();
    63. gtsam::PriorFactor priorBias(B(0), prevBias_, priorBiasNoise);
    64. graphFactors.add(priorBias);
    65. // add values
    66. graphValues.insert(X(0), prevPose_);
    67. graphValues.insert(V(0), prevVel_);
    68. graphValues.insert(B(0), prevBias_);
    69. // optimize once
    70. optimizer.update(graphFactors, graphValues);
    71. graphFactors.resize(0);
    72. graphValues.clear();
    73. //预积分器需要bias ,而bias每次优化都会发生变化,所以需要重新设置
    74. imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_);
    75. imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
    76. key = 1;
    77. systemInitialized = true;
    78. return;
    79. }
    80. // 每100 次 重新设置优化器,这个是gtsam 的实现要求的,在每次优化的过程中,
    81. // gtsam 会缓存一些数据在贝叶斯树中,随着优化次数增大,该树会越来越大,执行效率就会变慢,
    82. // 所以要周期性清理
    83. // reset graph for speed
    84. if (key == 100)
    85. {
    86. // 在最开始的时候 每个因子的噪声模型都是人工经验值,这里需要把协方差矩阵传递下去,给新的优化器
    87. // get updated noise before reset
    88. gtsam::noiseModel::Gaussian::shared_ptr updatedPoseNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(X(key-1)));
    89. gtsam::noiseModel::Gaussian::shared_ptr updatedVelNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(V(key-1)));
    90. gtsam::noiseModel::Gaussian::shared_ptr updatedBiasNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(B(key-1)));
    91. // reset graph
    92. resetOptimization();
    93. // 新的因子图需要新的先验
    94. // add pose
    95. gtsam::PriorFactor priorPose(X(0), prevPose_, updatedPoseNoise);
    96. graphFactors.add(priorPose);
    97. // add velocity
    98. gtsam::PriorFactor priorVel(V(0), prevVel_, updatedVelNoise);
    99. graphFactors.add(priorVel);
    100. // add bias
    101. gtsam::PriorFactor priorBias(B(0), prevBias_, updatedBiasNoise);
    102. graphFactors.add(priorBias);
    103. // add values
    104. graphValues.insert(X(0), prevPose_);
    105. graphValues.insert(V(0), prevVel_);
    106. graphValues.insert(B(0), prevBias_);
    107. // optimize once 预热一次
    108. optimizer.update(graphFactors, graphValues);
    109. graphFactors.resize(0);
    110. graphValues.clear();
    111. key = 1;
    112. }
    113. // 从这里开始 真正处理数据因子图优化了
    114. // 1. integrate imu data and optimize
    115. while (!imuQueOpt.empty())
    116. {
    117. // pop and integrate imu data that is between two optimizations
    118. sensor_msgs::Imu *thisImu = &imuQueOpt.front();
    119. double imuTime = ROS_TIME(thisImu);
    120. // 取两个lidar 时间戳之间的IMU 数据提供给IMU积分器进行积分,
    121. if (imuTime < currentCorrectionTime - delta_t)
    122. {
    123. double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt);
    124. imuIntegratorOpt_->integrateMeasurement(
    125. gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
    126. gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt);
    127. lastImuT_opt = imuTime;
    128. imuQueOpt.pop_front(); // 用一个 删一个,每一次优化周期内,没有上一周期的IMU数据
    129. }
    130. else
    131. break;
    132. }
    133. // add imu factor to graph
    134. const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);
    135. // IMU 因子 提供 K-1 时刻的状态变量 与K 时刻的状态变量,与预积分器
    136. // 在gtsam::ImuFactor 中定义好了 残差计算和雅可比的计算
    137. gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
    138. graphFactors.add(imu_factor);
    139. // BIAS 残差 用gtsam::BetweenFactor 因子,表示两帧lidar时刻的BIAS 应该保持一致,对因子图进行约束
    140. // add imu bias between factor
    141. graphFactors.add(gtsam::BetweenFactor(B(key - 1), B(key), gtsam::imuBias::ConstantBias(),
    142. gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias)));
    143. // lidar 位姿约束因子只跟当前 状态量有关系,属于一个先验因子
    144. // add pose factor
    145. gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu);
    146. gtsam::PriorFactor pose_factor(X(key), curPose, degenerate ? correctionNoise2 : correctionNoise);
    147. graphFactors.add(pose_factor);
    148. // 因子相当于给因子图优化问题提供了观测值,
    149. // 还需要给优化变量提供初始值
    150. // 以上一时刻的状态为基准,从预积分器中预测当前的状态值,作为因子图优化变量的初值
    151. // insert predicted values
    152. gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);
    153. graphValues.insert(X(key), propState_.pose());
    154. graphValues.insert(V(key), propState_.v());
    155. graphValues.insert(B(key), prevBias_);
    156. // optimize
    157. optimizer.update(graphFactors, graphValues);
    158. optimizer.update();
    159. // 优化完之后,因子图把相关约束 添加到优化器optimizer的贝叶斯树中,所以在因子图上可以删除
    160. graphFactors.resize(0);
    161. graphValues.clear();
    162. // Overwrite the beginning of the preintegration for the next step.
    163. gtsam::Values result = optimizer.calculateEstimate();
    164. prevPose_ = result.at(X(key));
    165. prevVel_ = result.at(V(key));
    166. prevState_ = gtsam::NavState(prevPose_, prevVel_);
    167. prevBias_ = result.at(B(key));
    168. // Reset the optimization preintegration object.
    169. imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
    170. // check optimization
    171. if (failureDetection(prevVel_, prevBias_))
    172. {
    173. resetParams();
    174. return;
    175. }
    176. //因子图优化完之后,得到当前优化状态,需要设置imuIntegratorImu_ 的BIAS 重新预积分
    177. // 2. after optiization, re-propagate imu odometry preintegration
    178. prevStateOdom = prevState_;
    179. prevBiasOdom = prevBias_;
    180. // first pop imu message older than current correction data
    181. double lastImuQT = -1;
    182. while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t)
    183. {
    184. lastImuQT = ROS_TIME(&imuQueImu.front());
    185. imuQueImu.pop_front();
    186. }
    187. // repropogate
    188. if (!imuQueImu.empty())
    189. {
    190. // reset bias use the newly optimized bias
    191. imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);
    192. // integrate imu message from the beginning of this optimization
    193. for (int i = 0; i < (int)imuQueImu.size(); ++i)
    194. {
    195. sensor_msgs::Imu *thisImu = &imuQueImu[i];
    196. double imuTime = ROS_TIME(thisImu);
    197. double dt = (lastImuQT < 0) ? (1.0 / 500.0) :(imuTime - lastImuQT);
    198. imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
    199. gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt);
    200. lastImuQT = imuTime;
    201. }
    202. }
    203. ++key;
    204. doneFirstOpt = true;
    205. }

  • 相关阅读:
    QT Widget: 自定义Widget组件及创建和使用动静态库
    Java浮点运算为什么不精确
    sql在plsql执行快,在mybatis中很慢
    C# 上位机Modbus Crc校验方法
    如何申请成为抖音本地生活服务商?一文详细步骤手把手教你入驻!
    最新消息:2022高被引科学家名单已公布,都想成为高被引,到底应该怎么做?(附名单)
    Git错误解决:如何处理“could not determine hash algorithm“问题
    【产品设计】APP提升用户注册率的五个方案探讨结论
    json转换
    要多坑有多坑springboot内置定时任务本地可以执行,部署到服务器就不执行了
  • 原文地址:https://blog.csdn.net/fuzi2012/article/details/126309442