2026/9/28 2:04:14

ROS2下用LIO-SAM跑通MID360:从数据集到真机的完整指南

ROS2下用LIO-SAM跑通MID360:从数据集到真机的完整指南 1. 为什么我最终还是选了LIO-SAM跑ROS2先交代一下背景。手头有一台搭载Livox MID360的移动平台想在ROS2环境下做实时建图评估了一圈方案FAST-LIO2、Point-LIO、LIO-SAM都有对应ROS2实现。FAST-LIO2和Point-LIO在中低算力设备上确实轻快但如果你需要后端因子图优化、闭环检测、多传感器紧耦合这种相对完整的SLAM链路LIO-SAM至今依然是绕不开的参照系。MID360作为国产360度非重复扫描固态激光雷达视野覆盖广、近场盲区小、价格相比传统多线机械雷达友好很多所以怎么把它和LIO-SAM在ROS2里跑起来就成了一个非常现实的问题。但这里有个绕不开的尴尬LIO-SAM官方仓库是ROS1时代的代码依赖的是tf、pcl_ros、geodesy、nav_msgs这些老接口而ROS2里这些包的API大部分都变了。直接git clone之后用colcon build大概率会第一时间在编译期被tf::StampedTransform、ros::Time这些报错劝退。这也正是很多人卡在装好了ROS2但跑不起来LIO-SAM的根因。真正动手之前我给自己定了个目标第一阶段先用公开的walking_dataset把整条数据链路打通包括点云去畸变、IMU预积分、因子图优化、RVIZ2可视化第二阶段再把MID360实时的点云和IMU数据接进来替换数据来源看算法在真机数据上表现如何。写这篇文章就是想把这个过程里踩过的坑、验证过的参数、改过的代码逻辑完整记录下来。2. walking_dataset能教给你什么先弄明白数据结构再谈建图2.1 walking_dataset的传感器配置与话题构成walking_dataset是香港大学火星实验室HKU-MARS发布的手持设备数据集采集设备包含了Livox Avia雷达、工业相机、IMU等传感器。整套设备背着走一圈覆盖室内走廊、室外环道、楼梯等场景。这个数据集最大的价值在于它采集时没有gps信号辅助纯靠雷达-惯性紧耦合跑完全程非常适合用来验证LIO-SAM这类里程计算法在长时间、大范围、退化环境下的鲁棒性。用rosbag info看一下典型的话题结构大概是这样的$ ros2 bag info walking_dataset.bag Topics: /livox/imu (sensor_msgs/msg/Imu) /livox/lidar (sensor_msgs/msg/PointCloud2) /cloud_registered (sensor_msgs/msg/PointCloud2) /path (nav_msgs/msg/Path)/cloud_registered是官方跑通LIO-SAM之后输出的配准点云/path是优化后的轨迹。这两个话题可以作为我们的标准答案来对照验证自己跑出来的结果是否正常。2.2 从bag里拆数据别急着直接灌给LIO-SAM拿到bag文件后第一件事不是直接播放而是先解包分析数据特征。我习惯用ros2 bag convert或者直接写个Python脚本把数据抽出来看一眼。最需要确认的是三件事点云话题的时间戳是否连续有没有突然跳变IMU频率是否真的有200Hz左右还是数据采集时丢帧严重点云是Livox定制格式还是已经转成了标准PointCloud2。walking_dataset的通用配置是点云频率10Hz、IMU频率200Hz时长大约几分钟到十几分钟不等数据量在几个GB级别。这些信息直接决定了后续LIO-SAM配置里的N_SCAN、Horizon_SCAN、pointCloudTopic、imuTopic等参数怎么填。如果你拿到的bag数据是直接从硬件录制而没有做过坐标变换还需要确认雷达和IMU的外参。这个外参是LIO-SAM初始化做得准不准的关键。2.3 点云格式、外参标定和坐标系约定仔细看数据集里的点云话题你会发现一个容易忽略的细节Livox点云默认自带offset_time字段表示每个点相对点云帧头的时间偏移。LIO-SAM在做运动补偿去畸变时需要用到这个时间去插值IMU数据。如果你的数据源把offset_time丢了那后面跑出来的点云畸变会非常夸张表现为墙角弯曲、地面呈弧面。另外一个常见问题是坐标系约定。walking_dataset里的点云是在Livox雷达坐标系下的IMU数据是在IMU坐标系下的而LIO-SAM内部要以IMU坐标系作为body坐标系来计算预积分。所以外参矩阵extrinsicRot和extrinsicTrans必须填对否则前端匹配出来的位姿会发散。实测中把外参填反最典型的现象是跑几十帧后轨迹直接朝某个方向飞出去点云图变成一张星空图。3. ROS2环境搭建与依赖坑位humble版本下的LIO-SAM编译3.1 环境版本选择别在foxy上跟自己过不去个人建议直接用Ubuntu 22.04 ROS2 Humble。LIO-SAM的第三方依赖里GTSAM的ROS2包装版本对Humble支持最好社区里维护得也最勤快。如果非要用Foxy你会发现很多老的patch和新版本GTSAM的接口对不上往往要自己改CMakeLists。安装ROS2 Humble这一步就不展开了网上教程很多。关键是把以下依赖装齐sudo apt install ros-humble-pcl-ros ros-humble-pcl-conversions ros-humble-geodesy ros-humble-nav-msgs ros-humble-tf2-geometry-msgs ros-humble-rviz2 sudo apt install libgtsam-dev libboost-all-dev libeigen3-dev3.2 LIO-SAM的ROS2分支选择这里是第一个大坑网上能搜到的LIO-SAM ROS2分支非常多质量参差不齐。我最后用的是TixiaoShan/LIO-SAM官方主分支自己改配置并结合社区里几个修复ROS2话题/参数接口的patch。需要改动的地方主要有四处CMakeLists.txt中的包名和依赖改为ament_cmake所有ros::改为rclcpp::ros::Time改为rclcpp::Timeros::Duration改为rclcpp::Duration话题订阅和发布改为rclcpp::create_subscriptiontf::StampedTransform改为geometry_msgs::msg::TransformStamped用tf2_ros::Buffer完成坐标查询。如果你不想从头改GitHub上有一个比较活跃的分支叫ros2的fork但我实测过它有几个小bug主要是mapOptimization.cpp里闭环回调的锁没有加对导致跑一段时间后程序直接卡死。所以我最终采取的策略是在ROS1原版代码基础上用ROS2的API逐文件迁移。3.3 编译过程中最常见的报错与修复编译时我遇到的第一个报错是error: ‘make_shared’ is not a member of ‘std’解决方案是在文件头部补上#include memory。这个看起来很蠢但ROS1时代的代码很多默认不显式引用memory头文件到了ROS2严格编译模式下就直接暴露。第二个高频报错是error: no matching function for call to ‘rclcpp::Node::declare_parameter(const char [20], int)’这个是因为ROS2的参数声明必须指定类型而原版LIO-SAM是直接private_nh.param(N_SCAN, N_SCAN, 16)这种写法。改成declare_parameter(N_SCAN, 16)再get_parameter(N_SCAN, N_SCAN)即可。第三个报错是pcl::PointCloudPointType::Ptr和PointCloud2::SharedPtr之间的转换问题。ROS2里接收回调拿到的不是ROS1的sensor_msgs::PointCloud2ConstPtr需要自己显式调用pcl::fromROSMsg来做转换。这个不改的话编译不报错但跑起来直接segfault排查起来非常费时间。4. 从walking_dataset到跑通配置参数、重放bag、RVIZ2观察4.1 参数配置一版可以直接用的配置以下是我验证过可以正常跑通walking_dataset的配置参数核心部分lio_sam: ros__parameters: pointCloudTopic: livox/lidar imuTopic: livox/imu odomTopic: odometry mapTopic: map lidarFrame: lidar_link baselinkFrame: base_link imuFrame: imu_link # 外参雷达-IMU extrinsicTrans: [0.0, 0.0, 0.0] extrinsicRot: [1, 0, 0, 0, 1, 0, 0, 0, 1] # VLP-16或Livox Avia的扫描线数 N_SCAN: 16 Horizon_SCAN: 1800 # 降采样 lidarMinRange: 1.0 lidarMaxRange: 100.0 # IMU预积分 imuAccNoise: 3.9939570888238808e-03 imuGyrNoise: 1.5638787047056855e-03 imuAccBiasN: 6.4356659353465698e-05 imuGyrBiasN: 3.5640312389903644e-05 imuGravity: 9.805 # 调度 mappingProcessInterval: 0.15 surroundSearchRadius: 50.0 # 闭环检测 loopClosureEnableFlag: true loopClosureFrequency: 1.0 surroundingKeyframeSize: 50 historyKeyframeSearchRadius: 25.0 historyKeyframeSearchTimeDiff: 30.0注意lidarFrame、baselinkFrame、imuFrame这三个坐标系名称必须和你的数据集对应上。walking_dataset一般是以livox_frame作为雷达坐标系以imu_link作为IMU坐标系。如果你直接复制官方参数文件而不改坐标系tf树查询不到变换系统会一直卡在等待lidar_link到base_link的变换上。4.2 重放bag的正确姿势ros2 bag play默认按录制时间戳发布但LIO-SAM启动后要先等IMU数据初始化所以建议加--clock参数和--rate参数来控制播放速度ros2 bag play walking_dataset.bag --clock --rate 1.0打开RVIZ2后添加PointCloud2显示话题选择/lmap或/map添加Path显示话题/odometry再添加TF显示坐标系固定为map。如果一切正常几秒内你就能看到点云地图在缓慢累积轨迹从原点开始延伸。如果在RVIZ2里看到点云在疯狂抖动或者直接飞走先不要怀疑算法优先去查IMU的话题频率。这里有个排查技巧在终端里用ros2 topic hz /livox/imu看一眼IMU发布频率。如果只有10Hz甚至更低说明bag播放的IMU数据本身不足LIO-SAM的预积分会非常不稳。这个时候可以尝试调低mappingProcessInterval或者检查bag中IMU数据是否在录制时有大量丢帧。4.3 跑通之后怎么看结果对齐官方轨迹判断建图效果的标准我一般看三点轨迹是否闭合如果数据集有回环跑完后轨迹的终点和起点应该基本重合点云地图是否清晰墙面上不能有明显的重影或毛刺输出轨迹和官方轨迹的误差可以用evo工具对比/odometry输出和数据集提供的/path真值。evo_traj bag walking_dataset.bag /path --save_as_json evo_traj bag ours.bag /odometry --save_as_json evo_ape json path.json odometry.json -a如果误差在几十厘米到一米量级对于walking_dataset这种手持场景是完全可以接受的。如果误差在几十米甚至更大基本可以断定参数配置有问题最可疑的还是外参和时间同步。5. 接入MID360从录包开始就要注意的细节5.1 MID360驱动与ROS2消息桥接MID360官方提供了Livox ROS2驱动livox_ros_driver2支持Humble。安装编译本身不算复杂但有一个关键点MID360默认发布的是自定义的livox_interfaces/msg/CustomMsg话题而LIO-SAM只认标准的sensor_msgs/msg/PointCloud2和sensor_msgs/msg/Imu。所以中间必须做一次话题转换。方案有两种一是用驱动自带的pointcloud_to_laserscan之类的转换节点但通常不支持CustomMsg转PointCloud2二是自己写一个转换节点。我自己写了一个轻量转换节点核心逻辑就是遍历CustomMsg.points中的每个点把x,y,z,reflection填充到PointCloud2对应的字段里同时把时间戳填成当前帧的时间。代码骨架大概是sensor_msgs::msg::PointCloud2 cloud; cloud.header.stamp custom_msg-header.stamp; cloud.header.frame_id livox_frame; cloud.width custom_msg-points.size(); cloud.height 1; cloud.is_dense true; cloud.fields {...}; cloud.data.resize(cloud.width * 16); // 这里要按实际字段计算这里一定要保留offset_time信息如果仅仅填充x/y/z/reflection而不带点的相对时间LIO-SAM运动补偿就无从谈起地图畸变是必然的。5.2 时间同步在线模式下的隐性杀手walking_dataset自带的数据已经做好了时间同步所以离线跑起来很顺利。但MID360在线接入时你必须面对雷达和IMU时间戳不同步的问题。MID360的点云帧率是10Hz但每个点内部的时间戳是连续的帧与帧之间也有时间间隔。而IMU一般通过另外一路USB或者共享时钟接入时间基准可能和雷达差了几毫秒甚至几十毫秒。LIO-SAM里用IMU做运动补偿时如果时间差太大点云的畸变校正就会错位。我的解决办法是尽量让雷达和IMU通过同一个同步信号触发MID360支持外部PPS同步如果硬件上做不到就在软件上做时间对齐校准。对于测试阶段一个简单的做法是先录制一段数据离线检查雷达和IMU时间戳的固定偏移量然后在线播放时给IMU话题统一加上这个偏移。5.3 外参标定别跳过这步MID360安装到平台上时不可能做到完全和IMU坐标系重合所以外参标定不能省。这里给一个简单的粗标定方法先记录一帧多传感器静止时的点云和IMU数据观察点云中地面法向量和IMU重力向量是否对齐。如果有角度差那说明雷达安装面本身不是水平的需要在extrinsicRot里补偿。精细标定的话我推荐用li_calib这类工具把雷达和IMU外参标到毫米级和0.1度级。LIO-SAM对旋转外参极其敏感即使差1度长走廊场景下几百米累计误差就能到几米甚至十几米。6. 仿真测试的必要性没有真机时怎么验证算法6.1 在Gazebo里搭一个带MID360的仿真车没有真机或者不想每次都背着设备出去跑那我强烈建议先在Gazebo里做一轮仿真验证。你可以用gazebo_ros_pkgs加载一个差速小车模型在车体上挂一个模拟的Livox MID360雷达插件和一个IMU插件。前提是你的仿真雷达插件要能输出类似Livox的扫描模式。MID360是360度非重复扫描但Gazebo里很多现成的GPU激光雷达插件是规则扫描线点云分布和真实MID360差别很大。如果只是验证LIO-SAM的算法链路非规则扫描的影响不大你可以先跑通但如果要验证特定室内场景下MID360的建图效果建议使用Livox官方提供的Gazebo模型。6.2 仿真环境的测试步骤与验证指标仿真环境里跑通LIO-SAM我最关注的是闭环检测和回环优化是否生效。在Gazebo里搭一个方形或者8字形路径让小车走一圈回到起点如果/odometry的终点和起点在RVIZ2里基本重合说明前端里程计正常如果再看到/map中出现了明显的回环修正跳变说明后端闭环也正常。我通常在仿真环境里先固定一个基线配置然后在真机上只调整外参和噪声参数这样可以最大程度避免算法参数调参玄学。仿真环境的价值不是替代真机而是帮你把代码逻辑和传感器硬件这两个变量解耦先确认前者没问题再去处理后者。6.3 一个关于逼真度的提醒别指望仿真里调好的参数能直接搬到真机上。Gazebo默认的IMU数据是非常干净的没有零偏没有高斯噪声以外的杂散干扰。而MID360那一路IMU不仅噪声大还有明显的温度漂移。所以仿真里可以把imuAccNoise调到1e-3量级但真机上我经常要调到1e-2量级才能稳定。如果你在仿真里用很低的噪声参数跑得很漂亮真机上却一上来就飘先检查IMU噪声参数有没有改回来这个坑我踩过不止一次。7. MID360建图中的经典问题抖动、漂移、重影与失败恢复7.1 点云飞点造成的地图噪声MID360有一个特性在强光下或者遇到玻璃、高反射物体时会产生一些距离异常的飞点。LIO-SAM前端的特征提取模块对离群点没有做特别强力的滤波这些飞点一旦被当成了角点或者平面点就会把匹配结果带偏。我的处理是在预处理阶段加一个点云滤波器除了设置lidarMinRange和lidarMaxRange再针对每个点做邻域密度检查离群点直接剔掉。这一步在离线bag处理时很容易验证效果对比滤波前后跑出来的轨迹你会发现轨迹平滑度有明显改善。但这会稍微增加CPU负载在低算力设备上要权衡一下。7.2 退化和几何对称场景下的漂移MID360视野虽然大但如果你在一个非常长的走廊里直线走所有点云特征在前进方向上几乎不提供约束LIO-SAM的前端里程计会开始累积漂移。此时有两种缓解手段打开loopClosureEnableFlag让后端在识别到回环时立刻修正调整surroundSearchRadius和historyKeyframeSearchRadius增大搜索范围让系统更早地发现潜在回环。但这两种方法都是治标不治本。最根本的解法是引入额外的传感器约束比如轮式里程计、视觉或者磁力计。在walking_dataset里你能看到这种退化场景手持设备在走廊里来回走如果没有回环LIO-SAM的漂移速度会让你怀疑人生。7.3 系统崩溃与状态恢复MID360长时间运行偶尔会遇到驱动或者算法节点崩溃。我试过很多次发现最稳妥的方案是写一个简单的看门狗脚本来监听LIO-SAM节点的状态如果进程意外退出自动重启并把当前地图保存下来。说实话LIO-SAM在ROS2下的稳定性没有ROS1成熟特别是长时间运行后内存增长的问题目前还没有完全根治。我的经验是每跑20到30分钟重启一次建图节点把累积的关键帧保存下来再继续跑。这对大多数测试场景来说已经够用。8. 几个容易被忽视但非常影响结果的参数细节8.1 N_SCAN和Horizon_SCAN别乱填N_SCAN代表雷达扫描线数Horizon_SCAN代表水平方向的分辨率。这两个参数直接决定LIO-SAM前端怎么把点云划分到不同的线束上。MID360虽然是非重复扫描但它最终输出的点云也可以按线束来组织只是线数不一定等于16。如果填错前端特征提取会失败LIO-SAM的雷达里程计基本不可用。一个简化做法是把MID360点云投影到距离图上看实际占据的行数再填N_SCANHorizon_SCAN则根据你的角分辨率来设一般1800、3600都可以试。8.2 IMU噪声参数的实际意义imuAccNoise、imuGyrNoise是IMU的连续时间噪声密度imusAccBiasN、imuGyrBiasN是零偏随机游走噪声密度。这些参数在GTSAM的预积分里直接决定权重。如果你手头的IMU数据噪声很大但参数却填得很乐观GTSAM会对IMU预积分非常信任一旦IMU飘了系统里又没有足够的视觉或者雷达观测来纠正位姿就直接发散。一个快速的办法是从IMU的艾伦方差曲线里提取这些值但大多数场景不需要这么精细。我通常的做法是先用默认参数跑一遍观察点云地图质量如果地图有弯曲或漂移再把IMU噪声参数调大2到5倍给雷达匹配更大的权重。8.3 mappingProcessInterval与CPU占用mappingProcessInterval是后端建图线程的处理周期。设置太小时CPU占用飙升一般设备根本扛不住设置太大后端的闭环检测就会滞后点云图可能更新不及时。我实测下来0.15秒是一个比较均衡的值如果你的CPU性能强可以压到0.1秒如果是在Jetson Nano上跑还是老实点用0.2秒以上。9. 从walking_dataset到MID360的路线总结少走弯路的建议我强烈建议新人按这个顺序来先用walking_dataset把LIO-SAM的编译、参数配置、bag重放、RVIZ2可视化整条链路跑通再接入MID360的真机数据。不要在第一步还没跑通的情况下就直接上真机否则你根本分不清是算法问题、驱动问题还是参数问题。从依赖安装到最终跑通MID360我前后花了大概两周时间。其中大部分时间消耗在ROS1到ROS2的代码迁移和真机数据的时间同步上。如果你只想快速出效果可以直接用社区维护的ROS2分支但一定要做好代码审计特别是回环检测和地图发布的线程安全部分。如果时间充裕还是建议自己迁移一遍代码这个过程能让你对整个算法框架的理解深一个层次。最后分享一个小技巧在所有节点启动前先把RVIZ2配置保存好包括话题选择、显示类型、坐标系设置这样每次启动就不用重新配置。对于反复调试的场景这能省下大量重复劳动。