☰
两轮差速机器人SLAM导航实战:从建图、定位到路径规划
2026/10/21 5:46:48 网站建设 项目流程

LingBot这台小车能动了。电机调速、转向控制都调顺之后,我盯着屏幕想了一分钟:接下来最难的不是让它跑,而是让它知道自己在哪、要去哪。这个需求直接催生了lingbot-map——我给LingBot写的地图与导航模块。简单说,它负责两件事:把周围环境变成一张机器人能理解的底图;在底图上回答“我现在在哪”以及“怎么走到目标点”。

这篇文章把我从零开始做这个模块的过程、每个关键选择的理由、以及跑烂三张图之后才总结出来的经验,一起写下来。适合正在做两轮差速机器人导航、第一次接触地图模块的朋友;如果你已经在用成熟的机器人操作系统框架,但总觉得地图质量一般、导航偶尔“发疯”,这里面的排查思路也能直接拿来用。

1. 从“能跑”到“认路”:lingbot-map要解决的三个核心问题

1.1 机器人不会读说明书:建图是第一步

“房间长什么样”这件事,人扫一眼就懂了。机器人不行。它拿到的只是激光雷达转一圈测出的几百个距离点:左边两米有墙,右边半米有腿,前方三米有桌角。这些点本身没有意义,必须拼成一张有空间结构的图,机器人才能理解“走廊”“房间”“拐角”这些概念。

建图本质上是同步定位与建图(业内常叫SLAM)的过程。机器人一边移动,一边估计自己的位姿,一边把新扫描到的激光点放进同一张图里。听起来顺理成章,但有个鸡生蛋的问题:要知道新数据该往哪里放,得知道自己现在在哪;要知道自己现在在哪,又得依赖之前建好的地图。所以说不清是先有位置还是先有地图,只能让两者同时迭代修正。

地图在lingbot-map里存成栅格地图:把环境均匀切成一格一格的小方块,每个格子记录一个占据概率。0表示肯定没人没东西,100表示这里肯定有障碍,中间值表示不确定。我第一次看到这张图时,它像一张黑白噪点图,墙是黑的、空地是白的、边缘是灰的。机器人导航用的就是这张格子图,不是人眼看到的彩色照片。

栅格地图之所以成为机器人地图的主流选择,是因为它计算简单、更新方便。导航算法判断“能不能走”,本质上就是在查“这个格子的占据概率高不高”。后面所有定位、路径规划,全是围绕这张格子图展开的。

1.2 定位:一个比建图更隐蔽的问题

很多新人以为地图建好就完事了。恰恰相反,建图只是热身,真正难的是“开机之后它知道自己在地图哪里”。建图时机器人是从零开始慢慢拼图的,导航时它手里已经有一张完整地图,但不知道自己的初始位置。

更麻烦的是,机器人的里程计会漂移。两轮差速底盘靠轮子转圈数推算走了多远,轮子打滑、胎压变化、地面摩擦不均匀,都会让这个推算越来越不准。你不能指望里程计长期提供精确位置。

我用一个类比给团队新手解释这件事:闭着眼睛走路,光靠心算“我走了几步、拐了几个弯”,走个二三十米还能大概知道在哪,再走下去就会越来越糊涂。每隔几秒睁开眼看一眼地标,纠正一下判断,才能走准。激光雷达就是机器人的眼睛,它把当前扫描和地图做匹配,匹配得分最高的位置就是机器人最可能所在的位置。这就是粒子滤波定位的核心思路:在地图上撒一堆表示“猜测”的粒子,每个粒子带一个分数,谁的扫描和地图最吻合,谁的分越高,最后高分粒子聚集的地方就是机器人真实位置的估计。

这个机制专业术语叫“自适应蒙特卡洛定位”。名字唬人,拆开看就三步:撒粒子猜位、根据雷达打分、淘汰差粒子重采样。我用的实现就是这个思路,后面会细说参数怎么调。

1.3 地图在导航链路里的真实角色

