ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

轻量级2D SLAM地图模块:栅格地图构建、扫描匹配与回环检测实战

轻量级2D SLAM地图模块:栅格地图构建、扫描匹配与回环检测实战

1. 为什么我宁愿自己写一个地图模块,也不直接用现成方案

一台自己攒的差速小车,配一块入门级 ARM 开发板、一款市售单线激光雷达、两个带编码器的直流电机,这就是我当时面对的全部硬件条件。需求却一点也不含糊:它得在自己画出房间的地图,然后从 A 点走到 B 点,遇到椅子、桌腿、门框能绕开,不能一头撞上去。

我一开始的想法和大多数人一样——直接找个现成的 SLAM 方案装上,省事。但试过几个之后发现,很多方案默认你有一台性能不错的工控机,CPU 随便造、内存管够。放到我这块 ARM 板子上,地图更新、回环检测、代价地图几个模块同时跑,CPU 直接拉满,机器人一动就开始卡顿。硬着头皮调了很久,发现问题的根源不是某一个参数,而是整个方案对资源的要求超出了手头硬件的承受范围。被逼得没办法,我才动手整理了一版属于自己的轻量地图模块,就是 lingbot-map。

lingbot-map 不是一个完整的机器人操作系统,它只负责“激光数据进、栅格地图和位姿出”这一条链路,同时给导航模块留好接口。它解决的核心问题有三个:实时建图、自定位、输出可用的代价地图。适合正在做低成本教学小车、室内巡检底盘、服务机器人底盘的人参考,也适合那些想彻底搞明白 SLAM 每一步到底在算什么的新手。

1.1 硬件条件决定方案取舍

先说我手头这套东西,因为后面所有设计决策都围绕它展开。

传感器方面,我用的是一款通信频率和精度都中规中矩的单线激光雷达,测距范围大概能覆盖十几米,放在室内完全够用。没有 RGB-D 相机,也没有三维激光雷达。为什么不加?原因很现实:深度相机对光照敏感,在走廊里逆光或者晚上开灯都会影响效果;三维激光雷达价格高,大多远超这辆小车的成本。单线雷达虽然只能看到二维平面,但对于室内平地上的机器人来说,这个维度恰好够用,稳定性和性价比都很平衡。

算力方面,主控是一块四核 ARM 开发板,内存不大。这就意味着任何需要跑深度学习模型或大规模图优化的方案都会被直接淘汰。我用过的那个重型方案,光是保存子图、构建多分辨率栅格、跑全局优化这几步,在板子上就要吃掉一半以上的 CPU。留给激光预处理和路径规划的余量几乎为零,机器人一旦动起来,地图更新就明显滞后。

任务负载方面,我主要是在室内房间、走廊、实验室这类半结构化环境里跑。地图面积大概几十到几百平方米,没有超大场景需求。机器人移动速度也不快,最高也就每秒零点几米。这个工况其实很常见,很多教学和科研小车都是这样。换句话说,我需要的是一个在低算力、低速场景下能把 2D 建图和定位做稳的模块,而不是一个能应对所有场景的全能选手。

1.2 现成方案的问题:功能全和跑得动往往两回事

不是说现成方案不好,它们的功能确实很全,但全带来的代价就是参数多、计算重、调起来费劲。我在选择困难时做了一个简单对比:

对比项经典粒子滤波建图图优化建图lingbot-map 的取舍
基本原理用一组粒子维护可能位姿分布维护位姿图并做全局优化前端扫描匹配 + 轻量位姿图优化
计算压力粒子数越多越准,但 CPU 消耗直线上升高,多分辨率搜索和子图构建开销大中等,固定搜索窗口 + 稀疏优化
回环能力依赖粒子多样性,低算力下回环弱强,全局闭环效果好中等,小场景完全够用
参数复杂度粒子数、重采样策略、运动模型等子图大小、优化频率、回环阈值等一大堆精简到几个核心参数
适合场景中大型室内环境、有足够算力要求高精度、算力充足的机器人低成本小车、教学演示、中小室内场景

这个对比不是要否定谁,而是想说清楚 lingbot-map 的定位:它不是要在所有指标上打败大而全的方案,而是在“低算力、中小场景、快速落地”这三个约束下做到最好。当你只需要在一个房间里建图导航时,上一整套重型方案,就好比在县城开一辆卡车买菜——不是不能开,是太占地方,还烧油。

