LVI-SAM复现指南:从源码编译到6轴IMU实机部署全流程

LVI-SAM复现指南:从源码编译到6轴IMU实机部署全流程 1. 项目概述1.1 LVI-SAM是什么为什么值得复现LVI-SAM是Tixiao Shan团队在2021年提出的激光-视觉-惯性紧耦合SLAM框架名字拆开看就是LiDAR-Visual-Inertial Smoothing and Mapping。它在LIO-SAM的基础上加入了视觉因子在VINS-Mono的基础上加入了激光因子用因子图把三类传感器观测统一优化。与纯激光方案相比它解决了长廊、空旷场景的退化问题与纯视觉方案相比它扛得住光照突变和弱纹理再加上IMU做运动先验动态环境下的鲁棒性也提升了一大截。我第一次接触这个框架是在一个室内移动机器人项目上。当时用的是16线激光雷达加单目相机加9轴IMU的组合跑ORB-SLAM3在空旷走廊里频繁丢失换LIO-SAM虽然定位稳了但建图边缘模糊环闭合一触发轨迹就跳变。后来把LVI-SAM拉起来跑了一圈数据集明显能感觉到它的优势视觉残差把激光帧间匹配约束得更紧建图质量提升了一个档次回环修正也更平滑。这篇文章就是记录我从零开始复现LVI-SAM到实机部署的全过程重点解决两个问题一是在Ubuntu20.04上把源码编译跑通二是手里只有一个6轴IMU没有磁力计的情况下怎么把整个系统调试到能稳定工作。如果你已经在ROS里玩过一段时间想把手里的传感器组合变成一套完整的紧耦合SLAM系统这篇文章应该能帮你省下不少时间。1.2 复现前需要掌握的基础LVI-SAM不是适合纯新手的入门项目。实机部署前我建议你先具备这些基础熟练使用ROS1的topic、launch、tf这些基本操作能手写简单的launch文件并理解节点间通信关系对相机内参标定、雷达外参标定、IMU噪声标定这些概念有清晰认知知道SLAM里的传感器坐标系定义至少得懂base_link、camera_optical_frame、imu_link这些坐标系绕来绕去的意义。如果你还不满足这些条件建议先去把ROS的基础工具链玩熟再花一两周跑通LIO-SAM和VINS-Mono的公开数据集。我见过不少一上来就编译LVI-SAM的最后卡在各种标定问题上进退两难——不是代码跑不动而是参数不对导致整体发散完全没法判断问题出在哪一环。2. 环境准备与依赖梳理2.1 Ubuntu20.04系统与ROS安装我用的是Ubuntu20.04.6 LTS桌面版ROS版本为Noetic。系统是双系统安装没有用虚拟机因为实机部署需要直接访问USB相机、雷达串口和IMU设备虚拟机做USB重映射虽然也能用但偶尔会出现设备掉线、时间戳抖动的问题排查起来非常头大。ROS Noetic的安装我直接用了鱼香ROS的一键安装脚本。这个脚本对国内网络环境非常友好它会自动处理软件源、密钥和依赖包的国内镜像几分钟就能装完完整的ROS桌面版。如果你手动安装时经常遇到软件源超时或依赖无法定位的问题用这个脚本能省去很多折腾。装完后记得验证一下环境变量source /opt/ros/noetic/setup.bash echo $ROS_DISTRO输出noetic就对了。建议把它写进~/.bashrc避免每次开终端都要手动source。2.2 依赖库版本选择与编译顺序LVI-SAM的核心依赖包括ROS Noetic、OpenCV、PCL、Eigen、Ceres Solver、GTSAM以及livox_ros_driver如果你用Livox雷达。这里的版本组合非常关键我踩过的坑大多发生在版本不匹配上。我的依赖版本组合是OpenCV 4.2.0Ubuntu20.04系统自带版本PCL 1.10同样系统自带Eigen 3.3.7系统自带Ceres Solver 1.14.0源码编译GTSAM 4.0.2源码编译。这个组合与LVI-SAM作者在README中给出的版本一致编译时很少出幺蛾子。编译顺序上先装Eigen和OpenCV这些底层库再装Ceres和GTSAM最后编译LVI-SAM工作空间。Ceres依赖Eigen和OpenCVGTSAM依赖Eigen所以必须按依赖关系从底往上装。一个常见的错误是装了太新的GTSAM 4.2或4.3版本LVI-SAM源码里使用的一些API已经被标记为弃用或删除编译时会报一堆类型错误而且很难修。老老实实用4.0.2后面你就知道省了多少事。2.3 鱼香ROS一键安装脚本的使用心得用鱼香ROS装完ROS后我还有几个建议第一脚本装完后建议手动运行rosdep update把rosdep的缓存更新一遍否则后续编译时rosdep check会卡住第二如果要用相机驱动、雷达驱动这些第三方包建议直接用apt安装系统预编译版本避免从源码编译时OpenCV版本冲突第三脚本带的rosdep和系统apt源是独立的装额外依赖时要用sudo apt install而不是pip这样才能保证库文件被系统正确识别。另外提一句鱼香ROS的脚本本质上是帮你配置了国内镜像源并批量安装了ROS核心包它不涉及shut-down-vpn之类的任何网络代理操作纯粹就是解决软件源访问问题可以放心使用。3. LVI-SAM源码获取与编译全流程3.1 创建工作空间与拉取源码首先创建工作空间并拉取LVI-SAM源码mkdir -p ~/lvi_sam_ws/src cd ~/lvi_sam_ws/src git clone https://github.com/TixiaoShan/LVI-SAM.git这里有个细节LVI-SAM源码里默认包含livox_ros_driver和gtsam两个子模块如果你不用Livox雷达livox_ros_driver可以不编译但源码结构里它的CMakeLists会影响整个工作空间的编译流程建议直接保留源码但不要把它加入编译白名单。方法是在lvi_sam_ws/src目录下新建一个CATKIN_IGNORE文件放在livox_ros_driver文件夹里touch ~/lvi_sam_ws/src/LVI-SAM/livox_ros_driver/CATKIN_IGNORE这样catkin_make时会自动跳过这个包不会因为缺少Livox SDK头文件而报错。3.2 修改CMakeLists.txt的关键坑LVI-SAM的CMakeLists.txt在默认情况下有两个常见问题第一个是OpenCV版本问题。Ubuntu20.04自带的OpenCV 4.2.0没有问题但如果你机器上装了多个OpenCV版本比如之前为了跑YOLO装了OpenCV 4.5CMake可能找到错误的版本。解决办法是在CMakeLists.txt顶部显式指定set(OpenCV_DIR /usr/lib/x86_64-linux-gnu/cmake/opencv4) find_package(OpenCV REQUIRED)第二个是GTSAM的CMake配置问题。源码里默认使用find_package(GTSAM REQUIRED)如果你的GTSAM编译时没有开启Quaternion和Eigen的接口LVI-SAM编译时会报找不到gtsam/quaternion目录下的头文件。我建议编译GTSAM时直接使用默认配置不要手动修改任何编译选项默认的Release模式已经包含了LVI-SAM所需的所有特性。3.3 编译遇到的报错与解决记录我在编译过程中遇到了几个典型报错整理在这里供参考第一个报错是error: ‘CV_LOAD_IMAGE_GRAYSCALE’ was not declared in this scope。这个是因为OpenCV 4.x删除了旧的CV_前缀宏定义而LVI-SAM源码是基于OpenCV 3.x写的。解决办法是打开出问题的源文件一般在utility.h或image_projection.cpp里把CV_LOAD_IMAGE_GRAYSCALE改成cv::IMREAD_GRAYSCALE把CV_RGB2GRAY改成cv::COLOR_RGB2GRAY。一共有四处左右逐个替换即可。第二个报错是error: ‘imshow’ is not a member of ‘cv’。这个是因为源码里用了OpenCV 3.x的头文件路径#include opencv2/opencv.hpp在OpenCV 4.x下编译时有些函数的命名空间发生了变化。解决办法是检查所有源文件如果缺少opencv2/highgui/highgui.hpp头文件手动加进去就行。第三个报错是链接错误提示找不到gtsam::ISAM2Params这个符号。解决办法是确认你的GTSAM确实编译成功且版本是4.0.2然后用ldconfig -p | grep gtsam检查系统能否找到libgtsam.so。如果输出为空说明GTSAM没有正确安装到系统目录回到GTSAM源码编译目录重新执行sudo make install。如果你的代码版本比较新可能还会遇到视觉特征提取回调函数里OpenCV 4.x与3.x的接口不兼容问题比如ptrORB()相关的内容这个在OpenCV 4.2里也是可用的但如果报错就检查opencv_features2d头文件是否齐全。3.4 数据集测试验证编译通过后先不要急着上实机用作者提供的数据集验证整个系统是否正常工作。LVI-SAM仓库的README里给出了Google Drive链接如果访问不方便也可以在B站和CSDN上找到粉丝分流的百度网盘链接。数据集下载后修改config/lvi_sam.yaml里的bag路径然后运行roslaunch lvi_sam lvi_sam.launch rosbag play your_bag_file.bag如果一切正常你应该能看到Rviz里同时显示激光点云、相机图像和轨迹。这个数据集的传感器配置是Livox Horizon雷达加两目相机加9轴IMU如果你的传感器组合不同后续需要修改的地方就多了。但先跑通数据集确认代码编译和环境配置完全正确再去处理传感器适配问题能帮你把问题隔离得更清楚。4. 实机传感器配置与时间同步4.1 传感器组合与坐标系定义我实机使用的传感器组合是速腾聚创RS-LiDAR-16激光雷达、Intel RealSense D435i相机带六轴BMI055 IMU、以及外接的BMI088六轴IMU。这里解释一下为什么相机自带IMU还要外接IMU第一D435i内置IMU输出频率最高只有400Hz且与相机共用USB带宽实际使用中数据会有抖动第二LVI-SAM需要较高频率的IMU数据做运动积分频率越高帧间运动的预测越准。传感器坐标系定义上LVI-SAM默认的坐标约定是雷达坐标系为lidar_link相机坐标系为camera_linkIMU坐标系为imu_link世界坐标系为map。这里有一个容易混淆的地方相机坐标系和相机光心坐标系是两个不同的东西。在config/lvi_sam.yaml中camera_link只用于TF树连接真正参与视觉投影的是相机内参矩阵定义的相机光心坐标系也就是camera_optical_frame。习惯上camera_link与camera_optical_frame之间有一个固定的旋转关系通常是从camera_link绕Z轴旋转180度得到。4.2 传感器时间同步与驱动配置LVI-SAM对时间同步的要求非常高。激光雷达每帧点云的时间戳、相机图像的时间戳、IMU数据的时间戳必须对齐到统一的时钟源上。实机部署时不能依赖电脑系统时间因为USB设备的驱动延迟会导致毫秒级的随机波动这个波动在SLAM系统里会直接带来几厘米到十几厘米的定位误差。我采用的方案是使用硬件时间同步把IMU和激光雷达的PPS信号接到一起用单片机统一触发各传感器的数据采集。如果你没有这个条件退而求其次的办法是使用软件时间同步确保IMU、相机和雷达的驱动都启用了ROS的TimeSynchronizer机制并且启动前用chrony或ptp同步系统时间。具体到D435i的驱动需要在启动realsense-ros节点时设置unite_imu_methodlinear_interpolation这样IMU数据会通过插值对齐到相机曝光时刻。对于外接BMI088我写了简单的I2C读取程序通过ROS驱动发布/imu/data消息消息头的时间戳使用系统时钟。有一个容易被忽视的细节IMU数据的时间戳必须尽可能反映真实的采样时刻而不是读取设备后的发送时刻。我实测下来如果直接把读取函数后的系统时间当作采样时间IMU数据的有效延迟会达到3-5毫秒这个延迟在LVI-SAM里会导致加速度计和陀螺仪的运动积分产生明显漂移。解决方案是在BMI088驱动里使用设备数据就绪中断的GPIO时间戳或者至少用DMA传输完成中断打时间戳。4.3 外参标定从粗对齐到精标定传感器外参标定是实机部署中最耗时的一步。我的做法分两步先用人工测量值做粗标定让系统能跑起来再用kalibr工具做精标定。粗标定很简单用卷尺量出各传感器在机器人坐标系下的位置加上用水平尺测出的姿态角。例如把雷达安装在机器人正前方30cm处IMU安装在雷达正下方5cm处相机安装在雷达右侧10cm处这些测量值可以直接填入yaml文件。测量值虽然粗糙误差通常在1-3cm和1-5度但足以让系统不立即发散便于后续精标定。精标定我使用的是kalibr工具标定相机到IMU的外参。操作流程是把相机和IMU固定在同一块刚性板上对着一个带黑白棋盘格的标定板做8字形、平移、旋转等动作录制约2分钟的rosbag然后运行kalibr_calibrate_imu_camera。标定完成后会得到camera_optical_frame到imu_link的旋转矩阵和平移向量。重点来了kalibr标定得到的是相机光心坐标系到IMU坐标系的外参但LVI-SAM的yaml文件里需要的是camera_link到imu_link的外参。因为这个坐标系的差异直接把kalibr标定结果填入配置是跑不通的。正确做法是把kalibr结果先变换到camera_link坐标系再把camera_link到imu_link的外参换算出来这个换算涉及一个固定的旋转偏移camera_link与camera_optical_frame之间的旋转通常在欧拉角上表现为绕Z轴旋转180度。很多人在这一步卡了很久我在后面的6轴IMU适配章节会再详细展开。5. 6轴IMU适配从代码逻辑到参数调整5.1 为什么6轴IMU需要特殊处理LVI-SAM原生代码支持的是9轴IMU含磁力计它对IMU数据的读取和处理在utility.h和imu_factor.h中通过use_imu_9axis参数控制。当你使用6轴IMU只有加速度计和陀螺仪时最直接的影响是缺少磁力计的航向参考系统的yaw角会缓慢漂移。在平坦地面上短时间工作可能看不出来但移动机器人长时间运行或者有爬坡、转弯时yaw漂移会越来越明显。但更隐蔽的问题在于代码内部对IMU数据的处理方式。LVI-SAM在IMU预积分部分假设IMU数据包含磁力计的三轴磁场值如果IMU消息没有磁场数据某些版本会在读取linear_acceleration和angular_velocity之外额外解析magnetic_field字段导致消息解析出错或预积分值异常。这个问题在官方源码中不一定会触发但如果你修改过消息类型定义或使用第三方驱动就会踩到这个坑。5.2 修改消息类型与数据解析我的解决方案是修改LVI-SAM源码中对IMU消息的处理逻辑让它只依赖加速度计和陀螺仪数据。首先检查utility.h中IMU消息的回调函数void imuHandler(const sensor_msgs::Imu::ConstPtr imuMsg) { // 原始代码可能包含磁力计相关处理 // 6轴IMU适配直接忽略magnetic_field字段 sensor_msgs::Imu::Ptr imu boost::make_sharedsensor_msgs::Imu(*imuMsg); imu-header.stamp imuMsg-header.stamp; // 只赋值加速度与角速度 ... }如果你使用的驱动在发布IMU消息时linear_acceleration和angular_velocity的协方差矩阵取默认值0LVI-SAM在预积分时会把这些测量当成完全可信的但实际IMU的噪声远大于0这会导致优化结果过度信任IMU测量最终轨迹出现锯齿状抖动。所以适配6轴IMU的一个重要步骤是正确设置消息协方差。对于BMI088这类传感器我推荐从手册获取噪声密度和零偏稳定性指标然后按以下公式计算协方差加速度计噪声密度: 180 ug/√Hz ≈ 1.76e-3 m/s²/√Hz 陀螺仪噪声密度: 0.004 °/s/√Hz ≈ 6.98e-5 rad/s/√Hz假设IMU输出频率为200Hz预积分频率约等于IMU频率那么加速度计的协方差约为(1.76e-3)² × 200 ≈ 6.2e-4 m²/s⁴陀螺仪的协方差约为(6.98e-5)² × 200 ≈ 9.7e-7 rad²/s²。把这两个值分别填入imuMsg-linear_acceleration_covariance和imuMsg-angular_velocity_covariance的矩阵对角线。5.3 参数文件调整与优化修改完源码后重点在config/lvi_sam.yaml里调整以下参数imuAccNoise: 加速度计噪声密度单位是m/s²/√Hz。对于BMI088我设置为1.8e-3如果你的IMU是MPU6050这类消费级传感器噪声密度会大一个量级建议设置为1.0e-2或更好实际标定。imuGyrNoise: 陀螺仪噪声密度单位是rad/s/√Hz。BMI088约7.0e-5MPU6050约1.0e-3。imuAccBiasNoise: 加速度计零偏随机游走建议值3.0e-3 m/s²/√Hz。imuGyrBiasNoise: 陀螺仪零偏随机游走建议值4.0e-5 rad/s/√Hz。这里有两个容易犯的错误。第一个是直接套用数据集里的参数值数据集用的是高精度工业IMU噪声指标比消费级IMU好一个量级以上直接套用会导致IMU权重过高视觉和激光因子被压制整体精度不升反降。第二个是忽略偏置噪声的参数只关注了噪声密度。偏置随机游走如果设得太小系统会认为IMU零偏恒定不变但实际开机后温度变化会引起零偏漂移最终导致定位漂移。如果你不确定自己的IMU参数最靠谱的办法是用imu_utils工具标定。这个工具需要一个静止的IMU数据集录制5-10分钟然后运行imu_utils计算Allen方差能同时得到噪声密度和偏置随机游走。我强烈建议你花一下午把这个标定做完它提供的数据比网上找到的任何参考值都更可靠。5.4 坐标系对齐与初始偏航角6轴IMU没有磁力计系统启动时的初始偏航角只能依赖其他传感器确定。LVI-SAM在启动时默认使用IMU的初始姿态作为世界坐标系的基准但如果IMU的yaw角与真实航向偏差很大比如手工放置机器人时没有对准方向后续的定位轨迹就会整体旋转一个角度。我这里的处理方法是在启动LVI-SAM前先手动获取一个初始偏航角。如果雷达有完整的360度视野可以把雷达的一帧点云投影到地面平面用点云的主方向或者预设的墙角方向对齐到map坐标系。或者更简单的方法在launch文件中使用静态TF先把base_link对齐到map启动后再通过/set_origin的rqt工具微调。一个实用技巧是在6轴IMU适配时把LVI-SAM的extrinsicRot和extrinsicTrans参数单独设置不要让系统自动从TF树读取IMU外参。我遇到过TF树的IMU外参因为父坐标系定义为base_link导致级联错误的情况直接硬编码配置能减少一个容易出错的位置。6. 实机运行效果与问题排查实录6.1 第一次实机运行的失败教训第一次实机运行LVI-SAM我满怀期待地把所有节点启动起来结果前5秒点云还正常第6秒开始漂移第10秒整个轨迹直接发散到墙外面去了。回看rosbag里的数据发现IMU数据频率只有80Hz配置的是200Hz导致预积分的时间间隔过大运动预测不准。这暴露了一个问题BMI088通过I2C读取的实际带宽受限于总线速度配置的200Hz并没有达到。解决方案是把IMU驱动改为SPI接口读取或者降低预积分频率要求。因为LVI-SAM的预积分频率与IMU频率绑定我最后选择了SPI接口成功把IMU频率提升到500Hz用200Hz发布问题解决。另一个教训是相机图像分辨率超高带来的性能问题。D435i默认输出1080p图像视觉特征提取的耗时导致整体帧率掉到10帧以下。解决办法是在realsense-ros启动参数里把rgb_camera.resolution设为640x480帧率30fps。这个分辨率对VIO来说足够了太高的分辨率并不会显著提升精度反而让系统实时性变差。6.2 常见问题与解决方案速查表症状可能原因解决方案编译报CV_LOAD_IMAGE_GRAYSCALE错误OpenCV 4.x移除旧宏定义改为cv::IMREAD_GRAYSCALE编译报GTSAM符号未定义GTSAM版本过新或未正确安装使用4.0.2并重新make install数据集能跑实机立即发散外参未标定或时间同步错位先做粗标定再精标定检查时间戳轨迹整体旋转但形状正确初始偏航角未对齐启动前校正初始航向或设置初始yaw偏移轨迹抖动明显IMU协方差设置过大或过小用imu_utils标定后填入准确参数点云建图边缘模糊激光-视觉外参误差大用Kalibr或lidar_camera_calibration重新标定回环触发后轨迹跳变回环检测误匹配或外参错误检查视觉特征匹配重新标定相机雷达外参系统延迟高、帧率低相机图像分辨率过高降到640x480关闭附加视觉处理6.3 6轴IMU适配后的运行效果对比适配完成后我在室内走廊场景和室外园区场景分别测试了LVI-SAM和LIO-SAM的定位效果。室内场景测试条件10m×30m走廊地面平整墙面有较多窗户和玻璃对激光雷达有干扰对视觉纹理有挑战。LIO-SAM在走廊尽头出现漂移回环修正后误差约15cmLVI-SAM在同样条件下定位误差约8cm轨迹更平滑建图边缘更清晰。室外园区测试条件长直道路、两侧有树木和路灯杆存在不可忽略的车辆动态物体。LIO-SAM在长直段有轻微漂移LVI-SAM依赖视觉特征约束轨迹保持稳定误差在10cm以内。一个明显的感受是6轴IMU适配后整个系统的表现比预想中好很多。因为没有磁力计yaw角确实会缓慢累积漂移但这个漂移会被视觉-激光回环不断修正。在我跑的这两组场景里20分钟的运行时长内没有出现不能容忍的航向误差。但如果你的机器人会在未知环境长时间运行还是建议在后端定期加入绝对航向参考比如GPS航向或者视觉罗盘。6.4 关于yaml文件的参数调优心得参数调优是最耗时的环节。我的经验是不要一次性改多个参数否则你无法判断是哪个改动起了作用。每次只改一个参数跑一遍数据包对比轨迹误差和建图质量再调下一个。测试数据包建议录制在固定场景里每次回放同样的数据这样对比才有意义。另外参数调优时不要盲目照搬别人的数值。不同的传感器指标、安装方式、环境特点都会导致最优参数不同。我建议以官方数据集的参数作为初始值再根据你的传感器性能指标按比例调整。比如你的IMU噪声密度是数据集配置里的2倍那IMU相关参数值也应相应放大再在数据包上逐步微调。7. 一点体会我从零开始复现LVI-SAM到在实机上稳定运行前后花了两周多。最花时间的环节不是编译是传感器标定和参数适配。如果你也是6轴IMU用户建议把重点放在外参标定和IMU噪声参数上这两个环节决定系统能不能收敛。最后分享一个小技巧调试过程中我在启动LVI-SAM之前会先单独跑一遍imu_utils或kalibr生成最新标定结果再把结果写入配置。每换一次传感器位置或者每挪动一次IMU的固定方式都要重新做一次这个流程。传感器装好后被磕碰过、固定螺丝松动过都可能导致外参改变而SLAM系统对这种细微变化非常敏感。养成这个习惯后你的LVI-SAM会少很多莫名其妙的问题。