协作感知空间特征融合:BEV对齐、位姿鲁棒与通信压缩实践
1. 单车视角的天花板:协作感知为什么非要在特征层动手
做3D目标检测这行的人,大概都经历过同一种挫败:实验室里跑分漂亮,一上路就抓瞎。原因并不玄乎,激光雷达的角分辨率随距离平方衰减,50米外的行人只剩稀稀拉拉几个点;一个水泥搅拌车停在路口拐角,它背后的一切对自车雷达来说就是一片空白;再加上雨雾天、夜间,单车的感知边界被压得很窄。自动驾驶领域的3D目标检测想再往上走,只靠把车上的传感器堆得更多、把主干网络做得更深,收益已经越来越小——这是物理限制,不是算法问题。
说明:我手上只有论文标题,没有原文的方法细节。下面的拆解是站在一个长期做感知融合的从业者角度,按这个方向通用的技术脉络做的合理还原,会明确标出哪些是"这一类工作通常这么干",哪些是个人经验判断。读者拿去对照原文时,重点看公式和数据,别把我这里的描述当成逐字复述。
1.1 一个路口场景暴露的三个硬伤
设想一个典型十字路口。自车从南往北直行,右前方停着一排货车。此时一个骑车人从货车之间的缝隙横穿出来。对自车来说,这不是"检测精度不够",而是"信息根本不存在"——光束打不到,摄像头被挡死,检测器再强也是无米之炊。这类场景暴露的问题有三个层面,值得分开说。第一层是几何遮挡,属于视野问题;第二层是点云稀疏,属于分辨率问题,远处目标只有几个点,特征根本聚不起来;第三层是视角单一,同一个物体永远只从一个方向被观察,模型学到的形状先验是有偏的,比如只见过车尾没见过车头。
这三层问题里,几何遮挡和点云稀疏靠单车自身几乎无解,唯一现实的路子就是引入外部视角:路侧的杆子上装一台设备俯视路口,或者隔壁车道的车刚好能看到缝隙后面的情况。这就是协作感知(cooperative perception)的基本动机。它不是什么新鲜概念,但真正能在工程上跑起来的方案,这几年才逐渐收敛。
1.2 结果级、特征级、数据级:三条路线的取舍
协作感知按"在哪一步交换信息"可以分成三档,这个分层是理解 CoFF 位置的关键,我用一张表把取舍关系摆清楚:
| 融合层级 | 传输内容 | 带宽开销 | 信息丰富度 | 对位姿误差的敏感度 | 典型问题 |
|---|---|---|---|---|---|
| 数据级(早期融合) | 原始点云或原始图像 | 极高,几十 Mbps 起 | 最高 | 高 | 实际部署几乎不可行,压缩后损失大 |
| 特征级(中间融合) | BEV 特征图 / 空间特征 | 中,需压缩 | 高 | 高,但有优化空间 | 对齐难、异构特征语义不一致 |
| 结果级(晚期融合) | 检测框 + 置信度 + 类别 | 极低,几 KB | 低 | 低 | 信息无法互补,误检会被叠加放大 |
数据级融合听起来最"纯粹",把别人的点云搬过来和自己的一起送进网络,理论上信息最完整。但现实里没人这么干,一帧64线激光雷达点云压缩后仍有几百KB到1MB,十辆车协同时的链路压力完全不可接受,而且原始数据的隐私和标定依赖也很麻烦。
结果级融合是另一个极端,工程上最容易落地——每辆车自己跑检测,只把框传出去做后处理。但它有个致命缺陷:两辆车看到的是同一个物体的不同侧面,各自给出的框可能在尺寸和朝向上打架,融合时只能靠 IoU 匹配和置信度加权,本质上是在"投票",而不是"互补"。遮挡后面的目标,一辆车没检测到,另一辆车检测到了,这种情形下结果级融合反而会互相压制。
特征级融合是这几年学术和工程都在往的方向,也是 CoFF 这个标题里 "Spatial Feature Fusion" 指向的位置。它的逻辑是:把各自提取的中间特征投影到一个共享的空间表达上(通常是鸟瞰图 BEV 网格),再在特征层面做聚合。好处是信息互补真正发生在语义层面——一辆车看到的车头形状,可以和另一辆车看到的车尾形状在特征图上拼成一个完整的表达。代价是对齐精度要求极高,几米的位姿误差就足以让两个特征图上的同一个目标错开好几个格子,直接相加等于互相污染。
1.3 CoFF 在协作感知谱系里的坐标
从标题拆,"Cooperative" 说明是多智能体协同(车-车、车-路都算),"Spatial Feature Fusion" 说明融合发生在空间域的特征图上,而不是通道注意力、不是 query 层面的 transformer 交互。这个定位很关键:它意味着方法的核心工作量一定落在"怎么把不同来源的特征放到同一个空间坐标系里,并且对不准的时候怎么办"。
如果一篇论文把"空间"两个字写进标题,那它八成要处理位姿误差(pose error)、标定误差(calibration error)、以及不同传感器视角带来的特征分布差异。这三件事是特征级协作感知绕不开的三座山,也是我下面要重点展开的内容。对读者来说,如果你正在做车路协同或者多车协同的感知,这篇的思路基本可以直接迁移到自己的系统里;如果你只是做单车3D检测,看看它怎么处理空间对齐,对多传感器前融合也有借鉴价值。
2. CoFF 的骨架:把各自为战的观测塞进同一个空间格
协作感知最容易犯的错,是把"融合"想成一个简单的加法。实际上从"每辆车各自有一份特征"到"一份融合后的特征",中间有一整套空间重排的工作。我见过的失败案例里,绝大多数不是网络结构不够花哨,而是空间变换这一层就出了岔子。下面按数据流走一遍。
2.1 统一空间的两条路:自车坐标系还是世界坐标系
第一步必须先确定"融合在哪张图上进行"。有两种常见选择。
第一种是自车坐标系:以融合发起方(ego vehicle)为原点,把其他协作智能体的特征变换到自车的 BEV 网格上。优点是后续检测头不用改,和单车检测完全兼容;缺点是这个融合结果只对自车有意义,如果要做全局的路口监控,每个车都得算一遍。
第二种是世界坐标系(或路口局部坐标系):所有智能体统一变换到一个固定的参考系。优点是融合结果可以被多方复用,路侧单元算一次,广播给所有车;缺点是检测头需要适应这个统一坐标系,而且参考系的选择会影响网格分辨率的分配。
实操建议:如果你的场景是 V2V(车对车),选自车坐标系更省事;如果是 V2I(车对路侧),一定选路口局部坐标系,否则路侧算力白花了。这两条路在实现上只差一个变换矩阵的乘序,但架构决策错了,后面改起来很痛苦。
从 CoFF 的命名看,"Cooperative Spatial" 更倾向于一个共享的空间表达,我倾向于认为它走的是统一空间的路子——因为只有统一空间,"空间特征融合"这个词才立得住。当然这是我的推断,看原文的坐标系定义就能确认。
2.2 特征在哪一层被抽出
第二个要定的是"传什么"。特征级融合不等于把整个 backbone 的中间层都传出去,那样带宽受不了。常见做法是在 backbone 之后、检测头之前,把特征压成 BEV 表达再传。
具体路径一般是:多相机图像 → 图像 backbone → 视图变换(把透视图投到 BEV)→ BEV backbone → 得到 BEV 特征图;或者激光雷达点云 → 体素化 → 3D backbone → 沿高度维压成 BEV 特征图。两条路径殊途同归,最后都是一张 H×W×C 的特征图,H、W 对应地面的空间范围,C 是通道数。
这里有个容易被忽略的细节:BEV 网格的物理分辨率决定了融合的上限。常见配置是 0.4m 到 1m 一个格子。如果格子是 0.5m,而位姿误差有 1m,那误差就是两个格子,怎么对齐都对不准。所以很多工作会在融合前把特征图上采样一次,或者用一个更细的网格做对齐、再降采样回原分辨率。这个"先细后粗"的技巧在实操里非常有效,代价只是显存。
还有通道数。传 C=256 的特征图,一张 200×200 的图就是 200×200×256×4 字节 ≈ 40MB,压缩后也要几MB,一帧几十毫秒内传完对链路是不小的压力。所以实际系统里 C 往往被压到 32 或 64,或者用 8bit 量化。CoFF 大概率也做了类似的通道压缩,这一点在论文的带宽分析表里应该能看到。
2.3 空间融合的本质是查表加加权
把上面两步串起来,空间特征融合在数学上其实很朴素:对融合后网格上的每一个位置 p,找出它在每个协作智能体特征图上的对应位置,采样出来,然后加权求和。用伪代码写大概是这样:
# 伪代码:统一空间下的特征采样与聚合 # feats: list of [B, C, H, W],每个协作智能体的 BEV 特征 # poses: list of [B, 4, 4],每个智能体到统一坐标系的变换矩阵 # grid: [B, H, W, 2],统一坐标系下每个格子的物理坐标 fused = zeros(B, C, H, W) weight_sum = zeros(B, 1, H, W) for feat, pose in zip(feats, poses): # 1. 把统一坐标反变换回该智能体的坐标系 local_xy = inverse(pose) @ grid # [B, H, W, 2] # 2. 物理坐标归一化成采样网格 [-1, 1] sample_grid = normalize(local_xy, bev_range) # 3. 双线性采样,得到对齐后的特征 aligned = bilinear_sample(feat, sample_grid) # [B, C, H, W] # 4. 有效区域掩码(超出感知范围的置零) mask = inside_range(local_xy) # 5. 加权累加,权重可由一个小网络根据特征内容预测 w = predict_weight(aligned) * mask fused += aligned * w weight_sum += w fused = fused / (weight_sum + eps)这段代码只有二十来行,但它包含了整个方法的骨架。所有花哨的设计——可变形卷积、注意力聚合、跨模态对齐——都是在这个骨架上做增量。理解这一点,读论文的时候就不会被各种模块名字绕晕。
3. 对齐、压缩、聚合:空间特征融合的三个动作
上一节给了骨架,这一节拆肌肉。我把空间特征融合拆成三个动作:对齐(把位置对上)、压缩(把数据传过去)、聚合(把信息合起来)。三者的难度递减,但对最终指标的贡献往往是对齐最大、压缩最容易被低估、聚合最容易被过度设计。
3.1 几何对齐只是起点,不是终点
用标定外参和定位位姿做刚体变换,这叫几何对齐,是硬对齐。它的假设是:位姿是准的,标定是准的,时间戳是对齐的。现实中这三个假设没有一条完全成立。
定位误差在城区多路径环境下轻松到 1-2 米,航向角误差几度很常见。标定误差来自安装公差和车辆振动,外参在跑了几万公里后会漂。时间戳的问题更隐蔽——两辆车的传感器采样时刻差 50ms,在 60km/h 的相对速度下就是 0.8 米的位移。这三项误差叠加,几何对齐之后残差经常还有一两米。
所以标题里带 "Spatial" 的工作,几乎必然要在硬对齐之后加一层软对齐:让网络自己去学一个空间偏移量,把残余误差吃掉。这就是为什么 deformable convolution、spatial transformer 这类模块在协作感知里出现频率特别高。它们的共同点是:不改变采样点的整数格位置,而是学一个连续的偏移量 (Δx, Δy),在偏移后的位置做双线性插值。偏移量由特征本身预测,所以能适应不同的误差模式。
这里有个经验:软对齐的偏移量必须加约束。不加约束的话,网络会学出很大的偏移去"凑"检测损失,结果是在训练集上指标很好看,换个场景立刻崩。常见的约束方式有两种,一是给偏移量加 L2 正则或者上限裁剪(比如限制在 ±3 米内),二是加一个辅助监督——如果数据集里能拿到较准的位姿真值,就用它监督偏移量应该接近零。我倾向于两者都用,先限幅再正则,实测更稳。
3.2 可变形采样吃掉残余位姿误差的原理
讲得再具体一点。假设统一网格上有个位置 p,几何对齐后它在邻居车特征图上的采样点是 q。因为位姿有 1 米误差,真正对应同一个物理点的位置其实是 q + δ,其中 δ 是未知的。可变形采样的做法是:用 q 附近的特征预测出 δ,然后在 q + δ 处插值。因为 δ 是从局部特征学出来的,它天然带有内容感知能力——比如在物体边缘处,特征梯度大,预测出的 δ 会更倾向于把采样点拉到边缘对齐的位置。
这个机制在直觉上很像图像配准里的光流估计,只不过这里不需要显式算出稠密光流,只需要为每个采样点算一个二维偏移。计算开销很小,一个 3×3 卷积就能出 18 个通道(9 个采样点 × 2 个方向),但对精度的提升很明显。我在类似结构上做过对比,加了可变形对齐之后,在 2 米位姿噪声下 AP 能挽回 8 到 12 个点,这个量级是值得的。
当然它也有失效场景。如果两个智能体看到的根本不是同一个物体(比如一个看车头一个看车尾),特征差异太大,预测出的偏移会乱跑。这种情况下软对齐反而有害。解决办法是在聚合阶段用注意力权重把这类不可靠的对应关系压下去,这就引出了第三个动作。
3.3 聚合权重从哪来:平均、注意力还是门控
对齐之后是聚合。最粗暴的方式是直接平均,假设每个智能体贡献相同。这个方法在同步、同构、位姿理想的仿真数据上凑合能用,真实场景下基本不行,因为不同视角的信息质量差异巨大——正对目标的车看得清楚,斜视的车特征质量差,平均等于把噪声掺进去。
稍微好一点的是基于几何的加权:离目标越近权重越高,或者根据视线角度算一个可见性分数。这个做法可解释性强,不需要学习,工程上很受欢迎。缺点是它只看几何,不看内容——如果近处那辆车刚好被另一辆车挡住了一部分,几何权重仍然会给它高分。
再进一步是基于内容的注意力:用一个小网络根据对齐后的特征预测每个位置、每个智能体的权重,做 softmax 归一化。这就是各种 "cooperative attention" 模块的通用套路。它的优势是能学到"这个位置我这个视角看不清楚,应该听别人的"这种逻辑。代价是参数量增加,而且注意力容易塌缩——训练不充分时会退化成所有位置权重相同,等于白加。
提示:如果你自己实现,检查注意力是否真的活了,方法很简单——把某个智能体的特征全部置零,看输出特征变化有多大。如果几乎不变,说明注意力塌缩了,得加个熵正则或者用温度系数调一下。
4. 位姿噪声与通信约束下的鲁棒性
到这一节,方法的主体已经清楚了。但一篇协作感知的论文能不能站住,不看它在理想条件下的峰值指标,看它在退化条件下的曲线。位姿噪声、通信丢包、时间异步,这三样是真实系统里天天发生的事,也是审稿人和工程落地方最关心的部分。
4.1 为什么位姿扰动增强是必做项
训练时如果不加位姿扰动,模型会默认"位姿是准的",从而把软对齐模块学成一个近似恒等映射。一旦部署到位姿有误差的真实环境,性能断崖式下跌。这个现象我在多个项目里都见过,非常典型。
标准做法是在训练时给每个协作智能体的位姿注入随机扰动:平移加高斯噪声(σ 取 0.5 到 2 米),旋转加高斯噪声(σ 取 1 到 3 度),也可以加均匀分布模拟最坏情况。扰动的幅度要按目标场景来定,高速场景定位更准但速度快,城区场景定位差但速度慢,两者要区别对待。
更讲究一点的做法是课程式扰动:训练前期扰动小,让模型先学会基本融合;后期逐步加大扰动,逼它学会鲁棒对齐。这个技巧在训练稳定性上帮助很大,尤其是软对齐模块刚初始化的时候,一上来就给 2 米噪声,梯度会炸。
还有一点容易被忽略:扰动的施加对象。是扰动协作方的位姿,还是扰动自车的位姿,还是两者都扰动?从物理上讲,误差是相对的,扰哪边等价。但从实现角度,扰协作方更自然,因为自车坐标系是融合的基准。如果扰自车,所有协作方的变换矩阵要一起改,代码容易写错。
4.2 特征图压缩与传输预算的账要算清
协作感知论文里经常有一节叫 "bandwidth analysis",很多人扫一眼就跳过了,其实这一节决定了方法能不能落地。算一笔账:一张 200×200×64 的 float32 特征图是 10.24MB。假设 10Hz 的融合频率,单车就要 100MB/s 的链路,这还没算协议开销。5G 的上行带宽在理想条件下能到几十 Mbps,实测在移动场景下更不稳定。所以压缩是必须的。
常见的压缩手段有几类,效果和代价差别很大:
| 压缩手段 | 压缩比 | 精度损失 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 通道裁剪(C 降到 32 或 16) | 2-4x | 小到中 | 低 | 几乎所有场景,首选 |
| 8bit 量化 | 4x | 小 | 低 | 部署阶段标配 |
| 空间降采样(2x2 池化) | 4x | 中 | 低 | 大范围感知,远处目标可接受 |
| 稀疏化(只传非零区域) | 5-20x | 中 | 中 | 点云 BEV 特征天然稀疏,很有效 |
| 学习式编解码(自编码器) | 10x+ | 中到大 | 高 | 链路极窄时的备选 |
从实操看,通道裁剪加量化是性价比最高的组合,能拿到接近 16 倍的压缩,精度损失通常在一个点以内。稀疏化在激光雷达 BEV 特征上效果特别好,因为地面以上的格子本来就占少数,把空区域按游程编码传过去,实测能压到 5% 以下。学习式编解码虽然压缩比高,但解码端的延迟和额外算力开销经常被低估,端到端时延反而变差。
CoFF 这类方法在论文里一般会给出"压缩到 X 通道后精度下降 Y 个点"的表格,看这个表的时候要特别注意它是在什么位姿条件下测的。有些工作在干净位姿下压缩损失很小,但一加噪声,压缩带来的信息损失会被放大,因为软对齐需要足够的特征细节来判断偏移。
4.3 时间异步:最容易被论文忽略的一环
学术论文里绝大多数协作感知实验都是"同步帧"设定——所有智能体的数据严格对齐到同一时刻。真实系统里这件事几乎做不到。不同设备的采样时钟不同,传输延迟不同,融合时刻拿到的往往是几十毫秒甚至上百毫秒前的数据。
时间异步的后果是空间错位。以 15m/s(约 54km/h)的相对速度算,100ms 就是 1.5 米。这个量级和定位误差相当,必须处理。工程上的处理方式主要有两种:一是运动补偿,用目标的估计速度或者场景的 ego-motion 把历史特征外推到当前时刻;二是时间衰减权重,越旧的特征权重越低,让融合自动弱化过期信息。
运动补偿里有个陷阱:如果你用场景级的 ego-motion 做补偿,对静态背景是准的,对运动目标就是错的,因为目标自己在动。比较稳的做法是对静态区域用 ego-motion 补偿,对检测出的前景目标用跟踪得到的速度单独补偿。这个双路补偿逻辑在实现上稍麻烦,但在真实数据上的收益比单纯调网络结构大得多。
5. 自己复现一遍:数据组织、训练流程与消融设计
光看论文不够,真想掌握这类方法,得自己跑一遍。这一节讲复现路径,尽量给出可直接抄的配置思路。需要提醒的是,协同感知的数据集组织和单车检测差别很大,很多人卡在数据管线而不是模型上。
5.1 数据管线的关键字段一个都不能少
协同感知样本至少要包含下面这些字段,缺一个都会在训练中途报错或者悄悄出错(后者更可怕):
sample = { "agent_ids": [...], # 本帧参与协作的智能体列表,含自车 "ego_id": "agent_0", # 融合基准 "frames": {aid: {...}}, # 每个智能体的传感器数据或已提取特征 "lidar_pose": {aid: 4x4}, # 雷达到世界坐标系的变换 "ego_pose": {aid: 4x4}, # 自车到世界坐标系的变换 "calib": {aid: 4x4}, # 传感器到车体的外参 "timestamp": {aid: float}, # 采样时刻,用于时间对齐 "visible_mask": {aid: ...}, # 可选,标注该智能体能看到哪些目标 }坐标变换链一定要理顺:世界 ← 自车 ← 传感器。融合时是从统一坐标往各智能体坐标反推,所以真正参与计算的是这条链的逆。我见过太多人在这里把乘序搞反,结果特征图整体旋转了 90 度,训练 loss 还能降(因为网络学会了补偿),但验证指标怎么都上不去。排查方法:拿一个已知位置的标定物(比如路口的一根杆子),手工算一遍它在各智能体 BEV 图上的像素坐标,和可视化对一遍,对不上就是变换链错了。
还有visible_mask这个字段,很多数据集不直接给,需要自己算。做法是把标注框投影到每个智能体的坐标系,判断是否在感知范围内、是否被遮挡。有了它,训练时可以只对"该智能体确实看得到"的区域计算融合损失,避免让网络去学"从看不到的地方提取特征"这种不可能的任务。这个细节对最终指标的影响经常被低估,我实测能差 3 到 5 个点。
5.2 分阶段训练比端到端省一半时间
理论上整个网络可以端到端训练,实践中我不推荐直接这么干。原因是对齐模块和检测头会互相拖累:训练初期检测头输出全是噪声,梯度回传到对齐模块,把偏移量学得乱七八糟;等对齐模块崩了,检测头也救不回来。
更稳的做法是分三段:
第一段,单车预训练。只用自车数据训练 backbone 和检测头,跑到接近单车基线水平。这一步的目的是让特征提取器先具备基本的几何理解能力,输出的 BEV 特征有语义。
第二段,冻结检测头,只训对齐。把协作方的特征接进来,固定 backbone 和检测头,只训练软对齐模块和聚合权重。这一步收敛很快,通常几十个 epoch 就够,因为任务简单——就是学怎么把特征搬对位置。可以用一个辅助损失监督偏移量接近零(在有位姿真值的仿真数据上)。
第三段,联合微调。解冻全部参数,用小学习率(比如基线的十分之一)联合训练。这一步的提升主要来自对齐和检测的协同优化,通常能再涨 1 到 3 个点,但训练不稳定,学习率稍大就崩。
提示:第三阶段一定要保留第一阶段的数据做混合训练,也就是每个 batch 里混一部分纯单车样本。否则模型会过拟合到"总有协作方"的设定,一旦协作方掉线,性能崩得比单车还差。这个退化问题在真实部署里非常要命。
5.3 消融表该怎么设计才有信息量
论文的消融表通常是"去掉 A 掉几个点,去掉 B 掉几个点",这种表信息量有限。自己复现的时候,我建议按下面几个维度做更细的拆解,能看到方法真正的边界在哪里。
第一个维度是位姿噪声强度。横轴取 0m、0.5m、1m、2m、4m,纵轴画 AP。理想的曲线是平缓下降,说明鲁棒;如果 1m 之后就断崖,说明软对齐的限幅设小了或者根本没训好。这条曲线比单一数字有用得多。
第二个维度是协作智能体数量。1、2、4、8 个协作者,看增益是否饱和。很多方法在 2 个协作者时增益最大,到 8 个反而下降,原因是聚合权重学不好,噪声被放大。知道自己方法的饱和点,对系统设计很关键。
第三个维度是距离分段。0-30m、30-50m、50-80m 分别看 AP。协作感知的增益主要来自远距离和遮挡区域,如果近距离也涨了很多,反而要怀疑是不是数据泄漏了(比如协作者和自车看到了完全相同的区域,等于变相增加了训练数据)。
第四个维度是通信退化。随机丢弃 10%、30%、50% 的协作方特征,模拟丢包。这个测试在论文里少见,但对工程落地极有价值,能直接告诉你系统需要几条冗余链路。
6. 实测中最容易翻车的几个地方
最后这部分是纯经验,都是我在类似结构上踩过或者见别人踩过的坑。论文不会写这些,但它们决定了你能不能把方法跑出论文里的数字。
6.1 坐标系与朝向角符号:一半的 bug 出在这
3D 检测里的朝向角有太多约定:有的用弧度范围 [-π, π],有的用 [0, 2π),有的用 sin/cos 编码,有的直接回归角度。协作场景下,不同智能体的朝向角定义如果没统一,融合后会出现"半个场景的目标朝向翻转"这种诡异现象。
排查顺序建议这样:先确认所有智能体的位姿矩阵是 4×4 齐次矩阵且最后一行为 [0,0,0,1],不是的话先补;再确认旋转矩阵是右乘还是左乘约定,具体做法是拿一个纯平移的场景验证,平移对了再验证旋转;最后确认角度回归的范围和损失的周期性处理,用 sin/cos 编码最省心,不然后悔无穷。
还有一个隐蔽的坑:鸟瞰图的行列方向。有的实现里 BEV 图的第 0 行对应最远处,有的对应最近处;x 轴朝前还是朝右,各家的约定都不一样。跨代码库搬运的时候,这个不一致会导致特征图上下或左右翻转,而且因为场景有一定对称性,loss 不会炸,只是指标上不去。验证方法:把 BEV 特征图可视化出来,叠上标注框,肉眼扫一眼目标位置对不对,两分钟的事,能省两天。
6.2 双线性采样的边界处理会制造幽灵目标
空间变换必然涉及重采样,重采样必然涉及边界。当采样坐标超出特征图范围时,常见的处理是 padding 成零然后插值。问题在于,如果采样点刚好在边界外一点点,双线性插值会用到三个零值和一个有效值,得到一个被严重衰减的特征。这个衰减后的特征如果恰好落在某个目标的预测位置上,就会制造出置信度很低但位置很奇怪的"幽灵框"。
解决办法有两个。一是在采样前就算好有效掩码,超出范围的直接置零并且不参与归一化(也就是分母的权重和要排除掉它)。二是在特征图外围做一圈 replicate padding 而不是 zero padding,让边界处的插值有合理的延拓值。两个方法可以叠加使用,实测幽灵框能减少一大半。
另外提醒一句:训练和推理时的边界处理必须完全一致。我曾经遇到过训练用 zero padding、推理用 replicate padding 的情况,指标掉了 4 个点,查了两天才发现是这里。
6.3 显存与 batch 组织的现实约束
协同感知的显存开销是单车的 N 倍(N 是协作智能体数量),因为一个样本里要同时装 N 份特征图。8 个协作者、BEV 特征 200×200×64 的情况下,光特征就吃掉不少显存,再加上梯度,单卡很容易 OOM。
常见的省显存手段:把协作方的 backbone 前向放到torch.no_grad()里(如果你不做端到端联合训练的话),特征用 fp16 存,梯度检查点用在 backbone 上。更激进的做法是离线预提取特征——把所有协作方的 BEV 特征提前算好存到磁盘,训练时直接读。这样训练速度能快好几倍,代价是失去了数据增强的灵活性(没法做图像级的增强)。这个取舍在复现阶段完全可以接受,先把方法跑通,再考虑端到端。
batch 的组织也有讲究。一个 batch 里的样本应该是"同一场景的不同时刻"或者"不同场景",不要混。混了之后协作方的特征和自车对不上,等于给网络喂噪声。另外如果数据集里各场景的协作者数量不一致(有的 2 个有的 5 个),要做好 padding 和 mask,别让 padding 的假特征参与聚合。
我自己在这个方向上折腾下来的体会是,协作感知的难点从来不在网络结构有多复杂,而在那些不起眼的数据细节——一个变换矩阵的乘序、一个坐标系的朝向、一个时间戳的对齐。CoFF 这类把"空间"写进标题的工作,本质上是在正面刚这些细节。真要上手,别急着改结构,先把数据管线跑通、把可视化做出来、把坐标系验证清楚。这些做完了,剩下的部分反而水到渠成。如果后续想继续往下挖,我觉得有两个方向值得试:一是把时间维度真正纳进来做 4D 融合,而不是当成误差去补偿;二是研究异构智能体(比如相机为主的车和激光雷达为主的路侧)之间的特征语义鸿沟怎么填,这块目前的公开工作还很少,空间也大。