2. 栅格地图是怎么被“一笔一笔”画出来的:机制与数学

很多人觉得建图就是把激光打到的点描在一张图上,其实没有这么简单。真正的难点在于:机器人一边动一边测,每一帧激光来自不同的位置,角度也一直在变;如果直接把所有点画在同一张图上,误差会叠加出一团模糊的线条。所以建图的核心,其实是在不断回答一个问题:这一帧激光,到底应该放在哪里?

先不急着回答这个,我先把“地图”本身讲透。lingbot-map 里用的不是照片风格的可视化地图,而是一张栅格地图,本质上就是一个二维数组。每个格子记录“这个位置有障碍物的概率”。数组的每个元素,就是一个栅格。

2.1 激光数据在进入地图前的标准化处理

雷达输出的原始数据通常是极坐标形式:每个点有一个角度和一个距离。比如“正前方 45 度、距离 2 米”。这种坐标直接画图不方便,第一步要转成直角坐标下的点:x = r * cos(theta),y = r * sin(theta)。

但这只是最简单的转换。真正影响地图质量的是坐标系之间的变换。雷达装在机器人上,它测到的点是在雷达坐标系下的;机器人在地面上的位姿是由里程计和后续匹配共同估计出来的,这事发生在底盘坐标系和世界坐标系之间。所以每一帧激光点都要经过一系列坐标变换:先把点从雷达坐标系挪到底盘坐标系,再把整帧点从底盘坐标系挪到世界坐标系。

这个坐标变换里藏着一个很常见的坑:雷达支架的安装角度和位置如果没写对,哪怕只有一两度偏差,地图就会整体旋转。我后面专门花了一整节讲这个问题,因为几乎所有自己搭小车的人都会踩到。你可以在机器人描述文件里声明雷达相对底盘的位置和姿态,但“声明”和“实际”往往有差距。

此外,原始激光数据里有很多无效点:距离为 0 的、超过最大量程的、打在反光表面上被“弹”到很远的离群点。这些点如果不提前过滤掉,进入地图后会形成一圈莫名其妙的噪点,严重时会让前端匹配误判。我在预处理里做三件事:剔除近距离无效值、限制最大量程、对相邻角度上距离变化异常的点做标记。同时压低点数——相邻角度上距离差很小的点保留一个就行,这样能有效降低后续匹配的计算量。

2.2 栅格更新:概率更新里的对数优势

栅格地图的每个格子存的是一个“占用概率”。但这里有一个细节很多教程不会讲:实际更新时,程序里存的并不是概率本身,而是一个叫对数优势比的东西。

为什么不直接存概率?因为概率的叠加非常不稳定。想象一下,某一帧激光雷达打了墙,告诉程序“这个格子大概率有障碍”,程序如果给这个格子概率加 0.1;下一帧没打中,又减 0.05。来回加加减减,数值很快会溢出到 0 或 1 的边界,而且单个噪声帧会造成很大的跳跃。

更稳的写法是存一个可以累加的 log-odds 值:

l_new = l_old + delta_hit,表示激光命中点所在的栅格; l_new = l_old - delta_miss,表示激光束穿过的栅格。

也就是说,一帧激光不只会告诉程序“终点有障碍”,还会告诉程序“从雷达位置到障碍物之间这片区域是空的”。射线经过的栅格都要做一次“空”的更新,这个过程用的是画线遍历算法,就像是计算机里用一条线扫过所有经过的格子。

每次更新后,当需要查询某个格子究竟有没有障碍时,再通过公式 p = 1 - 1 / (1 + exp(l)) 把 log-odds 值转回 0 到 1 的概率。l 值越大,概率越接近 1;l 值越小,概率越接近 0。为了让长期运行时不出现异常值,还需要把 l 限制在一个区间里,我一般会把它截断在 [-4, 4],这样即使同一个栅格被几百帧自由空间反复刷新,数值也不会跑飞。

这里有一个经验值:delta_hit 通常比 delta_miss 大。比如命中时加 0.4,穿过的栅格减 0.1。如果两者一样大,墙体的边缘会被“空”的更新不断蚕食,建出来的墙会慢慢变薄甚至消失。这个细节我在实战中踩过,后面排查时才发现原来是更新权重的比例没调好。