在lingbot-map里,地图不是一个孤立的文件,它是被定位节点和规划节点共同使用的共享数据。定位节点把地图当参考,用来匹配激光点云;规划节点把地图当底图,用来计算可行路径。这决定了地图质量会直接传导到导航效果上。

一个容易忽视的点:地图是“静态层”,只代表建图时刻的环境。日常使用中,椅子挪了、门开了、地上多了个纸箱,这些变化不会自动出现在地图里。所以导航链路里还会有一层“实时代价层”,用激光雷达不断刷新周围障碍信息,和静态地图叠加,才能做出正确的避障决策。这部分我会在导航章节展开。

理解这三个核心问题之后,lingbot-map的模块划分就自然出来了:负责建图的模块、负责定位的模块、负责规划决策的模块。下面我按数据流的顺序,把每个环节的落地方案逐一写出来。

2. 地图模块的数据地基:坐标系、TF树和传感器时间对齐

2.1 坐标系约定:先定一个“世界原点”

任何地图模块,第一步是定坐标系。没有坐标系的点数毫无意义。在lingbot-map里,我用了三个层次分明的坐标系:

  • 全局坐标(我们叫map系):以建图起点为原点,负责长期稳定;
  • 里程计坐标(odom系):以机器人开机位置为原点,负责短期连续;
  • 机器人自身坐标(base系):以底盘几何中心为原点,随机器人移动。

为什么要有两套“世界坐标”?因为没有任何一种方式能长期精确追踪机器人位置。里程计短期平滑,但会漂移;全局坐标长期稳定,但需要外部观测修正。导航时,我们用里程计数据做短时间内的连续推算,再用定位结果不断修正“全局坐标到里程计坐标”之间的变换关系。这样机器人看着平滑移动,同时又能回到正确的位置,两不误。

初次搭建时最容易犯的错是:直接把全局坐标和里程计坐标当成同一个坐标系。当时我看到定位模块一直报错,排查了很久才意识到问题出在坐标框架设计上——定位得到的修正量无处安放。正确的做法就是明确区分:里程计坐标负责“机器人在最近一段时间内的相对运动”,全局坐标负责“机器人在整张地图中的绝对位置”。

2.2 TF变换树:底盘、雷达、IMU之间的相对关系

坐标系定好后,还要告诉系统“谁在谁的什么位置”。这就是坐标变换(TF)树的作用。我把它理解为一张大家都认可的换算公式表:雷达装在底盘中心往前15厘米、往上30厘米的位置,那么雷达测到的点要换算到底盘坐标系,就得平移一下;如果雷达还装歪了3度,那就还得绕Z轴转3度。

这些关系里面,一部分是固定不变的,叫静态变换;另一部分是随时变化的,比如底盘位置相对里程计坐标的变换,就由轮式里程计实时计算。机器人操作系统框架里有专门管理这些变换的组件,叫做坐标变换树。lingbot-map启动时第一件事,就是广播这些变换,地图、定位、规划所有节点都依赖这棵树。

我记得第一次建图时,地图整体朝右前方偏了大约10度。检查了很久,最后发现雷达装在底盘上时,螺栓孔位有一点点装配偏角,代码里没配,地图自然就歪了。测量出实际安装角度之后,我把这个偏角写进静态变换配置里,再跑一次,地图立刻正了。

这里有个经验:安装传感器之前,先在图纸上量好相对位置,但不要完全相信图纸,实测为准。我通常会拿直尺和角度尺,在现场测量雷达相对于底盘中心的纵向、横向距离以及偏转角度,再把这些值写进配置。误差控制在1厘米和1度以内,对2D激光雷达建图来说就够用了。

2.3 时间同步:一次迟到的数据会毁掉整张图

坐标系解决了“从哪里来”的偏差,时间戳解决“什么时候到”的错位。每个传感器数据必须带上精确时间。雷达在时间T扫描到一面墙,里程计在时间T算出了底盘位置,这两个数据要配成一对。如果时间不同步,雷达说“我在A姿势看到了墙”,里程计却说“你在B姿势”,结果就是把墙画到错误的位置上。

