Advertisement

LIO-SAM运行自己数据包遇到的问题解决--SLAM不学无数术小问题

阅读量:

本文详细介绍了如何使用LIO-SAM算法进行3D-SLAM建图,重点描述了硬件配置、数据格式要求、标定过程以及配置参数的修改。具体步骤包括使用速腾聚创激光雷达和RSLidar-SDK进行标定,以及配置IMU内参、外参和Lidar到IMU的外参。通过激光雷达和IMU的标定,实现了LIO-SAM算法的有效运行,并在室内环境中取得了较好的建图效果。摘要涵盖了核心内容,字数控制在100到200字之间,符合要求。

LIO-SAM 成功适配自己数据集

本文测试环境基于Ubuntu18.04 ROS melodic版本,硬件配置包括速腾聚创16线激光雷达(RS-Lidar-16)和超核电子CH110型9轴惯导(IMU),IMU采样频率设置为100Hz。需要注意的是,该教程主要用于指导安装和运行,但目前的版本可能已不再具有时效性。2022年7月下载的LIO-SAM源码的 frame_id 参数无需调整,主要工作内容集中在标定环节。建议在GitHub上寻找标定工具的替代方案。通过尝试不同的开源数据集,尤其是两种以上,可以更有效地进行标定。在标定过程中,建议根据实际需求进行参数调整和优化。

背景

近期对3D-SLAM算法的性能进行了深入研究,重点考察了具有优异性能的建图算法。在入门阶段,已经对LOAM、LeGo-LOAM、BLAM等算法进行了测试。这些算法均基于单激光雷达数据进行建图,建图效果虽有差异但变化不大。值得注意的是,融合激光雷达与IMU的性能表现出色,为此特意进行了深入研究。经过近两个月的实践探索,发现这一方向具有较大的发展潜力。在单激光雷达建图算法中,LIO-SAM的表现尤为突出,是目前见到效果最好的建图算法,也是个人测试时间最长、踩过坑最多的算法。基于个人实验数据,我总结了一些实用的建图算法应用经验,与各位分享交流。需要注意的是,其他类型的激光雷达与IMU设备,仅作为辅助参考。

在这里插入图片描述
在这里插入图片描述

一、数据格式要求

1.1IMU数据格式

虽然IMU的格式要求尚未完全探索,但采样频率确实需要注意。原作者采用了500Hz的IMU,并在源码中对其进行处理,最终转换为REP-105格式的数据。原作者建议用户最低使用200Hz的IMU,而笔者使用100Hz的IMU仍能达到类似效果,建议进行测试。

1.2激光雷达数据格式

LIO-SAM算法对激光雷达的数据格式有着严格的要求。以往的单激光雷达建图算法未充分考虑这一要求,通常只需满足XYZI格式(x, y, z, intensity)即可,但LIO-SAM要求更为严格,需采用XYZIRT格式(x, y, z, intensity, ring, timestamp),即算法内需使用激光雷达的通道数(ring参数)和时间戳(timestamp参数)。启动算法时会自动检查是否具备这两个参数。那么如何获取这两个参数呢?如果你使用的激光雷达与笔者相同,只需更新至RSLidar-SDK版本即可采集数据,具体操作可参考笔者另一篇教程:Ubuntu18.04安装速腾聚创最新驱动RSLidar_SDK以获取XYZIRT格式激光点云数据。然而,采集到的数据目前仍无法直接使用,速腾格式的点云与Velodyne格式的点云均存在不足。这又该如何解决呢?笔者在某技术论坛上找到了一位大佬提供的数据格式转换节点,该节点可将速腾格式的点云转换为Velodyne格式。具体安装步骤请参考笔者另一篇教程:速腾聚创雷达点云格式转换为Velodyne雷达点云格式。目前,其他雷达型号的数据我尚未获取,因此无法提供更多信息。

( 1.3GPS 数据格式要求)

作为一个小目标,我尚未尝试将GPS数据融入项目中。需要注意的是,GPS数据必须遵循sensor_msgs/NavSatFix这一特定的格式。由于GPS支持多种数据格式,原作者采用了该格式作为标准。如若有人成功测试其他格式,欢迎留言讨论。

作为一个小目标,我尚未尝试将GPS数据融入项目中。需要注意的是,GPS数据必须遵循sensor_msgs/NavSatFix这一特定的格式。由于GPS支持多种数据格式,原作者采用了该格式作为标准。如若有人成功测试其他格式,欢迎留言讨论。

1.4 坐标系的一致性校准(根据2022年7月后的源码,可能已不再需要进行该步骤;在建图过程中遇到困难时,可跳过该步骤)

