我第一次认真做多相机感知的时候,最大的困惑不是网络长什么样,而是:六个朝向完全不同的摄像头,凭什么最后能变成一张干净利落的鸟瞰图?BEVFormer这篇论文回答的正是这个问题——它用时空Transformer,从多相机图像直接学习Bird's-Eye-View表示,是整个BEV范式里绕不过去的里程碑。这篇文章算是我读完论文之后的结构化笔记,把BEV queries怎么定义、空间交叉注意力怎么跨相机聚合、时间自注意力又怎么利用历史帧这几件事掰开揉碎讲清楚,适合正在读自动驾驶感知论文、或者想复现BEV类方法的同学参考。
开头先用一个生活场景帮你建立直觉:如果你坐在驾驶座上,实时接收来自前后左右的视频画面,你并不会在脑子里把六路视频各自单独处理,而是会本能地把它们"换算"成自己周围一圈空间的俯视图——哪里有人、哪里是路口、哪辆车在加速。BEVFormer做的就是这个换算,只不过它把"换算"这件事交给了Transformer。相比传统"先逐图检测、再投影融合"的思路,它直接把整个3D空间切成了网格,让每个网格自己去对应的图像里找证据。理解了这个角度反转,后面所有细节都会顺很多。
1. 透视视图的局限:为什么整个行业都在往BEV转
1.1 每个相机单独检测再融合,到底别扭在哪
早期多相机3D检测的主流写法是"每个相机各检测各的,再把2D结果投影到3D空间做融合"。这条路听起来直觉上很对,实际操作时会碰到几个绕不开的问题。
第一个问题是遮挡和重叠。透视图里近处的大车会盖住远处的小车,每个相机单独检测时,被遮挡的目标直接消失。第二个问题是尺度不一致。同一个目标停在正前方1米和10米,在图像里占的像素差了几十倍,检测器要同时应对这两种极端尺度,压力全在特征表征上。第三个问题是跨相机拼接。目标从右侧相机运动到前侧相机时,如果不做跨相机的特征关联,同一个目标很容易被重复检测,或者干脆跟丢。
BEV视角天然规避了这些问题:鸟瞰图上物体不会被前景遮挡——或者说遮挡转移到3D空间里由模型自己推理;尺度也被统一——一个0.4米分辨率的网格无论目标远近都是固定大小;更重要的是,所有相机被拉到同一个全局坐标系里,跨相机的目标拼接变成了"同一个平面上的相邻网格",而不是"两张图之间的像素匹配"。
1.2 BEV表示对自动驾驶感知,是一种天然友好的输出格式
自动驾驶的后端模块——预测、规划、控制——几乎全部工作在自车坐标系或俯视语义图上。感知模块如果直接输出鸟瞰视角的特征,下游就能无缝衔接,不需要再做一个"透视转俯视"的中间层。
这也是BEV表示最近几年成为感知主流范式的原因。它就像一个统一语言:检测、车道线分割、可行驶区域判断、运动预测,大家都用同一张俯视特征图表达,特征之间还能互相补充。BEVFormer的作者正是看重这一点,所以整篇论文的落点不是"用BEV做检测",而是"如何从多相机图像学到高质量的BEV表示"。检测和分割只是用来验证这个表示够不够好的两个下游任务。
1.3 但BEV不是白来的:最大的工程代价在于视图变换
把透视图变成俯视图,最朴素的想法是逆透视映射(IPM)。它假设地面是平面,把图像像素直接投影到地面坐标系。问题在于路面上全是立体目标:一个行人站在地面上,按平面假设投影后会被无限拉长,看起来像躺在地上的一条影子。IPM只能处理"地面本身",处理不了"地面上的东西"。
后来LSS(Lift-Splat-Shoot)类方法走的是显式深度估计路线:逐像素预测深度分布,把图像特征"抬升"到3D体素空间,再"拍平"成BEV。它能处理立体目标,但高度依赖深度估计的质量,而且显式深度监督或者稠密深度损失的成本很高。
BEVFormer选择了一条完全不同的路:不放图像特征做变换,而是先在BEV空间铺一批可学习的查询点,让每个查询点去图像里找自己的证据。这就是"由空间问视图"的思路——视图变换不再是几何投影的一锤子买卖,而是变成了一个可学习的注意力过程。理解这一点,BEVFormer的所有结构设计就都有了支点。
2. 核心架构拆解:BEV queries把3D信息摊平了再融合
2.1 Grid-shaped queries:一张"网格布"铺在路面上
BEVFormer在自车周围定义了一块3D空间,论文在nuScenes上的默认配置是横向和纵向各51.2米,共102.4米见方。这块空间被分成200×200的网格,每个网格实际大小为0.4米。也就是说,整个BEV区域就是一张密铺的网格布,每个格子都是一个query。
为什么用200×200而不是别的尺寸?因为要在表达精度和计算量之间取平衡。0.4米的分辨率放在当前感知任务里是一个相对经济的值——再细一倍,query数量翻四倍,注意力计算量直接爆炸;再粗一倍,小目标的位置精度就不够了。这个分辨率的选择在后来的很多工作里都被沿用,说明它基本踩中了大家公认的甜点。
每个BEV query都带一个可学习的嵌入向量,同时拼接上它所对应的网格坐标位置编码。位置编码有两个作用:一是让网络知道"这个query管的是哪块地面",二是让多个query之间形成相对位置关系。在时空注意力里,位置编码也是区分"同一个位置不同时刻"的重要线索。
2.2 Spatial Cross-Attention:3D参考点如何打到2D图上
空间交叉注意力要解决的核心问题是:一个BEV query,该怎么决定去看哪个相机的哪个像素?
BEVFormer的做法很巧妙。它把每个BEV query从地面网格"竖起来"——在z轴上均匀采样4个3D参考点。这4个点代表"这个网格位置在不同高度上可能存在物体"。比如自行车轮子在地面附近,车把在1米左右;卡车货厢可能到3米。z轴的取值范围是-30米到1米,默认情况下采样点高度都很低,因为地面目标和贴近地面的物体是感知的主体。
然后,这4个3D参考点通过相机的内外参数矩阵,投影到6个相机的图像平面上。投影点落在哪个相机的图像内,注意力就在那个相机的图像上围绕投影点做采样。一个query往往会同时落在多个相机里——比如一个站在右前方的行人,会同时出现在前视相机和右前相机的画面里。这样一来,跨相机特征融合就不是"检测完再拼",而是在注意力层面直接对多视图证据做聚合。
论文采用可变形注意力来采样:每个投影点附近学习8个采样偏移位置,网络决定从这些位置的图像特征里提取多少信息。多个相机、多个参考点采到的特征最后通过加权求和,更新这个BEV query。这一套操作下来,BEV query就变成了"在图像中收集完证据后的空间特征表达"。
我在复现时发现,理解这个流程最容易卡的地方是坐标变换的方向。记住这句话:永远是从3D BEV出发,投影到2D图像,而不是反过来。一旦你把方向搞反,后面所有维度都捋不顺。
2.3 Temporal Self-Attention:缓存的历史BEV是怎么帮现在回忆的
只靠单帧图像,很多感知难题无解。物体被完全遮挡、远处目标太小、运动方向判断不清,都需要时间维度上的信息来帮忙。BEVFormer在结构上做了一个非常优雅的设计:把历史帧的BEV特征缓存下来,作为当前帧时间自注意力的key和value。
具体做法是:当前帧的每个BEV query,在历史BEV特征上沿自己对应位置做可变形采样。这部分注意力输出与当前帧的BEV特征相加,就得到带着"时间记忆"的BEV表示。
这里有一个必须处理的细节——自车在运动。t-1时刻的某个位置,到t时刻已经因为车辆移动而漂移了。比如路口的一个路灯杆,前一帧在自车右前方5米,这一帧可能就到了右前方4米。如果直接用当前query去历史BEV里按老位置采样,采到的全是"拖影"。
BEVFormer用自车运动信息来对齐:根据两个时刻的自车位姿推算相对位移,把历史BEV特征的整体坐标平移到当前自车坐标系下。对齐之后,同一个物理位置在历史和当前特征里就对应于同一个BEV坐标,时间注意力才能学到真正有价值的运动信息。
这个时间注意力的价值在消融实验里看得非常清楚。论文把有时间建模的版本和没有时间建模的版本做了对比,后者在高难度场景里表现明显下滑。更直观的感受是:一辆车完全被前面的大货车挡住时,单帧模型彻底丢失目标;而带时间建模的模型能在历史帧里找到它之前的锚点,即使当前完全看不到,也能维持对它的预测。
2.4 为什么这里还带了一个"Deformable"前缀
BEVFormer的注意力层都被冠以Deformable前缀,原因是它直接借用了可变形注意力机制。简单说,传统Transformer的注意力要在所有位置上算相关性,而可变形注意力只在一组稀疏的、由网络预测的偏移位置附近采样。
对BEVFormer来说,这个设计几乎是必须的。BEV区域有200×200等于4万个query,如果每个query都对6个相机的全图做一次全局注意力,哪怕图像不大,计算量也完全不可接受。可变形注意力把"去哪里采样"变成了一个轻量回归问题,每个query只需要在固定数量的采样点上做特征聚合。
更关键的是,可变形注意力和"参考点投影"之间形成了很好的配合。参考点投影给了采样一个物理上的位置先验——从BEV网格出发,经过射影几何告诉注意力"应该去图像哪个区域附近找证据";注意力再在这个先验附近做微调,学一些几何投影无法覆盖的偏差,比如相机标定误差的补偿、物体高度的不确定性。你会发现这里没有一个参数是多余的,每一个设计都在解决一个实际问题。
3. 三大关键设计背后的权衡逻辑
3.1 为什么不用全局注意力,而是把稀疏采样坚持到底
我第一次读论文时问自己:既然Transformer这么厉害,为什么BEVFormer不直接用vanilla self-attention,让所有BEV query和所有图像token都互相看一遍?很快算了一笔账就放弃了。
4万个BEV query,6路相机,假设每路图像缩放到小尺寸也有2500个token,全图注意力就是4万×1.5万的复杂度,足有6亿量级的交互,这在任何单卡上都跑不动。即便强行切块做局部注意力,也会丧失跨相机全局关联的能力。
可变形注意力换来的是计算量和表达能力的平衡。每个query只采样固定数量的点,比如4个参考点、每个参考点周围8个采样点,那一个query的图像交互就限制在几十的量级。更重要的是,这些采样点不是均匀撒在图上,而是通过可学习的偏移量动态调整。网络可以决定某个BEV格子应该看图像里的人和地面,还是看天花板——这种灵活性是普通卷积或简单投影完全给不了的。
3.2 视图变换中"深度估计"被隐式取代了
LSS类方法的核心是先逐像素估计深度,再把图像特征沿着相机射线"填充"到3D空间里。你把它理解成给每张图先画好距离刻度,再按刻度把画布上的内容贴到立体空间。这种方式很直观,但显式深度估计本身就是一个额外负担:训练深度估计需要监督信号,哪怕用自监督也避免不了额外的损失项。
BEVFormer的query-driven方式则完全不同。BEV query通过可变形注意力直接在图像里找证据,不需要先预测深度。深度信息被隐式编码进了网络学到的采样权重和采样偏移里。一个BEV query如果发现某个图像位置的特征能帮助自己理解空间中的目标,它就会逐渐学会在那些位置投入更多注意力。
这种隐式做法省去了深度监督,训练目标更干净。代价是模型对深度的利用没有LSS类方法那么"外显",在某些极端尺度场景下,比如远处很小的目标,可能会比显式深度方法弱一些。但端到端训练的收益在大多数场景下更明显,而且省下来的计算量可以投入到更强的时序建模中去。
3.3 从检测到分割:同一套BEV特征的可复用性
论文不仅用BEVFormer做3D目标检测,还在同一套BEV特征上做了地图分割实验。检测任务走的是query refinement路线;分割任务则直接把BEV特征图接一个解码头,输出道路边界、车道线、可行驶区域等语义。
这引出了一个很重要的工程思想:BEV encoder是一个通用底座。一旦把多相机图像高质量地转换成BEV特征,下游任务就都可以在这个底座上搭新头,而不是每个任务单独做一个视图变换模块。这篇论文之后,很多自动驾驶感知模型都沿用了这个思路——用检测预训练好的BEV编码器,再迁移到分割、预测等任务上去。这种"先学通用表示,再做任务微调"的方法论,在BEV时代被验证得越来越扎实。
4. 论文里容易被忽略但很关键的实操细节
4.1 预训练与微调的正确打开方式
如果只看论文的主体结构,很容易低估预训练权重的作用。BEVFormer的完整流程是先在大规模检测数据集上把BEV编码器训练好,再在需要的时候把编码器冻结或微调,接到下游任务上。
实际操作中我体会最深的是:不要急着冻结编码器。在分割任务上微调时,如果数据量足够大,保留编码器的梯度更新、用一个小学习率去训,往往比直接冻结效果好。只有数据量很小、担心过拟合时,冻结编码器才更稳妥。论文的实验更多是在展示"这套特征具备可迁移性",而具体怎么迁移,还是要看你手头的数据规模。
4.2 时间对齐中的"姿态漂移"问题
时间自注意力依赖历史BEV特征,而历史特征的是否精确,又依赖自车位姿的精度。在真实数据上,位姿给出的相对变换通常是可靠的,但偶尔会有厘米级的漂移。
如果对齐稍微错位,时间注意力在历史特征里采样到的信息就会出现空间误差。这个误差对近距离大目标影响不大,但对远处小目标几乎是致命的——本来就只有几个网格的响应,一错位就采到背景上去了。从这个角度也解释了为什么带时间建模的模型在高速公路这种"远处目标密度高"的场景里收益最大。
4.3 从BEVFormer能顺藤摸瓜摸出哪些后续脉络
读论文不只是读懂这一篇,更重要的是知道它如何被后续工作继承和发展。时间建模这条路,后来被StreamPETR等改进成"用临时query替代缓存BEV",减轻了缓存特征和当前特征之间的表示鸿沟。任务统一这条路,UniAD这类工作直接拿BEV特征作为决策模块的输入,把检测、跟踪、预测、规划全部串起来。
还有一条分支是稀疏query方向,减少BEV网格的数量,只用几十个query做目标级查询,代表性的思路叫Sparse4D系列。它们很多概念都能在BEVFormer里找到对应:参考点投影、时间对齐、可变形采样。所以把BEVFormer的坐标-投影-采样链条吃透,后面读这几条技术脉络都会轻松很多。
5. 复现和使用中的几个坑(个人经验)
5.1 训不动?先检查参考点在图像边界外的比例
我第一次复现时遇到的情况是:loss掉得很快,但验证集指标卡住不动。后来统计了一下训练初期每个query平均能命中几个相机,发现问题出在参考点分布上——很多偏远位置的query投影到图像边界之外,完全没有拿到图像证据。
排查方法是打印一批训练样本,看每个BEV query投影到各相机后落在图像内部的平均点数。如果这个比例偏低,优先检查z轴采样范围。默认配置里参考点高度都很低,如果目标物离得很远或者相机俯仰角设置差异大,就需要适当调整参考点高度。还有一个很实用的技巧:把BEV范围稍微缩小一点点,让边缘query不至于太稀疏,整体收敛速度会有肉眼可见的提升。
5.2 多帧缓存的内存开销比想象中大
时间自注意力需要缓存历史BEV特征。训练时如果采样3到5帧历史,显存占用会显著上涨。很多人在单卡上复现失败,不是因为算力不够,而是被这个缓存撑爆了。
我的建议是最开始只用单帧历史跑通流程,验证逻辑没问题后再逐步加大帧数。另外注意对历史BEV特征做梯度截断——历史帧的梯度传播对训练的贡献有限,但显存开销巨大。在实际工程化部署中,推理阶段缓存几帧的特征成本是完全可控的,训练阶段的压力主要来自反向传播,所以这个截断操作能帮你省出非常可观的显存。
5.3 相机外参标定误差在BEV方法里会被方法放大
透视模型里,标定误差可能只造成几个像素的偏移,效果上还能容忍。但BEV方法里,3D参考点要经过完整的投影矩阵打到图像平面,任何微小的外参偏移都会被投影过程放大,尤其是距离车远的位置。
典型现象是:远处物体整体漂移,或者同一个目标在相邻相机拼接处出现重影。遇到这类问题不要急着调网络,先验一下相机的内参外参标定。我们在一个模拟项目里做过测试,外参旋转误差哪怕只有0.1度,对50米开外的目标也会造成接近1米的定位偏差。所以在真实车辆上部署BEV模型,标定精度直接决定了模型上限。
5.4 检测和分割联合训练时的损失平衡
如果你想在同一套BEV特征上同时接检测头和分割头,损失怎么配比会影响两边效果。简单地把检测损失和分割损失一比一相加,往往会导致分割头收敛过快,反过来拖慢检测头的学习节奏。
我在实践里比较稳妥的做法分两步:先用检测任务把BEV编码器训练收敛,再开分割头把编码器和解码头一起精调几轮,分割损失占比控制在一个较低的水平。比一次性把两个头从零开始联合训练稳定得多。这一点论文里不会明说,属于跳进代码里才看得到的经验。
最后再分享一个读论文的小技巧
读BEVFormer这类以空间结构为核心的论文,我的习惯是第一遍直接把坐标定义抄在纸上:自车坐标系怎么建立、BEV范围多少、z轴怎么采样、相机的内外参从哪里读。坐标定义一旦清楚,整个网络就是你看着坐标变换一步步走完的,而不是跟着公式机械地看下去。
这篇论文最值得我反复咀嚼的地方,其实不是某个具体的注意力层,而是它"让表示直接回答空间问题"的思路——不管下游任务是检测、分割还是更上层的决策,先问清楚特征在哪里、代表什么、怎么获取,再谈网络结构。把这个思维方式带进你读的每一篇BEV论文,比记住任何一张架构图都更有用。