当年我调试时遇到过一种灵异现象:机器人静止不动,地图却在缓慢旋转。后来发现是雷达驱动和里程计驱动用了不同的时间源,时间差达到几十毫秒,旋转就被“画”出来了。解决办法很直接:所有传感器统一用同一个系统时钟,并且检查驱动输出的时间戳是不是真实接收时刻,而不是消息发布时刻。

实际工作中我会定期看两个数据:雷达每帧时间戳的间隔是否稳定,里程计数据的更新频率是否够快。如果雷达是10Hz更新,里程计只有5Hz,那么两次里程计更新之间雷达已经转了一圈,匹配精度就会下降。我一般会要求里程计更新频率至少等于雷达频率,宁可冗余也不能缺数据。

这些准备工作看起来不起眼,但它们决定了后面所有算法能拿到什么质量的输入。坐标系错一格、时间戳差几十毫秒,日志里不会红字报警,地图上全都能体现出来。这个判断我反复验证了很多次,所以专门把这章放在前面。

3. 建图实战:把两轮差速底盘和激光雷达调成一支“测绘队”

3.1 硬件安装的细节决定地图上限

建图算法再强,也救不了装在歪柱子上的雷达。硬件安装是硬约束,先保证几个底线。

第一,雷达必须固定牢靠。建图时机器人难免有点抖动,如果雷达跟着颤,扫描数据就会变得毛糙。我用的安装支架是金属加工件,拧紧后纹丝不动。

第二,雷达安装高度要合理。单线激光雷达只能扫描一个平面。装得太低,桌子腿椅子腿到处都是,地图显得很杂乱;装得太高,矮小障碍物探测不到,容易撞。我按场景试过几档,室内桌面环境装到离地30厘米左右,既能扫到桌腿,又能保留大部分桌面轮廓。

第三,雷达不能歪。雷达平面要和地面基本平行,倾斜超过两三度,建出来的墙就会斜,导航时机器人会一直觉得墙在靠近。带安装孔的固定支架比胶粘靠谱得多。

第四,底盘的运动控制要顺。建图算法假设机器人在匀速运动,如果底盘速度忽快忽慢,激光扫描就会拉伸变形。我第一次用没调好速度环的小车建图,走廊墙边全是毛刺。先把底层速度控制调稳,再开建图。

3.2 参数配置:分辨率、帧率、速度权衡

硬件就绪后,进入参数配置环节。lingbot-map建图有四个参数至关重要,我给了它们一个联动关系:

map_resolution: 0.05 # 每个格子代表5厘米 laser_frequency: 10.0 # 雷达扫描频率,单位Hz max_linear_speed: 0.2 # 建图限速,单位m/s max_angular_speed: 0.5 # 建图限转角速度,单位rad/s

地图分辨率不是越大越好。0.05米意味着1平方米有400个格子,一张20米乘20米的环境就有16万个格子。如果设成0.01米,格子数量暴增25倍,计算量撑不住,而且单线雷达点距根本没那么密,细分出来的格子大多是噪声。对两轮差速底盘配单线激光雷达的场景,0.05米是我试下来性价比最高的值。

雷达频率和运动速度是一对必须搭配的参数。雷达转一圈需要0.1秒(10Hz),如果机器人以0.5米每秒的速度前进,那么雷达扫到上半圈和下半圈时,机器人已经平移了5厘米。0.05米正好等于一个栅格,勉强还能容忍;如果开到1米每秒,一圈内位移10厘米,会出现2个栅格的错位,地图边缘直接就糊了。所以建图阶段我严格限速0.2米每秒左右,导航阶段再放开速度。

建图算法的其他参数,比如匹配窗口大小、回环搜索范围,我保持了默认值,并没有花太多精力去改。先跑通再优化,别一上来就陷入参数海洋。

