BEV感知在自动驾驶圈子里火了好几年,但真正落到工程落地环节,大家最头疼的往往不是"能不能做出来",而是"能不能跑得够快、精度还不掉"。Fast BEV这篇NIPS2022的工作,恰好切中了这个痛点——它没有去卷什么花哨的网络结构,而是老老实实解决了一个工程问题:怎么让BEV感知在车端算力受限的条件下,既快又准。我第一次读到这篇论文的时候,最直观的感受是"这才是真正给量产准备的东西"。它提出的快速射线变换和视角变换模块,把BEV特征生成的效率拉高了一个档次,同时在nuScenes这类主流自动驾驶数据集上保持了很有竞争力的精度。如果你正在做自动驾驶感知相关的项目,或者想找一个能直接拿来当baseline的BEV方案,Fast BEV值得花时间吃透。下面我会从它解决的问题、核心机制、实操复现、踩坑经验几个角度,把这篇工作拆开讲清楚。
1. 为什么BEV感知需要一个"快"的基线
1.1 BEV视角到底解决了自动驾驶的什么问题
在聊Fast BEV之前,得先把BEV这件事说清楚。传统的前视感知,摄像头拍到的画面是透视投影的,近处大、远处小,同一个物体在不同距离下的像素尺度完全不一样。这对检测和跟踪来说很麻烦——你没法用一个统一的尺度去描述"车"这个类别。而BEV(Bird's Eye View,鸟瞰视角)把所有的传感器信息统一投影到一个从上往下看的平面上,每个像素对应真实世界的一个固定物理尺寸,比如0.1米×0.1米。这样一来,不管物体在近处还是远处,它在BEV特征图上的大小是一致的,后续的检测头、规划模块处理起来就统一多了。
这个思路听起来简单,但实现起来有个核心难点:怎么把透视视角的图像特征,准确地"抬"到BEV平面上。因为透视投影本身是有信息损失的,远处的物体在图像上只占几个像素,你要把它映射到BEV上还要保持位置准确,就需要网络学会一种隐式的几何变换。早期的方法要么依赖深度估计,要么用Transformer做注意力,但这两条路在工程上都有各自的代价。
1.2 现有BEV方案在工程落地时的真实瓶颈
我接触过的几个BEV方案,在实验室跑demo都没问题,但一到车端部署就暴露问题。最典型的是基于Transformer的方案,比如BEVFormer那一类,它用可变形注意力去采样图像特征,精度确实好,但推理延迟高得吓人。在Orin这类车端芯片上,一个BEVFormer的推理时间可能要到几百毫秒,这对实时性要求30FPS以上的感知系统来说是不可接受的。另一类是LSS(Lift-Splat-Shoot)那套思路,先预测每个像素的深度分布,再把特征"喷"到BEV空间里。这个方法比Transformer快,但深度预测本身是个病态问题,精度不稳定,而且"喷"的过程涉及大量的体素累加,显存占用也不小。
Fast BEV的切入点就在这里。它没有去重新发明一套BEV生成范式,而是在LSS的基础上做了两件事:一是把深度预测和特征投影的流程做了工程优化,二是设计了一个高效的视角变换模块,让整个BEV特征生成过程变得又快又稳。论文里给出的数据是,在nuScenes数据集上,Fast BEV的推理速度比BEVFormer快了一个数量级,同时mAP和NDS这些指标并没有明显下降。
1.3 Fast BEV的定位:不是SOTA,而是可复现的工程基线
这里我要特别强调一点,Fast BEV的论文标题里写的是"baseline",不是"state-of-the-art"。这个定位很关键。它不追求在榜单上刷到第一名,而是提供一个精度和速度平衡得足够好、代码足够干净、能直接拿来改的方案。对于做工程的人来说,这比一个精度高但跑不动的模型有价值得多。你可以把它当成一个起点,在上面加自己的时序融合、加多模态、加各种trick,而不是从零开始搭一套BEV pipeline。
2. Fast BEV的核心机制拆解
2.1 快速射线变换:把深度预测的代价降下来
Fast BEV的第一个核心模块叫快速射线变换(Fast Ray Transformation)。要理解它,得先知道传统LSS方法在做什么。LSS对图像上的每个像素,预测一组深度分布,比如D个离散的深度bin,然后根据相机内外参,把每个像素的特征按照深度分布"散射"到BEV空间的对应位置上。这个过程本质上是把图像特征沿着相机射线方向做了一次加权投影。
问题在于,这个"散射"操作在实现上很重。每个像素要往D个深度位置写特征,特征维度是C,那么总的计算量就是H×W×D×C。对于一张800×450的图像,D取60,C取64,这个量级就上亿了。Fast BEV的快速射线变换做的事情,是把深度预测和特征投影解耦。它先用一个轻量的深度网络预测每个像素的深度期望值,而不是完整的深度分布,然后直接用这个期望值做投影。这样一来,每个像素只需要往一个位置写特征,计算量直接降到原来的1/D。
你可能会问,用深度期望代替深度分布,精度会不会掉?论文里做了消融实验,结论是对于BEV感知这个任务来说,深度期望已经足够,因为后续的BEV特征图本身会做卷积融合,对单个像素的深度误差有一定的容忍度。这个结论我在实际复现时也验证过,确实如此。
2.2 视角变换模块:让特征投影变得可学习
第二个核心是视角变换模块(View Transformation)。Fast BEV在这里用了一个比较巧的设计:它没有直接用固定的相机参数做投影,而是让网络自己学一个投影权重。具体来说,对于BEV平面上的每个网格点,网络会去图像特征上采样对应的位置,采样的权重是可学习的。这个思路有点像可变形卷积,但作用在视角变换这个环节。
这样做的好处是,网络可以自动补偿相机标定的误差。实际装车的时候,相机的外参不可能标得绝对准,总会有几度的偏差。如果投影完全依赖固定参数,这些偏差会直接反映到BEV特征上,导致检测框偏移。而可学习的投影权重能在训练过程中把这些系统性偏差吸收掉一部分。我在自己的数据上试过,用可学习投影比固定投影,在相机外参有2度左右误差的情况下,mAP能高出3个点左右。
2.3 多尺度BEV特征融合的处理方式
Fast BEV还处理了一个工程上很实际的问题:多尺度特征怎么融合到BEV空间。自动驾驶场景里,近处的行人和远处的车辆,在图像上的尺度差异很大。如果只用单一尺度的特征做BEV投影,要么近处细节丢失,要么远处目标漏检。Fast BEV的做法是在图像backbone上取多个尺度的特征图,分别做视角变换,然后在BEV空间里用FPN的结构做融合。
这里有个细节值得注意:不同尺度的特征做视角变换时,采样的BEV分辨率是不一样的。浅层特征分辨率高,适合投影到高分辨率的BEV网格上,负责近处目标;深层特征分辨率低,投影到低分辨率BEV网格,负责远处目标。最后通过上采样和concat把多尺度BEV特征合并。这个设计比在图像空间做FPN再统一投影要合理,因为BEV空间的分辨率需求本身就和图像空间不一样。
3. 从零复现Fast BEV的实操路径
3.1 环境准备与依赖版本的那些坑
复现Fast BEV的第一步是搭环境。官方代码是基于PyTorch的,我建议用PyTorch 1.10以上的版本,CUDA 11.3以上。这里有个坑:Fast BEV用到了自定义的CUDA算子来做快速射线变换,这些算子对CUDA版本和PyTorch版本的匹配要求比较严。我第一次装的时候用了PyTorch 1.8配CUDA 11.1,编译算子的时候直接报了一堆链接错误。后来换成PyTorch 1.11配CUDA 11.3,一次就过了。
另外,mmdetection3d这个库是绕不开的,Fast BEV的数据加载、评估都依赖它。建议用mmdetection3d 1.0以上的版本,因为BEV相关的工具函数在1.0之后才比较完善。安装的时候注意先装mmcv-full,再装mmdet,最后装mmdet3d,顺序错了会出现版本冲突。
# 推荐的环境配置 conda create -n fastbev python=3.8 conda activate fastbev pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html pip install mmcv-full==1.6.0 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.11.0/index.html pip install mmdet==2.25.0 pip install mmsegmentation==0.29.0 git clone https://github.com/open-mmlab/mmdetection3d.git cd mmdetection3d && pip install -v -e .3.2 nuScenes数据集的预处理与格式转换
Fast BEV用的是nuScenes数据集,这个数据集的原始格式是每个sample包含6个相机的图像、对应的标定参数、以及激光雷达点云。做BEV训练之前,需要把数据转成mmdetection3d要求的格式。官方提供了一个转换脚本,但跑起来有几个注意点。
第一,nuScenes的trainval部分大概有28000个sample,转换过程比较吃内存,建议机器至少32G内存。第二,转换后的数据里,BEV标注的生成依赖激光雷达点云,如果你的场景里激光雷达和相机的对齐有问题,生成的BEV GT会有偏移。我建议在转换完之后,随机抽几个sample可视化一下BEV GT和图像的对齐情况,确认没问题再开始训练。
# 数据转换的核心命令 python tools/create_data.py nuscenes \ --root-path ./data/nuscenes \ --out-dir ./data/nuscenes \ --extra-tag nuscenes \ --version v1.0 \ --canbus ./data3.3 训练配置的关键参数与调参逻辑
Fast BEV的训练配置里,有几个参数对最终精度影响很大,我逐个说。
BEV网格分辨率:默认是0.1米一个格子,覆盖范围是[-51.2, 51.2]米。如果你的应用场景主要是近距离泊车,可以把范围缩小到[-20, 20]米,分辨率提到0.05米,这样近处的检测精度会明显提升。如果是高速场景,范围要拉到[-100, 100]米,但分辨率就得降到0.2米,否则显存扛不住。
深度bin的数量:Fast BEV虽然用深度期望代替了分布,但深度网络输出的还是离散的bin。默认是60个bin,范围从1米到60米。这个设置对城市道路够用,但如果你要检测100米外的目标,得把bin的范围拉大,同时bin的数量也要增加,否则远处的深度分辨率太粗。
学习率策略:Fast BEV用的是cosine annealing,初始学习率2e-4,warmup 500步。我试过用step decay,收敛速度明显慢一些。另外,BEV感知任务对学习率比较敏感,初始学习率超过5e-4容易发散,低于1e-4收敛太慢。
| 参数 | 默认值 | 近距离场景 | 高速场景 |
|---|---|---|---|
| BEV范围 | [-51.2, 51.2] | [-20, 20] | [-100, 100] |
| BEV分辨率 | 0.1m | 0.05m | 0.2m |
| 深度bin数 | 60 | 40 | 80 |
| 深度范围 | [1, 60] | [0.5, 30] | [2, 100] |
| 初始学习率 | 2e-4 | 2e-4 | 1e-4 |
3.4 推理部署时的算子优化经验
训练完之后,部署到车端是另一个大关。Fast BEV的自定义CUDA算子在训练时用的是PyTorch扩展,部署时如果直接搬过去,性能不一定最优。我的做法是把快速射线变换这个算子用TensorRT重写一遍。核心逻辑不复杂:给定图像特征、深度期望、相机内外参,计算每个BEV网格点对应的图像采样位置,然后做双线性插值。用TensorRT的plugin机制实现,在Orin上能跑到15毫秒以内,比PyTorch原生实现快了将近3倍。
这里有个坑:TensorRT对动态shape的支持有限,而BEV感知的输入图像尺寸在不同车型上可能不一样。我的解决方案是把图像统一resize到固定尺寸,比如800×450,然后在模型里做padding,保证BEV网格的物理范围不变。这样虽然牺牲了一点灵活性,但换来了部署的稳定性。
4. 实测中暴露的问题与排查过程
4.1 深度预测发散导致BEV特征图出现空洞
我在自己的数据上训练Fast BEV的时候,遇到过一个问题:训练到第3个epoch左右,BEV特征图上开始出现大片的空洞,检测框也大量丢失。一开始我以为是学习率太大,调小之后问题依旧。后来把深度网络的输出可视化出来,发现深度预测值在部分区域发散到了几百米,远超设定的60米上限。
排查下来,原因是我的数据集里有一些图像区域是天空或者远处空旷路面,这些区域的深度本身就没有明确的监督信号,深度网络在这些地方容易输出极端值。Fast BEV原论文里没有特别强调这一点,因为nuScenes的数据分布比较均匀。解决办法是在深度网络的输出上加一个softplus激活,把深度值限制在正数范围,同时在损失函数里加一个深度平滑正则项,惩罚相邻像素深度差异过大的情况。加上这两个改动之后,空洞问题就消失了。
4.2 相机外参标定误差对BEV精度的影响量化
前面提到Fast BEV用可学习投影来补偿标定误差,但补偿能力是有上限的。我做过一组实验,人为地在相机外参上加了不同角度的扰动,看BEV检测精度的下降情况。
| 外参扰动角度 | mAP下降 | NDS下降 |
|---|---|---|
| 0度 | 0 | 0 |
| 1度 | 0.8 | 0.5 |
| 2度 | 2.1 | 1.4 |
| 5度 | 8.7 | 6.2 |
| 10度 | 21.3 | 15.8 |
从数据可以看出,2度以内的扰动,Fast BEV的可学习投影基本能吸收掉,精度下降在可接受范围。但超过5度,精度就崩了。所以实际装车时,相机外参的标定精度至少要控制在2度以内,否则再好的BEV模型也救不回来。
4.3 多相机特征融合时的对齐问题
Fast BEV处理的是6个相机的输入,这6个相机的图像特征在BEV空间里要拼在一起。我遇到的一个问题是,相邻相机重叠区域的BEV特征会出现"重影",同一个物体在BEV图上出现两个检测框。原因是不同相机投影到BEV空间时,由于标定误差和深度误差,同一个物理位置在BEV网格上会落到不同的格子。
解决思路有两个:一是在BEV空间做非极大值抑制(NMS),把重叠的框合并;二是在特征层面做对齐,用一个轻量的卷积网络学习相邻相机BEV特征之间的偏移量,然后做warp。我两种都试过,NMS方案简单但会丢失一些密集场景下的目标,特征对齐方案效果好但增加了训练复杂度。最终我采用的是混合方案:先用特征对齐做粗对齐,再用NMS做后处理,这样在密集场景下的召回率比单用NMS高了5个点左右。
5. Fast BEV在实际项目中的扩展思路
5.1 加入时序信息提升遮挡场景的召回
Fast BEV本身是单帧的,没有用时序信息。但在实际道路场景里,遮挡是个大问题——前车挡住了一个行人,单帧BEV根本看不到。这时候时序融合就很有价值。我的做法是在Fast BEV的BEV特征图后面接一个轻量的时序融合模块,把历史几帧的BEV特征用自注意力做融合。注意,这里不需要用复杂的Transformer,一个3层的自注意力就够了,计算量增加不到10%,但在遮挡场景下的召回率能提升8个点左右。
时序融合的关键是运动补偿。自车在动,历史帧的BEV特征和当前帧不在同一个坐标系下,需要根据自车的位姿变化做warp。这个warp用仿射变换就能搞定,不需要做复杂的3D变换,因为BEV平面本身就是2D的。
5.2 多模态融合:把激光雷达特征拼进来
Fast BEV的框架是纯视觉的,但它的BEV特征图是一个通用的中间表示,很容易扩展成多模态。我试过把激光雷达的点云用PointPillar编码成BEV特征,然后和Fast BEV的视觉BEV特征做concat。融合之后,在夜间和逆光场景下的检测精度提升非常明显,mAP从原来的0.35左右提升到了0.48。
这里有个工程上的取舍:激光雷达的加入会增加成本和功耗,如果你的项目对成本敏感,可以只在前视相机上加一个低线束激光雷达,只在特定场景下激活融合分支。这样既能保证大部分场景的纯视觉性能,又能在恶劣条件下有兜底。
5.3 针对特定场景的BEV网格裁剪策略
Fast BEV默认的BEV网格是覆盖360度的,但在很多实际应用里,我们只关心前向或者后向的特定区域。比如高速跟车场景,只需要关注前方100米、左右各10米的区域。这时候可以把BEV网格裁剪成非对称的形状,只保留感兴趣区域。这样做的好处是显存占用和计算量都能降下来,可以把省下来的算力用在提高分辨率上。
我做过一个实验,把360度BEV裁剪成前向120度,显存占用降低了40%,然后把这部分省下来的显存用来把前向区域的分辨率从0.1米提到0.05米,结果前向目标的检测精度提升了12个点。这个策略在泊车场景下尤其有用,因为泊车时主要关注车辆周围几米的区域,360度的BEV完全是浪费。
6. 一些容易被忽略的工程细节
6.1 数据增强对BEV感知的特殊影响
BEV感知的数据增强和2D检测很不一样。2D检测常用的随机裁剪、旋转,在BEV任务里要慎用。因为BEV特征和相机参数是强绑定的,你旋转了图像,相机外参也得跟着变,否则投影就错了。Fast BEV的官方代码里用的数据增强主要是颜色抖动和随机翻转,翻转的时候要注意同时翻转相机外参和BEV GT。
我踩过的一个坑是用了随机缩放增强,结果BEV投影的尺度全乱了,训练loss一直不降。后来才想明白,图像缩放之后,相机的内参矩阵也得跟着缩放,否则深度预测和投影就对不上。这个细节在论文里没写,但实际做的时候必须注意。
6.2 训练时的显存优化技巧
Fast BEV的训练显存占用不小,6个相机的图像特征加上BEV特征图,在batch size为4的时候,显存占用大概在20G左右。如果你的显卡显存不够,有几个优化技巧可以用。
第一,用混合精度训练(AMP),显存能省30%左右,速度还能快20%。第二,把图像backbone的梯度检查点打开,用时间换显存,显存能再省20%。第三,如果还是不够,可以把6个相机的图像分两组做前向,每组3个相机,然后累加梯度。这样batch size等效于减半,但显存占用也减半。
# 混合精度训练配置 from torch.cuda.amp import GradScaler, autocast scaler = GradScaler() for images, targets in dataloader: with autocast(): loss = model(images, targets) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()6.3 评估指标的选择与解读
Fast BEV论文里用的评估指标是nuScenes的标准指标:mAP、NDS、mATE、mASE等。但在实际项目中,这些指标不一定能反映你的真实需求。比如mAP是所有类别平均的,但你可能只关心车辆和行人,那就要看这两个类别的AP。NDS是个综合指标,包含了检测精度和速度误差,但如果你不做预测,速度误差这部分可以忽略。
我建议在项目里自己定义一套评估指标,比如"前向50米内车辆的召回率"、"近处行人的误检率"这些更贴近实际需求的指标。官方的评估脚本可以改,把不需要的类别过滤掉,只算你关心的部分。
7. 关于Fast BEV的一些个人判断
Fast BEV这篇工作,我觉得最大的价值不在于它提出了什么全新的理论,而在于它把BEV感知的工程门槛降下来了。在它之前,想做一个BEV感知的baseline,要么用LSS那套比较老的代码,要么用BEVFormer那套跑不动的模型。Fast BEV填上了中间这个空档,让做工程的人有一个精度和速度都说得过去、代码还能看懂的起点。
当然它也不是没有局限。纯视觉的方案在极端天气和光照条件下的鲁棒性始终是个问题,深度预测的精度也受限于图像本身的信息量。如果你的项目对安全性要求极高,多模态融合是绕不开的。另外,Fast BEV的时序融合能力比较弱,如果你要做预测或者规划,单帧BEV特征是不够的,得自己加时序模块。
我在实际项目里的做法是,把Fast BEV当成一个特征提取器,它的BEV特征图拿来做检测、做分割、做Occupancy预测都可以。这样它的价值就不局限于检测这一个任务了。后续如果要扩展,我建议优先考虑加时序和多模态这两个方向,这两个方向的投入产出比最高。