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

图2.ros topic 发布与订阅关系图关系图(还是叫图2,因为是同一个图)
这个cpp文件主要有两个类,一个叫IMUPreintegration类,一个叫TransformFusion类。从数据流向和依赖程度的角度分析,应该先分析IMUPreintegration类,但是TransformFusion类写在该模块的前头,且这两个模块的数据流相互依赖,这里先分析TransformFusion类。
讲解之前,二话不说,先上该类的成员变量列表。
| 变量名 | 类型 | 注释 | 备注 |
| mtx | std::mutex | 因为ros回调函数是异步运行,在回调函数操作成员变量或者队列时,要先上锁 | - |
| subImuOdometry | ros::Subscriber | 订阅"odometry/imu_incremental" 虽然名字上有incremental ,但里程计,他就是里程计,代表着该时刻固定的frame 的位姿信息,由下面的imuPreintegration 发出, 虽然 由 gtsam 的预积分器算出来,但完全可以理解为不与预积分有关系,单纯文imu 数据的位姿积分解算 在这个类中 其中一个主要的目的就是 在优化过后的lidar 里程计基础上,叠加上lidar时刻到至今时刻的位姿差,以发出更高频率的里程计定位数据 | - |
| subLaserOdometry | ros::Subscriber | 订阅"lio_sam/mapping/odometry" 就是经过MapOptimization优化后的里程计信息,接收周期比慢一个量级,与 "odometry/imu_incremental" 的 frameid 应该是一致的,都是 odomframe,child_frame_id 都是odom_lidar 注意,这里/mapping/odometry和/mapping/odometry_incremental的区别,前者是激光里程计部分直接优化得到的激光位姿,后者相当于激光的位姿经过旋转约束和z轴约束的限制以后,又和原始imu信息里面的角度做了一个加权以后的位姿。 | - |
| pubImuOdometry | ros::Publisher | 发布imu 里程计 "odometry/imu" 结合了lidar 里程计的IMU 里程计# IMU pre-preintegration odometry, same frequency as IMU | - |
| pubImuPath | ros::Publisher | 消息名为 "lio_sam/imu/path" ,在图2 中由 rviz 订阅,其发布imu 里程计 "odometry/imu" 对应的轨迹,发布频率是10hz,每次发布都只是一小段,即lidar时刻到当前imu 时刻的位姿数组 | - |
| lidarOdomAffine | Eigen::Affine3f | 就是把"lio_sam/mapping/odometry" 的消息转化为位姿变换矩阵,代表但前lidar时刻 (机器人)的位姿 | - |
| lidarOdomAffine | Eigen::Affine3f | 就是把"lio_sam/mapping/odometry" 的消息转化为位姿变换矩阵,代表但前lidar时刻 的位姿 | - |
| imuOdomAffineFront | Eigen::Affine3f | 在loam 系列代码里看到这一对 front /back 的变换矩阵,就应该想到作差 相对位姿变换,这个变换矩阵是由lidar 时刻的最近的imu时刻的"odometry/imu_incremental" 的里程计消息转化而来,代表那个时刻的位姿 | - |
| imuOdomAffineBack | Eigen::Affine3f | 这个变换矩阵是由当前时刻imu时刻的"odometry/imu_incremental" 的里程计消息转化而来,代表那个时刻的位姿,作用是 与imuOdomAffineFront 作差后,附加到lidarOdomAffine变换上,得到结合地图优化的位姿的更高频率的位姿信息 | - |
| tfListener | tf::TransformListener | 从tf 树中得到 lidar2Baselink 的外参关系 | - |
| lidar2Baselink | tf::StampedTransform | 如果imu里程计与lidar 里程计的坐标系不是同一个坐标系,需要转化到同一个坐标系两个里程计数据才能融合 | - |
| lidarOdomTime | float | lidar时刻的时间戳,每次lidar 里程计来了都更换一次,imu 里程计队列imuOdomQueue的数据,都保留这个时刻之后的数据 | - |
| imuOdomQueue | deque | imu 里程计队列,都保留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。
- /**
- * 激光雷达里程计就保存当前的雷达里程计的数据到lidarOdomAffine里面,
- * 把时间戳存到lidarOdomTime里面去,没干别的,
- * 具体的主要逻辑在imu 里程计的回调函数中处理。
- *
- * */
- void lidarOdometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
- {
- std::lock_guard
lock(mtx) ; -
- lidarOdomAffine = odom2affine(*odomMsg);
-
- lidarOdomTime = odomMsg->header.stamp.toSec();
- }
-
- /**
- * 主要的处理逻辑在imu 里程计的回调函数中,`imuOdometryHandler`,
- * 在函数里面监听的是/imu/odometry_incremental(在下面imuPreintegration中发出,
- * 是imu 独立解算的里程计),把imu里程计放到imuodomQueue里面保存,
- * 然后把lidarOdomTime之前的数据扔掉,之后用最新和最旧时刻的mu里程计的计算差异,
- * 再加上lidarOdomAffine的基础进行发布。
- * */
- void imuOdometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
- {
- // static tf 这个地方究竟是在干什么?
- static tf::TransformBroadcaster tfMap2Odom;
- static tf::Transform map_to_odom = tf::Transform(tf::createQuaternionFromRPY(0, 0, 0), tf::Vector3(0, 0, 0));
- tfMap2Odom.sendTransform(tf::StampedTransform(map_to_odom, odomMsg->header.stamp, mapFrame, odometryFrame));
-
- std::lock_guard
lock(mtx) ; -
- imuOdomQueue.push_back(*odomMsg);
-
- // get latest odometry (at current IMU stamp)
- if (lidarOdomTime == -1)
- return;
- while (!imuOdomQueue.empty())
- {
- if (imuOdomQueue.front().header.stamp.toSec() <= lidarOdomTime)
- imuOdomQueue.pop_front();
- else
- break;
- }
- Eigen::Affine3f imuOdomAffineFront = odom2affine(imuOdomQueue.front());//前
- Eigen::Affine3f imuOdomAffineBack = odom2affine(imuOdomQueue.back());// 后
- Eigen::Affine3f imuOdomAffineIncre = imuOdomAffineFront.inverse() * imuOdomAffineBack;
- Eigen::Affine3f imuOdomAffineLast = lidarOdomAffine * imuOdomAffineIncre;
- float x, y, z, roll, pitch, yaw;
- pcl::getTranslationAndEulerAngles(imuOdomAffineLast, x, y, z, roll, pitch, yaw);
-
- // publish latest odometry
- nav_msgs::Odometry laserOdometry = imuOdomQueue.back();
- laserOdometry.pose.pose.position.x = x;
- laserOdometry.pose.pose.position.y = y;
- laserOdometry.pose.pose.position.z = z;
- laserOdometry.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw);
- pubImuOdometry.publish(laserOdometry);
-
- // publish tf
- static tf::TransformBroadcaster tfOdom2BaseLink;
- tf::Transform tCur;
- tf::poseMsgToTF(laserOdometry.pose.pose, tCur);
- if(lidarFrame != baselinkFrame)
- tCur = tCur * lidar2Baselink;
- tf::StampedTransform odom_2_baselink = tf::StampedTransform(tCur, odomMsg->header.stamp, odometryFrame, baselinkFrame);
- // 发布 odom 到 baselink 的tf 关系
- tfOdom2BaseLink.sendTransform(odom_2_baselink);
-
- // publish IMU path
- static nav_msgs::Path imuPath; // 静态数据,Path 实际是pose数组,
- static double last_path_time = -1;
- double imuTime = imuOdomQueue.back().header.stamp.toSec();
- // 控制发送频率 10HZ
- if (imuTime - last_path_time > 0.1)
- {
- last_path_time = imuTime;
- geometry_msgs::PoseStamped pose_stamped;
- pose_stamped.header.stamp = imuOdomQueue.back().header.stamp;
- pose_stamped.header.frame_id = odometryFrame;
- pose_stamped.pose = laserOdometry.pose.pose;
- imuPath.poses.push_back(pose_stamped);
- // lidar 时间戳1秒钟以内的数据,以前的数据删掉
- while(!imuPath.poses.empty() && imuPath.poses.front().header.stamp.toSec() < lidarOdomTime - 1.0)
- imuPath.poses.erase(imuPath.poses.begin());
- if (pubImuPath.getNumSubscribers() != 0)
- {
- imuPath.header.stamp = imuOdomQueue.back().header.stamp;
- imuPath.header.frame_id = odometryFrame;
- pubImuPath.publish(imuPath);
- }
- }
- }
成员变量列表:
| 变量名 | 类型 | 注释 | 备注 |
| mtx | std::mutex | 因为ros回调函数是异步运行,在回调函数操作成员变量或者队列时,要先上锁 | - |
| subImu | ros::Subscriber | //订阅"imu_raw",imu源数据 | - |
| subOdometry | ros::Subscriber | 订阅 "lio_sam/mapping/odometry_incremental" 这个消息是从MapOptimization 发布,经过地图优化且经过z_torellen和imu 阈值修正 | - |
| pubImuOdometry | ros::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 | - |
| systemInitialized | bool | 初始化标志,因子图优化器初始化完成后,就设置为true,或者积分出来的数据超过阈值,需要重新因子图优化器初始化 | - |
| 预积分因子模型噪声模型参数 | - | ||
| priorPoseNoise | gtsam::noiseModel::Diagonal::shared_ptr | 位姿噪声模型 | - |
| priorVelNoise | gtsam::noiseModel::Diagonal::shared_ptr | 速度噪声模型 | - |
| priorBiasNoise | gtsam::noiseModel::Diagonal::shared_ptr | 偏差噪声模型 | - |
| correctionNoise | gtsam::noiseModel::Diagonal::shared_ptr | 激光雷达位姿噪声模型 | - |
| correctionNoise2 | gtsam::noiseModel::Diagonal::shared_ptr | 激光雷达位姿噪声模型,协方差是否为单位阵,决定使用哪个噪声模型 | - |
| noiseModelBetweenBias | gtsam::Vector | 两帧的bias 形成一个betweenFactor, 也需要提供一个噪声模型 | - |
| *imuIntegratorOpt_ | gtsam::PreintegratedImuMeasurements | 预积分器,这个预积分提供预积分数据给因子图优化器 | - |
| *imuIntegratorImu_ | gtsam::PreintegratedImuMeasurements | 预积分器,这个预积分提供预积分数据给里程计累加用 | - |
| imuQueOpt | std::deque | 预积分器对应的imu积分数据队列 | - |
| imuQueImu | std::deque | 预积分器对应的imu积分数据队列 | - |
| prevPose_ | gtsam::Pose3 | 上一帧lidar数据到来时因子图优化后的位姿 | - |
| prevVel_ | gtsam::Vector3 | 上一帧lidar数据到来时因子图优化后的速度 | - |
| prevState_ | gtsam::NavState | 上一帧lidar数据到来时因子图优化后的状态,包含pose、vel | - |
| prevBias_ | gtsam::imuBias::ConstantBias | 上一帧lidar数据到来时因子图优化后的IMU偏差 | - |
| prevStateOdom | gtsam::NavState | 上一帧lidar数据到来时因子图优化后的状态,包含pose、vel;从prevState_ 赋值 | - |
| prevBiasOdom | gtsam::imuBias::ConstantBias | 上一帧lidar数据到来时因子图优化后的IMU偏差;从prevBias_ 赋值 | - |
| doneFirstOpt | bool | 只有经过因子图优化后,才进行imuIntegratorImu_预积分累积里程计 | - |
| lastImuT_imu | double | 上一次的imu 的时间戳,当前帧imu 来时,用当前imu时间戳 减去 这个值,得到dt,imu积分时需要这个值 | - |
| lastImuT_opt | double | 意思同上,就是对应不同的预积分器而已 | - |
| optimizer | gtsam::ISAM2 | 因子图优化器,这里用ISAM2作为优化器的后端 | - |
| graphFactors | gtsam::NonlinearFactorGraph | 因子图容器,用于添加各种因子 | - |
| graphValues | gtsam::Values | 因子图中各个节点,也就是待优化的变量 | - |
| delta_t | const double | 常量,距离lidar 时间戳的距离,这里固定为0 | - |
| key | int | 计数器,每100次 重新初始化 因子图优化器,这个跟gtsam的实现有关系,每次优化保留一些东西在贝叶斯树里头,多了会影响速度 | - |
| key | int | 计数器,每100次 重新初始化 因子图优化器,这个跟gtsam的实现有关系,每次优化保留一些东西在贝叶斯树里头,多了会影响速度 | - |
| imu2Lidar | gtsam::Pose3 | 原文注释:T_bl: tramsform points from lidar frame to imu frame 这个注释跟命名感觉不对劲,应该表示成Lidar2Imu ;在params.yaml 文件中 # Extrinsics: T_lb (lidar -> imu) 个人觉得也有问题 应该表示成T_lb (imu -> lidar) 但是 包括下边的两个变量的命名 应该是反着来的 | - |
| lidar2Imu | gtsam::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”里程计。虽然作者的变量命名很疑惑,但是代码实现得还是很老实的。
- void imuHandler(const sensor_msgs::Imu::ConstPtr& imu_raw)
- {
- std::lock_guard
lock(mtx) ; - // 这个函数这个地方写的非常绕,非值得注意的是,这里是把imu的数据转换到lidar坐标系下,
- // 但是又不完全转,只是保持与lidar坐标系的方向一致,差一个位移
- // 这里的操作就像对IMU 的数据进行修正,让IMU的坐标系与Lidar 坐标系的方向一致,
- // 然后 IMU 与 Lidar外参就只有位移关系了。
- sensor_msgs::Imu thisImu = imuConverter(*imu_raw);
- // 转换后的数据压入队列中
- imuQueOpt.push_back(thisImu);
- imuQueImu.push_back(thisImu);
- // 如果因子图还没有优化过,就直接返回了
- if (doneFirstOpt == false)
- return;
-
- // 获取该IMU数据的时间戳
- double imuTime = ROS_TIME(&thisImu);
- // 与上一帧IMU 数据时间戳作差,
- double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu);
- lastImuT_imu = imuTime;
-
- // 把当前帧的IMU 数据添加入预积分器
- // integrate this single imu message
- imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y, thisImu.linear_acceleration.z),
- gtsam::Vector3(thisImu.angular_velocity.x, thisImu.angular_velocity.y, thisImu.angular_velocity.z), dt);
-
- // 以上一帧lidar时刻因子图优化出来的状态为基准,预积分器预测出当前IMU时刻的位姿状态
- // predict odometry
- gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);
-
- // publish odometry
- nav_msgs::Odometry odometry;
- odometry.header.stamp = thisImu.header.stamp;
- odometry.header.frame_id = odometryFrame;
- // 这里实际应该是“odom_lidar”
- odometry.child_frame_id = "odom_imu";
-
- // transform imu pose to ldiar
- gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position());
- // imu2Lidar 变量的值给得应该表示成T_il,整个式子 则为 T_ol = T_oi*T_il
- gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar);
-
- odometry.pose.pose.position.x = lidarPose.translation().x();
- odometry.pose.pose.position.y = lidarPose.translation().y();
- odometry.pose.pose.position.z = lidarPose.translation().z();
- odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x();
- odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y();
- odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z();
- odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w();
-
- odometry.twist.twist.linear.x = currentState.velocity().x();
- odometry.twist.twist.linear.y = currentState.velocity().y();
- odometry.twist.twist.linear.z = currentState.velocity().z();
- odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x();
- odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y();
- odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z();
- pubImuOdometry.publish(odometry);
- }
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 中积分却不删数据的原因。
- /**
- * @brief 激光雷达里程计的回调函数
- *
- * @param odomMsg
- */
- void odometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
- {
- std::lock_guard
lock(mtx) ; - // 获取lidar 时间戳
- double currentCorrectionTime = ROS_TIME(odomMsg);
-
- // make sure we have imu data to integrate
- if (imuQueOpt.empty())
- return;
-
- float p_x = odomMsg->pose.pose.position.x;
- float p_y = odomMsg->pose.pose.position.y;
- float p_z = odomMsg->pose.pose.position.z;
- float r_x = odomMsg->pose.pose.orientation.x;
- float r_y = odomMsg->pose.pose.orientation.y;
- float r_z = odomMsg->pose.pose.orientation.z;
- float r_w = odomMsg->pose.pose.orientation.w;
- // 是否退化,什么情况下会退化? 初始情况下odomMsg->pose.covariance[0] == 1
- bool degenerate = (int)odomMsg->pose.covariance[0] == 1 ? true : false;
- // 这个位姿 是直接转化的里程计消息数据
- gtsam::Pose3 lidarPose = gtsam::Pose3(gtsam::Rot3::Quaternion(r_w, r_x, r_y, r_z), gtsam::Point3(p_x, p_y, p_z));
-
- // 初始化
- // 0. initialize system
- if (systemInitialized == false)
- {
- // 重置Isam2 优化器
- 积分
- // 优化器
- // optimizer = gtsam::ISAM2(optParameters);
- // 因子图容器
- // graphFactors = newGraphFactors;
- // 待优化的变量
- // graphValues = NewGraphValues;
- resetOptimization();
-
- // pop old IMU message
- while (!imuQueOpt.empty())
- {
- // imuQueOpt数据中小于lidar时刻的数据全部pop 出去
- if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t)
- {
- lastImuT_opt = ROS_TIME(&imuQueOpt.front());
- imuQueOpt.pop_front();
- }
- else
- break;
- }
- // 因子图是贝叶斯公式推导过来的,P(x|z) = a*P(z|x)P(x) 其中a 为系数,P(z|x)为似然函数,P(x) 则为先验
- // 所以 因子图里都要添加进 先验P(x)
- // 这里把 因子图中第一个优化变量设为先验
- // initial pose
- prevPose_ = lidarPose.compose(lidar2Imu);// compose 是什么运算??转到IMU 系 todo:T_oi = T_ol * T_li
- gtsam::PriorFactor
priorPose(X(0), prevPose_, priorPoseNoise) ; - graphFactors.add(priorPose);
- // initial velocity
- prevVel_ = gtsam::Vector3(0, 0, 0);
- gtsam::PriorFactor
priorVel(V(0), prevVel_, priorVelNoise) ; - graphFactors.add(priorVel);
- // initial bias
- prevBias_ = gtsam::imuBias::ConstantBias();
- gtsam::PriorFactor
priorBias(B(0), prevBias_, priorBiasNoise) ; - graphFactors.add(priorBias);
- // add values
- graphValues.insert(X(0), prevPose_);
- graphValues.insert(V(0), prevVel_);
- graphValues.insert(B(0), prevBias_);
- // optimize once
- optimizer.update(graphFactors, graphValues);
- graphFactors.resize(0);
- graphValues.clear();
-
- //预积分器需要bias ,而bias每次优化都会发生变化,所以需要重新设置
- imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_);
- imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
-
- key = 1;
- systemInitialized = true;
- return;
- }
-
- // 每100 次 重新设置优化器,这个是gtsam 的实现要求的,在每次优化的过程中,
- // gtsam 会缓存一些数据在贝叶斯树中,随着优化次数增大,该树会越来越大,执行效率就会变慢,
- // 所以要周期性清理
- // reset graph for speed
- if (key == 100)
- {
- // 在最开始的时候 每个因子的噪声模型都是人工经验值,这里需要把协方差矩阵传递下去,给新的优化器
- // get updated noise before reset
- gtsam::noiseModel::Gaussian::shared_ptr updatedPoseNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(X(key-1)));
- gtsam::noiseModel::Gaussian::shared_ptr updatedVelNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(V(key-1)));
- gtsam::noiseModel::Gaussian::shared_ptr updatedBiasNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(B(key-1)));
- // reset graph
- resetOptimization();
- // 新的因子图需要新的先验
- // add pose
- gtsam::PriorFactor
priorPose(X(0), prevPose_, updatedPoseNoise) ; - graphFactors.add(priorPose);
- // add velocity
- gtsam::PriorFactor
priorVel(V(0), prevVel_, updatedVelNoise) ; - graphFactors.add(priorVel);
- // add bias
- gtsam::PriorFactor
priorBias(B(0), prevBias_, updatedBiasNoise) ; - graphFactors.add(priorBias);
- // add values
- graphValues.insert(X(0), prevPose_);
- graphValues.insert(V(0), prevVel_);
- graphValues.insert(B(0), prevBias_);
- // optimize once 预热一次
- optimizer.update(graphFactors, graphValues);
- graphFactors.resize(0);
- graphValues.clear();
-
- key = 1;
- }
-
- // 从这里开始 真正处理数据因子图优化了
- // 1. integrate imu data and optimize
- while (!imuQueOpt.empty())
- {
- // pop and integrate imu data that is between two optimizations
- sensor_msgs::Imu *thisImu = &imuQueOpt.front();
- double imuTime = ROS_TIME(thisImu);
- // 取两个lidar 时间戳之间的IMU 数据提供给IMU积分器进行积分,
- if (imuTime < currentCorrectionTime - delta_t)
- {
- double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt);
- imuIntegratorOpt_->integrateMeasurement(
- gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
- gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt);
-
- lastImuT_opt = imuTime;
- imuQueOpt.pop_front(); // 用一个 删一个,每一次优化周期内,没有上一周期的IMU数据
- }
- else
- break;
- }
- // add imu factor to graph
- const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);
- // IMU 因子 提供 K-1 时刻的状态变量 与K 时刻的状态变量,与预积分器
- // 在gtsam::ImuFactor 中定义好了 残差计算和雅可比的计算
- gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
- graphFactors.add(imu_factor);
- // BIAS 残差 用gtsam::BetweenFactor 因子,表示两帧lidar时刻的BIAS 应该保持一致,对因子图进行约束
- // add imu bias between factor
- graphFactors.add(gtsam::BetweenFactor
(B(key - 1), B(key), gtsam::imuBias::ConstantBias(), - gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias)));
- // lidar 位姿约束因子只跟当前 状态量有关系,属于一个先验因子
- // add pose factor
- gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu);
- gtsam::PriorFactor
pose_factor(X(key), curPose, degenerate ? correctionNoise2 : correctionNoise) ; - graphFactors.add(pose_factor);
- // 因子相当于给因子图优化问题提供了观测值,
- // 还需要给优化变量提供初始值
- // 以上一时刻的状态为基准,从预积分器中预测当前的状态值,作为因子图优化变量的初值
- // insert predicted values
- gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);
- graphValues.insert(X(key), propState_.pose());
- graphValues.insert(V(key), propState_.v());
- graphValues.insert(B(key), prevBias_);
- // optimize
- optimizer.update(graphFactors, graphValues);
- optimizer.update();
- // 优化完之后,因子图把相关约束 添加到优化器optimizer的贝叶斯树中,所以在因子图上可以删除
- graphFactors.resize(0);
- graphValues.clear();
- // Overwrite the beginning of the preintegration for the next step.
- gtsam::Values result = optimizer.calculateEstimate();
- prevPose_ = result.at
(X(key)); - prevVel_ = result.at
(V(key)); - prevState_ = gtsam::NavState(prevPose_, prevVel_);
- prevBias_ = result.at
(B(key)); - // Reset the optimization preintegration object.
- imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
- // check optimization
- if (failureDetection(prevVel_, prevBias_))
- {
- resetParams();
- return;
- }
-
- //因子图优化完之后,得到当前优化状态,需要设置imuIntegratorImu_ 的BIAS 重新预积分
-
- // 2. after optiization, re-propagate imu odometry preintegration
- prevStateOdom = prevState_;
- prevBiasOdom = prevBias_;
- // first pop imu message older than current correction data
- double lastImuQT = -1;
- while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t)
- {
- lastImuQT = ROS_TIME(&imuQueImu.front());
- imuQueImu.pop_front();
- }
- // repropogate
- if (!imuQueImu.empty())
- {
- // reset bias use the newly optimized bias
- imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);
- // integrate imu message from the beginning of this optimization
- for (int i = 0; i < (int)imuQueImu.size(); ++i)
- {
- sensor_msgs::Imu *thisImu = &imuQueImu[i];
- double imuTime = ROS_TIME(thisImu);
- double dt = (lastImuQT < 0) ? (1.0 / 500.0) :(imuTime - lastImuQT);
-
- imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
- gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt);
- lastImuQT = imuTime;
- }
- }
-
- ++key;
- doneFirstOpt = true;
- }