2.3 地图动态扩展与多帧拼接

地图不是一个挖好的固定尺寸画布。机器人从客厅跑到卧室,地图边界要跟着变。否则机器人一走出地图范围,激光点就没地方写了。

我的做法是:在地图维护线程里检查机器人当前位姿是否接近地图边缘,如果距离边缘只剩 2 米左右,就触发一次扩展,按当前位姿方向向外延伸一圈新栅格,新栅格初始化为“未知”。扩展是个很廉价的操作,只涉及重新分配一个更大的数组,然后把旧数据拷贝过去。

多帧拼接的道理其实就藏在这张动态扩大的栅格图里:只要每帧激光在全局坐标系里的位姿是准的,那这帧的所有激光点就能直接写入这张图上,多个位置的观测自然就拼成了完整地图。这个逻辑听起来很简单,但建图效果的好坏,几乎完全取决于“每帧位姿准不准”这个问题。而这正是下一章的前端匹配要做的事。

3. 从位姿跟踪到全局优化:模块内部怎么串起来

lingbot-map 内部可以拆成三个相互配合的部分:前端做逐帧位姿跟踪,后端做回环检测和全局优化,地图维护线程负责把更新后的位姿和激光数据写成栅格地图。这三部分对应三种不同的时间尺度:前端要实时,每秒处理很多次;后端可以稍微滞后;地图更新跟着前端走,但不能太频繁。

3.1 前端:用扫描匹配做里程计的“修理工”

底盘编码器会给一个里程计信息,告诉你机器人大概走了多远、转了多少度。问题在于轮子会打滑、地面会有起伏,里程计给出的位姿会随时间不断漂移。跑个十米可能只差几厘米,但绕一圈回来就可能差半米。所以光是靠里程计没法建出闭合的地图。

前端要做的事,就是用雷达数据去校正这个漂移。思路是:在里程计给的位姿附近,把当前激光帧投影到已经建好的地图上,然后对比“雷达打到的点”和“地图里已知的障碍物”是不是重合。如果不重合,就微调位姿,直到两者重合得最好。

数学上,这个对比被写成一个最小二乘问题。对激光帧里的每个点 p_i,程序取出该点投影到地图格子上对应的占用概率 M(T * p_i),目标是让所有点的占用概率尽量接近 1,也就是“我看到的每个点都应该是墙或障碍物”。残差写成:

e_i = 1 - M(T * p_i)

把所有点的残差平方加起来,用高斯牛顿法迭代求解增量。每次迭代会解一个线性方程,修正位姿 T。这个过程很像在一个光滑的碗里滚一个小球,小球最终会停在碗底,也就是残差总和最低的位置。

不过这个碗不是光滑的,地图里充满了局部极值。所以实际操作时要分两步:先用里程计给一个初始猜测,或者在一个较小的角度和平移窗口里暴力搜索一个大概对齐的位置;然后在这个粗对齐的基础上做高斯牛顿精匹配。搜索窗口太大,计算量扛不住;窗口太小,机器人位姿稍偏就匹配不上。我用的窗口通常是平移 0.5 米、角度 0.4 弧度,这个范围在室内低速场景下足够覆盖两次匹配之间的运动误差。

3.2 后端:回环检测与全局位姿图优化

前端逐帧匹配只能保证短时间内的相邻帧之间误差很小,但误差会一点点累积。机器人走一圈回到起点,地图前端和地图尾端可能已经错开了很大一段距离。这时需要后端的回环检测来“认亲”。

回环检测做的事情和人类走迷宫时的感觉很像:你绕了一圈,看到熟悉的墙角,心里就知道“我回来了”。在程序里,这个“熟悉”是通过把当前关键帧和附近的历史关键帧做匹配来判断的。如果匹配得分足够高,程序就认为这是一个回环,并给位姿图加一条额外的约束,内容是“当前位置和历史位置应该是同一个地方”。

位姿图是后端的核心数据结构。节点是历史关键帧的位姿,边是相邻帧之间的相对位姿约束(来自前端匹配),以及回环约束(来自回环匹配)。后端优化就是要调整所有节点的位姿,让所有约束的残差总和最小。这个优化问题不需要实时跑,可以在后台线程里每隔一段时间运行一次。