简单来说,frame_id的设置有一定的要求。如果不打算修改源代码,可能需要在外部进行一些处理。其中涉及到内部坐标的映射关系,如果设置不一致,可能会导致ERROR,从而无法建立正确的图。原作提供的数据集中的frame_id设置遵循这一对应关系。

复制代码
    //激光雷达数据
    /points_raw-----------frame_id:"velodyne"--------
    //IMU数据
    /imu_raw--------------frame_id:"imu-link"--------
    //GPS数据
    /gps/fix--------------frame_id:"navset_link"-----

查看自己数据包的frame_id:

复制代码
    // 在播放数据包的时候使用如下指令查看某一Topic的frame_id:
     rostopic echo  /Topic    | grep   frame_id

如果与原作的代码不同,则需要进行更正。具体修改方法可以在ROS官方文档的Wiki中获取,该工具包名为bag_tools。该工具包包含了许多关于数据包的操作工具,并附带了详细的使用教程,用户可以通过百度获取。举个例子,以下是常见的修改指令:

复制代码
     rosrun  bag_tools  change_frame_id.py  -t  /需要要改的topic   -f    新的frame_id    -i      旧.bag    -o    新.bag

二、lidar_align进行激光雷达与IMU外参标定

以上第一步仅解决了数据格式的处理问题,如何实现两种传感器的有效配合仍需进行更深入的标定工作,这一阶段的问题较为突出。我采用了瑞士苏黎世联邦理工大学自动驾驶实验室开发的lidar_align标定工具进行标定工作,不多赘述,感兴趣的朋友可以自行查阅相关资料。

2.1下载lidar_align

复制代码
    mkdir  -p  lidar_align/src
    
     cd lidar_align/src
    
    git clone https://github.com/ethz-asl/lidar_align.git
    
    catkin_make

首次编译会缺库报错

2.2安装nlopt库

复制代码
    sudo apt-get install libnlopt-dev

将该配置文件复制至lidar_align/src/目录后进行二次编译。复制后运行编译器时,可能会出现错误信息。

2.3 处理定义冲突问题

复制代码
    //依次运行以下指令
    sudo mv /usr/include/flann/ext/lz4.h /usr/include/flann/ext/lz4.h.bak
    
    sudo mv /usr/include/flann/ext/lz4hc.h /usr/include/flann/ext/lz4.h.bak
    
    sudo ln -s /usr/include/lz4.h /usr/include/flann/ext/lz4.h
    
     sudo ln -s /usr/include/lz4hc.h /usr/include/flann/ext/lz4hc.h
    
     catkin_make

三次编译成功

2.4 改写IMU接口

该工具并非专为标定激光雷达与里程计设计,而是用于标定激光雷达与IMU。因此,需要对IMU接口进行改写以替代里程计接口。由此可见,该工具在精确标定激光雷达与里程计方面存在一定局限性,但其仍具有实用价值。改写接口的大佬尚未找到解决方案,导致无法正常连接。因此,建议删除该接口或与相关 parties 联系。以下是具体操作步骤:

  1. 打开loader.cpp文件
