干活之前先聊点实在的:BEV-former这名字,搞自动驾驶感知的朋友这两年耳朵都快听出茧子了。但越是热得发烫的概念,越容易变成"看了不少解读,真问起来还是一头雾水"。我自己刚接触那会儿也这状态——paper读了三遍,代码翻了两天,才把"Transformer怎么做BEV"这块硬骨头啃下来。
这篇文章不打算做那种逐行读代码的保姆级教程,我按自己理解的路子,把这个工作里真正关键的几个设计点拆开讲清楚:它到底在解决什么问题、为什么用Transformer做BEV比之前的路子更优雅、以及实际训练部署时你会踩到哪些文档里不写的坑。无论你是刚入行想搞懂这个方向的研究生,还是已经在做量产感知想参考它的思路的工程师,这篇简读应该都能帮你在脑子里搭起一张完整的地图。
1. 为什么BEV-former会火:先看它在解决什么
1.1 自动驾驶感知的老大难:多相机特征在"打架"
要做环视感知,车载摄像头一般有6个,分布在车四周。难点不是单张图要看准,而是这6张图各看各的,怎么把它们拼成车周围一个完整、统一的3D空间描述。
这个问题在BEV(Bird‘s Eye View,鸟瞰图)火起来之前,业内主流做法是"前视视角"路线:每个相机各自做2D检测,拿到2D框和类别,再用几何关系投影到3D空间。听着挺顺,实际一用就崩——单目2D检测本身就没有可靠的深度信息,投影出来的3D位置经常飘,不同相机对同一个目标的输出还会打架,得写一大堆后处理逻辑去融合。
后来有了LSS(Lift, Splat, Shoot)这套思路,才算是打开了新的窗口。LSS的核心是显式预测每个像素的深度分布,然后把图像特征"抬升"到3D空间,再压到BEV俯瞰图上去做检测。这条路能work,但它有个绕不开的硬伤:要预测稠密的深度分布,计算开销大、显存吃紧,而且深度预测不准时,整个BEV特征也就跟着歪了。
1.2 BEV-former给出的答案:让模型自己学会"怎么看"
BEV-former是清华大学和理想汽车在ECCV 2022上发的工作(arXiv:2203.17270,我读的时候代码也开源了)。它干了一件很"叛逆"的事:把LSS里显式的深度估计整个扔掉,改成用Transformer的注意力机制,让模型自己去决定每个BEV位置该看哪些图像区域。
用人话说,LSS的做法是"先把每个像素塞到3D空间,再看车周围有什么";BEV-former是"先在车周围铺好一张网格(BEV格子),然后问每一个格子:你想看哪个相机的哪块图像?自己去查"。这个范式一换,整套pipeline就变成了一个端到端可微的Transformer结构,不再需要手工设计深度分布预测那一大坨东西了。
我不夸张地说,这个思路几乎奠定了后续两年BEV感知工作的基础框架。哪怕今天再看,它依然是理解"怎么用Transformer做多传感器统一空间表达"最合适的入门样本。
2. BEV-former的整体架构:一次把每一个模块的作用捋明白
2.1 先建立整体印象:Encoder + Decoder,Transformer标准双件套
BEV-former整体走的是标准Transformer的encoder-decoder结构,只不过它输入的不是一串文本token,而是6路环视图像,输出也不是一个分类概率,而是车周围的BEV特征图。
我把整个流程按数据流向拆成几步:
- 6路环视图像分别过同一个CNN backbone(ResNet-101-DCN),拿到多尺度的图像特征图。这一步就是很常规的图像特征提取,各大检测网络都在用,不多说。
- 这些图像特征进入Transformer encoder,做尺度内和跨尺度的自注意力,本质上是在让每张图内部、以及不同尺度之间的特征先互相"对齐"一遍,把目标在不同尺度上的表达统一好,方便后续去查。
- Transformer decoder就是整篇工作的精华所在。它接收一组BEV query——你可以把它理解为车周围均匀铺开的一张网格,每个格子代表一个固定大小的物理空间。比如默认配置下,BEV特征是200x200的网格,每个格子对应0.512米,车前后左右各覆盖约51.2米范围。这些query就是我们要在decoder里逐步精炼的特征。
- 每个BEV query通过deformable attention(可变形注意力)去查图像特征里的信息,得到更新后的BEV特征。经过6层decoder的层层迭代,最终输出的BEV特征图再交给后面的检测头(比如Deformable DETR风格的检测头)去出3D框和类别。
2.2 Encoder部分:其实它干的是"图像内部打底"的活
很多人看BEV-former会把注意力全放decoder上,其实它的encoder也很值得琢磨。
先过backbone拿到多尺度特征,从backbone不同stage出来的特征分辨率不一样(比如1/16、1/32这样的比例),目标大小也是有大有小。直接把这些特征一股脑拿去做跨视角融合,细节信息和语义信息是没办法对齐的。BEV-former的encoder做的就是在这一步先把内部的尺度和空间关系摸清楚,用的是标准的deformable self-attention。
为什么不用普通的多头自注意力?因为图像特征分辨率太高(拿1/16分辨率都可能有几十万像素),直接做全局attention,计算量是O(N²),显存直接爆炸。deformable attention的聪明之处在于,它不让每个位置去看全图,而是只让模型自己学会挑几个关键点(默认采样4个点)去看,这样就把复杂度降到了接近线性的水平。这个设计在BEV-former整个结构里一以贯之,算得上是它的骨架逻辑。
2.3 Decoder部分:BEV query怎么变成真正有用的BEV特征
Decoder是BEV-former的灵魂,这一步需要多看一会儿。
每个BEV query其实就是一个可学习的特征向量(带一个固定的BEV坐标位置),在decoder每一层里,它会通过deformable attention去看自己对应物理空间位置附近的三维柱状区域(叫pillar,高2米、默认采样4个高度),把这些高度上的图像特征聚合起来,慢慢把自己更新成对这个位置"到底有什么"的更准确理解。
这个过程我后面会专门展开,这里先记住一点就行:经过多层迭代,每个BEV query都汇聚了它对应区域的多相机、多高度信息,整个BEV特征图就变成了对车周围场景的一种"鸟瞰状态描述",后面不管做检测、分割还是预测,都用它来做,通用性很强。
3. 三个核心设计的深度拆解:为什么非它不可
3.1 先在BEV预设一个网格:Grid Mask的意义
BEV-former在让query查询图像信息之前,先做了一步很多人忽略但非常关键的操作:根据相机的内外参,把每一个BEV query对应的3D坐标投影到图像平面上,找出它在哪些相机视野里是可见的,生成一个可见性mask,比如叫grid mask。
这个动作的工程意义极其重要。一辆车周围6个相机,2m高的pillar投影过去,可能只在1到2个相机的视野范围里,其他视角相机根本看不到。如果不提前算好mask,每个BEV query都对6路图全查一遍,计算量凭空多3倍,而且查了一堆无效区域,对特征表达也是一种噪声干扰。
从数学上讲,这一步是利用已知的标定参数加了一个先验的几何约束,把"该看哪里"的范围大大缩小了。它不增加任何可学习参数,纯纯的"白嫖"几何信息来降低计算开销。这也是BEV-former能跑得动的关键原因之一。
现实中有一类坑就出在这里——标定参数不准。grid mask是根据外参投影出来的,如果你的相机在车辆行驶中发生了轻微位移,没有做在线标定修正,那你grid mask画的可见范围就跟实际不符,该看到的区域被mask挡掉了,不该看的区域反而被纳入查询,BEV特征第1层就脏了,后面怎么迭代都救不回来。这也是我一直反复给人强调的:BEV路线对传感器的标定稳定性要求,比2D检测时代高了不止一个量级。
3.2 理解空间交叉注意力:从图像向BEV"搬运"信息
核心还是这个公式的意思:每个BEV query会先从自己的坐标出发,在z轴上均匀采样4个高度(-1m到2m),构成一个柱状体。然后这个柱状体里的每个采样点,通过相机内外参投影到图像的2D平面上,拿到2D参考点。接下来再用deformable attention,围绕这个2D参考点,在图像特征上采样4个点,加权聚合得到查询结果。
把这个过程翻译成人话,就是:"我这个格子想知道地上半空这片空间里有什么,那我先把这块空间坐标换算成每个相机图像上的像素坐标,然后去看那些像素附近长什么样"。
这里有个很妙的设计点:这个"看哪里"的采样偏移(offset)不是手工设计的,而是网络自己学出来的。LSS是老老实实把深度分布预测出来,相当于"先想清楚像素在哪,再搬到BEV";BEV-former则是"我先去图像上瞄一眼,反正注意力会帮我挑有用的位置"。这个设计让它少了深度预测带来的显存压力,而且可以任意扩大BEV感知范围,不需要为了遥远位置额外增加深度bin的数量。
我在工程落地时有个体会:BEV-former这种"按需查看"的方式,在感知范围比较大的场景下优势特别明显。比如你想把BEV范围扩到100米,LSS的深度分布密度就得跟着提高,计算量直接翻倍;BEV-former这边只需要多加几个query网格,再配合多尺度特征查一查就行,改善是线性的,不是爆炸式的。
3.3 时间信息怎么做:时间自注意力模块
单帧图像是有天然缺陷的——遮挡、运动模糊、并且速度信息丢失。BEV-former用了个很直接但有效的办法:加了一个时间自注意力(Temporal Self-Attention)模块,把前一帧的BEV特征拿过来跟当前帧融合。
操作细节是这样的:先把BEV特征按自车位姿变换到当前帧的坐标系下(如果历史帧和当前帧车辆朝向有变化,需要做坐标对齐),然后把当前帧的BEV query和上帧的BEV特征拼在一起,用标准的self-attention去处理。这样一个有速度的物体,当前帧的特征就能通过查询历史帧特征,感知到"它之前在那里",从而更好地推断当前状态。
这个设计其实有点"讨巧"——它没有做很复杂的光流或者预测网络,就是拿Transformer的自注意力机制天然能建模序列关系这个优势,加了历史BEV特征作为输入而已。工程上有个坑:第一帧没有历史特征怎么办?BEV-former是直接让模型去学一个可学习的初始化,效果也还能接受,因为模型慢慢就学出来了"没有历史就多看当前帧"这种鲁棒性行为。
另外,带时间的BEV特征在做自车运动补偿时,一定要算准历史帧和当前帧的位姿变换。我之前实际测试时,不做对齐直接裸拼接,动态目标的特征会明显"拖影",检测头报出一堆置信度不高的框。做了对齐后,这个现象基本就消失了。
3.4 从LSS到BEV-former:范式转变到底赢在哪
我在这节开个头就提到过LSS,这里再往深了对比一下,因为这是理解BEV-former价值的关键。
LSS有三步:lift(预测深度分布,把特征抬到3D)、splat(把3D特征压到BEV)、shoot(在BEV上做检测或规划)。它的核心是先验是"深度必须显式预测",但这个预测是在每个像素上做离散分布预测,计算量很大,而且很多像素(比如天空)对深度预测本身就没有意义。
BEV-former则换了个玩法:把所有不确定性都交给注意力机制去隐式建模,我不预测深度,我直接去图像上查。这种做法的优势在于:
- 不需要维护一套深度预测分支,网络结构更简洁。
- 对远距离目标更友好——没有了深度bin数量的限制,通过注意力采样天然就能覆盖远近不同区域。
- 训练收敛速度更快、更稳定。LSS如果深度分支训练不充分,BEV特征整体都是脏的;BEV-former的query是一层层迭代精炼的,哪怕前面几层效果差点,后面还能纠偏。
当然这也不是说BEV-former全面碾压LSS。之前有个实际感受是,在训练数据量不够大的情况下,显式的深度监督(LSS)反而是种很强的正则化,让模型更快理解几何关系。BEV-former如果训练数据不足,注意力很容易直接塌缩到只看某几个固定的图像区域,泛化性反而会受影响。所以"哪个范式更强"真得看数据量来谈。
4. 检测头与训练细节:BEV特征拿完之后怎么用
4.1 检测头:Deformable DETR,不是随便选的
BEV-former拿到200x200x256的BEV特征图之后,交给的是Deformable DETR风格的检测头,而不是传统的anchor-based检测头。这个选择有它的道理:DETR系是query-based的,天生可以灵活输出不定数量的目标,不需要调一堆anchor参数来适配不同目标尺寸。
检测头里用的是3D检测专用设计:每个object query负责预测一个目标,输出内容包括3D框中心点(x, y, z)、长宽高、朝向角,以及类别置信度。训练时使用匈牙利匹配算法,把预测框跟GT框做一一匹配,然后对匹配上的框算loss。
这种"端到端无anchor"的方式很贴合BEV特征这种结构化的场景特征。BEV特征本身就是一个"俯瞰视图",物体尺寸相对固定、不会被相机透视效应扭曲,所以query-based方式收敛起来比在图像上用DETR要顺利得多。
4.2 训练细节:两阶段和多帧的取舍
说几个实际训练时的细节,这些在paper的appendix里有,但注意力不集中特别容易漏掉:
第一阶段只训练encoder和decoder的主干部分,第二阶段再解开时间模块一起训练。这几乎是必须的。如果一上来就带时间融合训练,模型会过度依赖历史帧特征,当前帧的特征提取能力反而训练不充分,后面一旦遇到历史帧质量差的情况,整体性能就直接崩掉。这个细节特别重要,直接决定了模型鲁棒性。
另外BEV-former在整个训练过程中是随机丢掉历史帧的,做法是把时间模块从每层变成随机层,有概率让模型回到"只看当前帧"的模式。这个设计本质上是在做正则化,防止模型对时序特征产生过强依赖。
关于loss,分类用的focal loss,回归用的L1 loss,跟Deformable DETR保持一致。整体上训练收益最好的结果是:空间交叉注意力(即BEV query去查图像)负责提取空间结构,时间自注意力负责提取时序运动信息,检测头负责把它们变成语义明确的目标框。
5. 实操中的常见问题与工程经验
5.1 显存爆掉?先检查这几个地方
BEV-former的训练显存占用其实不低,我第一次拿单卡A100(40G)尝试复现时,跑了没几个step OOM了,当时被恶心了好久。最后定位下来主要是这几点:
- 多尺度特征带来的特征图数量太多。如果对显存敏感,可以先砍掉最大分辨率的那一层,或者把encoder的层数从6层降到3层。效果会掉一些,但很多推理场景够用。
- BEV的网格分辨率。默认200x200,如果觉得算不起可以先降到100x100,每个格子物理尺寸变成1米,精度损失显著,但显存压力大幅下降。
- 时间模块的历史帧。如果显存不足,可以先把时间融合暂时关掉或者改成隔帧融合。
5.2 预测框在边界处乱跳?大概率是grid mask出问题了
测试时最容易遇到的怪脾气是:车周围的框总体还行,但BEV边缘区域的框位置很飘。这一类问题,八成不是检测头的问题,也不是Transformer结构的问题,而是grid mask和外参标定的耦合出了问题。
解决方案是先做一层"mask膨胀":计算grid mask时不要只算精确的可见性,而是给边界多加几个像素的冗余。这样即使你的外参有小的标定误差,边缘区域也不至于被整个mask掉。另一个实用技巧是,如果发现某些相机的外参可能不准(比如撞过一次车、换过摄像头),可以把grid mask的计算从"硬mask"改成"软mask",让模型自己学一个mask的温度系数,给它一点调整空间。
5.3 小目标漏检?时间模块反而是关键
纯从单帧图像看,远处小目标在BEV特征里就几个像素,检测头很难分辨清楚。但BEV-former的时间模块恰好是应对这种情况的利器:远处小目标在当前帧特征很弱,但它在前几帧里可能有更明显的表观特征。
我测试过一个小实验:把时间融合关掉,远距离小目标的recall直接掉好几个点;打开之后有明显回升。这也提醒了一个训练上的要点——如果要做数据增强,比如图像裁剪、翻转,角度变化时要特别注意BEV坐标的同步变换。很多复现效果差,不是网络结构不对,而是数据增强把BEV网格和图像特征的空间对应关系破坏了。
5.4 BEV-former(v1)的已知短板与后续演进
BEV-former v1有个明显的短板:它的相机内参是固定的,如果同一辆车在不同场景用了不同的相机内参,模型需要重新训练才能适配。后续BEVFormer v2做了很大改进,采用了"先2D检测、再跨视图3D查询"这种两阶段的思路,还在多任务统一上下了功夫,性能上限高了不少。
另外,BEV-former本身没有做泊车这种低速场景的特殊优化。如果要做近距离低速场景感知,你会发现它的BEV网格分辨率不太够,1米或者0.5米的分辨率在车头前1米范围内很难精细到能用来做泊车的程度。这种情况就得考虑是不是该往上加一层局部高精度BEV了。
但我的整体看法很明确:BEV-former v1现在依然是入局BEV感知最佳的一个解剖样本。它结构紧凑、思路清晰、不依赖超大规模的算力,而且整个框架里每一个模块都有明确的设计动机——你在它身上学到的"为什么这么设计",几乎可以平移到后面所有BEV路线上,包括BEVFormer v2、UniAD这类复杂得多的系统。
最后再补一个实际容易踩的坑,也是我比较感慨的一点:复现BEV-former时,比模型结构更费时间的往往是数据处理那一环。BEV感知需要把3D标注、相机外参、时间戳、自车位姿全部对齐到同一个时间轴上,稍微有点偏差,注意力查询就会出现"看错时间、看错位置"的情况。先把数据链路理清楚、把可视化调试工具做好,再上模型调参,这条路我个人觉得会顺畅非常多。