真正需要小心的是回环匹配的得分阈值。阈值设太高,回环永远检不出来;设太低,会把相似但不相同的环境判断成同一个地方,导致地图被“拧”坏。我用的匹配得分阈值通常落在 0.75 到 0.85 之间,同时加一道校验:回环匹配成功后,再用匹配结果附近的最新几帧激光重新算一次相对位姿,如果两次结果偏差超过 0.1 米或 0.2 弧度,就拒绝这条回环。这一道校验在实际测试里至少拦住了三四次明显误检。

3.3 线程划分:为什么建图时不能卡顿

三部分工作如果全塞在一个线程里,机器人一旦转个弯、地图要扩张、回环又要优化,整个系统就会卡住。我最终把工作拆成三个线程:前端线程保持最高优先级,它只负责接收激光帧和里程计,尽快输出当前位姿;后端优化线程用较低频率运行,允许它稍微慢一点,结果通过消息队列发布;地图维护线程则用发布的最新位姿,把激光帧写进栅格地图并定时发布地图话题。

这里有一个很隐蔽的问题:后端优化矫正的是整个位姿图,包括很多历史状态。如果直接把优化后的全局位姿拿来覆盖前端的当前位姿,机器人会在地图上瞬间“跳”一下,下一帧激光的匹配基准就乱了。稳妥的做法是让地图维护线程等待优化结果,只更新受影响的关键帧和它们后续的轨迹,而不是把所有历史节点一次性全部覆盖。换句话说,全局优化负责把地图“捋直”,前端则继续负责实时跟踪,两者通过受控的方式交接,而不是互相抢占。

4. 直接抄作业:从环境准备到建图导航的完整搭建过程

原理讲完了,接下来是实操。我把整个跑通过程里能直接抄走的命令、参数和步骤都列出来。如果你手里已经有类似的雷达小车,照着这个流程走,大概率能在一个晚上跑出一张能用的地图。

4.1 环境准备:硬件、依赖和最容易忽略的配置文件

硬件清单很简单:

  • 一台差速驱动小车,带编码器里程计,能发布里程计话题。
  • 一款单线激光雷达,能发布二维激光扫描话题。
  • 一块能跑 Linux 的 ARM 开发板,内存尽量不低于 1GB。
  • 一个可选但建议加上的廉价 IMU,主要用来抑制偏航漂移。

软件方面,我基于 ROS 框架开发,在 Linux 系统上使用 ROS 2。核心依赖是三个:Eigen(用来解线性方程和矩阵运算)、OpenCV(用来做点云距离计算和一些基础滤波)、以及一个轻量级栅格地图库,这个库主要提供二维数组和画线遍历的能力。编译整个包大概需要几分钟,不算重。

但比装依赖更关键的,是把机器人的描述文件写好。其中最重要的是激光雷达相对底盘坐标系的安装位姿。先量好雷达在车体上的高度、前后位置、左右位置,如果支架有一点倾斜,也要先粗略量出角度,不然建图一开始就会歪。

启动前先做三件事:检查串口设备权限,让雷达节点能正常读取数据;通过话题工具订阅雷达话题,确认角度范围和距离值合理;再看里程计话题,手动推一下小车,确认里程计方向和底盘运动方向一致。这三步看起来基础,但几乎所有建图异常的根因都能在这三个环节提前暴露。

4.2 首次建图:一条启动命令加几个核心参数

环境就绪后,建图启动命令非常简单:

ros2 launch lingbot_map mapping.launch.py

然后用手柄节点或键盘节点控制小车缓慢移动。建图时速度别快,直线速度控制在每秒 0.2 米左右,角速度控制在每秒 0.3 弧度左右。速度太快,单帧激光在测量时间里发生明显移动,激光数据本身会被“拉伸”,匹配就很难对准。

建图参数里边这几个值是关键:

参数推荐值作用
栅格分辨率0.05 米/格每个格子代表 5cm,平衡细节和算力
初始地图尺寸30x30 米太小会导致频繁扩展
前端匹配频率10 Hz每 0.1 秒得到一个新位姿
地图发布频率2 Hz建图时够用,太频繁占带宽
关键帧间隔0.5 米或 0.8 弧度每隔这段距离插入一个新关键帧
回环匹配得分阈值0.8低于这个值不认为是回环