复制代码
    找到以下odom部分注释删掉都可
    /*  types.push_back(std::string("geometry_msgs/TransformStamped"));
      rosbag::View view(bag, rosbag::TypeQuery(types));
    
      size_t tform_num = 0;
      for (const rosbag::MessageInstance& m : view) {
    std::cout << " Loading transform: \e[1m" << tform_num++
              << "\e[0m from ros bag" << '\r' << std::flush;
    
    geometry_msgs::TransformStamped transform_msg =
        *(m.instantiate<geometry_msgs::TransformStamped>());
    
    Timestamp stamp = transform_msg.header.stamp.sec * 1000000ll +
                      transform_msg.header.stamp.nsec / 1000ll;
    
    Transform T(Transform::Translation(transform_msg.transform.translation.x,
                                       transform_msg.transform.translation.y,
                                       transform_msg.transform.translation.z),
                Transform::Rotation(transform_msg.transform.rotation.w,
                                    transform_msg.transform.rotation.x,
                                    transform_msg.transform.rotation.y,
                                    transform_msg.transform.rotation.z));
    odom->addTransformData(stamp, T);
      }
    */
    
    将以上部分替换为:
    
    	types.push_back(std::string("sensor_msgs/Imu"));
    	rosbag::View view(bag, rosbag::TypeQuery(types));
    	size_t imu_num = 0;
    	double shiftX=0,shiftY=0,shiftZ=0,velX=0,velY=0,velZ=0;
    	ros::Time time;
    	double timeDiff,lastShiftX,lastShiftY,lastShiftZ;
    	for (const rosbag::MessageInstance& m : view){
    	  std::cout <<"Loading imu: \e[1m"<< imu_num++<<"\e[0m from ros bag"<<'\r'<< std::flush;
    
    	  sensor_msgs::Imu imu=*(m.instantiate<sensor_msgs::Imu>());
    
    	  Timestamp stamp = imu.header.stamp.sec * 1000000ll +imu.header.stamp.nsec / 1000ll;
    	  if(imu_num==1){
    		 time=imu.header.stamp;
    			 Transform T(Transform::Translation(0,0,0),Transform::Rotation(1,0,0,0));
    		 odom->addTransformData(stamp, T);
    	 }
    	 else{
    		 timeDiff=(imu.header.stamp-time).toSec();
    		 time=imu.header.stamp;
    		 velX=velX+imu.linear_acceleration.x*timeDiff;
    		 velY=velX+imu.linear_acceleration.y*timeDiff;
    		 velZ=velZ+(imu.linear_acceleration.z-9.801)*timeDiff;
    
    		 lastShiftX=shiftX;
    		 lastShiftY=shiftY;
    		 lastShiftZ=shiftZ;
    		 shiftX=lastShiftX+velX*timeDiff+imu.linear_acceleration.x*timeDiff*timeDiff/2;
    		 shiftY=lastShiftY+velY*timeDiff+imu.linear_acceleration.y*timeDiff*timeDiff/2;
    		 shiftZ=lastShiftZ+velZ*timeDiff+(imu.linear_acceleration.z-9.801)*timeDiff*timeDiff/2;
    
    		 Transform T(Transform::Translation(shiftX,shiftY,shiftZ),
    				Transform::Rotation(imu.orientation.w,
    						 imu.orientation.x,
    						 imu.orientation.y,
    						 imu.orientation.z));
    		 odom->addTransformData(stamp, T);
    	 }
    	}
    
    并在开头添加头文件:
    
    #include <sensor_msgs/Imu.h>

第四次catkin_make编译。

2.5 录制数据以及标定

这一环节相对较为简单,只需同步采集激光雷达和IMU数据两分钟即可。建议将采集的数据量控制在2GB以内,若数据量超过2GB可能导致无法准确标定。采集两分钟的数据时,应尽量包含较大的旋转和平移量(即先直线行驶一段距离后再进行两次完整旋转),这样处理后,标定误差通常会收敛到几百个单位以下(实验者对数据包进行了初步标定,结果表明误差结果在17,000左右,供参考使用)。标定过程较为漫长,建议保持耐心。

2.5.1 修改launch文件

打开lidar_align.launch文件,将两分钟的数据包路经copy到下方:

在这里插入图片描述

改写了接口后,导入数据时无需使用表格形式,只需将数据包路径修改到launch文件中。以下是一些过程演示,标定时间可能需要一小时多一点,大约需要迭代300多次。

2.5.2启动launch开始标定迭代
复制代码
    source devel/setup.bash 
    roslaunch lidar_align lidar_align.launch
在这里插入图片描述

标定结束:

在这里插入图片描述

标定完成后,标定结果会在终端显示,同时生成相应的标定文件存放在result文件夹中。有了这个文件,就可以提取所需数据并写入LIO-SAM的配置参数文件。具体提取哪些数据将在后面详细说明。

在这里插入图片描述

三、imu_utils进行IMU内参标定

IMU内参是构建IMU模型的关键参数之一,在建图过程中,内参未标定往往会引发定位漂移(跑飞)现象。笔者采用港科大开发的标定工具imu_utils,通过Allan方差法对高斯白噪声和随机游走噪声(即零偏Bias)进行标定。完成内参标定后,即可着手进行IMU建图工作。

3.1下载imu_utils

在具体操作步骤中,我参考了大佬文章的内容。可以直接跳过,按照大佬的教程进行安装,随后在kalibr中完成相机和IMU的联合标定。

3.2准备标定需要的数据包

为确保数据质量,建议持续记录两个小时的IMU数据。请特别注意:在录制过程中,IMU必须保持静止状态,不允许有任何移动。确保在录制过程中,IMU的晃动幅度极小,建议仅采集Topic数据。无需担心内存占用,两小时的录制过程预计仅消耗约300MB的存储空间。建议将该数据包存储在imu_utils的工作目录中,与src目录保持一致。关于录制指令的详细说明,这里不做赘述,属于老手的常规操作。

3.3修改launch文件

在imu_utils目录下,存在多个launch文件,其中包含A3.launch文件。你可以将该文件复制,并将其重命名为test.launch。打开test.launch文件后,将其中的value字段替换为你的IMU数据topic。120表示数据采集的时间长度,持续两天即设置为120。

在这里插入图片描述

3.4开始标定

复制代码
    //启动launch
    source  devel/setup.bash
    roslaunch imu_utils test.launch
