1. 项目概述:从跑通Demo到处理自己的数据
拿到一个像LVI-SAM这样优秀的开源SLAM(即时定位与地图构建)项目,最让人兴奋也最让人头疼的,就是从跑通作者提供的演示数据集,到成功让它处理我们自己采集的数据。这个过程,远不止是改几个配置文件路径那么简单。它涉及到对整个系统数据流的理解、对传感器标定的敬畏,以及对ROS(机器人操作系统)这套生态的熟练运用。我花了相当长的时间,踩了无数的坑,才算是把“跑自己的数据”这条路趟平。今天,我就把这套从零到一的完整流程、核心原理和避坑指南梳理出来,目标就是让你能避开我走过的弯路,高效地把LVI-SAM用在你自己的机器人、无人机或者手持设备上。
LVI-SAM的核心价值在于紧耦合的激光-视觉-惯性里程计,它通过因子图优化,巧妙地融合了LIO-SAM的激光惯导里程计和VINS-Mono的视觉惯导里程计。当一种传感器失效(比如激光在长廊里退化,或者视觉在黑暗环境中失效),另一种传感器可以接管,保证系统不崩溃。但这也意味着,要让它跑得好,我们必须同时伺候好激光雷达、相机和IMU这三路“大爷”。你的数据质量,直接决定了最终建图与定位的精度和鲁棒性。
2. 数据准备:采集符合要求的“食材”
要让LVI-SAM这道“大餐”成功出锅,首先得准备好新鲜、合格的“食材”——也就是你自己的传感器数据。这一步是基础,也是最容易出问题的地方。
2.1 传感器硬件选型与同步考量
LVI-SAM的设计是针对特定传感器套件的,但它的架构允许一定的灵活性。你需要关注以下几个硬件参数:
激光雷达:原项目使用Velodyne VLP-16(16线)。你可以使用其他型号,如Ouster、速腾聚创、禾赛等。关键参数是:
- 扫描频率:通常为5-20Hz。需要在配置文件中正确设置。
- 点云话题:ROS中发布点云数据的Topic名称,通常是
/velodyne_points或类似。 - 点云类型:必须是
sensor_msgs/PointCloud2类型。
相机:原项目使用FLIR全局快门相机。强烈建议使用全局快门(Global Shutter)相机,因为卷帘快门(Rolling Shutter)在快速运动时会产生图像畸变,严重影响特征点跟踪。USB相机或树莓派相机模块(需要适配)均可,关键是要能通过ROS(如
usb_cam、cv_camera驱动包)发布sensor_msgs/Image消息。IMU(惯性测量单元):这是紧耦合系统的灵魂。要求比较高:
- 频率:至少100Hz,200Hz或更高更佳。
- 同步:理想情况下,IMU数据应该与图像和激光雷达数据在硬件上同步(通过触发信号)。如果做不到,也必须在软件层面保证时间戳的准确性和一致性。
- 话题:发布
sensor_msgs/Imu消息。
同步方案:这是最大的挑战。最佳实践是使用硬件同步,例如通过FPGA或专用的同步器(如ROS Timery)给所有传感器提供同一个触发脉冲。如果只能软件同步,则务必确保你的主控机(如NVIDIA Jetson、Intel NUC)系统时间准确,并且所有ROS节点的时钟源一致(使用
use_sim_time参数时需要特别注意)。
注意:很多新手失败的根本原因就是传感器数据不同步。时间戳哪怕只有几十毫秒的错位,在快速运动下都会导致融合算法发散,产生“重影”或直接崩溃。
2.2 数据录制:使用ROS Bag的正确姿势
录制数据我们通常使用ROS的rosbag record命令。这不是简单的rosbag record -a,为了后续处理方便,需要精确定义要录的话题。
# 假设你的传感器驱动发布的话题如下: # 激光雷达: /lidar_points # 相机: /camera/image_raw # IMU: /imu/data # 推荐的录制命令 rosbag record -O my_custom_data.bag /lidar_points /camera/image_raw /imu/data关键技巧与注意事项:
- 给Bag文件起个好名字:包含日期、场景和传感器信息,如
20240520_corridor_vlp16_imu.bag。 - 录制前先预热:启动所有传感器驱动节点后,等待几十秒,待数据流稳定后再开始录制和运动。
- 运动模式:录制时,应包含旋转、平移、加减速等丰富运动,避免长时间静止或纯匀速直线运动,这样有助于传感器标定和算法初始化。
- 环境特征:视觉部分需要丰富的纹理特征(如墙壁上的海报、桌椅的边角),激光部分需要几何结构(如墙角、柱状物)。避免在纯白墙、长走廊或无特征的空旷场地测试。
- 检查Bag内容:录制完成后,用
rosbag info my_custom_data.bag检查话题、消息数量、时长是否正确。用rqt_bag可视化查看图像和IMU数据是否正常。
3. 传感器标定:不容忽视的“内功”
标定是SLAM的“内功”,标定不准,后面所有优化都是徒劳。LVI-SAM需要两类标定:相机内参标定和传感器外参(标定)标定。
3.1 相机内参标定
使用ROS的camera_calibration包。你需要一个棋盘格(Checkerboard)或AprilTag标定板。
# 启动相机驱动 rosrun usb_cam usb_cam_node # 启动标定程序 rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.024 image:=/camera/image_raw camera:=/camera--size 8x6:棋盘格内角点数量(宽8个,高6个)。--square 0.024:每个方格的实际边长,单位米。- 上下左右移动标定板,直到“CALIBRATE”按钮亮起,点击它。计算完成后,点击“SAVE”保存。
标定结果会生成一个ost.yaml文件,里面包含了相机矩阵、畸变系数等关键参数。你需要将这些参数准确地填写到LVI-SAM的配置文件中(通常是config/params_camera.yaml)。
3.2 激光雷达-IMU外参标定
这是最复杂的一步。LVI-SAM在启动时会从配置文件中读取一个初始外参猜测extrinsicTrans和extrinsicRot,并在运行中进行在线优化。但这个初始值不能差得太离谱。
手动测量法(粗糙,仅作初始值):用尺子测量激光雷达坐标系原点与IMU坐标系原点之间的位移(x, y, z)。通过设计,确定两者的旋转关系(例如,IMU的X轴指向车头,激光雷达的X轴也指向车头,则旋转为单位矩阵)。
使用标定工具法(推荐):可以采用更专业的工具来获取更准确的外参。
- LI-Init:一个专门用于激光雷达-IMU标定的开源工具。
- Kalibr:功能强大的多传感器标定工具箱,支持相机-IMU、相机-激光雷达等。但相机-激光雷达标定需要用到标定板,过程较复杂。
实操心得:对于大多数地面机器人,如果激光雷达和IMU刚性连接且轴线大致对齐,手动测量一个近似的初始值(位移精确到厘米,旋转角精确到5度以内)通常可以接受。LVI-SAM的优化模块能够在此基础上进行微调。关键是确保你的config/params_lidar.yaml中的extrinsicTrans和extrinsicRot与这个测量值对应。
3.3 相机-IMU外参标定
LVI-SAM的视觉惯性里程计(VIO)部分继承自VINS-Mono,它假设相机和IMU是刚性连接的,并且需要一个初始的外参。这个外参同样可以在config/params_camera.yaml中设置。
标定方法:使用Kalibr是行业标准。你需要录制一个包含相机图像和IMU数据的bag文件,同时用标定板(棋盘格或AprilTag)在相机前做充分的激励运动(各个方向旋转和平移)。Kalibr会联合估计相机内参、相机-IMU时间偏移以及两者之间的空间变换。
简化方案:如果相机和IMU安装位置很近且轴线对齐,可以近似认为旋转为单位矩阵,位移则根据物理安装测量。时间偏移可以先设为0。对于很多应用,只要运动激励足够,VINS-Mono本身也能在线估计并优化一部分外参。
4. 配置文件适配:让系统认识你的传感器
LVI-SAM的所有参数都集中在config/目录下的YAML文件中。适配自己的数据,主要修改以下两个文件:
4.1 修改config/params_lidar.yaml
这个文件配置激光雷达和IMU相关参数。
# 激光雷达点云话题(必须与你录制的bag话题一致) pointCloudTopic: "/lidar_points" # 修改为你的话题 # IMU话题(必须与你录制的bag话题一致) imuTopic: "/imu/data" # 修改为你的话题 # 激光雷达参数 sensor: velodyne # 雷达类型,如 velodyne, ouster, livox N_SCAN: 16 # 激光雷达的线数(VLP-16就是16) Horizon_SCAN: 1800 # 每圈的点数 downsampleRate: 1 # 降采样率,计算资源紧张时可设为2或4 # 外参初始值 (激光雷达坐标系到IMU坐标系的变换) extrinsicTrans: [0.0, 0.0, 0.0] # 平移向量 [x, y, z] 单位:米 extrinsicRot: [1, 0, 0, 0, 1, 0, 0, 0, 1] # 旋转矩阵 (行优先),单位矩阵表示坐标系对齐 extrinsicRPY: [0, 0, 0] # 或者用欧拉角表示 [roll, pitch, yaw],单位弧度关键参数解析:
N_SCAN和Horizon_SCAN:必须与你的激光雷达型号匹配。错误的线数会导致点云投影到图像深度图时完全错误。如果不确定,可以查看雷达的官方文档或使用rostopic echo /lidar_points | head -n 20查看点云消息的height字段(对于多线雷达,height就是线数)。downsampleRate:在保持精度的前提下,增大此值可以显著降低计算量,提高实时性,尤其对于高线数雷达(如64线)。
4.2 修改config/params_camera.yaml
这个文件配置相机和视觉特征相关参数。
# 图像话题 image_topic: "/camera/image_raw" # 修改为你的话题 # 相机内参(来自 camera_calibration 标定结果) projection_parameters: fx: 458.654 # 焦距x fy: 457.296 # 焦距y cx: 367.215 # 光心x cy: 248.375 # 光心y distortion_parameters: k1: -0.28340811 k2: 0.07395907 p1: 0.00019359 p2: 1.76187114e-05 # 图像分辨率 image_width: 752 image_height: 480 # 相机-IMU外参 (相机坐标系到IMU坐标系的变换) body_T_cam0: !!opencv-matrix rows: 4 cols: 4 dt: d data: [ 0.0148655429818, -0.999880929698, 0.00414029679422, -0.0216401454975, 0.999557249008, 0.0149672133247, 0.025715529948, -0.064676986768, -0.0257744366974, 0.00375618835797, 0.999660727178, 0.00981073058949, 0.0, 0.0, 0.0, 1.0] # 这是一个4x4齐次变换矩阵示例 # 特征提取参数 max_cnt: 150 # 每帧图像提取的最大特征点数量 min_dist: 30 # 特征点之间的最小像素距离注意事项:
body_T_cam0这个矩阵是从相机坐标系到IMU(body)坐标系的变换。如果你用Kalibr标定,得到的通常是IMU到相机的变换T_cam_imu,你需要求逆后才能填入这里。data数组是行优先(row-major)排列的4x4矩阵。max_cnt和min_dist影响特征点的数量和分布。在纹理丰富的场景可以适当增加max_cnt到200+;在纹理稀疏的场景,过多的特征点可能都是噪声,需要降低。
5. 启动与运行:调试和可视化
完成配置后,就可以尝试运行了。
5.1 启动LVI-SAM
# 1. 启动ROS核心 roscore # 2. 在新的终端,启动LVI-SAM roslaunch lvi_sam run.launch启动后,你应该在终端看到一系列初始化信息,包括加载参数、启动激光惯性里程计(LIO)和视觉惯性里程计(VIO)节点。
5.2 播放自定义Bag数据
# 3. 在新的终端,播放你自己的bag文件 # 注意:如果bag文件的时间戳是过去的,需要启用模拟时间 rosbag play --clock my_custom_data.bag # 同时,在启动LVI-SAM的终端中,你需要确保run.launch文件中或启动时设置了 use_sim_time:=true # 通常可以在启动命令中指定 roslaunch lvi_sam run.launch use_sim_time:=true5.3 关键可视化工具
调试离不开可视化。主要使用RVIZ。
- 全局地图:在RVIZ中添加一个
PointCloud2显示,话题设置为/lio_sam/mapping/map_global。这是激光雷达构建的全局点云地图。 - 当前帧点云:添加
PointCloud2,话题/lio_sam/deskew/cloud_deskewed,可以看到经过运动去畸变后的当前激光帧。 - 轨迹:添加
Path,话题/lio_sam/mapping/path,这是激光里程计估计的轨迹。 - 视觉特征点:添加
Image显示,话题/feature_tracker/feature_img,可以看到VIO部分跟踪的特征点。 - IMU数据:可以通过
rqt_plot绘制/imu/data的角速度和线加速度,观察数据是否正常。
首次运行检查清单:
- RVIZ中是否能看到动态更新的点云?
- 特征点图像中是否有稳定的特征点被跟踪?
- 终端是否有大量红色错误信息?常见的错误是话题不对应、参数格式错误、缺少动态链接库等。
- 系统是否成功初始化?通常初始化需要几秒钟的运动(平移+旋转)。
6. 常见问题与深度排查指南
在实际运行自己的数据时,你几乎一定会遇到问题。下面是我总结的常见故障模式及解决方案。
6.1 问题一:系统无法初始化或立即发散
现象:启动后,终端不断打印初始化信息,但轨迹和地图不更新,或者更新一下就飞掉(数值变得极大)。
可能原因及排查:
IMU数据异常:这是最常见的原因。检查IMU数据的单位。LVI-SAM默认期望:
- 角速度
angular_velocity:单位为弧度/秒 (rad/s)。 - 线加速度
linear_acceleration:单位为米/秒² (m/s²)。 - 很多IMU出厂默认输出是度/秒和g,必须转换。
实操心得:写一个简单的ROS节点订阅你的原始IMU话题,打印出前几条消息,检查数值范围。静止时,角速度应接近0,线加速度的模长应接近9.8(重力加速度)。如果角速度数值在几百上下,那很可能单位是度/秒,需要除以57.3转换为弧度/秒。
- 角速度
外参初始值误差过大:激光雷达和IMU的物理安装方向搞反了。例如,IMU的Z轴朝上,但你在配置中设成了朝下。这会导致优化器无法收敛。
- 排查:在RVIZ中同时显示激光点云和IMU坐标系(通过
TF工具)。观察激光点云是否大致落在IMU坐标系的正前方、正下方等预期位置。如果点云出现在完全不可能的方向,说明外参旋转矩阵错了。
- 排查:在RVIZ中同时显示激光点云和IMU坐标系(通过
时间戳不同步:这是隐形杀手。检查bag文件中各话题的时间戳是否同步增长。可以用
rqt_bag打开bag,查看图像、激光、IMU消息的时间戳曲线是否基本对齐。- 解决:如果不同步,考虑使用
rosbag filter或编写节点进行软件同步,或者重新采集数据。
- 解决:如果不同步,考虑使用
6.2 问题二:建图出现重影、鬼影或严重拖尾
现象:地图中同一个物体出现多个轮廓,或者轨迹在转弯处像“彗星尾巴”一样拉得很长。
可能原因及排查:
激光雷达运动畸变校正失效:LVI-SAM依赖IMU进行激光点云的去畸变(Deskew)。如果IMU数据不准或外参不准,去畸变就会失败。
- 排查:观察话题
/lio_sam/deskew/cloud_deskewed的点云。在机器人旋转时,点云应该是清晰、稳定的。如果点云模糊、有重影,说明去畸变没做好。重点检查IMU数据和外参。
- 排查:观察话题
闭环检测失败或未触发:LVI-SAM的激光部分有闭环检测模块。如果环境特征重复(如长走廊)或视角变化太大,可能检测不到闭环,导致累积误差无法消除。
- 排查:查看终端是否有类似
[mapOptimization] loop found的提示。可以尝试调整config/params_lidar.yaml中闭环相关的参数,如loopClosureFrequency(闭环检测频率)、surroundingKeyframeSize(搜索范围)等。
- 排查:查看终端是否有类似
VIO部分失效:在纹理稀疏或光照剧烈变化的环境,视觉特征跟踪丢失,系统退化为纯激光里程计,精度会下降,可能产生漂移。
- 排查:观察
/feature_tracker/feature_img,如果特征点数量骤减或为零,说明VIO挂了。此时应依赖激光闭环。可以尝试在params_camera.yaml中调整特征点参数,或改善环境照明。
- 排查:观察
6.3 问题三:计算资源不足,系统卡顿
现象:ROS节点CPU占用率极高,RVIZ刷新很慢,数据延迟越来越大。
优化策略:
降低激光雷达频率:在驱动节点中或录制bag后,使用
rosbag filter或topic_tools/throttle对激光点云话题进行降频(例如从10Hz降到5Hz)。rosrun topic_tools throttle messages /lidar_points 5.0 /lidar_points_throttled然后在LVI-SAM配置中订阅降频后的话题。
降低图像分辨率:如果相机原始分辨率很高(如1920x1080),可以在相机驱动节点或中间环节进行下采样(如降到640x480)。这能极大减轻VIO的计算负担。
调整LVI-SAM内部参数:
params_lidar.yaml中的downsampleRate:增大此值(如从1改为2或4),对点云进行体素滤波。params_camera.yaml中的max_cnt:减少每帧提取的特征点数量。params_camera.yaml中的freq:降低视觉惯性里程计的运行频率。
6.4 参数调优速查表
下表列出了一些关键参数及其调优方向,供你在遇到特定问题时参考:
| 参数文件 | 参数名 | 默认值/示例 | 作用 | 调优方向 |
|---|---|---|---|---|
params_lidar.yaml | downsampleRate | 1 | 激光点云降采样率 | 资源紧张时增大(2,4),牺牲少量细节换速度。 |
params_lidar.yaml | edgeThreshold | 1.0 | 边缘特征提取阈值 | 点云稀疏或噪声大时调高,减少不可靠边缘点。 |
params_lidar.yaml | surfThreshold | 0.1 | 平面特征提取阈值 | 同上,调高以减少噪声平面点。 |
params_lidar.yaml | loopClosureFrequency | 1 | 闭环检测频率(Hz) | 计算资源足可调高(如2),增加闭环机会。资源紧则调低(如0.5)。 |
params_camera.yaml | max_cnt | 150 | 最大特征点数 | 纹理丰富时增加(200+),纹理稀疏时减少(100-),平衡跟踪鲁棒性与计算量。 |
params_camera.yaml | min_dist | 30 | 特征点最小像素距离 | 特征点聚集时减小(如20),使其分布更散。特征点太散时增大,避免过密。 |
params_camera.yaml | freq | 10 | VIO处理频率(Hz) | 资源紧张或图像分辨率高时降低(如5)。 |
7. 进阶:从ROS Bag到在线实时运行
成功用bag文件复现后,下一步就是连接真实传感器进行在线实时SLAM。这需要你编写或使用现有的ROS驱动节点。
- 启动传感器驱动:确保你的激光雷达、相机、IMU都有对应的ROS驱动包,并正确启动,发布标准格式的话题。
- 修改Launch文件:你可以复制一份
run.launch,将其中的<remap>语句或参数修改为你的实际话题名称,而不是播放bag文件。 - 处理时间戳:在线运行时,禁用
use_sim_time参数(或设为false),系统将使用ROS的实时时钟。 - 初始化:在线运行时,需要让设备进行充分的初始化运动(在水平面上进行“八字形”或绕圈运动),让VIO和LIO成功初始化并估计出初始尺度、重力方向等。
一个常见的在线启动文件 (my_robot.launch) 修改示例如下:
<launch> <!-- 不使用仿真时间 --> <param name="/use_sim_time" value="false"/> <!-- 启动LVI-SAM --> <node pkg="lvi_sam" type="lvi_sam" name="lvi_sam" output="screen"> <!-- 重映射话题到你的实际驱动话题 --> <remap from="/imu/data" to="/my_imu/data"/> <remap from="/lidar_points" to="/my_lidar/points"/> <remap from="/camera/image_raw" to="/my_camera/image_rect"/> <!-- 加载参数文件,路径可能需要根据你的工作空间调整 --> <rosparam command="load" file="$(find lvi_sam)/config/params_lidar.yaml" /> <rosparam command="load" file="$(find lvi_sam)/config/params_camera.yaml" /> </node> <!-- 可以在这里启动你的传感器驱动节点 --> <!-- <include file="$(find my_lidar_driver)/launch/driver.launch" /> --> <!-- <include file="$(find usb_cam)/launch/usb_cam.launch" /> --> <!-- <node pkg="my_imu_driver" type="imu_node" name="imu_node" /> --> </launch>走到这一步,你已经成功地将LVI-SAM从一篇论文、一个开源仓库,变成了一个能理解你自定义传感器数据、为你所用的强大SLAM工具。这个过程本质上是一个系统工程问题,考验的是对多传感器融合原理的理解、对细节的把握和耐心调试的能力。每解决一个报错,每调优一个参数让轨迹更平滑,都是实实在在的进步。记住,SLAM没有银弹,针对特定场景和传感器的调优永远是一个迭代的过程。当你看到自己采集的数据被实时地、稳定地构建成一张精确的地图时,那种成就感就是对所有努力最好的回报。