想象你是一位大宅的管家。以前你巡逻时,只认识房间里的猫、书桌、快递箱,因为主人教过你辨认这些物品。可有一天,地上落下一根叫不出名字的黑色水管,你叫不上名字,竟不知道该不该踩过去。真正的管家应该关注的是:脚边那块地砖,现在到底被没被占住?自动驾驶也是如此。Occupancy Network(占据网络)就是给汽车管家铺上一片“感应瓷砖”:把车周围的三维空间切成一个个小方块,并预测每一块“被占住”的概率。它不关心那个东西是狗还是水管,只关心能不能走。本文就带你从这套“感应瓷砖”出发,看懂体素化、稀疏表示、BEV 感知、遮挡补全和算力权衡。
对 AI 从业者而言,掌握占据网格意味着从“识别已知物体”跃迁到“通用空间理解”,这是自动驾驶感知、机器人导航与空间智能相关岗位的核心能力。
目录
可视化演示
动态演示Occupancy Netw的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Occupancy Netw:占据网格 占据网格预测的核心思想与动机 体素化表示与占据概率预测 核心机制可视化 Occupancy Netw整体架构与关键组件
为何需要占据空间:从认出物体到感知占满
管家最先学会的,是辨认每一件家具。可当他面对一团没有名字的黑影时,他会愣在原地。自动驾驶也是一样:传统感知依赖目标检测,需要提前定义类别,比如“车”“行人”“锥桶”。但真实世界里,总会出现你没训练过的长尾物体,比如倾倒的树干、掉落的轮胎、施工遗留的铁管。只做目标检测,天生就漏。
如果把视角从“认出物体”换成“空间占满”,事情就变得简单。世界是连续的,车能走的地方,是因为那里没有被东西占据。Occupancy Network 要做的事,就是对空间中的每个小方块体素,预测一个“被占据”的概率。它不关心占据它的具体是什么,只关心概率高不高。
数学上,可以定义每个体素 v 的占据概率:
回到管家的世界:他不需要知道脚下踩的是快递箱还是黑色水管,只需要知道那块“感应瓷砖”亮了。目标检测是在问“这是什么”,占据网格是在问“这能不能走”。后者才是安全兜底真正需要的答案。
- 占据状态变量o_v 表示体素 v 是否被占据,取值为 0 或 1。
- 条件概率后面的符号 \mid I 表示“在给定图像 I 的情况下”。
- 网络预测函数f_{\theta}(I)(v) 代表参数为 θ 的神经网络,输入图像,输出每个体素的概率。
体素网格表示:把空间切成感应瓷砖
管家想把整座宅子都铺上感应瓷砖。他不能把瓷砖做成任何形状,只能做成统一大小的方块,这样才能一块块拼起来。于是,连续的三维空间被切成了许多小立方体,每个小立方体就叫一个体素(Voxel)。体素的边长就是分辨率 r。
体素化的核心是把世界坐标离散化。假设整个空间有一个起点 (x_min, y_min, z_min),那么任意一点 (x, y, z) 都能换算成体素坐标:
瓷砖越小,管家对地面的描述越精确;但同样面积需要铺的瓷砖也越多。这就是后面要讨论的分辨率与算力权衡的雏形。
- 空间原点x_min、y_min、z_min 是包围盒起点,保证所有坐标换算后不会出现负数。
- 相对距离分子 x - x_min 表示该点距离原点多远。
- 向下取整\lfloor \cdot \rfloor 把小数坐标压成整数,得到格子编号。
- 分辨率r 是每个体素的边长,r 越小,体素越细密。
网络预测任务:判断瓷砖亮不亮
管家不能真的去踩每一块瓷砖,他靠监控画面来判断。Occupancy Network 也一样:输入来自摄像头、激光雷达或多传感器融合的特征,输出是全部体素的占据概率。
网络先把二维图像编码成特征,再通过三维卷积在体素空间中推理。因为任务只是“亮不亮”,模型不必区分语义。在训练时,我们使用真值标签 y_v 与预测概率 o_v 计算二值交叉熵损失:
管家在训练中不断修正自己的判断:看到阴影和高光,逐渐学会哪些区域大概率被占。最终,他不需要认识每个物体,也能在监控画面亮起时,第一时间知道哪块瓷砖不能踩。
- 真值标签y_v 是体素 v 的真实状态,1 表示被占据,0 表示空闲。
- 预测概率o_v 是模型输出,经过 Sigmoid 后落在 0 到 1 之间。
- 对数损失\log o_v 与 \log(1 - o_v) 分别惩罚“把占据看成空闲”和“把空闲看成占据”。
- 平均求和对场景中所有体素求和再取平均,得到整体损失。
稀疏表示优化:只记被踩的瓷砖
整座宅子铺满感应瓷砖后,管家发现绝大多数瓷砖常年没人踩。如果每天都要记录每一块瓷砖的状态,记忆库会爆炸。自动驾驶场景也一样:道路前方大部分区域是空的,只有少数体素真正被占据。
稠密体素网格意味着要存储和计算所有格子,哪怕其中 99% 是空闲。为了节省资源,可以只保存“被占据”或“状态未知”的体素,这就是稀疏表示。稀疏卷积只对有效位置做计算,体素哈希表则能快速找到“某块瓷砖在不在记录里”。
我们可以用稀疏度描述这种节省效果:
管家在记忆本上只记录被踩脏的瓷砖和从没见过的暗区。这样,即使宅子再大,他也能快速翻到关键位置,把省下的精力用来盯着那些“可疑的亮灯”。
- 有效体素集合\{v : o_v > \tau\} 表示占据概率超过阈值的体素。
- 阈值τ 用来区分“有效体素”和“可忽略体素”。
- 总数 N右侧 \left|\{v\}\right| 是空间里所有体素的个数。
- 稀疏度 S有效体素数占总数的比例,S 接近 0 表示极稀疏。
BEV感知协同:天花板上的鸟瞰镜
管家站在地面看走廊,视线会被家具挡住。但如果他在天花板上装一面大镜子,从上往下看,整层楼的瓷砖位置就一目了然。这个俯视视角,就是自动驾驶里的 BEV(Bird's Eye View,鸟瞰图)。
BEV 是二维平面,Occupancy Network 是三维体素。两者并不冲突:三维占据网格可以沿高度方向“压扁”成二维 BEV 占据图,供路径规划直接使用;规划模块只关心地面平面上哪些区域可走,不关心那堆障碍物到底有多高。聚合方式有很多种,最简单的是取高度方向的最大占据概率:
天花板上的镜子让管家不必反复弯腰确认。BEV 占据图也成为连接感知与规划的一座桥梁:下游模块不再需要“识别出那是椅子”,只需要知道“这里别走”。
- 二维坐标(x, y) 是地面平面上的格子位置。
- 高度轴z 表示垂直方向,max 表示沿高度扫描。
- 体素概率o_v(x, y, z) 是特定位置体素的占据概率。
遮挡与长尾障碍物:看不见也能猜
柜子挡住了墙角,管家看不到那片区域,但他不会轻易认为那里是安全的。自动驾驶同样如此:被遮挡的体素并不等于空闲,更合理的状态是“未知”。
Occupancy Network 的优势在于它是一个几何推理模型。它可以利用周围可见区域的上下文,推测被遮挡区域的占据概率。比如看到两辆车的夹缝里露出半个购物车,模型会补全出“购物车附近的空间被占”。同时,因为模型并不依赖“购物车”这个类别,它天然对长尾障碍物更友好。
为了安全,许多实现把预测从二分类扩展成三态:
管家在看不见的角落会主动放慢脚步。他可能不知道那里是墙还是猫,但他至少知道“那里未知”——这种保守,恰恰是安全驾驶最需要的品质。
- 状态集合c 可以取 free、occupied、unknown 三种状态。
- 类别概率p_c(v \mid I) 表示给定图像时体素 v 属于状态 c 的概率。
- 择优\arg\max 找出概率最大的状态,作为该体素的最终结果。
工程算力权衡:大瓷砖还是小瓷砖
想要看清地面上的小坑,管家需要更小的瓷砖;可整座宅子都换成小瓷砖,铺装成本会暴涨。体素分辨率也面临同样的甜蜜与烦恼:体素边长 r 减半,同一空间内的体素数量会变成原来的 8 倍。
计算量大致服从立方增长,而误差与分辨率成反比。工程上常用动态策略:近处用小体素保证精度,远处用大体素节省算力;或者先粗后细,先用低分辨率找到可疑区域,再对局部细粒度预测。
管家会在走廊尽头使用粗瓷砖快速巡逻,在主人脚边使用细瓷砖仔细检查。未来开源实现和量产方案,也都在寻找“哪里需要细、哪里可以粗”的平衡点。
- 感知范围D 表示需要考虑的空间边长。
- 体素数量\left(\frac{D}{r}\right)^3 是每边体素个数的三次方。
- 常数系数K 代表网络结构带来的固定计算开销。
- 量化误差E 约等于体素边长的一半,边长越小,空间边界越精确。
AI 应用场景
自动驾驶城市 NOA 安全兜底
阶段:感知 → 融合 → 决策
解决的问题:传统目标检测依赖预定义类别,遇到倒下的树干、掉落的轮胎等长尾障碍物容易漏检。Occupancy Network 把这些物体统一视为“占据体素”,让规划模块直接避让。
具体案例:一辆城市 NOA 车辆在夜间行驶,前方路面出现一段没有反光标识的施工水管。目标检测器没有“水管”类别,置信度很低;但占据网格在该区域输出高占据概率,车辆随即减速并绕行,避免了一场事故。
自动泊车与低速园区物流
阶段:局部建图 → 路径规划 → 运动控制
解决的问题:停车场环境复杂,有柱子、购物车、消防栓等形状不规则的障碍物。Occupancy Network 提供实时可通行区域,不依赖高精地图和精细语义。
具体案例:园区送货车遇到一辆半开的尾板货车,尾板部分超出车位。感知模型无法给出稳定 3D 框,但占据网格把尾板下方区域标记为占据,车辆重新规划路径,绕开危险区域继续送货。
自动标注与真值生成
阶段:数据生成 → 标注 → 训练
解决的问题:3D 语义分割和物体检测需要大量人工标注,成本高且类别有限。占用网格可以通过激光雷达点云聚合,自动生成稠密占据真值。
具体案例:数据团队采集同一路段多次通过的点云,离线拼接成高精度占据网格,再投影到每一帧图像上。模型训练时直接使用这些占据标签,减少了数以万计的框标注工时。
Python 演示
import numpy as np def voxelize_point_cloud(points, resolution=0.5): coords = np.floor(np.asarray(points) / resolution).astype(np.int32) coords -= coords.min(axis=0) unique, counts = np.unique(coords, axis=0, return_counts=True) return unique, counts def sparse_to_bev(voxel_coords, counts=None): if counts is None: counts = np.ones(len(voxel_coords), dtype=np.int32) bev = {} for (x, y, z), c in zip(voxel_coords.tolist(), counts.tolist()): key = (x, y) if c > 0: bev[key] = 1 return bev def evaluate_resolution(points, res): voxels, counts = voxelize_point_cloud(points, res) bev = sparse_to_bev(voxels, counts) return len(voxels), len(bev) if __name__ == "__main__": rng = np.random.default_rng(42) points = rng.uniform(low=[0, 0, 0], high=[10, 5, 2], size=(2000, 3)) for res in [1.0, 0.5, 0.25]: n_voxels, n_bev = evaluate_resolution(points, res) print(f"res={res}: voxels={n_voxels}, bev_cells={n_bev}")
coords = np.floor(np.asarray(points) / resolution).astype(np.int32)把连续坐标除以分辨率并向下取整,得到体素索引。coords -= coords.min(axis=0)平移坐标,保证所有体素索引非负,便于统计。np.unique(coords, axis=0, return_counts=True)找出所有被点云命中的体素及每个体素内的点数。for (x, y, z), c in zip(...)遍历每个有效体素,只保留 (x, y) 平面信息。bev[key] = 1只要该列存在任意点,就标记为占据,完成三维到二维的聚合。for res in [1.0, 0.5, 0.25]对比不同分辨率下体素数量的增长,直观看到立方膨胀。
常见误区
误区我以为占据网格就是把一张图片分成很多小格子,然后对每个格子画一个更小的 3D 框,本质上还是检测物体。
正解Occupancy Network 不做实例区分,也不预测类别。它只输出每个体素的占据、空闲或未知概率,回答的是“这里有没有东西”,而不是“这里是什么东西”。它甚至可以把一个从未见过的异形障碍物完整表示为占据区域。
为何常见:很多可视化图里把不同占据区域涂成不同颜色,看起来很像语义分割,容易让人误以为它仍在做分类识别。
误区既然要看得清楚,那体素越小越好,分辨率越高,占据网格就越精确,安全性自然越高。
正解分辨率翻倍,体素数量变成原来的 8 倍,内存和算力会爆炸式增长。同时,相机观测和真值本身有噪声,过高的分辨率不一定带来有效精度,反而可能让模型对噪声更敏感。
为何常见:人眼直觉是“越精细越清晰”,忽略了三维空间是立方增长的维度诅咒。
误区我觉得网上说的 BEV 感知和占用网络是一回事,都是画一个俯视网格,只是叫法不同。
正解BEV 是二维鸟瞰图,Occupancy Network 是三维体素表示。两者可以互相转换和协作:三维体素可以压成 BEV,BEV 特征也可以作为输入重建三维占据网格,但它们不是同一层表示。
为何常见:特斯拉等方案常把 BEV 可视化与占据网格叠加展示,让观众误以为两者边界相同。
误区如果传感器没有看到某个区域,那这个区域就是没有障碍物,应该标记为空闲,让车辆大胆通过。
正解看不见不等于不存在。优秀的三态占据网格会把被遮挡区域标为“未知”,规划模块会把它当成潜在的禁行区,从而更保守地驾驶。直接标为空闲会带来严重安全风险。
为何常见:许多初学者把“没检测到”等同于“没有物体”,忽略了传感器的视线遮挡和盲区。
误区GitHub 上有现成的 Occupancy Network 开源实现,跑通 demo 后,应该能直接装到量产车上。
正解开源实现通常只解决“网络推理”这一环。量产还需要处理传感器标定、多帧时序、功耗限制、安全冗余、雨雾退化、规控接口等一系列工程问题。从 demo 到量产,差距往往比模型本身更大。
为何常见:开源项目很容易在公开数据集上得到炫酷效果,让人低估了真实道路环境的复杂性和工程强度。
面试题
Q1. Occupancy Network 的核心输出是什么?
- A.目标类别与 3D 框
- B.空间体素占据概率← 正确答案
- C.道路中心线地图
- D.车辆运动轨迹预测
答案:B。Occupancy Network 是对每个体素预测占据概率,用于回答“这里能不能走”。目标类别、道路线、轨迹预测都不是它的核心输出。
Q2. 体素分辨率 r 变为原来的一半,同等范围内体素数量变为?
- A.原来的 2 倍
- B.原来的 4 倍
- C.原来的 8 倍← 正确答案
- D.保持不变
答案:C。三维空间中边长减半,相当于 x、y、z 三个方向各自翻倍,整体体素数量变为 2×2×2=8 倍。这是占据网格算力开销的关键来源。
Q3. 稀疏表示优化主要解决什么问题?
- A.模型训练时过拟合
- B.空闲体素的算力浪费← 正确答案
- C.传感器时间不同步
- D.输出类别不够丰富
答案:B。大场景中绝大多数体素是空闲的,稠密网格浪费大量内存和计算。稀疏表示只存储有效体素,用稀疏卷积与哈希表加速。
Q4. 三态占据网格中通常包含哪三种状态?
- A.占据、空闲、未知← 正确答案
- B.静态、动态、静止
- C.前景、背景、阴影
- D.地面、障碍、天空
答案:A。为处理遮挡,模型常输出 occupied、free、unknown 三种状态,其中 unknown 对应传感器看不见但不该冒然通行的区域。
Q5. 与目标检测相比,占据网格的优势包括?
- A.支持任意形状障碍物← 正确答案
- B.无需逐类标注训练← 正确答案
- C.彻底消除传感器成本
- D.完全替代高精地图
答案:AB。占据网格不依赖物体类别,因此能处理任意形状和长尾障碍物,也不需要逐类标注。但它不能消除传感器成本,也无法完全替代高精地图。
Q6. BEV 与 Occupancy Network 的关系正确的是?
- A.两者概念完全等价
- B.二维鸟瞰图可与三维网格协作← 正确答案
- C.两者互斥不可同时使用
- D.占据网格必须基于鸟瞰图
答案:B。BEV 是二维俯视表示,Occupancy Network 是三维体素表示。三维可以压成二维 BEV,二维 BEV 特征也可以辅助三维预测,二者是协作关系。
Q7. 关于遮挡区域的处理,正确说法有?
- A.被遮挡体素应标记为空闲
- B.可利用几何先验补全推测← 正确答案
- C.未知状态可用于保守规划← 正确答案
- D.提高分辨率即可彻底消除
答案:BC。被遮挡不等于空闲,模型可以用周围上下文补全,对不确定区域输出 unknown,让规划更保守。提高分辨率只能改善细节,无法彻底解决视线遮挡。
Q8. 网络输出用 Sigmoid 表示占据概率,主要原因是?
- A.使计算图具备可导性
- B.将实数压到零一到区间← 正确答案
- C.自动实现空间稀疏化
- D.避免梯度消失问题
答案:B。Sigmoid 把任意实数映射到 (0,1),可以解释为占据概率。可导性不是它独有的理由,它也不负责稀疏化,更不能保证避免梯度消失。
Q9. 若计算成本 C∝(D/r)^3,误差 E∝r,要使 E 减半且 C 不变,距离 D 应变为原来的多少倍?
- A.原来的四分之一
- B.原来的二分之一← 正确答案
- C.原来的两倍
- D.原来的四倍
答案:B。E 减半要求 r 减半。要保持 (D/r) 不变,D 也必须减半,因此 D 变为原来的二分之一。这体现了分辨率和感知范围之间的工程制约。
Q10. 以下哪些可作为占据网格感知的输入?
- A.多目相机图像← 正确答案
- B.激光雷达点云← 正确答案
- C.多传感器融合特征← 正确答案
- D.车载音频信号
答案:ABC。占据网格感知通常使用视觉图像、点云或融合特征作为输入。音频信号主要用于提示音播报,不参与三维空间占据感知。