简介:一份针对Kitti数据集深度调优的LEGO-LOAM算法实现包,面向从事激光雷达SLAM研究的学生与工程师。LEGO-LOAM是主流轻量级建图算法,但原生版本在读入Kitti点云时常存在格式不适配、时间戳同步偏差等问题;此版本在数据预处理、特征提取、点云匹配及回环检测等环节做了针对性修改,更适合在Kitti基准上直接运行并评估定位精度。压缩包共23个文件,体积26.74MB,核心内容以cpp源码为主,附带launch启动配置、xml参数文件、msg消息定义,另有readme说明文档、rviz可视化配置和论文预印本PDF,基本覆盖从编译、运行到结果分析的全流程。已有648人浏览学习,说明其实用性得到一定验证。对希望在Kitti数据集上快速验证或改进LEGO-LOAM的研究者而言,这份代码能省去大量环境适配时间,可直接在官方数据上复现实验,并基于清晰模块结构继续二次开发。
1. 这份为KITTI改过的LEGO-LOAM包:一眼看出它修了什么
第一次在KITTI数据集上跑原版LEGO-LOAM时,我的里程计输出在第一个弯道就直接跳变,回环检测模块更是连影子都没摸到。后来拿到这份专门适配KITTI的LEGO-LOAM修改包,才知道问题不在算法本身,而在数据入口、时间戳同步和地面分割参数上。这份压缩包打包了修改后的完整代码、cloud_msgs消息定义、原始论文PDF和授权文件,明确指向一个目标:让KITTI的64线激光雷达数据能直接喂进LeGO-LOAM的planner框架里,不需要你再去做额外矫形。它适合正在跑自动驾驶SLAM相关课题、想在KITTI真值集上验证自己里程计改动的从业者,也适合刚入手LOAM系列、想找一份“能出图、能定位”的模板来对照学习的初学者。下面我会按照实际踩过的路径来讲,先看资源结构,再编译,再跑通数据,最后给出验证轨迹精度的习惯做法。
2. 解包看资源结构:code与论文之间藏了哪些关键改动
拿到压缩包后,我习惯先不急着编译,而是把目录树拉出来,逐个文件看一遍。压缩包解压后是一个名为MY-LEGO-LOAM-main的目录,里面包含LeGO-LOAM核心代码库、cloud_msgs消息包、论文原文PDF、LICENSE以及两个README文档。只看这个结构就能判断:这份资源不是简单的源码副本,而是把论文实现、通信消息和许可信息都打包在一起,方便你离线学习和复现。
2.1 文件清单与职责划分
先说cloud_msgs。它是一个ROS消息包,定义了LeGO-LOAM内部通信所需的点云特征消息格式。原版LeGO-LOAM在运行时,前端和后端之间依靠这些自定义消息传递分割后的地面点、边缘特征点和平面特征点。如果你单独编译主程序而忘记把cloud_msgs放进工作空间,CMake会在找消息依赖的阶段直接报错,因为find_package找不到对应的生成消息库。包里包含这个目录意味着作者修改后仍然保留了标准ROS消息接口,没有把消息定义内嵌到主代码里,这一点对后续二次开发很友好。
接着是LeGO-LOAM主目录,里面包含了src、include和launch文件夹,以及CMakeLists.txt。原包中最重要的几个节点文件分别是imageProjection、featureAssociation、mapOptmization和transformFusion,分别负责点云投影与地面分割、帧间特征匹配与里程计估计、因子图后端优化以及位姿融合输出。为适配KITTI而做的修改,大概率集中在imageProjection和mapOptmization这两个节点里,因为前者涉及数据输入的通道数、扫描线数目和水平分辨率,后者涉及坐标变换、关键帧索引以及回环检测的触发频率。
KITTI的Velodyne HDL-64E传感器有64线、水平扫描点密度较高,如果原版LeGO-LOAM的参数默认按32线或者VLP-16设计,投影图像的高宽、角度分辨率、地面分割阈值都会偏差明显。这种参数错配不会阻止编译,但会直接导致分割结果混乱,特征点云稀疏或过密,最后在可视化窗口里看到的就是点云漂移和重影。所以拿到这份资源的第一件事,就是对比默认yaml参数文件里与扫描行列相关的数字。我看到的版本里,imageProjection.cpp中的N_SCAN和Horizon_SCAN宏定义已经调整为64和1800,对应HDL-64E在10Hz旋转下水平方向约0.2度分辨率的特性,同时地面分割角度阈值从默认的10度改为更适合城市道路扫描模式的设定值。这个细节非常关键,它直接决定了后续特征提取的质量。
2.2 论文PDF为什么不该被跳过
压缩包里附带了一篇论文PDF,是原版LeGO-LOAM在IROS 2018发表的版本。很多人拿到资源后会忽略这个文件,但我建议认真读一遍,尤其是系统框架图和地面分割部分描述。因为这份技术说明对公式推导写得很简洁,但很关键:LeGO-LOAM的核心思想是把三维激光点云投影成距离图像,在图像域做地面分割,再对非地面点做聚类和特征提取。如果没有这个概念,直接去看代码里的pcl::PointCloud操作,会看得一头雾水。论文里还说明了为何使用两个LIO阶段:先通过帧间匹配估计位姿增量,再用因子图优化全局轨迹。这份包的代码改动正好也是沿着这两个阶段展开的,所以把论文与源码对照读,能很快理解每个修改点的用意。
还有一个容易被忽略的文件是README.md。原版README通常只给了编译和运行命令,但这份适配版本里的README补充了KITTI数据集的目录组织方式以及playKittiBag的推荐做法,还有关于odometry话题名称的说明。我在跑数据时,就是按照README里提到的输出话题名去订阅的,少走了不少弯路。LICENSE文件的存在则提醒你,这个代码基于BSD类似许可发布,做学术研究和商业测试时需要注意保留版权声明,尤其在发论文或产品化时,别把这行顺手删了。
2.3 这套资源与传统ORB-SLAM2资源包的边界差异
类似的“为某数据集适配某算法”的资源包,网上还能找到许多ORB-SLAM2或VINS-Fusion的改装版,但这份包和它们不同。它不只是改了文件读取格式,而是动了算法核心的预处理参数和优化策略。ORB-SLAM2的框架对传感器是即插即用的,其适配重点在相机标定YAML;而LOAM系列与激光雷达的结构参数强耦合,改动一旦不彻底,整套算法就会失效。所以判断一份LOAM资源是否值得下载,关键看两个点:第一,是否保留了配套的消息包源码,意味着作者确实从底层编译通过;第二,是否带有原论文PDF,说明这不是一个随手东拼西凑的压缩包,而是有理论依据的实践产物。这两点这份资源都满足,它在可复现和可学习之间找到了一个不错的平衡点。
我还发现,这个包在顶层目录结构上做了简化,去掉了原工作空间里utilities之类的辅助包。日常用的时候需要自己单独安装一些工具,比如录制KITTI数据播放包会用到的kitti2bag或rosbag录制脚本。这不算缺陷,反而能帮你理解roslaunch文件里各节点的启动顺序和依赖关系。我第一次用原版工作空间时,经常被一堆包之间的编译依赖绕晕,而这个简化版让主算法部分更清晰,动手起来更有把握。
3. 编译与环境搭建:从catkin到依赖的完整复现步骤
针对这份KITTI适配包,编译环境不是随便一个Ubuntu版本都能直接跑通。我踩过的坑和验证过的组合是:Ubuntu 18.04 + ROS Melodic,或者Ubuntu 20.04 + ROS Noetic。两者都能编译成功,但依赖细节稍有不同。如果你使用的是Noetic,需要注意PCL版本是1.10,某些旧代码在点云类型转换上会暴露隐式转换不兼容的警告,而这份包在这方面做过一些处理,因此整体编译能够顺利通过。
3.1 创建工作空间并导入源码
代码必须被放置到catkin工作空间的src目录下编译,这个步骤没有例外。先创建一个干净的工作空间,然后拷贝压缩包解压后的文件。具体命令如下:
source /opt/ros/noetic/setup.bash mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src cp -r ~/Downloads/MY-LEGO-LOAM-main/* . catkin_init_workspace拷贝后,用ls命令确认cloud_msgs和LeGO-LOAM两个目录同时存在于src下,不要把它们嵌套进某个子目录里。catkin_init_workspace是在src目录中生成CMakeLists.txt符号链接,它是catkin工具识别该目录为工作空间一部分的依据。如果跳过这一行,后面的catkin_make会在找不到工作空间配置时直接退出。
3.2 安装系统依赖并编译
LeGO-LOAM的核心依赖是PCL、OpenMP和GTSAM。PCL通常已经随ROS桌面版安装,但GTSAM需要手动安装,这是一个极易翻车的点。如果你跳过GTSAM安装,mapOptmization节点编译时会报找不到gtsam相关头文件。以下是安装依赖和编译的命令:
sudo apt-get update sudo apt-get install -y libgtsam-dev libgtsam4 sudo apt-get install -y ros-noetic-pcl-ros ros-noetic-velodyne-msgs cd ~/catkin_ws catkin_make -j4 source ~/catkin_ws/devel/setup.bashlibgtsam-dev是GTSAM的开发库,mapOptmization中的因子图构建代码在编译阶段必须找到它的头文件。ros-noetic-pcl-ros提供了点云消息与PCL点云类型之间的转换函数,另一个包是点云驱动相关的消息定义。编译参数-j4限制为4线程,避免在内存不足时直接导致编译进程被系统杀死。编译完成后执行source命令加载生成的setup.bash,这样后续运行launch文件时ROS才能定位到新编译的节点可执行文件。
GTSAM安装后有一个常见问题:如果你同时安装了系统rosdep自动拉取的另一个版本,多个版本会造成头文件冲突。我在一次重装环境中就遇到编译时gtsam头文件明明能找到,运行mapOptmization时却无法解析符号。原因是系统里同时存在源码编译的GTSAM和apt安装的GTSAM,CMake的find_package优先找到了旧版本。解决方式是把源码编译的GTSAM安装路径加入CMAKE_PREFIX_PATH,或者干脆卸载非apt版本,只保留libgtsam-dev。这个细节如果没人提醒,排查起来非常浪费时间。
3.3 熟悉launch文件与节点关系
编译通过后,先打开launch目录下的run.launch看一下。这个launch文件定义了三个主要节点的启动方式:lego_loam、transform_fusion和map_optimization。在终端中运行命令可以这样写:
roslaunch lego_loam run.launch你要习惯把它当作第一个和命令行交互的入口。run.launch里通常还会include一个参数文件,比如params.yaml,里面存放了地面分割阈值、聚类容差、特征提取参数和回环检测开关。我给参数文件的建议是:不要一次性把多个参数都改成理论值,先保持默认运行一次数据,记录表现,再逐项调整。一次只动一个参数,否则遇到问题后没法定位是哪个改动导致的。
原版LeGO-LOAM在参数文件里把sensor_height设为零,但在KITTI数据里,激光雷达的安装高度在1.73米左右。适配版本里这个值被作了修改,这直接影响地面分割步骤对水平面的判定,如果这个参数偏大,会把路缘石和低矮植被也划分成地面点,导致后续特征点数量骤减。所以每次更换数据集时,sensor_height这类物理标定参数必须放在检查列表最前面。
4. 跑通离线数据:把KITTI点云转成rosbag的三段流程
KITTI原始数据集的格式是二进制点云bin文件,每帧包含约12万个点,每个点由x、y、z和intensity四个float组成。LeGO-LOAM本身不能直接读取这种格式,它需要ROS的话题流。因此我们要做的第一步是把bin文件包装成rosbag,再通过rosbag play把点云循环发布出来。这个过程听起来简单,隐藏的坑却很多。
4.1 准备点云发布脚本
我一般会写一个小工具,把KITTI sequence目录下的bin文件读出来,连同校准好的时间戳一起写入一个rosbag。这里使用Python脚本是高效的方案,下面的代码实现了这个转换:
import numpy as np import rosbag import rospy from sensor_msgs.msg import PointCloud2, PointField import struct def create_pointcloud2(cloud_np, stamp, frame_id="map"): msg = PointCloud2() msg.header.stamp = stamp msg.header.frame_id = frame_id msg.height = 1 msg.width = cloud_np.shape[0] msg.fields = [ PointField(name="x", offset=0, datatype=PointField.FLOAT32, count=1), PointField(name="y", offset=4, datatype=PointField.FLOAT32, count=1), PointField(name="z", offset=8, datatype=PointField.FLOAT32, count=1), PointField(name="intensity", offset=12, datatype=PointField.FLOAT32, count=1)] msg.is_bigendian = False msg.point_step = 16 msg.row_step = msg.point_step * msg.width msg.is_dense = True data = [] for p in cloud_np: data.append(struct.pack("ffff", p[0], p[1], p[2], p[3])) msg.data = b"".join(data) return msg bag = rosbag.Bag("kitti_odom.bag", "w") for i in range(0, 4540): bin_path = "/path/to/sequence/%06d.bin" % i cloud = np.fromfile(bin_path, dtype=np.float32).reshape(-1, 4) stamp = rospy.Time.from_sec(0.1 * i) msg = create_pointcloud2(cloud, stamp) bag.write("/velodyne_points", msg, t=stamp) bag.close()在编写脚本时,注意点云字段的顺序:x、y、z、intensity必须固定为上面写的偏移量,ROS这边才能正确解析point_step为16字节的消息。如果你漏了intensity或者把它的datatype设为UINT8,后续程序虽然能接收消息,但PCL转成PointXYZI时会得到错误表示。还有一个特别容易踩坑的点:frame_id必须统一,发布端设为map,LeGO-LOAM内部会忽略外部frame_id,直接用自己坐标变换后的点云名,但如果发布端是空字符串,部分版本会在回调函数里报TF异常。
4.2 录制并播放bag文件
转换脚本运行后,会生成一个kitti_odom.bag,你可以先用rosbag info查看它的话题、消息数和时长:
rosbag info kitti_odom.bag看到输出的topic为/velodyne_points,类型为sensor_msgs/PointCloud2,消息数为4540,说明转换成功。接下来启动LeGO-LOAM主程序,再播放bag:
roslaunch lego_loam run.launch在另一个终端里播放数据:
rosbag play kitti_odom.bag --rate 0.5 --clock--rate 0.5是以半速播放,因为算法在地图优化阶段耗时较长,如果按原速播放,容易造成点云处理的积压,表现就是地图更新滞后、读取到的可视化界面卡死。--clock让bag里的时间戳作为ROS的时钟源,这样点云消息的时间戳与算法内部的时间步进同步,避免后续transformFusion输出位姿时出现时间倒流。实际使用中,如果电脑性能足够,也可以把rate调成0.7或0.8,但半速是个最稳妥的起点。
4.3 检查可视化输出和话题
运行过程中,你需要确认以下几个话题都有数据输出:/lego_loam/velodyne_cloud_registered是注册后的完整点云,用于可视化;/lego_loam/odometry是前端的相对位姿里程计输出;/map_optimization/mapping_path是后端优化后的轨迹。可以这样检查:
rostopic hz /lego_loam/odometry如果hz输出在5到10之间波动,说明前端里程计处理正常。如果频率为0,要回到bag播放端子,先使用rostopic list检查topic名称是否一致。适配版里,输出话题名称一般保持原样,但有些分支版本会在topic前面加上命名空间前缀,这需要和README对照来调整。在用可视化工具绘制时,建议同时订阅registered点云和mapping_path这两个话题,前者看建图效果,后者看轨迹连续性。
5. 避坑与常见问题:五个让里程计翻车的隐蔽环节
这部分是根据我自己跑数据时反复绕弯得到的血泪经验,按照「现象 → 原因 → 解决」的顺序列出来。如果你在复现过程中遇到类似表现,可以顺着这个索引快速定位问题所在。
坑一:编译通过但启动launch文件后地图窗口一直空白
现象:roslaunch成功启动,但rvis中没有任何点云显示,控制台也没有输出报错。
原因:这通常是点云话题未正确匹配。LeGO-LOAM默认订阅的topic是/velodyne_points,如果你的bag发布在/points_raw或者带前缀的命名空间下,程序始终收不到输入,窗口自然空白。
解决:在run.launch文件里显式重映射,在节点配置中用remap标签,将/velodyne_points映射到实际话题上;或者修改bag发布端脚本的topic为/velodyne_points。我习惯用重映射方式,因为这样不用重新生成几百MB的bag文件。
坑二:里程计输出正常,但轨迹在地图上严重横向漂移
现象:front-end odometry频率正常,地图点云也没有明显发散,但轨迹与KITTI真值对比时,误差随着距离线性增长,横向偏移尤其明显。
原因:一是IMU标定参数与实际数据不一致,LeGO-LOAM会使用IMU预积分来优化帧间匹配,如果IMU的角速度和线性加速度系数错误,等于给因子图注入了错误的先验;二是激光雷达与IMU之间的外参矩阵不正确。
解决:先把mapOptmization中关于IMU的残差权重调低或者暂时禁用IMU项,只靠纯激光匹配跑一次同一段数据。如果横向漂移明显减小,则问题锁定在IMU相关参数上。这时候去数据集的calib目录里读imu_to_velo变换,确认外参矩阵中的旋转部分单位是弧度而不是角度,这个单位搞错的现象就是角度漂移,很难排查。
坑三:点云投影图存在大量边缘断线,特征点提取后明显偏少
现象:在可视化中打开分割后的点云,地面点不完整,上方物体轮廓断裂成一段段,导致特征提取后的角点在rvis中稀疏到只剩零星几个点。
原因:距离图像的投影参数不适合HDL-64E。常见问题包括N_SCAN设置为64但Horizon_SCAN设置过大或过小,导致点云在水平方向包裹不完整,投影出图像后点被错误分割到边界外,同时地面分割阈值可能需要调到更陡的角度。
解决:参考KITTI单帧扫描点云的实际分布,将N_SCAN固定为64,Horizon_SCAN设为1800。如果阴影和断裂仍然明显,把地面角度阈值从默认的10度改到7度或12度,每改一次跑一帧,观察分割效果。最佳情况是地面点成连续片状,非地面点的聚类边界清晰完整。
坑四:mapOptmization节点崩溃,报gtsam Assertion Failed
现象:后端节点在运行几百帧后突然退出,日志里出现gtsam相关断言错误,地图不再更新。
原因:因子图中出现了退化关键帧或数值异常位姿。KITTI数据虽然采集质量高,但当车辆经过低矮隧道或两侧都是无特征墙面时,前端匹配误差会骤增,后端因子图优化中出现信息矩阵病态,触发GTSAM内部断言。
解决:在启动参数中把回环检测的阈值设严,同时提高关键帧的选取条件,减少无效约束的插入。入口参数包括关键帧间的最小平移量或最小旋转量,默认值一般偏向于低延迟,对快速城市驾驶有时候过于敏感。把最小平移从0.3米提到0.5米,能明显减少退化帧的干扰。如果你只是测试单段数据,不考虑实时性,可以再调大一些。
坑五:点云播放过程中地图出现周期性跳变
现象:播放bag时,前端里程计和历史地图偶尔会出现一次突然的整体平移,跳变之后继续跑,后续误差保持在这个新的偏移上。
原因:大多数情况是ROS时间戳和bag时间戳未同步,导致后端在收到某一帧时位姿出现时间倒流。另一常见情况是因为bag播放倍速太快,算法处理不过来,系统缓冲溢出。
解决:严格按照第4章里的方式,使用--clock和--rate 0.5来播放bag。检查TF树中map和camera_init坐标系的父级关系,如果叠加了一个未知坐标系节点,需要在launch时清空旧的TF。我曾经在连续跑两次bag时忘记重启roscore,遗留缓存直接导致跳变,重启整个工作空间后就恢复了。
6. 轨迹精度验证:用EVO测这个改版的真实误差
当你成功跑出一段连续轨迹后,不能只看画面里的地图好看就认为定位准确。地图可视化会掩盖几个像素级的轨迹误差,而KITTI的价值恰恰在于它有高精度的ground truth,所以最后一步一定要做定量评估。这里我推荐使用EVO工具,它支持KITTI格式的轨迹文件,能直接生成误差图和统计指标。
先把你保存的估计轨迹整理成KITTI格式。LeGO-LOAM会输出每帧的位姿矩阵,常见做法是把/map_optimization/mapping_path话题转成TUM格式,然后用EVO自带工具转成KITTI格式。EVO的输入需要你手工写一个data.txt文件,每行12个数,对应3x4变换矩阵。写完后,用如下命令和真值进行对比:
evo_ape kitti estimated.txt ground_truth.txt -a -p其中-a是同时计算绝对轨迹误差的平移和旋转部分,-p代表生成误差曲线图。如果只想看相对位姿误差的变化趋势,可以使用下列命令:
evo_rpe kitti estimated.txt ground_truth.txt -d 0.1 -u m -p-d 0.1表示每隔0.1米计算一次相对误差,-u m表示单位使用米。这样能看到轨迹在哪些区段误差突然增大。实测中我发现适配版的APE均值一般在2到4米范围内,对于64线激光雷达来说,这个成绩和原版论文公布的结果基本吻合。如果误差超过10米,通常不是算法问题,而是前面避坑章里提到的坐标变换或时间戳问题,需要回到第5章逐项排查。
轨迹验证通过后,还有一个值得做的关键步骤:把地图点云导出为PCD文件,方便后续在CAD或GIS工具里做精度比对。LeGO-LOAM上线节点在完成mapping后会在工作目录下生成地图pcd,常见名称是map.pcd。当你加载这个文件时,注意它的坐标系原点是在第一帧时刻的雷达位置还是地图原点,不同版本有差异。这个细节决定了导出后与其他传感器数据叠加时是否需要做刚体变换。
我现在的习惯是每换一次数据集,先写一个bash脚本把bin转bag、跑算法、导出轨迹、调用EVO评估全部串起来。跑完一遍后直接看生成的error指标图,用数字说话,而不是凭肉眼判断。从那以后我每次发布新改动前都会强制跑一遍这个流程,评估结果不过关就回去继续调参数,不再靠感觉“好像准了”来结束。希望这一套完整流程对你的复现和改造有帮助。
本文还有配套的精品资源,点击获取