做自动驾驶感知的人,绕不开一个名字:3D-CVF。它是2020年一篇关于相机与激光雷达融合的3D物体检测论文,核心就一句话:用跨视角特征融合,把两种传感器拉到同一个空间里对齐。整个方案既不搞早期融合那样简单粗暴,也不搞晚期融合那样各说各话,而是先靠激光雷达出框,再用相机语义去修正,最后输出高质量的三维目标框。这篇文章不是复述论文公式,而是从一个常年做多传感器融合的工程师视角,把3D-CVF的设计逻辑、核心细节和落地坑讲透。手里有雷达和相机数据、想做3D感知的,无论是做研究还是做产品,都能少走弯路。
我最早看到3D-CVF论文时,第一反应是“又是相机点云融合”。但仔细看完才发现,它的重点不在“融合”本身,而在“怎么解决透视视角与鸟瞰视角的空间对齐”。这个点,恰恰是很多人在实际项目里反复踩坑的地方。
1. 3D-CVF 在解决什么核心问题
1.1 相机和激光雷达,为什么非要捏在一起
激光雷达的优点是几何精度高。它给的是真实三维坐标,物体的距离、尺寸、朝向都相对可靠。但它稀疏,远距离点云稀稀拉拉,而且没有颜色和纹理信息。一个穿了黑衣服的行人,在点云里可能只有几束反射;一个浅色箱体,在傍晚光线下点云和路面几乎分不开。
相机的优点正好是激光雷达的缺点。图像有密集纹理,有颜色对比,有语义线索。雨刷器、刹车灯、车道线,这些在图像里一目了然,点云根本无法反映。但图像本身没有准确的深度,单目做3D检测的精度天然受限,更别说遮挡和光照变化。
自动驾驶要的是“高召回、低误报、定位准”,任何一个传感器单干都做不到。所以主流做法肯定是多模态融合。3D-CVF的思路是:激光雷达负责提结构,相机负责补语义;两者不是简单叠加,而是互相修正。这个定位,就是它最大的设计前提。
1.2 难在“视角不同”:透视视角与鸟瞰视角
多模态融合最麻烦的问题,不是网络结构,而是坐标系统差异。
激光雷达点云通常投影到鸟瞰图BEV上处理。BEV这个词现在很火,简单理解就是从上往下看,把三维世界压成二维俯视图。BEV的好处是物体不重叠、尺寸稳定、朝向清晰,很适合做障碍物框的回归。
相机图像是透视视角,从车里的摄像头向前看,存在近大远小、物体遮挡、远端像素拥挤。同一个3D框,在BEV上是规则的矩形,在图像上却可能是各种形状的六边形或梯形。两个视角的特征图,像素位置并不是一一对应的。
直接相加,等于拿两张完全不对齐的图去硬压,结果就是特征互相污染。3D-CVF的核心贡献,就是为这种跨视角对齐提出了一套可落地的框架:先把雷达特征出框,再用相机特征去校验和修正这个框。用大白话说:雷达先说“我猜这里有个东西”,相机再看一眼说“嗯,确实是车,不是路牌”。这个协作方式,比一上来就让两个模态强行对齐要稳定得多。
2. 3D-CVF 的整体设计拆解:两阶段 + 跨视角对齐
2.1 第一阶段:LiDAR BEV 出 3D 候选框
3D-CVF不是一次到位的单阶段网络,而是先走一个基于点云的候选框生成网络。点云会先被离散化成BEV栅格,再用2D卷积骨干网络提取特征,最后接一个3D区域提议网络输出一批候选框。每个候选框包含中心位置、长宽高、朝向角,以及一个初判的置信度。
用激光雷达出候选框,是“稳”字优先。点云虽然稀疏,但结构性好,出一个大致不漏的框比相机容易。如果靠相机先出框,单目深度不准,框的位置可能一开始就跑偏,后面再怎么融合都救不回来。所以第一阶段交给雷达,等于把几何上的地基打好。
这个阶段还会做前景分割的辅助监督吗?要看具体实现。但在我自己的落地经验里,如果RPN阶段能输出一个简单的“有物体/无物体”概率,对后续相机分支的候选框过滤非常有用。否则相机分支会把大量背景当成潜在目标去提特征,白白浪费算力。
2.2 第二阶段:Proposal 再去两个视角取特征
拿到候选框之后,3D-CVF不会像很多方法那样直接把图像特征全局搬到BEV上,而是只针对每个候选框做特征提取。因为候选框已经是3D的,它可以被投影到相机视角,也可以直接被投到BEV特征图上。
在BEV侧,用候选框的中心和尺寸去BEV特征图上采样,得到一块雷达特征。在相机侧,把3D候选框的角点通过外参矩阵和内参矩阵投影到图像上,得到对应的图像区域,然后用区域特征提取的方式从图像特征图里取出相机特征。两块特征都出来之后,再通过一个小卷积或者加权融合,拼到同一个向量里,接分类头和回归头。
只对候选框做特征融合,是工程上非常聪明的地方。如果对整张透视特征图做全局重投影,计算量会非常大,而且很多像素在BEV里根本找不到对应的点。proposal级的融合,相当于“哪里可能有目标,就重点看哪里”,既保证信息充分,又把算力花在刀刃上。
2.3 和早期融合、晚期融合的区别
很多新手会问,为什么不能直接把图像像素RGV叠加到点云上?这是早期融合的常见做法,比如把相机点变成带颜色点,再送进点云网络。优点是实现简单,但问题也很明显:像素深度导出的伪3D点,位置本身就有误差;点云又稀疏,带颜色的点覆盖不完整;再加上遮挡区域,图像语义会被错误投影到雷达看不到的物体上。
晚期融合则是相机单独出一个检测框集合,雷达单独出一个检测框集合,最后用规则做匹配和合并。这种方案在工程里也很常见。但它最大的问题是两个模态各自出框后,框之间的匹配规则很难定,同一个目标可能相机认为是车、雷达认为是卡车,宽度和朝向各有说法,后期合并的时间成本并不低。
3D-CVF走的是中间路线:雷达先把框的结构定下来,相机再去给这些框补充语义。它不是“各自表达再投票”,而是“一个出题一个做题”。这样既没有早期融合的坐标误差,又避开晚期融合的框匹配难题。我自己的感受是,这种“结构先导、语义后补”的顺序,在多传感器融合里永远值得优先考虑。
3. 核心细节与实操要点:从 Proposal 到联合特征
3.1 标定与投影关系:一切融合的前提
所有跨视角融合的第一步,是把雷达坐标和像素坐标之间的关系算清楚。实际用到的只有两套参数:相机内参和雷达到相机的外参。外参包含旋转和平移,决定了雷达到相机的空间变换;内参决定了相机坐标系里的点如何落到像素平面。
假设雷达坐标系下的某个点,经过外参转换到相机坐标系,再乘以内参矩阵,就能得到像素坐标。公式展开是这样的:
import numpy as np def lidar_to_pixel(points_lidar, T_lidar2cam, K): # points_lidar: shape [N, 3] ones = np.ones((points_lidar.shape[0], 1)) points_h = np.concatenate([points_lidar, ones], axis=-1) # [N, 4] points_cam = (T_lidar2cam @ points_h.T).T # [N, 3] points_cam = points_cam[points_cam[:, 2] > 0] # 去掉相机背后的点 uv = (K @ points_cam.T).T uv = uv[:, :2] / uv[:, 2:3] # 除以深度得到像素 return uv这个公式不难,难的是参数质量。外参只要差个几厘米,近距离目标可能看不出问题,远距离目标会像马赛克一样错位。我在实际项目里见过标定文件里存的是毫米级外参,但换了一次雷达支架后没重新标,导致相机分支一直拉低模型效果,整整查了两周。
提示:外参标定是所有融合工作的第一优先级。外参错,后面的融合模块写得再精巧也是白搭。
另外,时间同步也经常被忽略。相机曝光延迟和雷达扫描延迟不一致时,运动目标的投影会明显滞后。如果车辆高速行驶,前方车辆在图像里的位置和点云里的位置可能相差半个车身。3D-CVF论文里没有大篇幅讲同步,但落地时这一步不做,融合性能会突然下降几个点。
3.2 特征采样:不要直接裁剪图像
到了特征采样阶段,很多人的第一反应是:把3D框投影到图像上,然后算一个包围盒,直接裁剪图像进来resize到固定尺寸。这样做在简单场景里勉强可用,但遇到目标互相遮挡、框斜着摆、或者候选框不够精确时,裁剪出来的区域包含大量背景,干扰特征。
更好的做法是在图像特征图上做双线性采样。也就是说,不管投影点在像素坐标里是不是整数,都用周围四个像素的加权平均来取值。这样可以让相机特征对候选框的边界更敏感,也方便梯度回传。3D-CVF的相机分支本质上是围绕这个思路展开的。
一个3D框投影到图像上,并不是一个轴对齐矩形,而是多个角点围成的多边形。工程上常用的做法是取所有角点投影后的最小包围盒,再做RoI特征提取。如果追求更精细,可以按多边形区域做掩码采样,把框外的像素直接过滤掉。这会稍微增加一点计算量,但融合后的特征更“干净”。
我之前实验过一个加速版本:不提取所有角点,只用框的中心点和底部两个角点投影,确定一个近似区域。效果没差距那么大,但速度快了。这也算一个偷懒技巧。
3.3 融合方式:通道对齐后再谈融合
雷达BEV特征和图像特征,通道数、空间尺度、语义层级都不一样。直接把两个特征向量拼在一起送进全连接,通常效果不稳定。3D-CVF的做法一定是先做特征变换,再融合。
常见的融合流程是先对相机特征做一个1x1卷积,把通道压到与BEV特征相同的维度;再做逐元素相加或加权求和。如果两个模态的置信度相差很大,加权求和里的权重最好由网络自己学,而不是拍脑袋设一个固定系数。
我见过一个很有意思的改进:在融合分支前,用一个小网络预估“这个候选框里相机信息的可信度”。如果目标被树挡住,相机可信度自动降低,融合结果就主要由雷达特征决定。这个做法很像3D-CVF的proposal级融合思路的延伸:既然只对候选框融合,那么每个候选框都应该有独立的融合权重,而不是整个场景共享一个权重。
融合的伪代码大概是这样的:
def fuse_features(bev_feat, cam_feat, proposal): # bev_feat: 从BEV特征图采样的特征 # cam_feat: 从图像特征图采样的特征 cam_feat = conv1x1(cam_feat) # 对齐通道 amodal_weight = gate(bev_feat, cam_feat) # 学习每个位置的可信度 fused = bev_feat + amodal_weight * cam_feat return fused这种“门控融合”在真实场景中特别实用。举个例子,夜间前车尾灯很亮,图像特征会让网络误以为是大目标;但如果门控机制能从BEV特征里学到雷达点云其实只覆盖了很小一块,它就会降低图像特征的权重,避免误判。
3.4 分类和回归目标
融合后的特征最终要接两个头:一个做物体类别判断,一个做三维框回归。分类头通常用交叉熵或者聚焦损失,目标是在正负样本不均衡时不会崩;回归头一般回归相对候选框的残差,比如中心偏移量、长宽高缩放、朝向角增量。
这里有个容易被忽视的细节:回归目标必须在候选框坐标下归一化,否则不同远近、不同大小的目标,回归难度差异很大。3D-CVF的候选框来自雷达RPN,框本身已经比较准,回归量相对小,网络训练起来会更轻松。这也侧面说明了为什么先雷达后相机,比反过来更稳。
再加上正负样本分配。雷达RPN给的候选框可能有几千个,但真正和GT匹配上的只有几十个。如果全部参与训练,背景样本会把相机分支彻底淹没。一般会设定一个匹配阈值,只有与标注框交叠超过一定比例的候选框,才作为正样本;负样本则通过难例挖掘控制数量。这个策略不复杂,但在多模态融合里非常关键。
4. 训练与落地中的关键环节
4.1 训练策略:先单模态再联合微调
如果你直接端到端训练整个3D-CVF,大概率会出两个问题:第一,相机分支还没学到有效语义,就已经把雷达分支的梯度带偏;第二,RPN在训练初期输出的候选框质量差,投影到相机后采到的全是背景,相机分支越学越歪。
实务上我建议分三阶段走:
- 只用点云BEV数据,把RPN和检测头预训练一轮,让候选框基本能框住目标。
- 冻结骨干网络,单独训练相机分支和融合分支,喂给它一批质量较高的候选框。
- 解冻全网络,端到端联合微调,用很小的学习率,避免前面的预训练特征被冲掉。
这个流程看着费事,但比一次性端到端收敛快得多。如果训练资源紧张,甚至第二阶段都可以省掉,直接跳到联合微调,但准备工作要做好,让RPN输出已经比较准。
数据增强同样要注意跨模态同步。如果你对点云做了随机翻转、旋转、平移,相机图像也要做对应的刚体变换,否则会让网络学到“相机特征和雷达特征对不上也正常”的错误先验。很多人在3D检测增强时只翻转点云,忘记同步改图像,结果融合分支几乎不起作用。
4.2 推理流程:候选框数量是延迟瓶颈
部署时整个流程是:点云进BEV化、骨干网络提特征、RPN出候选框、候选框投影到相机视角、图像特征采样、融合、分类回归。主干网络部分和纯雷达检测没有本质区别,多出来的开销主要集中在“候选框投影+图像采样”这一段。
候选框数量直接决定这里的耗时。如果RPN一口气输出几千个候选框,哪怕每个框只做一次投影和采样,累积时间也不低。实际工程里通常加一个阈值,把置信度低于某值的候选框提前滤掉,只保留几百个高置信度框进入融合分支。这样可以省掉大量无用计算,而精度损失很小。
我实测过不同候选框数量下的推理表现。候选框从2000个降到300个,融合分支的耗时可以下降一个数量级,但最终的AP只掉了零点几个点。这跟3D-CVF本身的设计初衷是一致的:融合信息只需要关注“可能是目标”的区域,背景区域全部跳过。
4.3 调参优先级:BEV分辨率和相机分辨率
如果效果不如预期,先看BEV分辨率。BEV栅格通常设置在0.1米或0.2米左右。栅格太粗,小目标如行人、骑行者的细节全没了;栅格太细,点云稀疏区域会出现大量空栅格,噪声被放大。折衷方案是在目标集中的近车区域用细栅格,远车区域用粗栅格,但这会破坏网络结构的规整性,目前多数实现还是用全局相同分辨率。
相机分辨率对融合效果的影响,主要体现在远距离小目标上。图像输入太小时,远处目标的像素只有几个点,采样出来的特征几乎没有语义信息。如果算力允许,相机分支最好用比主干分类网络更高分辨率的输入,至少保证远处目标能覆盖几十个像素。
我自己调参的习惯是:先把雷达单模态AP调到可接受范围,再打开相机分支。打开之后如果AP没涨,基本可以判断是投影错位或者时间同步问题,而不是融合方法本身有错。
5. 常见问题与排查技巧
5.1 问题速查表
| 常见问题 | 可能原因 | 处理思路 |
|---|---|---|
| 相机分支训练不收敛 | 雷达到相机的投影错位,特征采样采到了背景 | 可视化验证投影点是否落在对应目标上 |
| 融合后AP不升反降 | 图像分支把背景或遮挡信息带进来 | 加融合门控,让网络自己调整相机特征权重 |
| 小目标召回率低 | BEV栅格太粗,或相机特征分辨率不够 | 提高目标区域栅格精度,或增大图像输入尺寸 |
| 远距离目标经常漏检 | 点云稀疏,图像语义也不足 | 设置距离过滤,避免硬融合导致噪声放大 |
| 训练时损失震荡大 | 正负样本分配不稳定,或增强没同步 | 检查RPN预训练质量,统一图像和点云的增强变换 |
| 部署延迟超出预期 | 候选框数量太多,图像采样开销大 | 提高置信度阈值,限制进入融合分支的候选框数量 |
这张表是我在项目里反复用的排查清单。大多数问题不是网络结构引起的,而是数据对齐和样本分配引起的。
5.2 实际排错步骤
当融合分支不见效时,我一般按下面顺序排查:
第一步是可视化。把候选框投影到图像上,和标注框叠在一起看。如果投影误差超过几个像素,后续的所有特征采样都没有意义。这一步能直接判断标定和时间同步是否有问题。
第二步是分开跑两个分支。单独测雷达分支的AP,再单独输入一个假的高质量框给相机分支,看它能不能正确分类。如果单独跑都不行,说明是分支本身的问题;如果单独跑行、融合后不行,说明是融合策略或样本分配的问题。
第三步是检查融合前后的特征分布。把融合前的雷达特征和融合后的特征做一次统计对比,如果融合后大量特征值变成了常数,多半是图像分支的归一化和主分支不一致,导致数值覆盖了雷达特征。
这三步走完,基本能定位百分之八十的融合问题。剩下的百分之二十,可能要回到传感器本身,去查相机曝光和雷达扫描的硬件同步。
6. 我的思考、扩展和改进方向
3D-CVF对我最大的启发,不是网络有多深,而是“特征融合的本质是对齐,而不是堆叠”。后来的很多BEV感知方法,开始把图像特征通过显式的深度估计投影到BEV空间,其实也是想解决同一个问题。只是它们选择了更稠密的表示方式,而3D-CVF选择了更稀疏、更工程化的proposal级融合。
如果要在自己的框架里借用3D-CVF,我的建议是从最小版本开始:先做雷达BEV检测,加一个相机分支,只对候选框采样融合。不要一上来就上Transformer,不要一上来就做全BEV的密集融合。先证明你的传感器标定和时间同步是可靠的,再逐步增加融合密度。
另外,你可以在融合分支里加一个简单的可信度门控,哪怕只是把相机特征的均值作为权重乘到融合结果上,在很多场景里也能稳定涨点。这个技巧在公开数据集上不一定显著,但在真实路测数据上,尤其在雨雾、逆光、遮挡场景里,能明显减少误检。
最后想分享一个实操体验:多模态感知的难点从来不是网络结构,而是数据是否对齐、特征是否有区分度、负样本是否可控。3D-CVF这种方案,本质上就是把这三个问题缩小到proposal级别去处理。它没有把所有问题一次性解决,但它在计算开销、精度稳定性和工程落地之间找到了一个很实用的平衡点。如果你也在做相机和雷达融合,我建议认真吃透它,哪怕只是作为你后续方案的一个baseline,也值得。