3.3 走图手法:环线、掉头、回环

参数配好,接下来就是“人肉遥控机器人”扫图。这一步看着不像技术活,实际上门道很深。很多人建出来的图歪歪扭扭,算法没毛病,纯粹是“走得不对”。

我的走图手法总结成三句话:先沿墙走一圈定轮廓,再蛇形走内部补细节,最后回到起点闭合回环。

先绕环境最外围走一圈,让机器人扫描到完整的边界。这一圈跑得特别慢,遇到拐角停下来原地转个弯再走。拐角是雷达匹配最容易失准的地方,因为视野变化剧烈,快速转弯容易把地图拐歪。

内部区域用蛇形路线覆盖,保证每片区域都被雷达从不同角度看过。雷达从不同角度观察同一面墙,后续回环检测才能找到匹配特征。这也是建图和扫地的区别——扫地只想覆盖面积,建图还要求光线重叠。

最后一步是回环,也就是机器人走回已知区域,让当前扫描和之前的地图对上。回环一旦闭合,累计误差就会被拉回原点,整个地图瞬间“收紧”,这是所有建图算法最激动人心的一步。如果回环失败,你就会得到一张两端对不上的图,像一条没封口的蛇。

建图跑完,我会在可视化界面里看地图颜色。墙线要连续锐利,地面干净无杂点,边缘没有毛刺。合格的地图在导航阶段能省一大半调参时间。如果看到重影或模糊,别急着保存,回到5.1部分的排查思路去查。

4. 导航上路的完整链路:从定位概率到路径可行性判定

4.1 开机后第一步:重定位

地图有了,机器人正式进入导航模式。这时的第一步不是规划路径,而是让机器人先搞清楚自己在哪。因为刚开机,里程计坐标从零开始,全局坐标完全未知,机器人手里只有一张地图,也不知道红点上那个自己到底在地图哪里。

于是粒子滤波定位派上用场。我一般会在地图上撒2000个粒子,每个粒子代表一个可能的位姿猜测。雷达扫描一圈后,把每个粒子位置上的虚拟扫描和实际地图作对比,相似度高的粒子得分高,下一轮就多复制一些,得分低的粒子被淘汰。反复十来轮后,粒子会从地图上的四面八方聚拢到真实位置附近。

这个过程需要一个初始猜测,否则粒子撒满整张地图也能收敛,但速度太慢而且还可能“多峰收敛”——机器人可能在两个长得特别像的走廊位置来回犹豫。所以我通常会在可视化界面上手动点一个大概的起始位置,告诉机器人“你大概在这附近”,剩下的就交给粒子滤波器精确定位。

粒子数量不是越多越好。2000个粒子在20平米房间定位,效果很好;但到了几百平米的空旷大厅,2000个粒子就会显得稀疏。我习惯根据环境面积调整:小房间1000到1500个,大厅可以加到3000个。粒子越多,收敛越稳,但CPU占用也越高,小底盘主机算不过来时宁可少一点,也别把整机卡死。

4.2 代价地图:把“能不能走”变成一张算账表

机器人确定了位置,下一步就是规划路径。但栅格地图只回答了“这里有障碍物吗”,没回答“机器人能贴着障碍物走吗”。代价地图负责把栅格地图换算成一张“每个位置的通行代价表”。

代价地图通常分多层。静态层来自建图时保存的底图;障碍层由激光雷达实时更新,发现新出现的物体就在障碍层里画出来;膨胀层把每个障碍物向外扩一圈距离,越靠近障碍物,通行代价越高。

这一步的膨胀半径需要根据底盘物理尺寸定。LingBot的底盘半径是0.2米,我不可能让机器人中心靠障碍物只有0.2米,那样误差一波动就蹭上了。我留了0.05米的安全余量,膨胀半径设成0.25米。这个0.25米以内,规划器会认定不可通行,0.25米到某个阈值之间,通行代价随距离递减。

