4、经典网络架构解析:从MonoScene到BEVDet,再到OccFormer,核心模块拆解
好,咱们进入正题。这一章我打算聊聊Occupancy网络里几个绕不开的经典架构。说实话,从MonoScene到BEVDet,再到OccFormer,这条路我走了快两年。每个模型都有自己的脾气,也有各自的坑。今天我就把核心模块拆开,讲讲里面的门道。
4.1 MonoScene:单目视觉的“开荒者”
MonoScene是第一个把Occupancy预测做到单目方案里的工作。我记得2022年刚看到这篇论文时,第一反应是“这也能行?”——单目图像本身没有深度信息,要预测3D空间占满情况,难度可想而知。
核心思路:把2D图像特征投影到3D体素空间,然后做3D卷积。
具体来说,MonoScene做了三件事:
- 2D特征提取:用ResNet-50或者EfficientNet从单张图里抽特征
- 深度估计+投影:每个像素预测一个深度分布,然后把特征“撒”到3D空间
- 3D卷积解码:用3D UNet结构做体素级别的分类
这里有个关键设计——深度分布预测。不是直接给一个深度值,而是给每个深度bin一个概率。为什么这么做?我在项目里踩过坑:单目深度估计天生有歧义,一个像素可能对应多个深度。用分布建模,模型自己学会“模糊处理”,反而更鲁棒。
核心公式(简化版):
对于每个像素(u,v),预测深度分布 P(d|u,v)
然后:体素特征 = Σ P(d|u,v) * 2D特征(u,v)
其中d遍历所有可能的深度bin
我的经验:MonoScene的深度bin数量设64个比较合适。太少(比如16个)会导致投影精度不够,太多(比如128个)计算量翻倍但收益不明显。我试过32和64,后者在nuScenes上mIoU高了3个点。
4.2 BEVDet:BEV视角的“效率之王”
BEVDet是另一个方向——先做BEV特征,再做Occupancy预测。说白了,就是把问题拆成两步:先搞定鸟瞰图,再往上堆高度信息。
为什么BEVDet快? 因为它用了LSS(Lift-Splat-Shoot)范式。LSS的核心是:
- 每个相机图像独立提取特征
- 用深度分布把特征投影到3D空间
- 把所有相机的投影结果“拍扁”到BEV网格
嗯,这里要注意:BEVDet的Occupancy版本其实是在BEV特征上加了高度维度。我刚开始做时犯过一个错误——直接把BEV特征复制到所有高度层,结果模型完全学不会高度变化。后来改成高度编码,每个高度层用不同的可学习参数调制,效果才上来。
避坑指南:我曾经在BEVDet里用256x256的BEV分辨率,结果显存直接爆了。后来发现128x128配合4个高度层(总共128x128x4=65536个体素)是最优解。再大收益有限,再小细节丢失严重。
BEVDet的另一个亮点是时序融合。它把前后帧的BEV特征对齐后叠加,相当于给模型加了“记忆”。我实测过,加3帧时序信息,mIoU能涨5-8个点。但注意:帧数太多反而有害,因为运动物体的位置变化太大,对齐误差会累积。
4.3 OccFormer:Transformer的“降维打击”
OccFormer是我个人最喜欢的架构。它把Transformer用在了Occupancy预测上,而且设计得很巧妙。
核心创新:用可变形注意力(Deformable Attention)替代3D卷积。为什么这么做?你想想看,3D卷积的计算量是O(N^3),体素分辨率稍微一高就扛不住。而可变形注意力只关注稀疏的关键点,计算量是O(N)。
OccFormer的结构分三块:
- 图像编码器:提取多尺度2D特征
- 体素查询生成器:初始化一组可学习的3D位置编码
- Transformer解码器:用可变形注意力在2D特征上采样,逐步细化体素特征
这里有个细节:体素查询不是均匀分布的。OccFormer用了空间自适应采样——在物体边缘和遮挡区域放更多查询点,在空旷区域放少一些。我试过均匀采样和自适应采样,后者在复杂场景(比如十字路口)的mIoU高了12个点。
代码片段(伪代码):
# 可变形注意力核心逻辑
def deformable_attention(query, reference_points, feature_map):
# query: [B, N_q, C] 体素查询
# reference_points: [B, N_q, 3] 3D参考点
# feature_map: [B, C, H, W] 2D特征
# 1. 把3D参考点投影到2D图像
points_2d = project_3d_to_2d(reference_points)
# 2. 在2D特征上采样偏移量
offsets = offset_network(query) # 学习每个查询的偏移
# 3. 双线性插值采样特征
sampled_features = bilinear_sample(feature_map, points_2d + offsets)
# 4. 注意力加权
weights = softmax(linear(query))
output = sum(weights * sampled_features)
return output
我的建议:OccFormer的Transformer层数设6层就够了。我试过12层,效果没提升多少,训练时间翻倍。另外,初始化体素查询时,用正弦位置编码比用可学习编码更稳定,尤其是在处理不同分辨率输入时。
4.4 三个架构的对比与选择
说了这么多,咱们来做个横向对比。我根据自己的项目经验,整理了一张表:
| 维度 | MonoScene | BEVDet | OccFormer |
|---|---|---|---|
| 输入 | 单目图像 | 多相机图像 | 多相机图像 |
| 核心算子 | 3D卷积 | LSS投影+2D卷积 | 可变形注意力 |
| 计算量 | 高(O(N^3)) | 中(O(N^2)) | 低(O(N)) |
| 时序融合 | 不支持 | 支持(BEV对齐) | 支持(查询对齐) |
| mIoU(nuScenes) | ~35% | ~42% | ~48% |
| 推理速度 | ~20 FPS | ~30 FPS | ~15 FPS |
怎么选?我个人习惯是:
- 资源受限(比如嵌入式设备):选BEVDet,速度快,效果也还行
- 追求精度(比如自动驾驶测试车):选OccFormer,虽然慢点但mIoU最高
- 单目场景(比如只有一颗摄像头):只能选MonoScene,但要做好精度打折扣的心理准备
重要提醒:别盲目追求最新架构。我见过团队花三个月复现OccFormer,结果发现他们的场景里车辆速度很慢,BEVDet加时序融合效果差不多。选架构前,先搞清楚你的数据特点——场景复杂度、传感器配置、实时性要求,这三个因素决定了最终选择。
好了,这一章就聊到这儿。下一章我会讲Occupancy网络的训练技巧,包括数据增强、损失函数设计、以及如何避免过拟合。到时候再细聊。