cartographer 算法主要有三个文件夹
1、cartographer_ros (该文件件里的代码是cartographer 在 ros平台中的接口)
2、cartographer (核心slam算法)
3、ceres-solver (google 的开源 优化库)
由于C++功底不够,google的代码用到了C++的很多新特性,所以这篇文章可能会有些地方有错误,请见谅。
首先使用Understand分析下这份源码,(源码版本2017-03-23,后面应该比较稳定了,不会大改)
进入cartographer_ros文件夹找到 node_main.cc 进入main()函数,
前面几句是配置用的,直接进入Run()函数
run函数首先读取lua文件的配置参数,然后下面的全都是收集传感器数据,打包封装成cartographer统一的格式数据,包括 kLaserScanTopic、KMultiEchoLaserScanTopic(2D选这两个中的任何一个)、kPointCloud2Topic(3D) 、kImuTopic(2D可选,3D必须)、kOdometryTopic (2D/3D可选) 最后一段是地图完成服务调用,用于保存地图的。
转到 cartographer_ros的sensos_bridge.cc
这里面各种传感器消息的封装,起一个桥梁的作用。
通过这个接口的过度,正式从cartographer_ros 转到了cartographer中。
转到cartographer/mapping / trajectory_builder.h 我们又看到一个接口类 TrajectoryBuilder,各种消息都统一调用了虚函数AddSensorData(),用Understand看看它的Derived Class 发现CollatedTrajectoryBuilder继承了这个类。
这个类的定义在cartographer文件夹中的 mapping\collated_trajectory_builder.h中。
转到mapping\collated_trajectory_builder.cc 看到了AddSensorData()的实现,该类的构造函数说明通过统一调用HandleCollatedSensorData()函数,来轮询处理三大类消息 kImu(IMU消息)、kRangefinder(测距消息,不仅仅是激光)、kOdometer(里程计消息)。消息处理的实体通过wrapped_tarjectory_builder指针调用,该指正定义为GlobalTrajectoryBuilderInterface。
GlobalTrajectoryBuilderInterface是一个2D与3D SLAM全局接口类,在mapping\global_trajectory_builder_interface.h中,开头有段注释说明了这个slam算法的思想,该slam算法首先使用本地slam算法估计初始位姿,然后使用扫描匹配检测闭环,终于通过稀疏姿态图优化(sparse pose graph optimization,原理参见论文)算法优化整个位姿序列。
这个接口类被2个类继承mapping_2d\GlobalTrajectoryBuilder 与mapping_3d\GlobalTrajectoryBuilder。这里只看看2D的。
转到cartographer\mapping_2d\global_trajectory_builder_interface.cc中。
先看看IMU。AddImuData传入参数为时间、加速度、角速度,首先调用local_trajectory_builder中的AddImuData函数,再调用sparse_pose_graph中的AddImuData,一个是用于本地优化的,一个是用于全局优化的。
转到cartographer\mapping_2d\local_trajectory_builder.h 看看介绍说这部分是本地SLAM部分,完成UKF,扫描匹配 ,但是没有闭环部分。
看到mapping_2d/local_trajectory_builder/AddImuData的实体 ,看看Predict()函数,这个函数主要是运用运动学模型(陀螺仪的积分,里程计的积分),简单的预测姿态平移矩阵以及旋转矩阵。预测完了后,又加入了加速度与角速度的观测。
再看mapping_2d/local_trajectory_builder/AddOdometerData函数,首先执行了predict(),然后使用里程计的数据,得到了一个里程计的校正量odometry_correction_。
再看mapping2d_trajectory_builder/AddRangefinderData()函数,和IMU部分一样有一个本地优化ocal_trajectory_builder_.AddHorizontalLaserFan,还有一个全局优化 sparse_pose_graph_->AddScan。
本地优化里调用了一个重要的函数LocalTrajectoryBuilder::ScanMatch(),这个函数首先对激光数据进行了投射处理、栅格化滤波,然后实时在线优化real_time_correlative_scan_matcher_.Match,这个优化是可选的,优化方式比较简单,就是在之前预测位姿附近开一个三维窗口,然后在这个三维窗口内,均匀播撒候选粒子,对每个粒子计算匹配得分,选取最高得分的粒子作为优化的位姿。该优化的位姿还要传入一个ceres_scan_matcher_.Match()在子地图中完成局部优化,最终得到本地优化后的位姿。
今天就写这么多了,下次再仔细分析下一两个函数
ceres_scan_matcher_.Match() 与
sparse_pose_graph_->AddScan()。
前者为子地图局部优化,里面构造了优化约束条件,然后调用ceres slover 库做优化。
后者是全局地图的优化,里面有闭环检测的策略,重点是分支定界寻找闭环点,然后构造闭环优化约束,最后再次调用ceres slover。