有朋友问过:半径不需要等于机器人半径吗?我理解是,机器人不是数学上的圆,不同方向宽度不同;激光点也在跳跃,一个点扫到的障碍边界和实际物体边缘本来就有误差。多加5厘米余量,看起来“浪费”了点通道宽度,但换来的是长期稳定的低碰撞概率。这是我在办公室试了整整一个下午得出的结论——代价地图的参数必须和底盘尺寸、传感器噪声一起考虑,不能单独设。

4.3 全局规划与本地规划的分工

代价地图做完,路径规划分两层执行。全局规划在整张代价地图上搜索一条从当前位置到目标点的可行路径。搜索算法很经典,在格子图上从起点向外扩张,逐步计算到每个格子的最小代价,直到找到终点。

全局路径算出来后,机器人不能真的一条道跑到黑。地图是旧时的快照,房间里多了把椅子、走来一个人,底图根本不知道。所以本地规划器每零点几秒重新计算一小段局部路径,在全局路径的大方向指导下,用激光雷达最新数据实时微调,绕开动态障碍物。

我习惯把这两层的关系类比成远程导航和本地驾驶:全局规划负责“从上海开到杭州”,本地规划负责“这个路口怎么变道”。如果只有全局规划,遇到临时路障就得整车停下来重新搜索;如果只有本地规划,遇到大方向分叉就会迷路。没有哪一层可以替代另一层。

本地规划最核心的参数是最大线速度、最大角速度和加速度限制。LingBot在室内导航时,我设置最大线速度0.3米每秒,最大角速度0.8弧度每秒,加速度不大于0.5。数值越大,动作越激进,越容易因为控制延迟和定位误差发生碰撞;数值太小,又会在窄通道里磨磨蹭蹭出不来。这个参数组合是跑了很多圈才定下来的,室内场景保守一点远比“看起来更敏捷”重要。

4.4 一条容易忽略的联动关系:定位精度到规划失败的传导链

导航调参时我碰到过一个非常隐蔽的问题:地图没问题、路径规划参数没问题,但机器人就是频繁“撞墙”。后来发现根源在定位精度——粒子分布在走廊里发散,定位输出的位置来回跳,代价地图里的障碍物跟着来回晃,规划器看着“墙在移动”,自然频繁重新规划,行为看起来像发疯。

这暴露了整条链路的传导关系:定位是地基,代价地图是决策依据,规划是执行。地基抖一下,上层所有环节都会跟着乱。排查这类问题时,我们团队有一个固定的检查顺序:先看定位输出稳不稳,再看代价地图更新的障碍是否稳定,最后才看路径规划算法参数。别一上来就调规划参数,那是在给错误的地基打补丁,治标不治本。

5. 跑烂三张图之后总结的调参经验与避坑清单

5.1 地图重影和毛刺:先别急着换算法

建图阶段最常见的成品问题是重影。地图上同一面墙出现了两条平行线,或者边缘像长了一层毛。遇到这种图,我的第一反应是换更好的算法吗?恰恰相反,先把下面三项检查一遍:

第一步,检查雷达固定是否松动。用手轻压雷达安装支架,如果有哪怕一毫米的旷量,重影就来了。这个原因占了重影案例的一半。

第二步,检查静态变换参数。重新测量雷达相对底盘中心的平移量和偏角,重点看偏角。装配误差导致雷达相对底盘歪一两度,转弯时地图误差会被放大得很明显。

第三步,检查建图速度。如果当时机器人用0.5米每秒而不是0.2米每秒在跑,扫描畸变本身就会造成边缘毛糙。回到0.2米每秒重跑一遍,八成毛病消失。

这三项都排除,才值得去考虑回环检测是否失败、匹配窗口是否太小。实际上我在lingbot-map项目里遇到的重影问题,百分之八十都是硬件安装和运动速度造成的,算法真是躺枪背锅。

5.2 导航走S形:轮距与控制器的问题占九成

