RCBEVDet 这个关键词,我头一回看到是在翻一批雷达-视觉融合的检测方案时,当时第一反应是"又一个把雷达点云塞进 BEV 的文章"。但真正把它跑通、在自己的数据切片上对着指标看了一轮之后,我改主意了——它解决的不是"多加一个传感器"这种堆料问题,而是纯视觉 BEV 方案在距离估计和恶劣光照下的系统性偏差。这篇东西适合三类人看:一是正在做 BEV 感知、想给纯视觉方案补一块短板的算法工程师;二是手里有毫米波雷达数据、但一直没想好怎么用起来的团队;三是对多传感器融合感兴趣、想知道"融合到底该发生在哪一层"的学生和研究者。下面我把这套方案的骨架、模块细节、复现路径和我自己踩过的坑,一次性讲透。
1. 先把话说清楚:RCBEVDet 到底在补哪块短板
1.1 纯视觉 BEV 方案卡住的三件事
做 BEV 感知的人心里都有数,纯视觉那条路走到一定精度之后,会撞上三堵墙。第一堵是深度估计的天花板。相机拿到的是一张二维投影图,从像素反推三维距离本质上是病态问题,靠显式深度分布或者隐式网络去猜,误差在远处会迅速放大——50 米开外,深度误差几个百分点就意味着横向框位置飘出去一米以上。第二堵是光照和天气的鲁棒性。强逆光、夜间无路灯、大雨起雾,图像质量一掉,特征就崩,而 BEV 上的框会跟着一起崩。第三堵是运动目标的径向速度。单目和环视相机靠多帧匹配来估计速度,遇到遮挡或者目标刚出现在视野边缘,速度估计基本靠猜。
这三件事恰好是毫米波雷达的强项。雷达测距靠飞行时间,几十米范围内的径向距离精度可以做到厘米到分米级,而且完全不受光照影响;多普勒直接给出径向速度,不需要跨帧匹配。所以 RCBEVDet 的思路并不神秘:把雷达当成一个"低分辨率但测距极准"的几何传感器,用它去校准和补强视觉分支。真正有意思的是它把这件事做在了哪里。
1.2 毫米波雷达为什么一直被"用不起来"
说起来雷达便宜、成熟、车规量产早就铺开了,但学术界和工程界对它的态度一直很暧昧。原因也很实在:雷达点云太稀疏太脏。一帧点云几百到几千个点,撒在几十米乘几十米的场景里,连个车轮廓都描不出来;噪声点满地跑,金属井盖、护栏、龙门架都能反射出强回波;高度信息基本不可信,z 轴误差能到几十厘米。直接拿来做 3D 检测,效果远不如激光雷达。
所以历史上雷达在感知系统里的角色一直很边缘:要么做 AEB 的独立通道,要么做前融合里权重很低的一路。近两年风向变了,一批工作开始尝试把雷达点云抬到 BEV 空间里跟视觉特征对齐,RCBEVDet 是其中把"融合位置"想得比较清楚的一个。它的核心判断是:雷达不该只做后融合的投票,也不该只做前融合的拼接,而应该在图像主干和 BEV 特征两个层面分别注入。
1.3 一套骨架,两个注入点
把 RCBEVDet 拆开看,整体是三条支路:图像支路走主干网络提取多尺度特征,再通过视角转换打到 BEV 空间;雷达支路用一个专门设计的编码器把稀疏点云变成 BEV 特征图;中间夹着两个融合点——图像主干的深层插入双交叉注意力模块,BEV 空间里做多尺度交叉注意力融合。
这个设计的取舍很值得说。为什么不干脆在 BEV 上拼一次就完事?因为纯视觉的 BEV 特征是在"深度估计已经错了"的前提下生成的,你在错误的特征上做融合,雷达只能纠偏到一定程度,误差已经固化了。反过来,如果在图像主干的深层就把雷达的几何信息注进去,相当于让网络在生成 BEV 特征之前就知道"这块图像区域大概率在 30 米左右",深度分布的预测会明显收敛。两个注入点,一个治本一个治标,这是这套方案我愿意花时间研究的主要原因。
2. 雷达分支不是随便卷一下:点云编码的真实难点
2.1 毫米波点云的三个反直觉特性
动手写代码之前,得先接受雷达点云跟激光雷达点云完全是两种东西。第一,稀疏且不均匀。近处点密、远处点稀,而且密度跟目标材质强相关,一辆大货车可能返回来几十个点,一辆摩托车可能只有三五个。第二,高度不可信。俯仰角的测量精度远低于方位角,z 坐标基本只能当"这有个东西"的粗提示,不能当真实高度用。第三,附带信息比坐标更值钱。每个点除了 xyz,还有 RCS(雷达散射截面)和径向多普勒速度,这两个量在纯视觉里根本拿不到。
理解了这三点,就明白为什么不能拿 PointPillars 那套直接套过来。RCBEVDet 的雷达编码器做了两件事:一是用双分支逐点编码把点的局部关系挖出来,二是用雷达柱编码器把散点规整成多尺度 BEV 特征图。这个"先逐点、再成柱、再卷积"的三段式,是权衡了表达能力和计算量之后的结果。
2.2 双分支逐点编码:为什么要两条路
单个雷达点自己没什么信息量,价值全在邻域关系里。所以逐点编码用了并行的两路:一路是 PointNet 风格的多层感知机,对每个点独立提特征,速度快、感受野是单点;另一路是局部注意力聚合,在一个小邻域内做注意力,捕捉点与点之间的相对位置关系。
为什么要两路?我自己的理解是:MLP 那一路负责记住"这个点自身的属性"(RCS、速度、绝对位置),注意力那一路负责记住"这个点跟周围点的关系"(一串点构成一条线,说明可能是护栏;一簇点集中在一个小区域,说明可能是车辆尾部)。两路特征拼起来再 scatter 到 BEV 网格上,得到逐点的 BEV 表达。
这里有个实操细节值得单独拎出来:scatter 的网格分辨率要跟后续的图像 BEV 特征对齐。图像支路常见的 BEV 网格是 0.4 米或 0.8 米一格,雷达这边如果用了别的值,后面融合时就得做插值,插值会引入额外的位置误差。我的建议是雷达柱编码的阶段就用 0.4 米,跟图像 BEV 的高分辨率层对齐,深层的低分辨率特征靠卷积下采样得到,这样对齐关系最干净。
2.3 RCS 通道:最容易被丢掉的一个免费特征
RCS 是雷达散射截面,说白了就是"这个目标反射电磁波的能力有多强"。它跟目标的材质、朝向、体积都有关系——大货车、金属护栏、广告牌回波强,行人、自行车回波弱。这个信息在很多雷达点云格式里是直接存在第五个维度里的,但大量方案在读数据的时候直接截断成 xyz 四维甚至三维,白白扔掉了。
RCBEVDet 把 RCS 当成一个独立的特征通道送进编码器,让网络自己去学它的分布。实测下来这一路的收益在区分大目标和小目标、抑制静态杂波上比较明显。我做过一个粗糙的对照:把 RCS 通道去掉,远处大车的召回会掉零点几个点,护栏误检会上升。数值不夸张,但这一路几乎不增加计算量,属于典型的"不加白不加"。
2.4 时序对齐:雷达帧率跟相机对不上怎么办
车载系统里相机和雷达的帧率、触发时刻往往不同步,这在实际部署时是个很烦人的问题。RCBEVDet 的雷达分支带了时序融合能力,把前若干帧的雷达点云按自车运动补偿到当前帧坐标系下,再跟当前帧一起编码。这一步的收益在稀疏场景下的召回上特别明显——单帧只有三五个点的远距离目标,攒上五帧就能凑出十几个点,特征就立得住了。
补偿的数学不复杂:用自车从 t-k 到 t 的位姿变换,把历史点云变换到当前帧。麻烦的是时间戳对齐策略。我的做法是:以相机帧的时间戳为基准,对每个相机帧选最近的雷达帧,如果时间差超过阈值(比如半个雷达周期)就丢弃这一帧的历史。宁可少用几帧,也不要用错帧——错帧的点云叠加会把目标糊成一团,比不用还差。
3. 融合发生在两处:DCA 与 CAMF 的分工
3.1 图像主干里的双交叉注意力
这是 RCBEVDet 我认为最有价值的设计。做法不复杂:在图像主干的深层(比如 stage 3 之后),插入一个双交叉注意力模块。图像特征作为 Query,雷达 BEV 特征作为 Key 和 Value,通过相机内外参把每个图像特征像素对应的 3D 射线投到 BEV 平面上,找到对应的雷达特征位置,做交叉注意力。
效果是什么?相当于在图像特征里注入了一条"这个像素大概在多远"的先验。后面的视角转换模块在做深度分布预测时,不再是纯靠图像纹理猜距离,而是有了一个来自雷达的几何锚点。这直接缓解了 1.1 里说的第一堵墙。
这里必须解释一个取舍:为什么只插在深层,不插在浅层。浅层特征分辨率高,做交叉注意力的计算量是平方级增长的,而且浅层语义弱、位置噪声大,把雷达信息注进去反而容易污染细节。深层特征分辨率低、语义强,正好是"需要几何先验"的时候。这个判断我在自己的数据上验证过——把 DCA 挪到 stage 2 之后,精度没涨,训练时间涨了三成,不划算。
3.2 CAMF:多尺度融合的分层策略
第二个融合点在 BEV 空间,叫交叉注意力多层融合。图像支路会产出多个尺度的 BEV 特征(比如 stride 8、16、32),雷达支路也是多尺度的,两边的融合策略并不一样。
深层(低分辨率、强语义)用交叉注意力:图像 BEV 特征当 Query,雷达 BEV 特征当 Key/Value。原因很直接,深层的每个格子覆盖的物理范围大,需要判断"这块区域到底是什么",雷达提供的回波强度和分布是很好的判据。浅层(高分辨率、强位置)用直接拼接加卷积:浅层关注的是细粒度位置,交叉注意力在这种分辨率下既贵又容易引入噪声,简单拼一下让后面的卷积自己去学权重就够了。
这种分层处理是典型的工程妥协,但我觉得妥协得很聪明。很多融合方案要么全用 concat(融合太浅,学不到跨模态关系),要么全用 attention(算不动,小目标精度反而掉),分层是唯一兼顾两头的方式。
3.3 融合位置选错会发生什么
我试过一个反面案例:把雷达 BEV 特征和图像 BEV 特征在最终检测头之前直接相加,不做任何注意力。结果很有意思——近处目标几乎没变化,远处目标的横向定位反而变差了。原因是图像 BEV 特征在远处的深度误差已经很大,雷达特征的位置准但语义弱,两者相加等于把准确的位置信息和错误的位置信息混在一起,网络学不出该信谁。
这反过来证明了 RCBEVDet 那两个注入点的必要性:雷达信息必须在特征生成的早期介入,而不是在特征已经定型之后去纠正。注意力机制在这里的作用不是"融合特征",而是"教会网络在不同区域该信哪个模态"。
4. 动手复现:环境、数据管线和训练配置
4.1 环境与依赖清单
这套方案是典型的 PyTorch 生态,视角转换、BEV 池化这些算子建议直接用成熟的 3D 检测工具箱里的实现,自己写 CUDA 算子调试成本太高。我用的组合大致是:Python 3.8 到 3.9、PyTorch 1.10 以上、CUDA 11.3 到 11.7,配一个成熟的多模态检测工具箱。显存方面,如果图像输入是 704×256 这一档、BEV 网格 128×128,单卡 24G 跑 batch size 4 比较稳;想上 batch size 8 就得多卡或者降分辨率。
环境里最容易出问题的是视角转换算子的编译。这类算子需要跟 CUDA 版本严格对应,版本错一位就编译失败。我的习惯是先单独跑一遍算子的单元测试,确认 BEV 池化能正常出结果,再去跑整个模型,不然训练崩了你根本分不清是算子问题还是模型问题。
4.2 雷达点云从原始帧到柱体的处理流程
数据管线这块,我整理了一条固定的处理链,每一步都有坑:
第一步,读原始雷达数据并过滤。原始雷达帧里通常带一大堆无效点(标记为无效的回波、超出量程的点)。必须按有效性标志过滤,这一步偷懒会导致后面一堆莫名其妙的误检。
第二步,坐标系变换。雷达安装位置跟自车中心不重合,要先做外参变换到自车坐标系。不同车型的安装位置不一样,这个外参一定要从标定文件里读,不能硬编码。
第三步,点云范围裁剪。BEV 网格是有边界的,超出边界的点直接丢。我一般把 x 方向裁到 [-50, 50] 米、y 方向 [-50, 50] 米,z 方向 [-5, 3] 米。这里有个经验:z 方向的裁剪范围要比真实目标高度宽一些,因为雷达 z 噪声大,裁太紧会把大车的顶部点全丢掉。
第四步,柱体化与特征组装。每个点组装成 [x, y, z, RCS, 速度, 时间差] 这样的多维向量,按 0.4 米网格 scatter 到 pillar 里,每个 pillar 内取点的特征最大值或均值。空 pillar 要显式标记,不能当零向量处理。
注意:数据增强里的水平翻转必须同步处理雷达点云。图像翻转后,雷达点的 x 坐标要取反,径向速度的符号也要跟着变。这一步忘掉,训练损失会莫名其妙地震荡,而且极难定位。
4.3 训练超参与我的调参记录
训练策略上,我用的是分阶段加端到端联合的方式。第一阶段先冻结图像主干,只训雷达分支和两个融合模块,学习率给大一点(2e-4 到 4e-4),大概十几个 epoch。这一阶段的作用是让雷达分支先收敛到一个合理的表达,避免一上来联合训练时雷达分支的梯度被图像分支淹没。第二阶段解冻全部参数端到端训,学习率降到 1e-4 以下,配余弦退火。
优化器用 AdamW,权重衰减 0.01。采样策略上,那套类别平衡采样必须开,否则行人类别几乎学不动——这类小目标在数据集中占比低,再加上雷达点少,不做过采样基本没戏。
数据增强方面,图像侧做常规的缩放、裁剪、颜色抖动;雷达侧做全局旋转(±22.5 度)、缩放(0.95 到 1.05)、以及前面提到的同步翻转。禁用任何会改变点云局部结构的增强,比如随机丢点、随机抖动坐标,这些在小数据集上会直接伤害精度。
一个我调了很久的参数是雷达分支的独立学习率倍数。因为雷达分支参数量远小于图像主干,梯度范数也小,用同一个学习率它收敛得特别慢。最后我把雷达分支的学习率设成主干的 2 倍,收敛速度明显改善。
4.4 推理耗时这笔账要提前算
工程落地绕不开耗时。雷达分支本身的点云编码开销不大,几百到几千个点,逐点 MLP 加小邻域注意力,在主流车端芯片上大概是十几到几十毫秒的量级。真正贵的是 DCA 和 CAMF 里的交叉注意力,尤其是深层高分辨率的那一层。
整体算下来,相比同配置的纯视觉基线,端到端推理耗时大概增加 15% 到 25%,具体取决于点的数量和 BEV 网格大小。这个代价换来的是远处小目标的召回提升和夜间场景的稳定性——我认为是划算的,但如果是算力极度受限的平台,可以考虑砍掉 DCA 只保留 BEV 层的融合,精度会掉一些但耗时会回到接近基线的水平。
5. 踩坑实录:这半年遇到的问题与排查路径
5.1 典型问题速查表
下面这张表是我在实际调试中攒下来的,按"现象—可能原因—排查动作"三列整理,遇到问题可以先对一遍:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 训练 loss 剧烈震荡,前几个 epoch 就发散 | 图像翻转时雷达点云未同步翻转,或速度符号未处理 | 关掉所有增强跑一次,确认 loss 平滑后再逐个开增强 |
| 远处目标横向定位明显偏移 | BEV 网格分辨率不一致,融合时插值引入误差 | 打印两边 BEV 特征图尺寸和实际物理范围,确认逐格对应的米数一致 |
| 训练精度正常,验证集 mAP 低一大截 | 时序融合用了错帧,历史点云污染当前帧 | 检查时间戳匹配逻辑,收紧时间差阈值 |
| 小目标(行人、自行车)召回极低 | 类别采样未开启,或雷达点太少导致 pillar 为空 | 开启类别平衡采样,检查空 pillar 的 mask 逻辑 |
| 融合后精度不如纯视觉基线 | 融合位置过于靠后,或注意力层数过多导致过拟合 | 减少融合模块,把融合点前移到主干深层 |
| 夜间场景改善不明显 | 雷达分支权重学习不足 | 提高雷达分支学习率倍数,或延长第一阶段训练轮数 |
| 显存溢出 | 高分辨率 BEV 上的交叉注意力太吃显存 | 降低输入分辨率,或只在深层做注意力 |
5.2 几个不太容易想到的细节
空 pillar 的处理比想象中重要。雷达点云稀疏,BEV 网格上大量格子是空的。如果这些格子用零向量参与注意力计算,注意力权重会被这些"零"稀释掉,导致真正有信息的格子权重上不去。我的做法是给每个格子加一个可学习的存在性 embedding,同时在注意力里对空格子做 mask。这一改动带来的提升比我预期的大。
高度通道别当真坐标用。前面提过雷达 z 精度差。我一开始老老实实把 z 归一化后当特征输入,结果 3D 框的中心高度预测飘得厉害。后来改成把 z 也做范围裁剪后分桶编码(类似 one-hot 的软编码),让网络自己学"这个高度值有多可信",效果立竿见影。
雷达特征维度不是越高越好。我在早期版本里把雷达 BEV 特征通道数设得很大,想着信息多一点总没坏处,结果过拟合非常严重——训练集 mAP 一路涨,验证集早早平台期。把通道数砍到图像特征的一半左右,配合一点 dropout,泛化明显改善。雷达信息的有效维度本来就低,堆通道等于给网络留了死记硬背的空间。
两个融合模块的初始化要小心。交叉注意力的输出层如果用默认初始化,一开始会给图像特征注入很大的扰动,训练前期 loss 会先涨后降。我的做法是把融合模块的输出投影层初始化为接近零(类似残差门控的思路),让网络从"不使用雷达"的状态开始慢慢学。
多卡训练时注意采样器的随机种子。这类融合模型对 batch 内的样本组成比较敏感,不同卡上的类别分布不均会导致 BN 统计量偏移。用分布式采样器的时候记得开同步 BN,或者干脆在小 batch 下换成 GroupNorm。
最后分享一个我在排查精度异常时的习惯:把雷达分支单独拿出来做一个只输入雷达点云的检测头,看看它能达到什么水平。如果单独跑雷达分支的召回都很差,那融合后的效果不可能好,问题一定在雷达数据管线而不是融合模块。这个二分法帮我省了大量时间,比盯着总 loss 猜要高效得多。