
简介一份针对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安装的GTSAMCMake的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_idmap): msg PointCloud2() msg.header.stamp stamp msg.header.frame_id frame_id msg.height 1 msg.width cloud_np.shape[0] msg.fields [ PointField(namex, offset0, datatypePointField.FLOAT32, count1), PointField(namey, offset4, datatypePointField.FLOAT32, count1), PointField(namez, offset8, datatypePointField.FLOAT32, count1), PointField(nameintensity, offset12, datatypePointField.FLOAT32, count1)] 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, dtypenp.float32).reshape(-1, 4) stamp rospy.Time.from_sec(0.1 * i) msg create_pointcloud2(cloud, stamp) bag.write(/velodyne_points, msg, tstamp) bag.close()在编写脚本时注意点云字段的顺序x、y、z、intensity必须固定为上面写的偏移量ROS这边才能正确解析point_step为16字节的消息。如果你漏了intensity或者把它的datatype设为UINT8后续程序虽然能接收消息但PCL转成PointXYZI时会得到错误表示。还有一个特别容易踩坑的点frame_id必须统一发布端设为mapLeGO-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主程序再播放bagroslaunch 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固定为64Horizon_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指标图用数字说话而不是凭肉眼判断。从那以后我每次发布新改动前都会强制跑一遍这个流程评估结果不过关就回去继续调参数不再靠感觉“好像准了”来结束。希望这一套完整流程对你的复现和改造有帮助。本文还有配套的精品资源点击获取