导航阶段另一个高频问题是:明明直线目标点就在正前方,机器人却走出一条S形曲线。很多朋友第一反应是规划参数不对,我也这么想过,但九成情况真相在底层。

底盘运动模型里,两轮差速的转弯半径和轮距直接挂钩。如果代码配置的轮距是0.3米,实际底盘两个轮子间距是0.35米,那么转弯时实际转过的角度比模型算出来的小,里程计角度会持续偏离,定位就跟着偏。定位一偏,机器人觉得自己歪了,努力打方向修正,结果又修过头,S形就出现了。

排查方法非常朴素:让机器人按遥控指令直线走两米,看它是否真的走直;再让它原地转360度,看误差有多大。LingBot第一次做这个测试时,转完360度之后角度误差有近20度。修正轮距参数后,误差降到3度以内。这一步我建议所有人在调导航参数之前先做,不然等于瞎子调参。

轮距修正完,还需要检查底层速度控制是否过冲。给轮子一个目标速度,实际速度要快速跟上但不能猛冲。速度环PID参数没调好,机器人会一顿一顿,导航曲线自然不可能顺滑。我的习惯是先让小车在空旷地方跑一条“回”字路线,曲线平滑才算过了底盘关。

5.3 定位漂移导致“发疯”:特征稀疏环境的应对

还有一种场景很考验心态:机器人跑着跑着突然定位漂移,然后像无头苍蝇一样乱转,最终报“无法规划路径”。这种情况在特征稀疏环境里特别常见——长长的走廊、大片的空白墙面,激光扫描每个角度看到的都差不多,粒子滤波分不清自己在走廊哪一段。

应对手段按优先级排列:

第一,建图时多制造可辨识的对照物。走廊里如果只有白墙,建图时可以在明显位置放几个纸箱或锥桶,让雷达有“锚点”可参考。地图上多一点特征,定位就能一直咬住。

第二,降低导航速度。速度越快,连续帧之间雷达视野变化越大,匹配越容易跟丢。在走廊场景我一般把最大线速度从0.3降到0.2。

第三,提高粒子数量。特征少的时候,需要更多粒子来覆盖不确定性。把粒子数从2000提到3000到4000,能明显减少漂移发生概率,但代价是CPU占用上升,需要确认主机扛得住。

如果环境就是完全的“白墙地狱”,物理上的对策已经很有限,我会考虑后续引入更丰富的传感器做多源融合,这条路我在lingbot-map的下一阶段正在趟,到时候单独写一篇。

5.4 一组实测可用的初始参数模板

最后分享一组我在lingbot-map场景下实测能跑的初始参数。不同底盘、不同环境肯定要改,但作为起点它比所有默认配置都靠谱:

参数建议初值说明
地图分辨率0.05米/格单线雷达配差速底盘的甜点值
建图限速0.2米/秒保证雷达一圈内位移不超过一个栅格
建图限转角速度0.5弧度/秒拐角不失匹配的核心限制
膨胀半径底盘半径+0.05米留出传感器噪声和底盘误差的余量
最大导航速度0.3米/秒室内场景保守优先
粒子数量2000大面积环境加量,小房间减量
轮距校准方式原地转360度实测累计角度误差应小于3度,否则重测

这套参数不一定是最优解,但能让你在第一次跑通时少一些“它们全都有关联,不知道该动哪个”的迷茫。先把这一组跑进正常状态,再逐项微调才有意义。

我个人的体会是,lingbot-map这个模块最难的部分一直不是某条具体算法,而是数据链路上那些“看起来没事、实际上已经错了”的环节。坐标系对应错一格、时间戳差个几十毫秒、轮距差了5厘米,这些问题在日志里都不会红字报警,但在地图上、在机器人走出的每一步里全都会体现出来。所以如果你也在做类似的项目,我会劝你把硬件安装、坐标系变换、时间同步、里程计校准这四件事打牢,再考虑算法参数怎么调。等基础链路稳了,往里面加语义标注、动态物体过滤这些高级功能,才有得放矢。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询