深夜的自动化仓库里,新保安小明夹着一本空白的值班簿,握着手电筒开始第一次巡逻。仓库漆黑一片,他没法一眼看清整个空间,只能用光束一点一点地扫。每走一步,他就在值班簿的方格纸上做记号:手电筒光畅通无阻扫过的地砖,画一道浅线表示“这条路可走”;光束在某块地砖上被挡住、反射出亮点的地方,画一个红叉表示“这里有东西堵着”;光束根本没照到的远方,就保持空白不做判断。走完一圈,空白的方格纸变成了一张“哪里能走、哪里是墙”的草图。第二天、第三天,他每晚都带着新的观察来更新这张草图,图上的红叉和浅线越来越可靠。 机器人世界里,这张“值班簿”就是占据栅格地图。它把连续的地板切成一个个小格子,每格用一个概率记录“这里是否被障碍物占据”。手电筒光束的解读规则对应逆传感器模型,每晚叠加新观察的操作对应贝叶斯更新。本文就以这本值班簿为线索,讲清楚它如何从一次观测、一次更新开始,最终成为机器人自主导航的基石。
一句话价值:占据栅格地图是机器人导航、自动驾驶规划与 SLAM 系统的地基数据结构。理解它的概率更新机制,你才能动手搭建真实建图与规划系统,是机器人感知岗位的必备技能。
可视化演示
展示机器人激光扫描更新栅格地图、贝叶斯概率更新先验→观测→后验及逆观测模型的三区域(自由/占据/未知)概率分布。
本节配有交互动效(进入视口后自动加载,约 3KB)。
栅格地图扫描更新 机器人R发射激光束扫描环境 束路径标记自由,终点标记占据 cell: free(OK) / occ(ACC) / unk(GR) 多束扫描逐步构建完整地图
建图动机
班长让小明细数仓库里每一粒灰尘的位置——这显然不可能,连续空间里点的数量是无限的。但班长实际只关心一个问题:这块地砖上能不能走人?把地板切成地砖,问题就从“描述无数个点”简化为“判断有限块砖”,这就是离散化的智慧。
移动机器人导航同样需要环境地图才能回答“哪里能走、哪里要绕”。连续世界的精确几何描述既昂贵又冗余,占据栅格地图干脆把空间切成网格,用一格一变量的方式牺牲细节来换计算可行。这是建图的第一动机:把不可能精确描述的世界,变成一组可以计算的单元。
技术上说,地图 m 被定义为 N 个独立格子变量的集合,每个格子 m_i 取二元值:0 表示空闲、1 表示占据。这里藏着两张关键简化:一是空间离散化,用格子代替连续坐标;二是占据假设,格子状态只用“有/无”描述,不关心障碍物到底是墙还是桌子。
1. 地图的集合表达:m 表示整张地图,它由 N 个格子的状态共同组合而成。
2. 格子的二元取值:每个 m_i 只能在 0(空闲)和 1(占据)之间二选一,让复杂的几何问题退化为“是与否”的判断。
小明值班簿上的每一格,正是这样一个 m_i——先画红叉还是浅线,取决于他到底有没有把握。
栅格定义
小明刚开始很纠结:一个格子只写“有/没有”太武断,手电筒一晃可能看错。于是他改记“信心值”:正数表示越看越像有东西,负数表示越看越像空的,0 表示完全没把握。这个“信心值”就是占据栅格地图里的对数几率。
直接存储占据概率 p(m_i) 会带来数值问题:概率只能取 0 到 1,极端值附近加减都容易溢出边界。把概率映射到对数几率后,值域扩展为整个实数轴,更新也从乘除变为加减——这是栅格地图最关键的工程选择。
对数几率定义如下:p 越接近 1,l 越大;越接近 0,l 越小;当 p=0.5 时 l=0,恰好对应“完全未知”的先验状态。
1. 分子 p(m_i):当前认为该格子被占据的概率,取值为 0 到 1 之间的小数。
2. 分母 1 - p(m_i):该格子空闲的概率,两者之比称为“发生比”,刻画占据对空闲的倍数关系。
3. 取对数:把发生比从 (0, +∞) 映射到 (-∞, +∞),让后续的概率乘法更新退化为简单的加法更新。
从此小明的值班簿不再写“有/无”,而是写一排可以反复加减的数字——这就是他每天更新草图时用的“记账本”。
贝叶斯更新
小明不是每晚重新画图,而是在昨天的值班簿上“叠写”新信息:昨天的信心值 + 今晚的新观察 - 初始的偏见。为什么要减初始值?因为手电筒的观察里已经天然包含了一次“从零开始”的判断,不减去就会把出发时的先验重复记账。
数学上,每次新观测都要用贝叶斯定理把先验与似然相乘,但直接乘概率会很快饱和到 0 或 1。用对数几率表达后,这一过程退化为三项加法,这也是占据栅格地图最核心的递推公式。设 x_{1:t} 为历史位姿、z_{1:t} 为历史观测,格子 m_i 的后验对数几率可递推更新:
1. 更新后的后验:综合到时刻 t 为止所有位姿与观测后,对该格子的最新信心分数。
2. 继承旧证据:上一时刻的后验分数作为旧笔记被完整保留,递推式让地图不用重头计算,只需增量更新。
3. 注入新观测:本次观测通过逆传感器模型折算成的分数增量,在下一节详述。
4. 扣除先验:减去初始对数几率 l_0(通常取 0)。因为每一项都隐含先验,不扣除会重复累加,导致同一点被反复扫描时分数虚高。
值班簿上每一格的数字,就在这一次次“叠加”中向真实世界收敛:扫过十次的格子,远比扫过一次的格子更有说服力。
逆传感器模型
手电筒的光束其实非常“诚实”:光畅通无阻经过的地砖,几乎可以断定是空的——否则光会被挡住;光束尽头那个亮斑所在的地砖,大概率有东西——那是物体表面在反光;再往远处,光完全没到达的地方,只能保持“不知道”。这“光的语言”就是逆传感器模型。
传感器测距模型回答“如果环境是 m,测得 z 的概率是多少”;而建图需要反过来——“看到测量 z,格子 m_i 处于什么状态”,这就是逆传感器模型。它把一次测距 z_t(光束从当前位置出发测得的障碍距离)翻译成沿途格子的分数贡献。
对第 i 个格子,记其中心到传感器的距离为 d_i,测距噪声范围为 δ,逆传感器模型可写成分段函数:
1. 被光束穿过的格子:格心到传感器更近,说明光束曾从它上方经过,标记为 l_free(负值,偏向空闲)。
2. 光束尽头的格子:格心距与测量值相差不到 δ,是物体表面所在的“反射环”,标记为 l_occ(正值,偏向占据)。
3. 光束未到达的格子:距离更远的区域被物体遮挡,观测无法提供信息,保持先验 l_0 不变。
小明每次手电筒一扫,不是只改一个格子,而是沿光束把一整排格子的分数各归其位——这比“看到一个点改一个点”高效得多,也解释了为什么一帧激光数据能同时更新几百个格子。
更新流程
既然值班簿记的是“分数”,那最终要画成能给班长看的地图时,就要把分数翻译回“概率百分比”。分数越高翻译成概率越接近 100%,分数越低越接近 0%,分数为 0 则输出 50%——依然表示“不知道”。
从对数几率恢复占据概率,用的正是 sigmoid 函数的反变换。每一帧传感器数据到达后,机器人依次执行:先由逆传感器模型计算每个被观测格子的增量分数,再用贝叶斯递推累加,最后按需换算成概率供规划模块使用。
恢复公式如下:e^{-l} 项让分数 l 越大概率越接近 1、越小越接近 0,且结果始终落在 (0,1) 区间内,天然适配“概率”语义。
1. 指数项 e^{-l}:把可正可负的分数压缩成正数,l 越大该项越接近 0。
2. 分母 1 + e^{-l}:保证整体结果恒在 0 与 1 之间,防止概率越界。
3. 整体取倒数:完成 sigmoid 归一化,l=0 时输出 0.5,l>0 输出大于 0.5,l<0 输出小于 0.5。
于是值班簿的完整工作流变成:空白册子 → 每夜叠加新分数 → 需要决策时换算成概率。建图循环日复一日,地图越来越接近仓库真相。
工程实践
如果班长要求把地砖切成 1 毫米见方,小明会直接哭出来——值班簿根本画不下。格子切得越细,笔记本页数越多,写起来也越慢。真实机器人面临同样的三难:范围、分辨率与内存不可兼得。
占据栅格地图的内存占用随分辨率呈平方甚至立方增长,这是工程实现的第一道坎。其次是传感器融合:激光雷达、超声波、相机各有噪点,需要统一折算成逆传感器模型的分数贡献。此外环境并非永远静止,行人、推车会让“永恒地图”失效,必须保留在线更新通道。
假设地图长 W 宽 H,分辨率(格边长)为 δ,每格存储 B 字节,则总内存为:
1. 横向格子数 W/δ:地图长度除以单格边长,得到一行有多少个格子。
2. 纵向格子数 H/δ:同理得到列方向的格子数量,两者相乘即总格子数。
3. 乘每格字节数 B:工程上常用 1 字节存 256 级分数,或用 1 bit 只存“已知/未知”,B 越小地图越省内存。
为了不让值班簿失控,工程师发明了分层栅格、动态分配、多分辨率地图等“压缩笔记本”的手段——本质都是在精度与体积之间做取舍。小明只负责一条走廊,用小尺度的笔记本就够了;负责整个园区,则要分层分区记录。
典型应用
值班簿画完的最终读者是巡逻路线规划员:他看一眼图上的红叉与通路,就能绕开货架、挑最短的路走。若是把平面地砖换成三维货架空间,这本“值班簿”就升级成“货架全景立体册”——每个小立方体记录哪里有空位、哪里有货物。
占据栅格地图最直接的应用是路径规划:规划器把概率图二值化为“可通行/障碍”,再交给 A* 等搜索算法找路。其次是辅助定位:机器人把实时激光扫描与栅格图匹配,校正自身位置。向三维推广则得到占据体素地图,是自动驾驶与机械臂避障的基础。
二值化决策使用阈值 τ(常取 0.5~0.65),将概率图转化为机器人可判读的通行图:
1. 阈值 τ:一个可调的门限,决定地图对障碍的敏感程度,调高更保守、调低更激进。
2. 判定为占据:后验概率不低于门限时输出 1,规划器禁止机器人进入该格子。
3. 判定为空闲:概率低于门限时输出 0,视为可通行区域。
从漆黑的仓库到能自动巡逻的机器人,中间只隔着一本不断被书写的值班簿:感知把世界切成格子,概率把噪声熬成信念,规划把信念变成脚步。
AI 应用场景
扫地机器人的家庭建图
阶段:感知与建图(SLAM 前端)
解决的问题:传统吸尘器随机乱撞,扫不干净还容易卡住。扫地机器人装载激光雷达或 ToF 传感器,在移动中不断用“手电筒法则”更新家庭的占据栅格地图,得到客厅、走廊、家具边界的概率分布图,从而规划全覆盖路径、避开充电座和台阶。
具体案例:主流扫地机器人在首次清扫时一边行走一边用 SLAM 构建 2D 栅格地图,用户可在 App 上看到实时绘制的房间轮廓;二次清扫直接复用地图,按“弓字形”规划路径,遇到新增障碍(如椅子被挪动)时在线更新局部栅格。
自动驾驶的 BEV 占据栅格
阶段:多传感器融合感知
解决的问题:相机与激光雷达输出的是不同坐标系下的点云和图像,传统方法难以统一表达“车周围哪里被占”。感知算法把多传感器输出投影到以车体为中心的鸟瞰视角(BEV)栅格上,每个格子计算占据概率,供决策规划层判断可行驶区域。
具体案例:自动驾驶系统的占用网格(Occupancy Grid)实时更新:前方 50m × 50m 区域被划分为 0.2m 的栅格,融合视觉分割与激光点云后输出占据概率热力图,车辆借此识别路沿、施工锥桶与临时障碍并减速绕行。
仓储 AMR 多机协同调度
阶段:多机地图融合与调度
解决的问题:多台自主移动机器人(AMR)在同一仓库运行,若各自独立建图,地图不一致会互相“撞车”。调度系统维护一张全局占据栅格地图,各机器人实时上报传感器观测,由中心把观测折算成增量分数并广播合并,实现无碰撞调度。
具体案例:电商仓库 200 台 AMR 共享一张 50m × 80m、分辨率 5cm 的占据栅格地图,地图服务器按每秒 10 帧的频率融合所有机器人的激光观测,动态区域(临时放置的料箱)在数秒内被标记为占据,路径规划器随即重新选路。
Python 演示
import math class OccupancyGridMap: """2D occupancy grid map with log-odds updates.""" def __init__(self, width, height, l_occ=0.9, l_free=-0.7): self.width = width self.height = height self.l_occ = l_occ self.l_free = l_free self.log_odds = [0.0] * (width * height) def to_probability(self, log_odds): return 1.0 / (1.0 + math.exp(-log_odds)) def update_scan(self, robot_x, robot_y, angle, max_range, is_obstacle): """Cast a beam from the robot and update cells along it.""" for r in range(1, max_range + 1): gx = int(round(robot_x + r * math.cos(angle))) gy = int(round(robot_y + r * math.sin(angle))) if not (0 <= gx < self.width and 0 <= gy < self.height): break idx = gy * self.width + gx if is_obstacle(gx, gy): self.log_odds[idx] = min(self.log_odds[idx] + self.l_occ, 5.0) break self.log_odds[idx] = max(self.log_odds[idx] + self.l_free, -5.0) def probability_at(self, x, y): return self.to_probability(self.log_odds[y * self.width + x]) def ground_truth(x, y): """True wall area used to simulate the real environment.""" return 10 <= x <= 13 and 8 <= y <= 10 def main(): grid_map = OccupancyGridMap(30, 30) poses = [(3, 3), (10, 3), (20, 3), (20, 12), (20, 20), (10, 20), (3, 20), (3, 12)] for px, py in poses: for angle_deg in range(0, 360, 30): grid_map.update_scan(px, py, math.radians(angle_deg), 25, ground_truth) print("建图完成, 关键格子的占据概率:") for x, y in [(3, 3), (11, 9), (6, 6), (18, 18)]: print(" (%2d,%2d): %.3f" % (x, y, grid_map.probability_at(x, y))) if __name__ == "__main__": main()
self.log_odds = [0.0] * (width * height)用一维数组存储二维栅格,初值 0 表示“完全未知”(对应概率 0.5)。def update_scan(self, robot_x, robot_y, angle, max_range, is_obstacle):模拟一束“手电筒光”,从机器人位置出发沿 angle 方向逐格前进。if is_obstacle(gx, gy):命中真实障碍格时,按逆传感器模型把该格分数上调 l_occ,并 break 截断波束。self.log_odds[idx] = max(self.log_odds[idx] + self.l_free, -5.0)波束穿过的格子下调分数记录“空闲”,-5.0 是下限截断,防止数值溢出。return 1.0 / (1.0 + math.exp(-log_odds))sigmoid 反变换,把对数几率兑换成 0~1 的占据概率。for angle_deg in range(0, 360, 30):每个位置发 12 束波,模拟多波束测距传感器的一帧扫描。if __name__ == "__main__":提供可执行入口,直接运行即可看到建图结果。
常见误区
误区激光雷达打到一面墙,返回一个距离值 z,那这次观测就只把那个墙面格子标记为占据就行了,其他格子不用动。
正解一束波束携带一整条“行踪信息”:从传感器到测距点之间所有被穿过的格子都应更新为偏向空闲,只有波束末端附近的格子更新为偏向占据。忽略沿途格子会让地图产生大量鬼影——明明扫过的地方却还是未知。回想小明的值班簿:手电筒照亮的整条通道都要被记录,而不只是尽头的亮点。
为何常见:初学者容易把“一次测量”理解成“一个点”,忽略了测距本质上是“波束穿过空间的旁证”。
误区贝叶斯更新嘛,就是把新观测算出的概率和旧概率乘起来,或者直接把两次概率加起来平均。
正解直接把 0~1 的概率相乘会向 0 快速饱和,相加又会超过 1。正确做法是把概率换成对数几率(-∞~+∞),更新退化为加法,再按需用 sigmoid 转回概率。小明的“信心值”可正可负、无限累加,正是因为更新发生在分数空间。
为何常见:概率的直觉是“0 到 1 的小数”,加减乘除都看似合理,容易忽视概率乘法带来的数值饱和问题。
误区这个格子概率是 0.5,说明它一半有东西一半没东西,机器人要小心半穿过去。
正解p=0.5 在占据栅格地图里对应 l=0,语义是“完全未知”,是地图初始化的默认状态,绝不表示物理上的一半占据。只有经过多次观测、分数明显偏离 0 后,地图才给出“空闲”或“占据”的判断。就像小明看到格子空白时,意味着“还没看过”,而不是“半堵墙”。
为何常见:把概率与“面积占比”混淆;而这里的概率是“信念程度”而非“占据比例”。
误区栅格分辨率设得越高,地图越精细,机器人越聪明,所以直接上 1 毫米分辨率。
正解分辨率翻倍,二维地图内存与计算量翻四倍,三维翻八倍;同时传感器噪声固定,过细的格子反而成为“噪点放大器”,导致同一面墙在相邻格子间忽绿忽红。工程上要根据传感器精度(如激光雷达 2~5cm)选择匹配的分辨率,并用多分辨率分层结构折中。
为何常见:直觉上“细节越多越好”,忽略了分辨率与传感器噪声、存储代价的匹配关系。
误区离线把地图建好保存下来,之后机器人按图导航就行,地图不用再更新。
正解真实环境存在行人与移动障碍,静态地图无法反映“新出现的纸箱”或“刚被挪走的椅子”。商用机器人普遍采用“先加载先验地图 + 局部在线更新”策略:全局图做定位骨架,局部格子在运行中持续融合新观测,动态障碍几秒内就能出现在栅格上。
为何常见:教材为讲清静态 SLAM,常展示“一次建图终身可用”的理想场景,初学者便忽略了在线更新机制。
面试题
Q1. 占据栅格地图的核心思想是什么?
- A.将空间离散为网格并记录占据概率← 正确答案
- B.用连续函数描述整个环境
- C.只记录机器人当前的位置坐标
- D.用多张照片拼接成全景地图
答案:A。占据栅格地图的本质就是把连续空间切成网格,每格记录一个占据概率;连续函数描述是另一类地图表达,C 是定位问题,D 是纯视觉建图,都不符合。
Q2. 栅格概率为 0.5 时通常表示什么?
- A.格子有 50% 面积被物体占据
- B.传感器读数误差恰好为一半
- C.对该格子的状态完全未知← 正确答案
- D.格子处于空闲与占据之间
答案:C。p=0.5 对应对数几率 l=0,是地图初始化的先验状态,语义是“还没观测过、完全不知道”,而不是物理上一半被占。A/D 都把信念概率误当成面积比例。
Q3. 对数几率变换的主要用途是什么?
- A.将概率的乘法更新变成加法更新← 正确答案
- B.将概率的加法更新变成乘法更新
- C.放大观测噪声在更新中的影响
- D.压缩地图文件在磁盘上的体积
答案:A。贝叶斯更新本质是概率乘除法,但对数几率把乘除变成加减,于是“旧分数 + 新增量”即可递推。C 与噪声无关,D 是存储工程问题,都不是变换目的。
Q4. 贝叶斯更新公式中为什么要减去初始值 l0?
- A.消除传感器系统的固定偏差
- B.避免把初始先验重复计入← 正确答案
- C.让整张地图更新速度更快
- D.使所有格子的分数统一清零
答案:B。每一项后验都隐含着初始先验,若每次都叠加增量而不减去 l0,同一格子反复被扫描时先验会被重复累加,导致分数虚高。减 l0 保证“旧分数 + 本次增量”严格等于真实后验。
Q5. 逆传感器模型中,光束穿过(前方)的格子应标记为何种状态?
- A.该格子应当标记为占据
- B.该格子保持未知不更新
- C.该格子概率重建为一半
- D.该格子应当标记为空闲← 正确答案
答案:D。光束能从传感器到达测距点,说明沿途没有遮挡,因此被穿过的格子偏向空闲(l_free);只有光束末端反射点附近的格子才偏向占据。选错 A 就是把“被测到”与“被穿过”混淆。
Q6. 100m×100m 区域、分辨率 0.1m、每格 1 字节的栅格地图,内存约多大?
- A.约 100 千字节(KB)
- B.约 10 兆字节(MB)
- C.约 1 兆字节(MB)← 正确答案
- D.约 100 兆字节(MB)
答案:C。每方向格子数为 100/0.1=1000,总格数 1000×1000=10^6,每格 1 字节约 1MB。这也说明分辨率翻倍时内存平方增长,工程上必须权衡。
Q7. 更新对数几率时对分数做上下限截断(clamping)的主要目的是什么?
- A.防止极端值导致数值溢出← 正确答案
- B.让地图颜色显示更加美观
- C.提升激光雷达的测距精度
- D.缩短一帧观测的处理耗时
答案:A。概率 0 或 1 对应的对数几率是无穷大,多次同向更新会让浮点数溢出或饱和。截断到 [-5, 5] 之类区间保持数值稳定,代价是放弃区分“非常确定”与“极其确定”。
Q8. 占据栅格地图依赖哪些基本假设?
- A.各格子的状态相互独立← 正确答案
- B.环境在短期内近似静态← 正确答案
- C.只能使用激光雷达数据
- D.每格占据取值非零即一← 正确答案
答案:ABD。独立性让贝叶斯更新可逐格累加;静态假设使地图在观测窗口内成立;m_i∈{0,1} 的二元占据假设是基石。传感器类型并不限定,超声波、相机都能建栅格,C 错。
Q9. 下列哪些属于占据栅格地图的典型应用?
- A.机器人路径规划中的避障← 正确答案
- B.激光定位时的地图匹配← 正确答案
- C.图像语义分割的像素标注
- D.三维体素占据地图扩展← 正确答案
答案:ABD。栅格地图直接服务路径规划与激光定位匹配;向三维推广即体素占据地图。图像语义分割属于视觉感知任务,与占据栅格建图无关,C 错。
Q10. 关于对数几率更新与直接概率更新,下列说法正确的是?
- A.对数几率把连乘更新变成连加← 正确答案
- B.两者在任何条件下完全等价
- C.对数几率更新天然避免饱和← 正确答案
- D.对数几率初始值通常取零← 正确答案
答案:ACD。log-odds 的核心价值就是把概率连乘变连加(A 对),配合截断操作天然避免 0/1 饱和(C 对),初始 l0=0 对应 p=0.5 的先验(D 对)。但引入截断与数值近似后,两者结果并不严格等价,B 错。