教程 · 中级 · 35 分钟
机器人激光雷达:一次扫描给你什么,又如何变成地图
旋转式激光雷达是如何拼出一帧扫描的,为什么转速越快角分辨率就越低,以及射线投射如何把一串距离读数变成占据栅格地图。
引言
一台测距传感器只告诉你沿一条线某个东西有多远。地图则告诉你一个房间的形状。导航里所有有趣的东西都住在这两者之间的空隙里,而这一页讲的就是那个空隙。
这两个端点在本站已经讲过:一侧是单点测距,另一侧是 SLAM和占据栅格上的 A*。但那些都假设栅格已经存在。没有任何地方说它是从哪儿来的,而答案——把一次扫描射线投射进格子——既简单又处处是坑。
二维激光雷达就是一台装在转台上的飞行时间传感器
这是最有用的一点认知,因为它意味着测距那一半你已经懂了。
业余级二维激光雷达里面是一颗光学飞行时间测距模块,和 VL53 系列转接板上的完全是同一类,横向指着然后被旋转起来。一个角度编码器报告每次测量时它朝哪儿看。电源和数据通过滑环或者光电/电感耦合跨过那道旋转边界。
所以一次“扫描”不是一张照片。它是一个 (角度, 距离) 对的列表,一次采一个,采满一整圈。下面每一条限制都从那句话里来。
这也告诉你为什么超声波传感器不被旋成激光雷达。超声波束有几十度宽,旋它几乎得不到任何好处——你只是在把一道本来就宽的波束抹着涂满整个房间。而正是窄光束让角分辨率一开始就有意义。用舵机扫一颗飞行时间传感器,就像扫描超声教程里做的那样,真的就是一台慢速低分辨率的激光雷达。
采样预算是固定的,所以分辨率和新鲜度要取舍
这是最让人意外的部分,而且值得在买任何东西之前就弄明白。
测距模块只有一个吞吐量数字:它每秒能采多少次测量。在 RPLIDAR A1 上大约是每秒 8000 个采样,而且你改转速它也不会变。转速是你自己选的,通常 1–10 Hz。
于是你每圈得到的点数,就只是预算除以转速:
points per revolution = samples per second ÷ spin rate
angular step = 360° ÷ points per revolution
| 转速 | 每转点数 | 角步 | 扫描周期 |
|---|---|---|---|
| 5.5 Hz | 1455 | 0.25° | 182 ms |
| 10 Hz | 800 | 0.45° | 100 ms |
**你不可能同时拥有新鲜的扫描和精细的角分辨率。**转到 10 Hz 把你的延迟减半,角分辨率也几乎减半。这一个取舍对机器人行为的决定作用,超过宣传资料上那个量程数字;而且它是第一件该被有意识地设定、而不是丢给驱动默认值的东西。
在开阔走廊里快速移动的机器人:转快一点,你需要新鲜度,而墙又很大。在杂乱房间里慢慢建图的机器人:转慢一点,你需要细节,而且付得起那份延迟。
波束会发散,所以远处的障碍会掉在波束之间
角步是一个角度,不是一个距离。两条相邻的波束在传播过程中会分开:
gap between beams ≈ range × angular step (in radians)
在 0.45° 下,这意味着一米处有 7.9 mm 的间隙,四米处有 31 mm。中间的空间没有任何东西采样。
拿一条 20 mm 的椅腿来说。以 10 Hz 旋转时,波束间隙在约 2.5 m 处超过 20 mm——再往外,那条腿完全可以整个坐在两条波束之间,什么都不返回。5.5 Hz 时你要到约 4.6 m 才会遇到同样的事。
这就解释了一种否则看上去像传感器坏了的行为:细的障碍随着机器人靠近才出现,在一两米外突然跳进地图。激光雷达并没有失灵。那条腿从来没有被采样过。
有两个值得围绕它做设计的后果:
- **不要相信单次扫描去找细障碍。**像下面那样把多次扫描累积进栅格,才是真的能把桌腿找出来——每次扫描采的角都略有不同。
- 椅子腿和桌腿是家用机器人的经典失败项,因为它们细、正好在激光雷达的高度上,而机器人很晚才知道它们。
扫描不是快照
一圈里的点不是同一瞬间测出来的。在 5.5 Hz 下扫一圈要 182 ms,而机器人不会礼貌地等着。
- 以 0.5 m/s 行驶时,机器人在一圈里走了 91 mm。
- 以 1 rad/s 转向时,这次扫描被抹开了 10.4°。
把那个点的列表当成在某一个位姿上拍的一张刚性快照,会把世界掰弯。直墙会变成弯曲或重影,而扫描匹配会与自己动作造成的修正打架。
这就是 sensor_msgs/LaserScan 为什么在测距旁边还带 scan_time 和 time_increment:它们让使用者能算出每个波束是何时采的。修正这一点——按该波束时间戳上机器人所处的位姿去变换每一个点——叫做去畸变,也叫运动补偿,而好的 SLAM 前端会替你做。
这在实际中意味着:如果你的地图静止时干净、一动就糊,那么在怀疑传感器之前,先长时间怀疑去畸变和时间戳。**建图时慢下来。**速度减半,畸变就减半,白拿的。
你只能拿到一个水平切片
二维激光雷达量的是一个平面。这个平面之上或之下的东西都不存在。
经典的三类:
- **悬空物。**一张 700 mm 高的桌面,对一台 150 mm 高的激光雷达是隐形的。机器人自信地开到底下,然后卡住。
- **低矮障碍。**门槛条、电缆、一只袜子。全都在平面之下。
- **坠落。**一段向下的楼梯在激光雷达能看的每个方向上都是空的。它读起来像漂亮干净的地面。
再多的扫描处理也修不了这个,因为信息从来没有被采集到。真实的机器人会在激光雷达之外保留保险杠、悬崖传感器,通常还有一台朝下或前倾的测距仪——不是因为激光雷达不好,而是因为平面就是平面。当你读到某台吸尘器“有激光雷达却还有保险杠”时,原因就在这里。
从扫描到栅格:射线投射
现在到建图这一半。占据栅格把世界划分成方格,并为每一格保存“我有多确信它被占据”。
让它能工作的那个洞见是:**一次测距读数告诉你的是整条射线上的每一格,而不只是末端那一格。**如果一道波束走了 3 米才碰到东西,那么它穿过的所有空间必然都是空的——否则它早就停了。
于是每一道波束产出三个判定:
- **射线穿过的格子 → 空闲的证据。**沿着从传感器到命中点的这条线走一遍,把每一格往空的方向轻推一点。
- **读数落下的那一格 → 占据的证据。**把它往满的方向推。
- **命中之后的格子 → 什么都没有。**波束停了。你对那面墙后面是什么一无所知,所以必须不去动那些格子。
走那条线就是线段光栅化——Bresenham 算法,和在屏幕上画一条直线用的是同一个。
达到最大量程的读数需要单独处理。很多驱动在什么都没返回时会报最大值或者无穷大。那不是“12 m 处有一面墙”,而是“什么都没找到”。把射线标为空闲,而末端不标任何占据格。
对数几率:为什么用加法而不是乘法
你当然可以给每格存一个概率,把贝叶斯更新乘起来。实践中所有人都改存对数几率:
l = ln( p / (1 - p) ) p = 1 / (1 + e^(-l) )
有两个理由。把许多小概率乘起来会下溢,而把对数加起来不会。而且它让更新变成一次加法,便宜到可以对每一道波束的每一格、每一次扫描都做一遍。
取 p = 0.5(毫无头绪)对应 l = 0,再为命中和未命中各选一个值——比如 p_occ = 0.7(l = +0.847)和 p_free = 0.4(l = −0.405)——更新就是:
l_cell += l_occ # this beam hit this cell
l_cell -= 0.405 # this beam passed through this cell
一格从 l = 0 出发,经过三次命中:
| 观测 | 对数几率 | 概率 |
|---|---|---|
| 起始(未知) | 0.000 | 0.500 |
| 命中 | +0.847 | 0.700 |
| 命中 | +1.695 | 0.845 |
| 命中 | +2.542 | 0.927 |
| 穿过 | +2.136 | 0.894 |
| 穿过 | +1.731 | 0.850 |
证据会累积,分歧会把它拉回来。整个算法就是这些。
**把总量钳住。**这件事比看上去重要。连续二十次命中不做钳位,会把一格推到 l = 16.9,也就是 p = 0.999999。现在有人把椅子挪走了。要撤销那份笃定,需要 42 次空闲观测。椅子就留下一个永久的幻影,而你的规划器会一直绕着那个不存在的东西转向。
把 l 钳到大约 ±3.5 就能解决:强信念依然是强的,但只要几次矛盾的扫描就能推翻它。如果你的地图上累积起已经消失的物体的鬼影,几乎总是这个原因。
未知是第三种状态,不是空闲的同义词
这是产出自信而危险的规划的根源。
刚初始化好的栅格完全是未知的。如果你的规划器把未知当作可通行,它会高高兴兴地直接穿过你从未看过的那些世界部分——而正因为它们没被探索过,那里恰恰就是意外住的地方。如果它把未知当作占据,机器人起步就会拒绝移动,因为一切都是未知。
两种都是错的;把三种状态分清楚,然后有意识地决定:
- 占据——不许规划穿过。
- 空闲——规划穿过。
- 未知——只有付出代价时才规划穿过,或者只在有意探索时穿过。
前沿探索,也就是机器人决定下一步往哪看,完全建立在空闲/未知的边界上。把两者混掉,你就把它扔了。
选格子尺寸
常规答案是室内机器人 5 cm,而“能更细就细”并不是好理由,因为它有代价。
比典型距离上的波束间距还小的格子,是在凭空发明你并不拥有的精度——在 4 m 处波束相距 31 mm,所以 1 cm 的格子会在射线之间留下永远填不上的永久空洞。而比机器人间隙余量大得多的格子会让门口看起来过不去,因为一个占据格就能把整条缝吞掉。
实用规则:格子尺寸要比你必须钻过去的最窄缝隙略小一点,而且在你实际作业的距离上不要小于波束间距。然后在规划时按机器人半径膨胀障碍,而不是靠缩小格子来补偿。
出问题时
| 现象 | 通常原因 |
|---|---|
| 细障碍只在靠近时才出现 | 那个距离上波束间距超过了它的宽度——预期行为,不是故障 |
| 行驶时墙弯曲或重影 | 扫描没做去畸变;点被当成同一瞬间 |
| 静止时地图干净,一动就糊 | 同样原因;也试试更慢地建图 |
| 机器人钻到桌子底下卡住 | 二维平面看不见悬空物;需要第二台传感器 |
| 楼梯被读成空旷地面 | 坠落对水平平面不可见;需要悬崖传感器 |
| 已经搬走的障碍还留在地图上 | 对数几率没做钳位,于是笃定撤销不掉 |
| 整张地图边缘填满障碍 | 最大量程的“无回波”读数被标成了命中 |
| 规划器穿过未探索空间 | 未知和空闲混为一谈 |
| 细栅格里全是麻点状空洞 | 格子小于波束间距;射线之间什么都采不到 |
| 玻璃墙整个漏掉 | 光直接穿过去——这是超声波唯一赢的地方 |
最后一行是整页最诚实的补充。激光雷达在分辨率、速率和量程上都赢过声呐,然后在一扇玻璃门上输得一塌糊涂,因为声音会被反射而光不会。这正是不要只选一种传感器就宣称问题已解决的最清楚论据。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读