架构
- src/include
- apps:节点文件
- front_end_node.cpp:前端节点
- data_pretreat_node.cpp:节点的数据预处理
- back_end_node.cpp:后端节点
- loop_closing_node.cpp:回环检测节点
- data_pretreat:数据预处理节点相关代码
- back_end_node:与后端节点相关的代码
- loop_closing_node:回环检测节点相关代码
- models:框架主模块
- publisher:发布者
- subscriber:订阅者
- tools:框架具有小功能,如保存轨迹txt文件
- apps:节点文件
- slam_data 数据保存目录
- key_frames:关键帧点云
- map.pcd:全局点云
- trajectory:真实轨迹和估计轨迹
- scan context:回环检测数据
- index.bin:scan context索引
- scan_contexts.proto:scan context数据
- ring_keys.proto:ring key数据
- key_frames.proto,关键帧数据
- cmake ros第三方库
- config 配置文件
- Log 日志输出
- srv 服务
其他的略
编译
打开lidar_localization/config/scan_context输入以下命令生成文件夹pb文件
protoc --cpp_out=./ key_frames.proto protoc --cpp_out=./ ring_keys.proto protoc --cpp_out=./ scan_contexts.proto mv key_frames.pb.cc key_frames.pb.cpp mv ring_keys.pb.cc ring_keys.pb.cpp mv scan_contexts.pb.cc scan_contexts.pb.cpp
修改生成的三个.pb.cpp文件如下,以下ring_keys.pb.cpp为例。
// Generated by the protocol buffer compiler. DO NOT EDIT! // source: ring_keys.proto #define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION #include "ring_keys.pb.h" 替换为 #include "lidar_localization/models/scan_context_manager/ring_keys.pb.h" #include <algorithm>
之后,使用上述步骤生成.pb.h文件替换lidar_localization/include/lidar_localization/models/scan_context_manager 同名文件。 将.pb.cpp文件替换lidar_localization/src/models/scan_context_manager中的同名文件。
这样编译就没问题了。
内容简述
预处理节点
- 订阅原始gnss数据,imu数据,点云数据
- 得到gnss里程计(真值)并对点云进行畸变补偿
前端节点
front_end.yaml 中 whether_use_back_end 设置为NO
- 订阅gnss里程计和变形补偿后的点云
- 通过前端算法配准点云获得lidar里程计,与gnss里程计一起发布,可以做evo评测
front_end.yaml 中 whether_use_back_end 设置为YES
- 订阅畸变补偿后的点云
- 通过前端算法配准获得lidar里程计,发布lidar里程计
服务:保存前端地图
rosservice call /save_front_end_map
后端节点
-
订阅畸变补偿后的点云,lidar里程计,gnss里程计、回环检测节点的回环因子
-
关键帧是否距离上一帧位移足够远,gnss关键帧,关键帧点云(这里的关键帧只包括pose、id索引、时间戳、点云分开存储)
选择关键帧的方法大致分为三种
- 距离上一个关键帧的帧数是否足够,缺点明显。当运动缓慢时,会产生大量冗余关键帧,运动速度快,失去重要关键帧
- 上述问题可以通过远离上一个关键帧的位移来解决。然而,当来回扫描同一物体时,也会产生大量冗余关键帧
- 上一个关键帧的共视点上述问题。
展望:目前只使用第二种方法,后期可以改进
-
将新的关键帧顶点添加到图形优化器以及上一个关键帧的边缘gnss作为观测的先验一元边的位置
-
根据图优化器中的关键帧顶点数,gnss判断是否需要优化因子数量和回环因子数量。如果需要优化,则在优化后保存更新位置数据
服务:后端轨迹数据保存
rosservice call /save_back_end_pose
回环检测节点
-
订阅后端发布的关键帧,gnss关键帧,关键帧点云
-
将当前关键帧转换为scan context和ring key,加入回环系统
-
从上一个关键帧之间的关键帧数量足够多开始回环检测
-
先通过ring key获得回环关键帧的候选人,然后计算候选人sector key获得准确的匹配方案
-
匹配候选人和当前关键帧scan context,利用余弦距离获得最高相似度的最佳候选人: d ( I q , I c ) = 1 N s ∑ j = 1 N s ( 1 ? c j q ? c j c ∥ c j q ∥ ∥ c j c ∥ ) (5) d\left(I^{q}, I^{c}\right)=\frac{1}{N_{s}} \sum_{j=1}^{N_{s}}\left(1-\frac{c_{j}^{q} \cdot c_{j}^{c}}{\left\|c_{j}^{q}\right\|\left\|c_{j}^{c}\right\|}\right)\tag{5} d(Iq,Ic)=Ns1j=1∑Ns(1−∥∥cjq∥∥∥∥cjc∥∥cjq⋅cjc)(5)
参考我之前文章:
https://blog.csdn.net/weixin_44035919/article/details/124926041
-
验证1:回环因子的两帧的id得到gnss位置误差小于阈值
-
点云配准优化回环因子的位姿:当前帧和最佳候选者的局部地图配准
-
验证2:返回点云配准的可靠得分大于阈值,最终全部通过即可发布回环因子
服务:回环数据保存
rosservice call /save_scan_context
可视化节点
这个比较简单直观,略。
主要工作
- 将之前前端拆分为两个节点,仍保留了原先前端算法对比的功能,只需要将front_end.yaml 中 whether_use_back_end 设置为NO即可
- 对所有订阅者加上了线程锁,保证数据的存取正常进行。
- 添加了后端和回环检测部分,注意要将front_end.yaml 中 whether_use_back_end 设置为YES。
期间遇到一个bug,在点云发布者中,点云点数不等于宽×高导致报错如下,
pcl::toPCLPointCloud2(const pcl::PointCloud<PointT>&, pcl::PCLPointCloud2&) [with PointT = pcl::PointXYZI]: Assertion `cloud.points.size () == cloud.width * cloud.height' failed.
将点云设置为无序点云解决:
cloud_ptr_input->width=cloud_ptr_input->points.size(); cloud_ptr_input->height = 1;
结果
- 最大误差:3.597209 %
- 平均误差:1.864868%
- 最小误差: 0.883433%
- RMSE:1.993375%
- 最大误差:0.549552m
- 平均误差:0.095091m
- RMSE:0.132866m
平均绝对轨迹误差只有0.132866m,相比于前端有了很大的改进。
展望
只说一下目前想到的待改进之处吧
-
后端可以使用多态试试因子图的效果如何
-
NDT算法改进
-
gnss位置作为观测先验时,需要考虑权重,当gps位于失效场景中时,要减小权重。
-
关键帧选择上采用的是位移足够远,是否能采用共视点数足够多,解决来回扫描的场景。
框架链接如下:
链接: https://pan.baidu.com/s/19Rr1mcVWIUyAKoucRkTSYg 密码: nkbk –来自百度网盘超级会员V4的分享
如有错误,请加我v:peak1229258698交流指正。