雷达SLAM方向的论文精读,我一直保持着写笔记的习惯,这个系列写到第七篇了。今天想拆的是LIO-mapping,一篇对我实际工作影响很大的工作。它做的事可以概括成一句话:把激光雷达和IMU重新组合成一套完整的定位建图方案,输出高频里程计和一致性地图。但真正让我服气的是它的系统设计思路——并没有把IMU当“辅助传感器”来用,而是实打实让IMU参与进了后端优化。
这篇论文适合谁读?如果你在做机器人导航、自动驾驶感知,或者手持扫描设备的实时建图,只要你手里同时有激光雷达和IMU,这篇就是绕不过去的经典。尤其是它启发了后来一系列LIO方案,把这篇吃透,再看后面那些工作会轻松很多。
1. 从“能用”到“好用”:LIO-mapping到底做了什么
1.1 松耦合与紧耦合:IMU在系统里的角色
先说背景。在很多早期的激光雷达方案里,IMU就是个“工具人”。最典型的是LOAM系的做法:IMU负责在两帧激光之间提供一个粗略的旋转初值,帮助点云去畸变,或者给帧间匹配一个初始猜测。这在大部分场景下够用,但有个问题——IMU的测量信息只参与了“前端计算”,没有进入后端的优化目标函数。也就是说,IMU的误差、IMU的漂移,系统根本不关心,也不会去修正它。
这种方案就是松耦合或者半耦合。IMU精度再高,也只是个“初始值提供器”。
LIO-mapping的思路完全不同。它把IMU预积分结果做成了因子图里的一个约束项,和激光配准得到的约束放在一起,由后端优化器统一调整。IMU不再是打杂的,而是和激光雷达平起平坐的传感器。这就是“紧耦合”的直观含义:两类传感器的原始信息共同决定状态估计的结果。
我当时第一次跑通这个系统,最大的感受是:在快速旋转或者剧烈加减速的时候,纯激光方案很容易丢匹配,而LIO-mapping靠着IMU因子能撑过去,位姿不会突然断掉。这种“兜底”能力,松耦合方案给不了。
| 对比维度 | 松耦合方案 | LIO-mapping紧耦合方案 |
|---|---|---|
| IMU角色 | 提供初始值/去畸变辅助 | 作为因子参与全局优化 |
| 传感器误差修正 | IMU误差不会被后端修正 | 零偏在优化中在线估计 |
| 退化场景表现 | 激光退化时整体失效 | IMU可短暂支撑,缓解漂移 |
| 计算复杂度 | 较低 | 较高,需要预积分和因子图优化 |
| 代表思路 | LOAM、部分改进方案 | LIO-mapping、后续LIO-SAM等 |
1.2 三个关键贡献:紧耦合、描述子、增量优化
如果只记住这篇论文的三个关键词,我建议是这三个:紧耦合因子图框架、旋转不变的点云描述子、基于ISAM2的增量优化。
第一点容易理解,上文已经解释过。第二点是针对回环检测的。很多激光方案在建大场景时容易漂移,因为没有闭环能力。LIO-mapping在提取特征的同时,还为每帧点云生成一个全局描述子,利用描述子之间的相似度判断是否回到曾经去过的地方,触发回环约束。第三点,它没有用每次全量优化的方式,而是用GTSAM里的ISAM2做增量优化。通俗讲就是“哪变了改哪”,而不是每次把所有东西重算一遍,这样长时间运行的计算压力小很多。
这三件事叠在一起,构成了一个完整的“里程计+建图+闭环”闭环系统。在2019年那个时间点,这个组合是相当超前的。
2. 三个核心模块:特征、预积分、因子图
2.1 特征提取:曲率、分区与全局描述子
LIO-mapping的特征提取逻辑继承自LOAM那一套,但做了扩展。具体做法是:对每一帧点云,按扫描线组织数据,对每个点计算它和邻域点的曲率。曲率大的点通常是角点、边缘点,适合做线特征;曲率小的点一般是平面点,适合做面特征。
计算曲率的方式并不复杂:取当前点左右各若干个点,算这些邻域点到当前点的距离方差。方差越大,说明该点附近几何变化越剧烈,越可能是边缘。这个思路很朴素,但确实管用,到现在很多方案还在沿用。
为了保证特征在空间中分布均匀,而不是全部挤在某个方向,论文里把每一帧点云按方位角分成多个子区域,每个区域里再按曲率排序,挑出指定数量的角点和平面点。我复现的时候发现,这个“分区采样”非常重要——如果只是全局取曲率最大的点,遇到树木、栏杆多的场景,特征会扎堆,匹配效果反而下降。
LIO-mapping还多了一步:生成全局描述子。它把点云投影到水平方向的网格里,每个网格统计该区域内的几何分布信息,编码成一个高维向量。两帧点云之间的描述子距离足够小,就认为可能回到了同一个地方,再用ICP做精配准验证,确认后回环因子才真正加入优化。
这里的经验是:描述子不是越复杂越好,关键是“旋转不变”。车或者机器人转了个角度回到原地,描述子如果对朝向敏感,闭环就会漏检。LIO-mapping在设计描述子时显然考虑了这一点,实际测试里,横向、纵向偏移几十米都能正确找回。
2.2 IMU预积分:从原始测量到优化因子
IMU预积分是这篇论文里最容易劝退人的部分,但也是紧耦合的核心。我尽量用大白话讲清楚。
IMU输出的加速度和角速度频率很高,一秒钟几百次。如果每一次优化迭代都把两帧之间这几百个测量重新积分一遍,计算量完全扛不住。预积分的思路是:把两帧之间的所有IMU测量先“离线”积成一个相对量,比如两帧之间的相对旋转ΔR、相对速度变化Δv、相对位移Δp,以及对应的协方差矩阵。这样优化器看到的不再是几百个RAW数据,而是三四个带协方差的相对约束。
关键是,这个预积分量本身带零偏。IMU的陀螺零偏和加速度计零偏会在运行中缓慢漂移,所以预积分量不能一劳永逸。每次优化更新了零偏,需要对预积分量做一阶修正。论文里提供了零偏扰动时的雅可比推导,这部分数学功底很扎实。
我在代码里读到预积分更新时,直观感受就是:它把“积分”变成“查表+微调”。因为预积分量只依赖两帧之间的IMU测量,和位姿无关,所以不管优化器把位姿怎么改,预积分量都不变。唯一需要更新的是零偏影响的修正项。这让优化效率提升非常明显。
另外,协方差传播也是重点。IMU的噪声模型通过线性传播最终体现在预积分因子的信息矩阵里。信息矩阵越大,说明IMU对这个约束越“自信”,优化器就会更重视它。实际调参的时候,IMU噪声参数如果设置得过于乐观,系统会过分相信IMU而忽略激光匹配,反而容易出现奇怪的漂移。
2.3 因子图构建:节点定义与四种因子
因子图是LIO-mapping的后端组织方式。先定义节点:滑动窗口内每一个关键帧的位姿算一个节点,每个关键帧还附带速度、陀螺零偏、加速度零偏这些状态量。窗口大小一般是固定的,新帧进来,最旧的帧就滑出去。这样既限制了计算规模,又保证优化结果能跟上实时性。
再看因子。第一类是IMU预积分因子,连接相邻两个关键帧,约束两者之间的相对位姿、速度和零偏。第二类是激光里程计因子,把当前帧的特征点与局部地图做匹配,匹配残差成为约束。第三类是回环因子,当描述子匹配成功后,用ICP精配准得到当前帧和历史关键帧的相对位姿,作为约束加入。第四类是先验因子,把第一帧或GPS等外部信息固定住,给整个优化问题一个锚点。
这四种因子在GTSAM里被统一建模,交给ISAM2做增量优化。我理解ISAM2类似于“做了缓存的高斯牛顿法”,它利用因子图的稀疏结构,只重新线性化受影响的部分,所以能在嵌入式平台上维持实时性。
我当时有一个困惑:为什么LIO-mapping要用滑动窗口加回环,而不是全局Bundle Adjustment那样把所有帧都优化?后来看懂了,滑动窗口控制的是“局部一致性”,回环约束解决的是“全局一致性”。两者组合,既保证了计算量可控,又能在大场景下消除累积漂移。这个设计思路后来几乎原样继承到了LIO-SAM里。
3. 把论文跑起来:环境、参数与数据
3.1 环境搭建:依赖版本与代码结构
复现LIO-mapping,环境是第一关。依赖项主要是这几个:ROS(我用的是Melodic和Noetic都试过)、PCL、Eigen、GTSAM、Ceres。其中最容易出问题的是GTSAM版本。
我的建议是严格按照开源仓库说明里的版本装。GTSAM版本差异挺大的,不同版本对因子定义、ISAM2接口的兼容性不同,版本不对经常编译报错。如果你跟我一样用的是Ubuntu系统,建议先用apt装ROS和PCL,GTSAM用源码编译安装在系统路径下,这样后面的catkin编译能顺利找到它。
代码结构上,主程序包负责节点管理和回调,工具包处理坐标系转换,点云配准包封装ICP和特征匹配。第一次看代码时建议从launch文件入手,把launch里加载的yaml找到,再跟踪主程序里对应的回调函数。不要一上来就钻代码细节,先把数据流走通。
3.2 参数配置:弄懂每个yaml字段
LIO-mapping的yaml配置项不算多,但每个都直接影响精度。下面这份是我在实际项目中用过的配置,字段名在不同版本里可能略有差异,以你拉的仓库为准:
common: lid_topic: "/points_raw" # 点云话题 imu_topic: "/imu/data" # IMU话题 lidar_type: 0 # 传感器类型标志 n_scans: 16 # 激光雷达线数 scan_rate: 10.0 # 激光帧率 time_offset: 0.0 # 时间补偿 imu: rate: 200.0 # IMU输出频率 acc_n: 0.01 # 加速度计噪声密度 gyr_n: 0.001 # 陀螺仪噪声密度 acc_w: 0.0001 # 加速度计零偏随机游走 gyr_w: 0.0001 # 陀螺仪零偏随机游走 feature: edge_num: 15 # 每帧边缘特征数量档位 planar_num: 30 # 每帧平面特征数量档位几个容易踩坑的点:
IMU频率必须和传感器实际输出一致。如果IMU实际只有100Hz,配置里写成200Hz,预积分的协方差会被过度压缩,优化器会把IMU约束当“铁律”,反而把激光匹配好的位姿拉歪。反过来,实际200Hz写成100Hz,约束太软,退化时IMU兜底能力就弱了。
噪声参数不是越大越好。加速度计/陀螺仪噪声密度可以先按IMU数据手册的Allan方差值填,如果找不到,用上面的量级作为初始值,再在公开数据上微调。判断标准很简单:回放数据时看地图是否分层、轨迹是否平滑。
特征数量参数需要明白它的含义。它不是精确的“取N个点”,而是一个控制稀疏度的档位。太少了匹配不稳定,太多了匹配速度下降而且容易引入噪点。16线雷达我一般用上面那组参数,64线雷达可以把planar_num加大。
3.3 数据准备:让第一帧顺利跑起来
复现用的数据,我建议先找公开的自动驾驶数据集训练集,很多是“激光雷达+IMU”组合采集的,转换成ROS bag格式就能直接播。一定要先跑通公开数据,再动自己的设备,能少走很多弯路。
使用公开数据,可以这样跑:
# 启动LIO-mapping主节点 roslaunch lio_mapping lio_mapping.launch # 回放数据 rosbag play your_data.bag # 打开可视化 rosrun rviz rviz -d lio_mapping.rviz第一次跑通,看到的应该是:随着bag播放,地图点云逐渐增长,轨迹平滑延伸,小车回到起点附近时地图能和之前的点云对得上。如果这一步正常,恭喜你,系统链路已经通了。
我自己第一次跑的时候犯了个低级错误:忘了检查点云和IMU话题是否被bag正常发布,结果主节点一直在等待数据,Rviz里空空如也。排查方法是直接用rostopic echo看话题频率,先确认数据流,再怀疑算法问题。这条经验后来帮我节省了大量排查时间。
4. 复现中踩过的坑和排查思路
4.1 时间戳不同步导致的地图分层
这是我被坑得最惨的一次。现象是:轨迹整体看起来还行,但同一面墙在点云地图里变成了两层,像“重影”。仔细检查发现,IMU时间戳和激光时间戳有将近几十毫秒的偏差。
为什么会有重影?因为激光点去畸变靠的是IMU提供的帧间运动,如果IMU数据比激光晚了几十毫秒,去畸变用的运动补偿就错了,每帧点云本身的内部一致性被破坏,叠到一起自然重影。
排查方法很直接:用rosbag info对比两个话题的时间戳范围,再在代码里打印首帧时间差。如果偏差是固定值,在配置里设置time_offset手动补偿。如果是动态变化的,问题出在采集环节,建议重新采集或者做软件同步。
时间同步问题在IMU和激光雷达硬件来自不同厂家时特别常见。别指望两个传感器时间戳天然对齐,买设备之前优先问清楚有没有硬件同步引脚。
4.2 外参误差的连锁放大
IMU到激光雷达的外参,也就是两个传感器坐标系之间的旋转和平移,是系统误差的主要来源之一。如果外参给得不准,最典型的表现是:直线运动时地图正常,一旦有旋转,地图点云就开始“画圈”,轨迹越走越歪。
外参不准为什么这么致命?因为点云去畸变要把IMU的运动信息转换到激光坐标系下。外参错一点,去畸变就错一点。这个误差在每一帧里都会重复引入,随运行时间累积放大,而且不会像零偏那样在优化里被修正。
我的处理流程是:先在静止状态下采集数据,用手动方式尽可能量准外参;再用标定工具做优化,获得精细结果;最后放到LIO-mapping里用小范围场景跑一遍,观察地图是否干净。每一步都能排查出上一阶段的问题。
4.3 退化场景下的逃生思路
既然叫紧耦合,IMU的兜底能力在退化场景会体现得很充分。所谓退化场景,就是激光特征极少或者几何结构不明确的地方,典型的有长走廊、大停车场、隧道。
在这些场景里,纯激光方案的激光里程计因子会变得非常“软”,因为角点和平面点太少,匹配残差会在多个方向上都接近零,约束不住位姿。LIO-mapping因为还有IMU预积分因子,至少能让位姿不瞬间崩掉,短时间内还能维持相对合理的状态。但如果退化持续时间太长,IMU零偏估计也会慢慢出问题,最终还是会漂。
我的经验是:不要指望单靠算法解决长时退化。配合回环检测,让系统在离开退化环境后尽快纠正历史轨迹,才是更务实的方案。如果在项目中遇到频繁的退化场景,优先考虑引入其他传感器约束,比如轮速计或者UWB,和LIO-mapping做融合。
4.4 依赖冲突与编译运行问题
老版本代码在新系统上编译,最常见就是GTSAM版本冲突。我自己遇到过编译通过但运行时断言失败,查了很久发现是系统里同时存在两个GTSAM版本,链接库时选错了版本。建议复现时用容器或者单独的工作空间,把依赖版本锁死。
另一个容易忽略的问题是PCL版本与代码接口不匹配,尤其是点云类型定义在PCL 1.8和1.10之间有些小变化。如果你在编译时报错涉及PointCloud相关函数,优先检查是不是PCL版本差异导致。
运行时报错“Assertion failed”不一定是你代码逻辑错了,先想想是不是第三方库版本不一致。把环境复现到和作者一致,比调代码更高效。
5. 这篇论文和后续方案的取舍
5.1 学习路径建议
以我个人的体会,LIO-mapping不适合作为第一个SLAM项目来入门。建议的学习路径是:先用LOAM把点云配准和特征提取基础打牢,再接触IMU预积分的概念,最后再读LIO-mapping的因子图和增量优化部分。这样每一步的知识都能对上号。
如果你工作里用的是后来的LIO-SAM、FAST-LIO这类方案,我仍然建议回头读一遍这篇论文。很多后来方案的“改进点”,其实是在LIO-mapping框架上做了工程化取舍。比如LIO-SAM把滑动窗口改成了关键帧+局部地图,换来更高的计算效率,但核心的紧耦合思路一脉相承。理解了源头,再看变体会清晰得多。
5.2 选型时要想清楚的问题
复现和落地是两回事。LIO-mapping在论文里展示了很好的精度和全局一致性,但工程落地时要想清楚几个问题。
计算资源够不够。ISAM2和描述子匹配都比较吃计算,在低算力平台上可能跑不满实时。这种情况下,LIO-SAM那种轻量化的局部地图方式可能更合适。
是否需要全局回环。如果你只在室内小场景短时间运行,可能不需要完整的描述子回环,简单的里程计就够。如果场景较大、运行时间长,回环约束几乎必不可少。
数据质量是否有保障。IMU预积分对IMU频率和时间同步要求较高,如果你手上的IMU很老或者频率偏低,紧耦合带来的提升可能不明显,反而增加了调参难度。
从个人实际经验来说,我倾向于在项目早期用LIO-mapping跑通整个“里程计+回环”逻辑,验证传感器组合可行性,再根据产品需求决定是否换成更轻量的方案。这样既能利用论文方法的高精度验证硬件,又能避免在最终产品上背上过重的计算负担。