4、BEVFormer 原理与架构:什么是BEV(鸟瞰图)?BEVFormer的核心设计,Temporal Self-Attention的作用
4.1 先聊聊BEV——为什么自动驾驶离不开鸟瞰图?
说实话,我刚入行做感知的时候,团队还在用前视图+单目深度估计那一套。那时候有个头疼的问题:前视图里一辆大卡车和一辆小轿车,在图像上可能占据差不多的像素面积,但实际物理空间差远了。你想想看,这种尺度歧义对下游规划来说简直是灾难。
BEV(Bird‘s Eye View),说白了就是把所有传感器数据统一投影到俯视的二维平面坐标系里。这个坐标系里没有透视变形,没有近大远小,每个格子代表固定的物理尺寸(比如0.5米×0.5米)。
我个人习惯把BEV理解为“给自动驾驶车辆画的一张上帝视角地图”。在这张图上,车道线是平行的,车辆是等比例的,障碍物的位置和朝向一目了然。为什么业界现在都在往BEV方案转?因为规划和控制模块天生就在BEV坐标系下工作——你想想,路径规划是在俯视图上画轨迹,而不是在图像上画曲线。
BEV的核心优势:
- 尺度统一:每个像素对应固定物理尺寸,消除透视畸变
- 时序友好:车辆运动在BEV下是刚体变换,容易做时序融合
- 端到端:感知结果直接喂给规划模块,不需要坐标转换
4.2 BEVFormer的核心设计——从图像到BEV的“变形金刚”
BEVFormer这个名字起得很妙。它把Transformer的“注意力机制”用在了BEV特征生成上。我之前看过不少BEV方案,有的用IPM(逆透视映射),有的用LSS(Lift-Splat-Shoot),但BEVFormer的思路让我眼前一亮——它用可变形注意力(Deformable Attention)代替了全局注意力,计算量直接降了一个数量级。
BEVFormer的整体架构可以拆成三个关键部分:
4.2.1 BEV Query——可学习的“锚点”
BEVFormer首先初始化一组可学习的BEV Query。这些Query对应BEV空间中的不同位置。你可以把它们想象成“探针”——每个探针负责去图像特征中“询问”自己位置附近有什么物体。
具体来说,BEV Query是一个形状为 [H_bev, W_bev, C] 的张量。H_bev和W_bev是BEV网格的分辨率,比如200×200。每个网格点对应一个C维的特征向量。这些特征向量一开始是随机初始化的,通过训练逐渐学会“关注”哪些图像区域。
# 伪代码:BEV Query初始化
bev_queries = nn.Embedding(H_bev * W_bev, embed_dim)
# 形状: [200*200, 256]
4.2.2 Spatial Cross-Attention——从图像到BEV的桥梁
这是BEVFormer最核心的设计。每个BEV Query会通过可变形注意力,从多视角图像特征中采样信息。
我刚开始看论文时有个疑问:为什么不直接用全局注意力?后来在项目中试了一下,6个摄像头图像加起来分辨率太高,全局注意力的计算量根本扛不住。BEVFormer的做法很聪明——每个Query只关注图像中与它投影位置相关的几个点。
具体流程是这样的:
- 将BEV网格点投影到每个相机的图像坐标系
- 在投影点周围采样K个偏移位置(比如K=4)
- 用可变形注意力聚合这些采样点的特征
避坑指南:我曾经在部署时踩过一个坑——投影计算时忽略了相机外参的标定误差。哪怕0.1度的偏差,在远距离投影时就会偏移好几个像素。建议在预处理阶段加入标定板校验,或者用在线标定补偿。
4.2.3 Temporal Self-Attention——让BEV“记住”历史
这部分是BEVFormer区别于其他单帧BEV方案的关键。Temporal Self-Attention(时序自注意力)让当前帧的BEV特征能够融合前一帧的信息。
为什么要这么做?单帧感知有个天然缺陷——遮挡。比如一辆车被前车挡住了一半,单帧图像很难判断它的完整轮廓。但如果你把前一帧的BEV特征拿过来,那辆车之前是完整可见的,信息就补全了。
具体实现上,BEVFormer维护了一个历史BEV特征队列。当前帧的BEV Query会与历史BEV特征做自注意力计算。注意,这里用的是“自注意力”而不是“交叉注意力”——因为历史BEV和当前BEV Query在同一个特征空间里。
# 伪代码:Temporal Self-Attention
# history_bev: 前一帧的BEV特征 [1, H_bev*W_bev, C]
# current_query: 当前帧的BEV Query [1, H_bev*W_bev, C]
# 对齐:根据自车运动将历史BEV warp到当前帧坐标系
aligned_history = warp(history_bev, ego_motion)
# 自注意力计算
temporal_attention = MultiheadAttention(
query=current_query,
key=aligned_history,
value=aligned_history
)
注意:时序融合有个隐含假设——场景中的静态物体在帧间是不动的。但动态物体(比如其他车辆)会移动。BEVFormer的处理方式是让注意力自己学习哪些区域是静态的(可以融合),哪些是动态的(需要抑制)。实际训练中,模型确实能学会这种区分。
4.3 Temporal Self-Attention到底解决了什么问题?
我总结一下,Temporal Self-Attention带来了三个实实在在的好处:
| 问题 | 单帧方案的表现 | 加入时序后的改善 |
|---|---|---|
| 遮挡 | 被遮挡物体漏检或速度估计不准 | 利用历史帧补全遮挡区域信息 |
| 速度估计 | 需要多帧后处理才能算速度 | 时序注意力直接输出带速度信息的特征 |
| 静态场景理解 | 每帧独立检测,车道线容易抖动 | 时序平滑,减少帧间抖动 |
举个例子,我在高速场景测试时发现,如果没有时序融合,远处一辆静止的工程车在单帧里看起来就像路边的广告牌。但加上Temporal Self-Attention后,模型能通过多帧的视角变化判断出它是一个三维物体——这就是时序信息的价值。
4.4 一点个人体会
BEVFormer这套设计,说白了就是把“多视角融合”和“时序融合”两个问题统一到了一个Transformer框架里。我去年在一个量产项目里尝试复现它,最大的感触是:训练收敛比想象中慢。原因在于BEV Query需要同时学习空间位置和时序关联,相当于两个任务一起学。
我的建议是:先用单帧数据预训练Spatial Cross-Attention部分,冻结后再加入Temporal Self-Attention微调。这样收敛速度能快30%左右。嗯,这个小技巧在论文里可没写,是我自己试出来的。
总结一下BEVFormer的核心设计思路:
- 用可学习的BEV Query作为“探针”
- 用Spatial Cross-Attention从多视角图像中提取空间信息
- 用Temporal Self-Attention融合历史帧,解决遮挡和速度估计问题
- 三者通过Transformer的注意力机制统一在一起,实现端到端训练
下一章我会深入BEVFormer的代码实现,包括如何构建BEV Query、如何实现可变形注意力采样、以及如何对齐历史BEV特征。到时候咱们手撕源码,把每个细节都过一遍。