在这里插入图片描述
复制代码
    //以两百倍速播放IMU数据包
    rosbag play -r200  imu.bag

当终端接收数据时并未产生反应,直到任务完成才会显示标定结果。此外,在data文件夹中会产生多个文件,其中我们关注的是第一个。

在这里插入图片描述

四、修改LIO-SAM配置参数

现在,我们终于到达了这个阶段,成功地获得了所有必要的参数配置。打开配置文件夹中的params.yaml文件,(建议备份一份源文件以供后续操作)。请注意,目前我们仅启用了激光雷达和IMU,而GPS未被配置。

4.1 修改订阅话题

在红框的地方修改雷达和IMU订阅话题

在这里插入图片描述
4.2修改IMU内参
在这里插入图片描述

在IMU设置部分,就是修改内参的地方。打开第三步标定好的IMU内参文件,文件路径为A3_imu_param.yaml,找到以下参数:

在这里插入图片描述

对应关系为:

复制代码
    // Acc误差模型高斯白噪声
      imuAccNoise<---------->acc_n 
      // Gyro误差模型高斯白噪声
      imuGyrNoise<----------> gyr_n
      // Acc误差模型随机游走噪声
      imuAccBiasN<---------->acc_w
      // Gyro误差模型随机游走噪声
      imuGyrBiasN<----------> gyr_w
      
      imuAccNoise: 1.5126001818597128e-02
      imuGyrNoise: 6.5980273374716312e-05
      imuAccBiasN: 2.3794907799562417e-04
      imuGyrBiasN: 8.9601374044261405e-07

按照以上对应关系修改参数即可

4.3修改Lidar–>IMU外参

启动下一步标定激光雷达与IMU的外参配置的calibration_oprater_hp_5402_590670726118389616.txt文件,定位所需参数。

在这里插入图片描述

将上述方框标注的参数对应写到下方配置文件中:

复制代码
      extrinsicTrans: [0.00290376, -0.00715269, 0.0395843]
      
      extrinsicRot: [-0.975156 ,-0.221516 ,-0.000975888,
                  0.221498  ,-0.975118 , 0.00911853,
                 -0.00297151 ,0.00867584 ,0.999958]
      extrinsicRPY: [-0.975156 ,-0.221516 ,-0.000975888,
                  0.221498  ,-0.975118 , 0.00911853,
                 -0.00297151 ,0.00867584 ,0.999958]

其他参数建议保持不变,修改后及时保存。建议在编译完成后,没有错误的情况下运行launch文件启动播包。此外,默认情况下不会保存pcd文件,用户可以自行调整,但每次运行前需修改路径,否则会覆盖上次生成的文件。

五、起飞

这一系列操作逐一执行起来确实颇为费力。听听歌曲放松心情,起飞前做好准备 芜湖起飞.

复制代码
    source devel/setup.bash 
    roslaunch lio_sam run.launch

启动之后可以播包试试了
下面是笔者自己数据包建图效果(室内):

在这里插入图片描述
在这里插入图片描述

保存到本地的pcd文件

在这里插入图片描述

六、总结

本文仅介绍了融合速腾激光雷达与IMU方案的具体实施步骤,并未展开理论分析,下面将列举一些参考文献。

建图效果读者自评,参数优化程度尚待提升,仅进行粗略标定。值得注意的是,激光雷达与IMU的外参定位精度未达理想状态,但相较于仅采用激光雷达的紧耦合算法,该方法较其他单激光雷达传感器算法在效果上已有显著提升。自发现LIO-SAM建图效果以来,逐步适配并测试自己的数据集,直至获得上述效果,大约两个月左右。作为3D-SLAM学习者,上述结果仅反映个人建图过程中的问题与解决方法,期待进一步探讨更优解决方案。

以下是笔者参考过的文章,感谢各位大佬分享:

安装教程参考:

LIO-SAM:环境配置、测试安装、数据集适配

各种知识点参考:

  1. 激光SLAM在运行LIO-SAM过程中可能存在的潜在问题

2.LIO-SAM源码阅读分析(1)–配置文件Tips.

3.Lidar与IMU标定代码实战:lidar_align.

4.【学习总结】Lidar与IMU标定.

5.LIO-SAM论文翻译.

第6步,通过imu_utils对IMU进行校准,随后将其应用于kalibr软件中,用于相机与IMU的综合校准。

7.IMU误差模型和校准.

8.lio-sam源码解析,重点:imu预积分.

第9项,激光雷达与IMU协同标定并运行LIOSAM算法

版权声明信息:本文为个人原创文章,遵循CC 4.0 BY-SA授权协议。如需转载,请附上原文链接及版权声明说明。访问该文章的详细信息,请访问:

全部评论 (0)

还没有任何评论哟~