第一次建图时,我建议先走一条“回”字形路线,包括直行、转弯、斜穿,尽量覆盖房间边界。走完之后再补一遍边角,不用太追求一次建完。建图过程中有两个禁忌:一是别连续原地急转,激光会无法覆盖运动中的环境;二是在同一个地方反复来回绕太多遍,会让地图里同一片区域被写入太多次,栅格概率被“过度自信”锁定,后面想改都难改。

4.3 保存地图与导航验证:从静态地图到实时路径规划

建图完成后,需要把地图保存成图片格式和配套的配置文件,方便后续每次启动直接加载,不用重新建图。保存时会同时输出栅格地图的元数据,包括分辨率、原点位置、地图尺寸,这些信息在导航加载时缺一不可。

导航阶段要做两件事:一是加载已保存的静态地图作为先验,二是在线让激光雷达持续更新局部障碍信息。我把代价地图分成全局和局部两层,全局层基于静态地图规划长距离路径,局部层根据实时激光数据做近距离避障。全局路径规划用的是 Dijkstra 算法,优势是保证最优性和确定性,代价是栅格多的时候计算稍慢,但在房间级地图上完全没问题。局部路径规划用动态窗口法,核心逻辑是在机器人当前速度空间里采样一批可行速度组合,然后根据“朝向目标程度、离障碍物距离、速度大小”三项得分选出一个最合适的。

导航验证时先用 2D 目标点发布命令,让机器人从一个房间的门口规划到另一侧的墙角。第一次可以走空载,确认它能避开门框和桌腿。第二次稍微加载一点重量,确认底盘能力变化后,局部规划器仍然可以稳定跟踪全局路径。这一步做完,整个地图模块就算真正跑通了。

5. 实测中最容易翻车的四个场景与完整排查记录

建图和导航跑通只是起点,真正折磨人的是各种莫名其妙的问题。我把实测中遇到的四个典型翻车场景以及完整排查链路整理出来,每一个都对应一类常见故障,值得仔细看看。

5.1 地图斜着长:明明走得是正的,画出来的墙却是歪的

现象:小车沿着一条笔直的走廊走,里程计显示走的直线,但地图里画出来的墙整体斜了一个角度,反复建图都一样。

排查过程:

  1. 先怀疑前端匹配的搜索窗口设得太大,导致每帧都在错误位置找最优解。把搜索窗口从 0.5 米缩小到 0.2 米,重跑一遍,地图仍然斜。
  2. 再怀疑里程计标定有问题。把雷达停掉,只看里程计路径,结果里程计输出很直,没有明显偏转。
  3. 最后检查坐标变换树,发现雷达相对底盘的安装位姿里没有声明俯仰角。实际安装时雷达支架前面略高,存在约 2 度的俯仰倾斜,激光束在地面上扫出的投影整体偏转,前端匹配就把这个偏转当成了真实地图旋转。

修复方法:在雷达关节里补上这个固定俯仰补偿,把支架的安装角度写进去,然后用一段平整墙面重新建图做验证。修复后地图中墙体的角度偏差从肉眼可见的斜回归到正常。这个问题的启示是:建图出问题,先检查坐标变换树,而不是一上来就调算法参数。

5.2 回环闭合后地图突然扭曲:走廊错位,墙角对不上

现象:机器人绕了一圈回到起点附近,按下回环优化后,原本还能看的地图突然扭曲,一段之前明明是直线的走廊变得错开。

排查过程:

  1. 先怀疑回环匹配阈值设太低,导致把相似但不同的走廊误认为同一位置。把阈值从 0.8 降到 0.7 测试,误匹配反而更多,地图更乱。
  2. 决定增加二次校验。匹配成功后,用最近五帧激光重新计算相对位姿,如果两次结果偏差超过 0.1 米或 0.2 弧度,就拒绝这条回环约束。
  3. 再加一道保险:单次优化时限制节点位姿变动幅度,任何节点一次最多移动 0.5 米,如果超过就分成多次优化,避免一次优化把整张地图震散。

修复后测试几条包含多次直角转弯的方形路线,闭合误差从最开始的 0.4 米以上降到了 0.08 米左右。经验是:回环检测不能只靠一个得分阈值,必须有二次校验和优化幅度限制作为兜底。

5.3 导航路径在障碍物前反复横跳:机器人像喝醉了酒

