听到“ScanContext”这个词,你可能觉得非常学术,但其实它做的事情特别像我们认路的本能。下面我就用生活化的比喻,带你一步步理解它。
1. 它到底要解决什么问题?
想象你被蒙上眼睛,在一个陌生的屋子里转悠。你小心翼翼地伸出手,摸到一张矮矮的茶几,又摸到一圈软软的沙发,角落里还有个高高的大衣柜。突然,你心里一亮:“这个触感组合我刚刚摸到过!我又转回来了!”——这就是回环检测(地点识别)。
机器人也是这么干的,只不过它用的不是手,而是激光雷达。雷达会向四面八方发射激光,测量周围物体的距离和高度,生成一片密密麻麻的点(点云)。 ScanContext 的任务,就是把这堆复杂的点云,变成一张简单、好比较的“记忆卡片”。每次机器人走到一个新地方,就拍一张这样的卡片存起来。以后它再看到相似的“卡片”,就知道自己回到了老地方,这就完成了回环检测,从而修正累积的定位误差。
2. 怎么制作这张“记忆卡片”?——从点云到矩阵
我们把机器人站的位置想象成一个靶心,开始画靶子。
-
第一步:切蛋糕(扇形划分) 像切蛋糕一样,把机器人周围 360° 的空间,平均切成若干个扇形。比如我们切成 8 块,每块覆盖 45° 的方向,用来区分“东西南北”。
-
第二步:画同心圆(圆环划分) 再像画靶子一样,用不同半径的同心圆,把空间从里到外分成若干个圆环。比如分成 3 环:
-
近处:0 ~ 1.5 米
-
中等:1.5 ~ 3 米
-
远处:3 ~ 4.5 米
这样一来,“蛋糕块”和“圆环”交叉,就把周围空间切成了一个个小格子(比如 3 环 × 8 扇区 = 24 个格子)。
-
-
第三步:给格子填数(填入高度值) 激光雷达扫出的点云会落进这些小格子里。对于每个格子,我们只记录里面最高的那个点的高度。 比如,近处某个格子里有茶几的边角,最高点是 0.5 米;中等距离某格子里有沙发靠背,最高点是 1 米;远处某格子有柜子,最高点是 2 米。
把这些高度值按顺序填入一个矩阵(表格)里:
-
行 代表第几个圆环(距离远近)
-
列 代表第几个扇区(方向角度)
这个填满高度的矩阵,就是 ScanContext。你可以把它当成这个地点的“高度指纹”——它用最简练的方式,记录了“某个方向上、某个距离处,有多高的东西”。
-
3. 两张“指纹”怎么比对?——相似度和旋转不变性
如果机器人两次都面对同一个方向,那么只要把两个矩阵对应格子的高度一一比对,算出整体差异就行了(差异越小越相似)。
但问题来了:机器人朝向很可能不同。 这次它面朝北,下次它面朝西。那么整个环境的“高度指纹”在矩阵上,就相当于列发生了循环平移——原本北边的扇区,下次可能跑到西边的扇区那一列去了。
怎么办?很简单:把其中一张指纹“转”着比。
我们把矩阵 B 的列循环左移或右移,每移动一列,就跟矩阵 A 比较一次相似度。移动一圈下来,找到相似度最高的那个位置。这个最高分就代表了:在某个旋转角度下,两张图对得特别齐。如果这个分数超过一个阈值,就认为它们来自同一个地点。 这个过程,就天然实现了旋转不变性——不管你怎么转,我总能找到对齐的方式。
4. 太慢了怎么办?——两阶段匹配:粗筛 + 精排
如果机器人存了成千上万张“指纹”,每来一张新图,都要跟历史上每一张做一遍全旋转对齐比较,那计算量会爆炸,机器人当场“卡死”。
于是我们用了一个非常巧妙的两阶段加速法:
第一阶段:Ring Key 粗筛(快速海选)
我们给每张 ScanContext 矩阵,生成一条极简的“摘要”: 把每一行(同一个距离环)里所有扇区的高度取个平均值。 比如,3 环就得到 3 个数:(近处平均高度,中等距离平均高度,远处平均高度)。这个向量就叫 Ring Key,非常短,比较起来极快。
机器人走到新地方,先算 Ring Key,然后去数据库里快速过滤,只找出 Ring Key 最相似的前几名(比如前 10 名)作为“候选人”。大部分明显不一样的地方,在这一步就被淘汰了。
第二阶段:列平移精排(终选验证)
对留下来的这少数几个候选人,机器人再拿出它们的完整 ScanContext 矩阵,跟新图的矩阵做细致的列平移匹配,算出真正的最高相似度。 如果这个最终得分足够高,就判定为“回环”;否则就认为只是 Ring Key 凑巧有点像,实则不是同一个地方。
目的很简单:粗筛保证速度,精排保证准确。
5. 客厅与卧室的假想例子
现在我们来模拟一次完整的经历。假设机器人用的是 3 环(近/中/远) × 8 扇区(每 45° 一个方向)的 ScanContext。我们记扇区 0 为正北,顺时针编号。
场景设定
-
客厅中央:半径 0.5 米的矮茶几(高 0.5m),一圈离茶几 2 米的沙发(高 1m),东北角(扇区 1~2)有个高柜(高 2m)。其他地方为空。
-
卧室:一张离中心 2 米的大床(高 0.8m),床头柜在近处(高 0.6m),远处只有墙,没有高柜。
首次到客厅 —— 生成矩阵 A
机器人面朝北(0° 方向为北)。它观察到的“最高高度”如下表(空白表示 0m):
| 近 (0-1.5m) | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 |
| 中 (1.5-3m) | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
| 远 (3-4.5m) | 0 | 2.0 | 2.0 | 0 | 0 | 0 | 0 | 0 |
矩阵 A 就是上面的数字阵列。我们顺便算出它的 Ring Key(每行均值):
-
近环平均:0.5
-
中环平均:1.0
-
远环平均:(0+2+2+0+0+0+0+0)/8 = 0.5 → Ring Key_A = (0.5, 1.0, 0.5)
走到卧室 —— 生成矩阵 C(对比用)
机器人溜达进卧室,扫描得到完全不同的高度分布(简化表示):
-
近环有些方向有床头柜(0.6m)
-
中环有床(0.8m)
-
远环全是 0
它的 Ring Key 算出来大概是 (0.3, 0.7, 0)。 机器人拿这个跟记忆里的 A 比较,Ring Key 差别巨大,粗筛就直接淘汰,判定“没来过这儿”。
回到客厅但转了方向 —— 生成矩阵 B
机器人从卧室又逛回客厅中央,但这次它面朝正西。也就是原来的正北(扇区0)现在跑到了它的正东(相当于矩阵 A 的列向右循环平移了 2 格,因为朝向转了 90°?注意:面朝西意味着北在右侧,若以它自身坐标系,原先北方向的环境特征会出现在扇区 2。我们直接假设列平移 2 列)。矩阵 B 看起来像这样:
| 近 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 |
| 中 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
| 远 | 0 | 0 | 0 | 2.0 | 2.0 | 0 | 0 | 0 |
你可以看到,高度值的分布规律跟 A 一模一样,只是列的位置循环移动了(原来在东北角的高柜,现在跑到了东北偏东的扇区)。 它的 Ring Key:近环 0.5,中环 1.0,远环 (0+0+0+2+2+0+0+0)/8 = 0.5。Ring Key_B = (0.5, 1.0, 0.5),与 A 完全相同。
演示匹配过程
粗筛:机器人刚回到客厅,生成矩阵 B 和 Ring Key_B。去数据库里查,发现 Ring Key_A 与 Ring Key_B 几乎一模一样,立马把 A 列为强候选人(可能还有其他一两个 Ring Key 相似的也被拉进候选池)。
精排:拿出矩阵 A 和矩阵 B,开始做列平移匹配。固定 A,把 B 的列挨个平移:
-
平移 0 列(不对齐):远环高柜对不上,得分低。
-
平移 1 列:还是错位。
-
平移 2 列时,B 的扇区 2(北)移到了 A 的扇区 0(北),高柜在扇区 3/4 处也对齐,瞬间 匹配得分飙到 0.96。
判定:0.96 远超预设的阈值(比如 0.85),系统认定:“这就是客厅!” —— 一次漂亮的回环检测完成。
总结
ScanContext 就是把三维空间切成环和扇区,用每个格子的最高高度拼成一张“高度指纹”。 比较指纹时,通过循环平移列来克服机器人朝向的变化。 为了实时运行,先用极简的 Ring Key 粗筛,再对少数候选做列平移精排。
这样一来,机器人就能像你凭触感认出熟悉的房间一样,用激光高度信息认出自己“曾经来过的地方”。



