写这篇激光SLAM与三维占据栅格地图构建的文章,先把一个真实场景放在前面。三年前我在测试一台搭载16线激光雷达的移动底盘,任务很简单:在室内走廊走一圈,实时生成一张三维占据栅格地图。前30秒一切正常,屏幕上点云像积木一样一块块拼出来。到了转角,地图开始出现重影,定位坐标开始小幅跳变。再往前走几米,整张地图完全散架,程序还在跑,但地图已经没法看了。
这个场景是我见过的最典型的激光SLAM失败现场。问题不在“建图”本身,而是一个更隐蔽的耦合关系:定位一旦飘,地图就跟着崩;地图一旦崩,定位就更找不到参照。
所以这篇文章想讲清楚一件事:激光SLAM与三维占据栅格地图实时构建,真正考验的不是某个算法有多强,而是你能不能把传感器输入、位姿估计、地图更新和计算资源这几条线同时稳住。单次跑通很容易,稳定跑完很难。
1. 先搞清楚激光SLAM解决的是哪类问题:不是“建图”,而是“边走路边回答我在哪”
很多人第一次接触激光SLAM,会把注意力放在“地图”上。这个方向不能说错,但容易让人误解。激光SLAM的完整表述是“同步定位与建图”,它要同时做两件事:估计传感器在世界坐标系里的位姿,同时用观测到的激光点更新地图。位姿是地图的前提,地图是位姿的参照。这两个问题互相依赖,形成一个循环。
这也是SLAM和单纯建图的本质区别。如果机器人位置已知,那建图就是简单的点云拼合;如果地图已知,那定位就是配准和一帧点云对齐到已有地图。难点恰恰在于两者都未知。
1.1 定位与建图的耦合关系,才是问题的核心
在激光SLAM里,假设你拿到一帧新的三维点云。要把它加入地图,你必须知道这帧点云是在哪里采的。这里的“在哪里”不是一个大概位置,而是一个足够精确的六自由度位姿:三个位置增量加三个旋转角。方位角稍有偏差,远处几米外的点就会在空间里偏移几十厘米。你用这样一帧点在已有地图里做匹配,匹配结果自然被带偏。下一帧继续错,地图彻底发散。
这正是“实时构建”和“离线拼接”最大的不同。离线建图可以把所有点云收集起来,用全局优化算法慢慢对齐。实时构建没有这个条件,每一帧必须在下一帧到达之前给出位姿估计,否则地图更新就会积压,延迟越来越大,最终程序卡死或内存溢出。
所以,激光SLAM主流程的三个环节:传感器数据预处理、帧到地图的配准、位姿图优化,本质上都是在做同一件事——尽量让当前帧的位姿估计不产生累计漂移。占据栅格地图只是这个循环最后呈现出来的结果。
1.2 为什么这个循环过去很难做好
早期SLAM使用扩展卡尔曼滤波,把机器人位姿和地图特征放进一个状态向量,实时更新。这个思路在小场景、低噪声环境下可行。一旦环境变大、特征变多,状态向量的维度会急剧膨胀,计算量按平方级增长,实际没法跑。后来粒子滤波被引入,Gmapping这类2D方案曾经统治了很长时间。粒子滤波用一群粒子代表机器人可能的位置,每个粒子维护一张地图,最后用权重筛选粒子。这种方法在走廊、小房间这类场景精度很好,但粒子数量和环境复杂度直接相关,环境一大,粒子数量就爆炸。
这是2D时代的经典矛盾:定位越准,地图越清晰;地图越清晰,维护成本越高。3D激光SLAM把问题搬到了空间里,维度从两维变成六维,这个矛盾被放大了。
1.3 当前的主流方案在做什么
图优化方案是现在的主流方向。Cartographer和LOAM系方案都属于这个路线。它们放弃了“实时维护一张全局地图”的思路,改为维护一个位姿图。图中每个节点是一帧激光数据对应的位姿,边是相邻帧或回环检测给出的相对约束。后端定期做图优化,把累计漂移分散到整个图里,再生成更新后的地图。
这个思路的关键是回环检测。机器人回到曾经来过的位置,算法把当前点云和历史地图做匹配,一旦认出来,就形成一个长距离的边,把漂移拉回来。没有回环的话,图优化改善有限。这也是为什么很多SLAM评测环境都喜欢带闭环,因为闭环直接决定最终地图能不能收敛。
对工程实践来说,这意味着你在规划测试路线时,一定要设计回环轨迹。直线走到底再原路返回,比绕一个环形更容易产生漂移,因为前者的回环机会少。
2. 三维占据栅格地图是怎么“记住”环境的
点云地图和占据栅格地图是两种完全不同的产物。点云地图直接保存所有激光点,直观、精确,但无法表达“这个区域是不是真的没有障碍物”。占据栅格地图把空间划分成一个个小立方体,每个立方体记录一个状态:占用、空闲或未知。
为什么需要这种区分?因为机器人导航不能只靠“哪里有障碍物”,还得知道“哪里可以走”。激光雷达只能测到物体表面的点,物体背后的区域、测量不到的区域,在点云地图里都是空白。这个空白到底是“可以通过”还是“未知”,点云地图没法回答。占据栅格地图用概率状态解决了这个问题。
2.1 体素的概率状态不是一次写死的
三维占据栅格地图的最小单元通常叫体素。每个体素存储的不是一个0或1的标签,而是一个概率值,代表这个格子被障碍物占据的可能性。雷达光束扫到一个点,这个点所在的体素占用概率增加;光束从这个点到传感器之间穿过的所有体素,占用概率减少。这个机制对应的是激光测距的基本物理事实:光沿直线传播,中间必须是空的,末端必须有什么东西挡了一下。
用概率而不是直接标记,好处是可以用多帧观测来修正单帧噪声。一帧点云可能因为材质反光、边缘效应产生杂点,但多次观测里,真实的障碍物会被反复确认,噪声点只有一个方向的支持,概率值会被慢慢压下去。这也是为什么直接用点云降采样做地图,容易出现“鬼影墙”,而占据栅格地图会更平滑。
2.2 体素化决定了地图的精度和内存边界
这里的核心参数是体素大小。常见开源方案里,室内场景一般用0.05米到0.2米之间的体素。体素越小,细节越丰富,但内存和计算量会呈立方级增长。一个10米 × 10米 × 3米的空间,用0.1米体素划分,会产生30万个候选体素;用0.05米体素,就是240万个。这还没算哈希表管理和更新频率。
所以,三维占据栅格地图不是一个“分辨率越高越好”的问题,而是你必须在精度和资源之间找一个平衡点。我的建议是:先确定作业场景的最小障碍物尺寸,再倒推体素大小。比如AGV要避开直径5厘米的障碍物,体素就不能粗于2.5厘米到3厘米;如果只是做建筑结构扫描,0.2米体素已经足够,因为墙体内部细节对导航没有意义。
注意:体素分辨率不是一个可以随意调的参数。改小之前,先估算一下环境范围和环境特征密度。很多实时构建卡顿,不是算法问题,而是体素开得太细,内存和CPU顶不住。
2.3 地图更新策略决定实时性
实时构建不只是“每帧都更新地图”,更重要的是“怎么更新”。最直接的方法是每个体素独立判断光束命中或穿过,但这需要为每一条激光射线遍历路径上的每一个体素。一帧16线雷达大约3万个点,如果体素很细,这个遍历量会非常大。
实际系统中的优化包括:
- 用体素哈希表而不是预分配全部空间,只保存被观测过的体素。
- 使用射线遍历算法,比如3D DDA或Bresenham,快速定位光束路径上的体素。
- 定期把短期高频更新的体素合并到全局地图,而不是每帧全量更新。
- 对超过最大距离的激光点做截断或降权,避免远距离观测的不确定性污染地图。
这些优化不会改变占据栅格地图的理论模型,但它们决定了系统能不能在实车上跑起来。如果你在调一个开源SLAM,发现CPU占用异常高,先检查是不是每次配准完都对全图做了重投影。
3. 实时构建的真正难点不是算法,而是三笔账
很多人以为实时构建难在数学。调试久了你会发现,数学问题大多是稳定的,难的是资源问题。实时构建本质上是三笔账:时间、内存、精度。这三者互相牵制,任何一个短了,系统都会出问题。
3.1 时间账:每帧必须在下一帧到来前算完
激光雷达按固定频率输出。16线雷达通常是10赫兹,这意味着每100毫秒必须完成一帧的位姿估计、配准和地图更新。如果你的算法处理一帧需要120毫秒,帧率赶不上输入,构建速度就会越来越慢。表面上看是“程序还活着”,实际上地图的实时性已经失效了。
这里常见的做法是引入前端和后端的分离。前端用快速配准算法立刻估计位姿,保证帧率;后端用图优化慢慢修正全局误差,不阻塞实时管线。这也是Cartographer架构的核心。理解了这个分离,你就知道为什么很多SLAM代码里会看到两个线程,一个高频处理匹配,一个低频做优化。
在设计自己的管线时,有两件事很重要:
- 给前端配准预留的时间要足够保守,不能卡在极限上。
- 地图更新和后端优化要放在独立线程,不能和前端配准共用锁。
3.2 内存账:体素数会随运行时间无限增长
如果说时间账还容易通过线程设计解决,内存账就是更隐蔽的坑。点云转成占据栅格地图后,体素会随着探索范围扩大而增加。大场景连续走30分钟,体素数量很容易达到百万级别。如果每个体素保存完整的状态,包括概率值、最近更新时间、锁等信息,内存占用就非常可观。
Map结构如果设计不当,还会出现内存碎片和哈希冲突增加的问题。常见的对策包括:
- 定期清理“从未被更新”的体素。
- 设置地图范围上限,超出范围的观测丢弃或存档。
- 使用自定义内存池,避免高频创建和销毁体素。
- 把占据概率压缩到更小的数据类型,避免浮点存储。
3.3 精度账:所有实时方案都会在某个环节丢精度
实时构建不能保证每一帧都绝对精确。为了赶时间,前端匹配可能只迭代几次就收敛;为了控制内存,体素分辨率不可能无限小;为了抑制噪声,地图更新会做概率平滑。这些都会让最终地图和真实环境有偏差。
关键是要控制偏差的类型。随机噪声可以通过多次观测平滑;系统性偏差,比如外参标定错误造成的固定偏移,会一直存在,而且会让地图产生“重影”或“双层墙”。调试SLAM时,如果发现同样的错误在每一帧都出现,优先怀疑外参,而不是算法参数。
4. 从2D SLAM到3D SLAM:选型不是越高级越好
热搜里常见的“2D SLAM有哪些”“激光雷达做hector_slam”,说明很多人还在2D SLAM和3D SLAM的分叉路口。这里我给出一个比较稳定的判断:2D SLAM和3D SLAM不是简单的新旧关系,而是两类不同硬件、不同场景、不同成本的选择。
4.1 常见的2D方案与三维方案对比
2D时代最具代表性的方案是Gmapping、Hector SLAM和Cartographer的2D模式。它们有一个共同前提:机器人的运动被约束在平面上,高度方向可以忽略。这意味着激光雷达基本都是单线的,扫描结果是一个平面截面。机器人位置只有三个自由度:x、y、偏航角。这个简化让问题变得容易很多。
3D SLAM则使用多线激光雷达或固态激光雷达,点云覆盖了整个前方空间,位姿估计需要六个自由度。代表方案包括LOAM系、LIO-SAM、Cartographer的3D模式,以及一些基于NDT配准的商用方案。
| 维度 | 2D SLAM | 3D SLAM |
|---|---|---|
| 传感器 | 单线激光雷达 | 多线激光雷达、固态激光雷达 |
| 自由度 | 3个(x、y、yaw) | 6个(x、y、z、roll、pitch、yaw) |
| 地图形式 | 2D占据栅格图 | 3D占据栅格/点云/TSDF |
| 计算开销 | 较低,普通工控机可跑 | 较高,通常需要GPU或高性能CPU |
| 典型场景 | 扫地机器人、AGV、室内导航 | 自动驾驶、无人机、建筑扫描 |
| 典型开源方案 | Gmapping、Hector、Cartographer 2D | LOAM、LIO-SAM、Cartographer 3D |
从这张表可以得出一个结论:如果你的应用场景真的只有平面运动,用3D方案不一定更好。3D方案要处理的信息量大,调参复杂,地图文件也大。反而是2D SLAM在室内小场景下更稳定、更成熟。
4.2 我建议的选型逻辑:先问场景,再问硬件,最后问算法
看到很多读者纠结“学哪个SLAM更好”。我的建议是反过来的:先明确你要让机器人做什么。
- 如果是室内导航和避障,2D SLAM往往是第一选择,因为计算简单、地图数据量小、定位稳定。
- 如果是AGV在工厂环境下走固定路线,2D SLAM可以满足绝大多数需求。
- 如果需要跨楼层、爬坡、无人机或室外作业,才需要考虑3D SLAM。
- 如果连传感器都还没有,不要先定算法。先选激光雷达,再选方案。
有很多人用单线激光雷达硬跑三维SLAM,这是不现实的。单线雷达只有一平面光束,无法感知高度方向结构,三维重建的几何信息严重不足。当然,也有用单线雷达加IMU或里程计做2.5D重建的研究,但离稳定生产还有距离。
4.3 贝叶斯定位和粒子滤波定位,在实际工程中怎么选
上面提到过贝叶斯定位和粒子滤波定位,这两个概念经常出现在SLAM的讨论里。贝叶斯定位是一个广义框架,把定位看成基于历史观测和运动模型估计状态的后验概率问题。粒子滤波是贝叶斯滤波的一种实现方式,用一组带权重的采样粒子近似后验分布。
在3D激光SLAM里,图优化是主流,但粒子滤波的思想并没有退出。许多方案在定位阶段仍然使用粒子滤波来初始化位姿或处理全局定位问题。区别在于:
- 粒子滤波擅长处理多峰分布,适合全局定位和绑架问题。
- 图优化擅长处理连续漂移,适合实时配准和回环修正。
- 实际系统中,两者常混合使用,粒子滤波负责启动阶段或重定位,图优化负责日常位姿跟踪。
如果你在看源码时遇到粒子滤波相关代码,不要以为这是过时实现。它是全局定位的重要兜底手段。
5. 一套最小可运行的落地流程:从标定到地图更新
理论讲完,进入实操部分。下面这套流程是通用处理思路,不是某个特定开源方案的官方教程。落地前需要基于实际依赖版本做微调。
5.1 环境准备和坐标关系
在启动SLAM之前,必须完成两件事:确认传感器能输出数据,确认外参标定完成。
激光雷达驱动装好后,先订阅原始点云话题,确认点云里有有效的三维坐标,没有大量NaN值,频率稳定。这一步用可视化工具观察点云时,注意点云应该随着传感器移动而移动,这是坐标系正常的基本表现。
外参标定指的是求解激光雷达和机器人本体之间的相对位姿。许多实时构建异常,尤其是地图重影和定位旋转飘,根源都在外参未标定准确。常用的标定方法有手动测量加微调,以及用标定板自动标定。如果ROS中使用的是静态变换,可以直接在URDF或TF树里配置。
坐标系关系至少要保证:
- 机器人本体坐标系base_link存在。
- 激光雷达坐标系lidar_link通过固定变换关联到base_link。
- 里程计坐标系或IMU坐标系能提供位姿先验。
- 最终的地图坐标系map由SLAM算法维护。
注意:千万不要在没有任何运动先验的情况下直接跑3D SLAM。没有初始位姿,实时配准会快速发散。移动机器人的轮式里程计,或IMU预积分,至少要有一个可用。
5.2 最小可运行流程:单帧、位姿、配准、地图更新
我把一个最小系统拆成四个环节:
- 获取一帧点云,做体素降采样和离群点滤除,减少数据量,提高匹配稳定性。
- 使用初始位姿作为起点,把当前帧点云与已有子图做配准。常见方法包括ICP和NDT。
- 配准成功后得到新位姿,把当前帧按新位姿投影到占据栅格体素空间,更新每个体素的占用概率。
- 定期检查位姿图的规模。如果累计帧数达到阈值,或检测到回环,就把约束发给后端做图优化。
先别急着把这四步都做到完美。第一次实验先跑通前三步,用一个很小的场景,把地图生成出来。哪怕是10米×10米的房间,只要能稳定闭环,就已经证明管线通畅。
5.3 关键参数理解:体素、最大距离、更新阈值
参数从哪里调起?优先级最高的三个参数可以按下面的顺序调整:
| 参数 | 作用 | 常见问题 |
|---|---|---|
| 体素分辨率 | 决定体素大小和地图精度 | 设置太小导致内存爆炸 |
| 最大有效距离 | 决定哪些远处的点参与地图更新 | 设置太大引入远距噪声 |
| 配准收敛阈值 | 决定多近才算匹配成功 | 设置太严导致配准失败比例升高 |
最大有效距离这个参数经常被忽略。激光雷达远距离观测有较大噪声,特别是低线束雷达,远处点的垂直间距会非常大。比如一个20米外的点,在相邻扫描线之间的间距可能达1米以上。这样的点直接用来更新占据栅格,反而会把墙面画得很厚。常见做法是把最大距离限制在激光雷达稳定测距能力的一半左右。
5.4 单任务验证:怎么判断刚才的构建是否成功
构建完成后,先不要看地图漂不漂亮。按以下顺序确认:
- 地图闭合区域是否对齐:走一圈回来,起点和终点附近的地图边缘是否重叠。
- 墙面是否连续:单面墙不应该出现断层或双层。
- 障碍物轮廓是否清晰:小尺寸障碍物如果糊成一团,说明分辨率不够或位姿飘了。
- 定位轨迹是否平滑:轨迹图不应该出现大量跳变点。
如果这些检查都没问题,再进入批量或自动运行阶段,分批跑不同场景,观察地图能否持续稳定。
6. 最常见的问题链路:定位旋转飘、地图重影和资源暴涨
热搜里有一个词非常真实:“cartographer定位时候旋转飘”。这几乎是所有激光SLAM使用者都会遇到的一类问题。我把常见问题按一个统一的排查链路拆开。
6.1 先看现象,不要急着调参数
现象大致可以分为三类:
- 定位旋转飘:机器人原地转向时,地图坐标出现偏移或抖动。
- 地图重影:同一面墙在最终地图里出现两层轮廓。
- 资源暴涨:运行一段时间后内存和CPU占用持续上升,最终卡死或延迟。
这三类现象对应的原因链完全不同。先明确你遇到的是哪一类,再进入下一步。
6.2 再按输入、环境、参数、工具边界逐层排查
第一层:输入
确认点云话题的帧率、时间戳、坐标系是否正确。ROS下常见的坑是时间戳同步问题,一帧点云由于等待时间差,传感器数据到达时已经滞后了一两个周期。检查方法是订阅原始点云,看数据频率和雷达标称频率是否一致,以及点云数据的framestring是否和外参配置一致。
第二层:环境
检查机器人的运动场景是否适合当前方案。反向旋转过快、地面打滑、传感器视野被遮挡,都会导致前端配准找不到足够约束。Cartographer这类方案在狭窄走廊里旋转,经常因为周围特征是后退的,无法准确估计旋转分量。这时候即使IMU也在提供姿态,仍然会看到旋转飘移。
第三层:参数
到了这层才考虑调整算法参数。对于旋转漂移,优先检查:
- 前端匹配的收敛阈值是否过宽松。
- 初始位姿估计是否有运动预测,还是直接拿上一帧位姿当起点。
- 回环检测是否开启,如果关闭,长距离累积漂移会快速反映在旋转上。
第四层:工具边界
最后确认是不是方案本身的限制。部分定位算法只适合低速、平滑运动,快速旋转或剧烈颠簸会导致配准失败。此时应该改用或增加传感器融合方案,比如加入IMU预积分。
6.3 针对旋转漂移的经典修复顺序
如果确认是“旋转时飘”,我的建议排查顺序是:
- 检查外参标定,特别是激光雷达相对于IMU的旋转。
- 检查IMU数据方向和频率,确认IMU的yaw增量与真实旋转一致。
- 降低旋转速度,观察是否改善。
- 增加前端配准的局部表面提取或平面特征提取。
- 最后检查地图更新时是否用最新位姿更新,而不是旧位姿。
这个顺序是把最可能导致旋转误差的系统性原因放在前面,算法调参放在后面。
6.4 地图重影的常见原因
重影通常有两种来源。第一种是漂移未回环,位姿图还没收敛时地图就已经更新到全局,旧位姿和新位姿叠加。第二种是外参不准,同一个障碍物在不同角度看到的几何中心有偏差。区分方法是:如果重影在运行一开始就有,明显是外参;如果重影是运行很久后才严重,则偏向漂移和回环问题。
6.5 资源暴涨的定位
资源暴涨和参数强相关。最常见的是体素分辨率设置太小,或地图范围不设上限。排查时先打开资源监控,观察是CPU还是内存先爆。CPU先爆,优先检查前端配准迭代次数;内存先爆,优先检查体素数和地图范围。
7. 三维占据栅格地图的适用边界和长期工程化
很多项目做完演示就没下文了,原因不是SLAM跑不出来,而是长期稳定运行需要补的东西太多。这里我把适用边界和工程化拼图分开说。
7.1 适合什么,不适合什么
- 适合:室内外中等规模场景,激光雷达有清晰几何结构的环境,建图目标是要用于导航或碰撞检测的项目。
- 适合:需要空间感知的移动机器人,比如安防巡检、AGV、无人机避障。
- 不适合:空旷无结构的大平面,比如开阔广场,仅靠激光点云很难准确约束位姿,必须配合IMU或里程计。
- 不适合:动态物体频繁的环境。占据栅格地图假设环境是静态的,如果行人、车辆大量出现在建图过程中,会产生大面积幽灵障碍物。动态物体过滤是必要的,但这会增加额外实现成本。
7.2 长期使用还缺哪几块拼图
从实验环境走到生产环境,需要补齐:
- 日志和状态监控:SLAM算法跑着跑着飘了,没有日志的情况下完全无法复盘。
- 自动恢复机制:当定位置信度下降到阈值,系统要能自动重新定位,而不是一直带着偏差跑。
- 地图版本管理和更新:长期运行的场景会变化,地图需要定期更新和局部校正。
- 异常数据隔离:雷达潮湿、盖子脏污、电源波动都会产生异常点云,这些需要在前处理阶段剔除。
7.3 一个可以长期坚持的开发顺序
如果你现在刚接触激光SLAM,我的建议路径是:
- 先在仿真环境或开源数据集上跑通带标注的回放数据,熟悉输入输出和坐标系。
- 在真机上用小车跑小范围建图,先解决外参、时间戳、运动模型这些工程问题。
- 验证回环检测和地图质量。
- 再加动态物体过滤、地图增量更新等高级模块。
不要一上来就追求大场景、高精地图或复杂融合。激光SLAM是一个系统性工程,每个环节都要稳定,才能把能力叠加起来。
回到一个更底层的判断
和激光SLAM与三维占据栅格地图打了几年交道,我最大的体会不是某个算法多精妙,而是这类系统的边界几乎都在工程细节上。外参差一度,地图重影;帧率差毫秒,定位飘移;体素小一档,内存爆炸。每个单独看起来都像是小问题,但组合在一起,就是稳定性和实时性的差距。
如果你现在正要开始一个这类项目,第一步不是去研究最新论文,而是把传感器数据流、坐标变换、外参标定和日志监控做好。这三件事不做,后面所有算法调优都是在沙子上面盖楼。
建图的目标从来不是“养眼”,而是让机器人长期在一个真实空间里知道自己在哪里、哪里可以去、哪里不能去。理解了这一点,你就不会被某个炫酷的演示视频带偏方向。