4. Occupancy Network基础:从3D检测到占据网格、语义占据与通用占据、OccFormer与TPVFormer介绍
4.1 为什么我们需要Occupancy?
做自动驾驶感知的兄弟应该都有体会——3D检测做到一定程度,瓶颈就出来了。我2019年刚入行那会儿,团队还在死磕激光雷达的3D检测,觉得能检出车和行人就差不多了。但实际跑起来才发现,路上有太多「非标准物体」:掉落的轮胎、施工用的锥桶、甚至被风吹起的塑料袋。这些玩意儿,你让检测头去学,样本少得可怜。
说白了,3D检测的本质是「我认识什么,我就检出什么」。但现实世界是开放的,总有你没见过的东西。Occupancy Network的思路就聪明多了——我不去猜你是什么,我只关心「这个格子有没有东西」。这就像你走在路上,不需要知道迎面来的是猫还是狗,只要知道「有个东西过来了,躲开」就行。
核心转变:从「这是什么物体」到「这里有没有物体」。
4.2 从3D检测到占据网格
先聊聊最基础的版本——占据网格(Occupancy Grid)。
我记得第一次在实车上跑Occupancy时,最直观的感受是:原来路沿、花坛、隔离带这些「非目标」也能被感知到了。传统3D检测对这些东西基本是「视而不见」的,因为标注数据里压根没它们。
占据网格的做法其实很朴素:
- 把空间划分成均匀的体素网格(比如0.1m × 0.1m × 0.1m)
- 对每个网格,判断它是否被占据
- 输出一个二值化的3D占用图
这里有个关键问题:怎么判断?早期做法是用激光雷达的点云直接投影到网格里,有回波就算占据。但纯激光的方案有个硬伤——稀疏。远一点的地方,点云稀稀拉拉的,网格里全是「未知」状态。
后来大家开始用BEV特征来做。把环视相机的图像通过Transformer编码成BEV特征,然后上采样到体素空间,每个体素做一个二分类。嗯,这里要注意,纯视觉的Occupancy在近距离(0-30m)效果还不错,但远距离的深度估计不准,体素的占据置信度会下降。
我的经验:在实际量产项目中,我建议用激光雷达做近距离(0-20m)的占据真值,用视觉做中远距离的语义补充。两者融合的效果,比单一传感器好30%以上。
4.3 语义占据:给每个格子贴标签
二值占据够用吗?说实话,不够。你想想看,自动驾驶决策层需要知道「前面是个车」还是「前面是棵树」。如果是车,可能要减速跟车;如果是树,那直接绕过去就行。行为完全不同。
语义占据(Semantic Occupancy)就是在占据网格的基础上,给每个被占据的格子分配一个语义类别。常见的类别有:车、行人、骑车人、路障、路面、建筑、植被等。
我曾在项目中踩过一个坑:语义占据的类别数设了20类,结果模型在「摩托车」和「电动车」之间反复横跳。后来我反思,其实对于下游规划来说,这两类都属于「可移动障碍物」,没必要分那么细。最终我们把类别压缩到8类,模型精度反而提升了5个点。
避坑指南:我曾经在语义占据的类别设计上过于追求细致,导致模型过拟合。建议量产项目控制在10类以内,优先保证「动静态分离」和「可通行区域」的准确性。
4.4 通用占据:不止是语义
通用占据(General Occupancy)是最近两年才火起来的概念。它不局限于语义分类,而是输出每个体素的「属性向量」。这个向量可以包含:
- 占据概率
- 语义类别
- 运动状态(静态/动态)
- 速度矢量
- 甚至材质信息(金属、塑料、植被)
说白了,通用占据就是想把体素空间变成一个「信息容器」,下游规划需要什么,就从里面取什么。这个思路很优雅,但实现起来难度不小。我见过一些团队直接把所有属性堆在一起学,结果每个属性都学不好。
我个人习惯的做法是:分阶段训练。先学占据概率和语义,再冻结这两个分支,单独学运动状态和速度。这样每个分支的梯度不会互相干扰。
4.5 OccFormer:3D空间的Transformer
聊完概念,我们看看具体怎么实现。OccFormer是2023年比较有代表性的工作。
它的核心思路是:把3D体素空间当成一个「3D序列」,用Transformer的注意力机制去建模体素之间的空间关系。
具体来说:
- 从BEV特征出发,通过一个3D卷积上采样到体素空间
- 每个体素作为一个token,加上3D位置编码
- 用自注意力机制让体素之间互相「交流」
- 最后接一个MLP头,输出每个体素的占据概率和语义
这里有个实际的问题:3D体素的数量太大了。一个100m × 100m × 5m的空间,0.1m分辨率,就是5000万个体素。全做自注意力,显存直接爆炸。
OccFormer的解决方案是:只在局部窗口内做注意力,类似于Swin Transformer的思路。窗口大小设为8×8×8,这样每个体素只和同窗口内的512个体素做交互,计算量可控。
关键参数:窗口大小8×8×8,体素分辨率0.1m,特征维度128。这个配置在A100上能跑到实时(30FPS)。
4.6 TPVFormer:三视角的优雅解法
TPVFormer是我个人比较喜欢的一个方案。它的名字很有意思——TPV是Tri-Perspective View的缩写,也就是三视角。
为什么是三视角?你想想看,BEV虽然好,但它丢失了高度信息。纯3D体素又太贵。TPVFormer的聪明之处在于:用三个正交的2D平面(俯视图、前视图、侧视图)来近似表达3D空间。
具体做法:
- 三个平面分别用Transformer编码
- 每个3D点通过投影到三个平面上,拿到三个特征向量
- 三个特征拼接或相加,得到该点的最终特征
- 再通过MLP输出占据和语义
这个方案的好处是:计算量比纯3D体素小了一个数量级。我在实际部署时,TPVFormer在Jetson Orin上能跑到20FPS,而同等精度的3D体素方案只能跑8FPS。
我的建议:如果算力有限,优先考虑TPVFormer。如果算力充足且对高度精度要求极高(比如地下车库场景),再考虑OccFormer。
4.7 三种方案的对比
| 方案 | 空间表达 | 计算量 | 高度精度 | 适合场景 |
|---|---|---|---|---|
| 占据网格 | 3D体素 | 低 | 中 | 快速障碍物检测 |
| OccFormer | 3D体素+注意力 | 高 | 高 | 高精度建图 |
| TPVFormer | 三视角平面 | 中 | 中高 | 量产部署 |
4.8 量产落地的几点思考
最后聊点实际的。Occupancy Network从论文到量产,中间还有不少坑:
- 标注成本:语义占据的标注比3D检测贵5-10倍。我建议先用自动标注(激光+时序融合)生成伪标签,再人工修正。
- 长尾问题:有些场景(比如暴雨、夜晚)的Occupancy效果很差。我的经验是,在这些场景下降低置信度阈值,宁可多报假阳性,也不要漏检。
- 时序融合:单帧Occupancy抖动很大。加上时序后,稳定性提升明显。我习惯用简单的指数滑动平均,效果就很好。
嗯,这一章的内容就到这里。下一章我们会深入Occupancy Network的训练细节,包括损失函数设计、数据增强策略,以及如何用自监督学习降低标注成本。到时候我会分享一些我在实际项目中踩过的坑和总结的技巧。