现象:全局路径刚绕开一把椅子,机器人又快走回障碍物方向,在障碍物前来回抖动,始终走不好一条平滑路线。

排查过程:

  1. 先看局部代价地图,发现同一把椅子的激光点在地图上的位置一直在跳动,而不是稳定的圆圈。怀疑是机器人自身位姿有微小抖动。
  2. 检查底盘编码器反馈,发现差速轮在地砖缝附近有打滑,瞬间的位姿噪声让激光点云在局部地图里产生了细微偏移。
  3. 处理方式做了三层:第一,对激光点云做体素滤波,降低单帧噪点;第二,在局部代价地图里加大激光点的最小有效距离,太近的噪点不再进入障碍层;第三,对最终输出的速度指令加一阶低通滤波,让机器人不会因为瞬时位姿抖动而猛打方向。

修复后在房间门框和桌腿密集的区域测试,路径抖动明显消失,机器人能沿着一条平滑弧线绕过障碍物。结论是:导航问题不一定只出在规划算法,底层位姿噪声和点云滤波同样关键。

5.4 长走廊里机器人一路“漂移”:定位越来越偏

现象:机器人沿一条 60 米长的走廊直线行驶,地图看着正常,但实际位置越走越贴近右侧墙壁,最终几乎擦墙。

排查过程:

  1. 先观察里程计数据,发现里程计本身在长直线上累积了不小的横向偏差,但这是正常的,任何编码器里程计跑几十米都会这样。
  2. 再看雷达匹配,发现走廊场景有严重的“退化”:左右两侧都是平整墙面,特征高度重复,雷达帧在横向上能找到很多类似解,前端匹配很容易选错位置。
  3. 处理思路是减少等价解出现的概率:缩小前端匹配的搜索窗口,让匹配更依赖上一个时刻的位姿;同时加入廉价 IMU 的陀螺仪数据做偏航角修正,抑制漂移的方向性发散。

修复后沿 60 米走廊直线测试,横向偏差控制在 0.15 米以内,机器人能保持稳定行驶。这个场景是 2D 激光 SLAM 的经典难题,也说明了单线雷达的固有边界——不能只靠优化算法硬扛,合适的传感器组合和参数默契更重要。

6. 当前的边界与下一步可以扩展的方向

lingbot-map 目前能稳定处理二三百平方米内的室内环境,在低算力硬件上保持十赫兹的前端位姿输出,导航时不会出现明显卡顿。但它有明确的边界:只支持 2D 环境、只处理静态地图、动态障碍物主要靠局部代价地图来处理,无法长期维护动态环境的稳定模型。如果你要做的是大面积仓储搬运车,或者动态人流量很大的场景,这个模块的定位就不太合适了。

6.1 扩展路线一:IMU 进前端,解决打滑和退化场景

把廉价 IMU 的加速度和角速度数据接进前端,是一个性价比很高的升级。实现思路是在姿态预测阶段加入陀螺仪偏航角,在匹配前给出更好的初值,同时用加速度计修正一点俯仰和横滚信息。这样处理之后,轮子打滑、长走廊退化这两个老大难都能得到明显缓解。

6.2 扩展路线二:动态障碍物剔除,让静态地图更干净

如果工作环境里经常有人走动,静态地图会被“脏点”污染。可以给地图模块增加一个短时背景模型,根据最近若干帧的激光点判断某个区域是不是“突然出现”的物体;如果是,就不写进静态栅格地图,只在局部代价地图里保留用于实时避障。这个方向实现成本不高,但对实用性的提升非常明显。

6.3 扩展路线三:多楼层地图切换

机器人在多层建筑里工作时,需要能识别当前所处楼层并切换到对应地图。可以在楼层交界处安装一个标记,或者用气压传感器辅助判断楼层变化;切换地图时要把当前位姿重新初始化到新地图对应的入口位置,避免定位错乱。这个扩展涉及更多的状态管理逻辑,但架构上可以用“地图 ID”做索引,逐步完善。

说到底,lingbot-map 最值得参考的不是某个算法,而是它做减法的思路:知道自己要什么场景、有多少算力、能接受哪些边界,然后只保留为此服务的部分。我最后的建议是,如果你也想从一开始就少走弯路,别急着写前端的扫描匹配,先把栅格概率更新、关键帧记录和回环二次校验这三个基础模块做好,后面所有调试都会舒服很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进