☰
OccFusion:3D占据栅格预测与多传感器体素级特征融合实践
2026/10/27 20:11:30 网站建设 项目流程

做自动驾驶感知的同行大概都有过这种体验:检测框的指标在验证集上已经刷得很漂亮,一上路却还是会被各种“编外物体”打脸——斜插在路边的施工围挡、掉在车道里的一半纸箱、横在路口的三轮车拉的超长钢管。这些目标在标注规范里找不到对应类别,检测头再强也使不上劲。OccFusion这类思路要解决的正是这个问题:不再执着于“这个东西是什么、框在哪”,而是回答“这块空间被占了没有”。它把自车周围的近场空间切成三维栅格,逐格判断占用、自由还是未知,再把相机和激光雷达两路证据在体素层面融到一起。

这个方向适合谁看?如果你正在做3D 占据栅格预测的算法落地,或者手上有一套 BEV 感知链路想往体素空间延伸,又或者你只是好奇“多传感器融合”在体素粒度上到底怎么实现,下面这些内容应该都能对上号。我会把 OccFusion 从问题定义、架构选型、参数换算,一直拆到能跑起来的伪代码和踩坑记录。

1. OccFusion 要解决的到底是什么问题

1.1 从“画框”到“占格子”:任务定义变了

传统 3D 检测的输出是一个个带朝向的立方体,每个框对应一个类别标签。这套范式隐含了一个假设:世界上的东西都能被穷举成有限的类别集合。现实显然不是这样。工程车伸出来的机械臂、临时摆放的锥桶阵、货车上没绑牢的板材,这些东西要么没类别,要么框出来形状根本对不上。

占据栅格换了个问法:把自车周围划成一个三维网格,比如前后左右各 40 米、高度 6.4 米,格子边长 0.4 米,那么就是一个 200 × 200 × 16 的网格,一共 64 万个格子。每个格子输出三种状态之一:被物体占据、可通行自由、以及没被任何传感器看到过的未知。这个定义的好处是不依赖类别体系,未知物体天然被覆盖;坏处是输出维度暴涨,算力和显存的开销比检测框高一个数量级。

OccFusion 在这个任务里承担的角色,是把“多源证据合成一张占据图”这件事做扎实。相机提供稠密但缺深度的语义线索,激光雷达提供精确但稀疏的几何线索,两者在体素空间里互补。我自己在做方案对比时最大的感受是:单看某一模态的占据预测,永远有一类场景是系统性失效的,而融合能把这类失效从“必然”压到“偶发”。

1.2 单模态方案撞上的三堵墙

纯视觉路线的问题很集中。第一是深度。单目图像到体素的映射依赖一个隐式的深度分布估计,这个分布在纹理丰富、光照正常的场景里还算靠谱,一到夜间、逆光、雨雾、大面积白墙,误差就是米级的。我实测过一个典型 case:空旷路面上有一段隔离护栏,视觉分支把护栏后面的自由空间也判成了占用,因为深度分布拖了一条长尾过去。

第二是远距离衰减。分辨率固定的情况下,图像上远处目标只占几十个像素,投影到体素后有效证据极少,占据预测在 30 米开外就开始糊成一片。

纯激光雷达路线的问题同样明显。点云稀疏性决定了它对细小物体、远处物体、低反射率物体的召回有天生的上限;一根直径 5 厘米的杆子,在 20 米处可能只有两三个点落上去,体素化后很可能直接被判定为空。另外点云没有颜色和纹理,遇到“玻璃幕墙”这类目标,几何上近乎透明,语义上却是实体障碍,只靠几何很难判对。

所以融合的动机不是“多一路输入显得高级”,而是两个模态的失效模式几乎正交:视觉怕光照和深度不准,雷达怕稀疏和低反射。正交的失效模式才是融合真正的收益来源。

1.3 三条融合路线的取舍

真要做,第一个决策就是融合发生在哪一层。我把常见的三种做法列一下,都实际跑过或者评估过:

融合层级具体做法优点主要问题
前融合(数据级)用雷达点给图像像素补深度,生成带深度的点云再体素化实现简单,深度信息直接可用投影误差会被后续全链路放大;点稀疏处补不出稠密深度
特征级融合两路各自提特征,投影到同一体素网格后做注意力或门控加权模态互补保留最完整,端到端可训对齐误差敏感,显存开销大
后融合(结果级)两路各自出占据概率,最后做加权或证据合成模块解耦,单路可独立迭代单路的系统性错误无法被另一路纠正

OccFusion 走的是特征级融合,我认为这是当前性价比最高的选择。前融合的致命伤在于它把深度估计的误差固化进了输入,后面的网络再怎么学也难纠正;后融合看起来工程友好,但视觉分支把护栏后面判成占用这件事,雷达分支给不出任何反向证据,因为那块区域点云本来就是空的,属于“未知”而不是“自由”,融合权重根本不知道该往哪边压。

特征级融合的前提是两路特征能在同一个栅格上对齐,这就把坐标系的问题推到了台前,也是后面最容易出事的地方。

2. 整体架构拆解:融合该发生在哪一层

2.1 坐标系与特征对齐,一切的地基

体素占据预测里,坐标系的错误是最贵的一类 bug,因为它在指标上表现为“整体精度掉几个点”,很难定位。我的做法是把所有坐标统一到一个固定的自车坐标系:原点在後轴中心地面投影,X 向前,Y 向左,Z 向上。所有传感器外参都标到这个系上,体素网格也定义在这个系里,前向范围 [-40, 40],侧向 [-40, 40],高度 [-2.0, 4.4]。

相机分支的关键一步是把体素中心点投影到图像平面。这一步的公式本身很朴素:

# 伪代码:体素中心批量投影到某个相机 # grid_xyz: [N, 3] 体素中心在自车坐标系下的坐标 p_cam = grid_xyz @ R_cam.T + t_cam # 转到相机坐标系 z = p_cam[:, 2] valid = z > 1e-3 # 相机后方或过近的点剔除 u = fx * p_cam[:, 0] / z + cx v = fy * p_cam[:, 1] / z + cy inside = valid & (u >= 0) & (u < img_w) & (v >= 0) & (v < img_h)

真正难的不是这段,而是标定的时间同步和安装公差。相机和雷达的硬件时间戳如果不同步到 10 毫秒以内,车辆在 60 km/h 下每 10 毫秒位移 17 厘米,正好是半个体素,投影就会系统性偏移。我一般会在数据预处理阶段做两件事:一是按硬件触发信号对齐时间戳而不是按消息到达时间;二是做一次在线标定校验,用静止场景下的标定板或车道线做残差检查,残差超过 0.3 度就报警重标。

提示:投影前务必做一次“可视化自检”——把体素网格中心投影到图像上画点,肉眼确认点云投影和图像里的物体边缘是否贴合。这一步花十分钟,能省掉后面两天的调参。

2.2 特征级融合的具体形态

两路特征都投影到 64 万个体素上之后,怎么合?我试过三种:直接相加、通道拼接后过卷积、门控加权。相加最简单但对齐误差零容忍,两路特征只要错一格,相加就等于互相污染。拼接加卷积参数量上去之后小数据集容易过拟合。

最终我用的是一种模态门控结构:先算出每个体素位置上两路的可靠程度,再按权重混合。这个“可靠程度”不需要额外标注,让网络自己学。

# 伪代码:单个 OccFusion 融合块 def fuse(cam_vox, lidar_vox, lidar_cnt): # cam_vox: [B, C, X, Y, Z] 相机投影得到的伪稠密体素特征 # lidar_vox: [B, C, X, Y, Z] 点云体素化后的稀疏特征 # lidar_cnt: [B, 1, X, Y, Z] 每个体素内的点数,作为稀疏度先验 gate = sigmoid(mlp(concat([cam_vox, lidar_vox, log1p(lidar_cnt)], dim=1))) fused = gate * lidar_vox + (1.0 - gate) * cam_vox return fused + conv3d(fused) # 局部 3x3x3 平滑,补偿对齐残差

把点数作为额外输入是我踩坑之后加的。纯粹靠特征本身,网络分不清“这个体素雷达没点是因为那里真的空”还是“因为物体反射率低没返回”,而点数分布能间接区分这两件事——前者通常邻域也没有点,后者邻域往往有点。

2.3 时序融合值不值这个显存

占据预测有一个天然优势:场景在短时间内是准静态的。前一帧看到的东西,只要知道自车怎么动的,就能大致搬到当前帧。这对补全遮挡和稀疏区域帮助极大,尤其是视觉分支在远处糊掉的那片区域,靠历史帧累积能拉回来不少。

具体做法是把上一帧的占据 logits 按自车位姿变化 warp 到当前帧坐标系,再和当前帧结果做置信度门控融合。位姿从组合导航或轮速加 IMU 拿到,做一次 SE(3) 变换,然后对体素做三线性采样。

代价很直接:显存翻倍,并且需要一个按序列组织的采样器。我实测在 200 × 200 × 16 的网格上加一帧历史,训练显存大概多 1.5 到 2 GB(batch=4,混合精度);推理延迟增加 8 到 12 毫秒。值不值?我的判断是:如果你的场景里遮挡严重(城市路口、密集停车),值;如果是高速直道为主,收益不明显,可以先不上。

还有一个容易忽略的细节:位姿估计本身的误差会随时间累积,所以历史帧不能无限往下挂。我的经验是只挂一帧,最多两帧,再多的话 warp 误差反而把结果带偏了。

2.4 解码头与监督信号的搭配

占据头的输出是每个体素一个类别分布。三个类别(自由、占用、未知)里,“未知”这个类不能靠网络凭空学出来,必须靠监督信号的 mask 来定义——没被任何传感器射线穿过的体素,在算 loss 时直接忽略,模型自然学不到它的规律,推理时它的输出就是低置信度的,我们在后处理里把它归到未知。

解码头的结构我倾向轻量:两层 3D 卷积加一个 1×1×1 的分类层就够了。这里踩过一个坑,一开始用了比较深的三维卷积堆叠,参数量上去了,但 mIoU 反而降了 1.2 个点——感受野过大导致相邻体素特征互相污染,细长物体(杆子、护栏)直接被抹平。后来把卷积核限制在 3×3×3,深度砍到两层,细长目标的召回明显回升。

监督信号方面,除了最终的占据标签,我还加了两个辅助头:一个预测每个体素的“可见性”(是否被至少一个传感器观测到),另一个是深度分布的中间监督(用雷达点投影到图像上生成稀疏深度作为监督)。辅助头只在训练时用,推理时丢掉,不增加部署成本。

3. 核心参数与细节设计

3.1 体素分辨率、范围与显存预算的算式

这一块是纯工程账,算清楚了再动手能省很多返工。假设网格是 X × Y × Z,特征通道 C,数据类型 float16(2 字节),batch 为 B:

单个特征图显存 = X × Y × Z × C × 2 字节

以 200 × 200 × 16、C = 32 为例,640000 × 32 × 2 ≈ 41 MB。看起来很小,但训练时反向传播要保存激活,实际占用通常是特征图本身的 3 到 5 倍;如果时序融合再存一份历史,多尺度再叠两三层,B = 4 的情况下很容易冲到 8 GB 以上。

分辨率的选择逻辑是这样的:体素边长决定了能分辨的最小物体。0.4 米的体素,一根直径 5 厘米的杆子基本不可能被单独占住,只能靠邻域特征“糊”出来;0.2 米的体素能分辨,但网格数量翻 8 倍,显存直接爆炸。我的折中是近场用 0.2 米、远场用 0.4 米的两级网格,近场范围 20 米,远场到 40 米。这样近场的精细度和整体显存都能接受。

体素边长网格规模(80m×80m×6.4m)单帧特征显存(C=32, fp16)适用场景
0.2 m400×400×32约 328 MB泊车、狭窄路段,算力充足
0.3 m267×267×21约 90 MB城区中低速
0.4 m200×200×16约 41 MB城区 + 高速通用
0.5 m160×160×13约 21 MB高速为主,追求帧率

还有一点要提醒:范围定得越大,边界处的体素越依赖外推,误差越大。我一般会在训练时对超出点云有效范围 10% 的体素做 loss 降权,避免模型在“瞎猜区”过拟合。

3.2 相机深度的不确定性怎么处理

视觉分支从图像特征到体素特征,中间隔着一次深度估计。主流做法是在每条相机射线上采样一串离散深度,得到每个深度区间的概率,再把图像特征按概率加权铺到体素上:

# 伪代码:视锥特征按深度分布铺到体素 # ray_feat: [B, N_ray, C] 每个射线方向上的图像特征 # depth_prob: [B, N_ray, D_bin] 归一化后的深度分布 voxel_feat = einsum('bnc,bnd->bcd', ray_feat, depth_prob) # 按深度加权求和

这个操作对深度分布的质量极度敏感。我做过一个实验:把深度分布换成 one-hot(取 argmax),mIoU 掉了将近 4 个点,因为离散化误差被硬编码进了特征。所以深度分布一定要保留软性,用 softmax 而不是 argmax,并且训练时给它独立的监督。

另一个细节是深度区间的划分。均匀划分在近处太粗、远处太细;我改成按对数间隔划分,近处 0.5 米一档、远处 3 米一档,总共 64 个 bin,实测深度估计的误差分布更均匀。用 88 个 bin 收益就很小了,显存和计算量却上去了。

注意:深度监督的标签要用雷达点投影生成,但投影时要剔除落在运动物体上的点并做时间补偿,否则运动模糊会让深度分布学出一个双峰,推理时沿着物体边缘出现“体素拉丝”。

3.3 稀疏与稠密的边界

这是 OccFusion 里最反直觉的一个点。激光雷达分支天生稀疏,10 万个点落在 64 万个体素里,非空体素可能只有 3 到 5 万个,占比不到 8%。相机分支产生的体素特征却是稠密的——每条射线沿途都会被铺上特征。于是融合时两边的“密度”严重不匹配。

如果直接用稠密三维卷积,雷达分支里那 92% 的空体素也参与计算,等于让网络在大量无信息的位置上学参数,不光浪费算力,还会带来一个隐蔽的副作用:空体素位置的特征会逐渐趋同,融合门控在这些位置学不到区分度,最后视觉分支的伪稠密特征会盖过雷达的稀疏证据。

我的处理是分两段。融合阶段用稀疏卷积,只在“相机有效或雷达有点”的并集体素上计算,这个并集大概占全网格的 20% 到 25%。解码头再转回稠密卷积,因为占据预测本身要求对每个体素都输出结果,包括空体素。

稀疏化阈值要调。我一开始用特征 L2 范数大于 0.01 作为有效判据,结果发现相机分支在远处射线上铺出来的特征范数普遍偏低,被误剪掉,导致远处占据召回下降。后来改成“相机射线命中且深度置信度大于 0.3”的几何判据,比纯特征阈值稳得多。

3.4 损失函数与样本不均衡

64 万个格子里,真正被占据的可能只有几千个,占比千分之几。这个不均衡程度比二维检测里的前景背景失衡还要极端。直接上交叉熵,网络会学到一个“全判自由”的退化解,准确率 99% 以上,但完全没用。

我用的组合是 focal loss 加类别权重。focal 的 gamma 取 2.0,占用类的权重给到 5 到 8 倍,自由类压到 0.5。这几组数我是这么试出来的:先只用 focal 不加权,占用类召回 45% 左右;加权重到 3 倍,召回上到 62%,但虚警明显多了;最后停在 6 倍配合 gamma=2,召回 71%,虚警率还在可接受范围内。

另外加一个 lovasz 风格的 soft IoU 损失做辅助,专门对付细长结构。交叉熵是逐体素独立的,学不到“结构完整性”,而 soft IoU 会把整块目标的预测绑在一起,杆子、护栏这类细长目标断裂的情况能减少一大半。

损失项权重作用去掉后的影响
focal loss (gamma=2)1.0主监督,缓解类别不均衡占用召回掉 15 点以上
soft IoU loss0.5结构完整性细长目标断裂明显变多
深度分布 KL0.3约束视觉分支深度估计远处 mIoU 掉 2 到 3 点
可见性 BCE0.2让网络学会区分未知未知区域误判为自由的概率上升

3.5 数据增强与标定扰动

体素任务的增强比二维少很多自由度,因为三维几何一变,标签也得跟着变。我实际在用的有这几样:

几何类增强只做全局的旋转和缩放,角度在 ±10 度、缩放 0.95 到 1.05 之间。别做翻转,自车坐标系不对称,翻转之后左右相机的外参全乱。

标定扰动是我最看重的一项,也是很多实现里被忽略的。做法是给相机外参加 ±0.5 度旋转、±2 厘米平移的随机噪声,模拟装配公差和振动;内参的焦距给 ±1% 的扰动。这一项对鲁棒性的提升非常直观,我做过消融,加了标定扰动之后,在人为注入 0.3 度外参偏差的测试集上,mIoU 只掉 1.8 个点;不加扰动的版本直接掉 6 个点以上。

点云这边做随机丢点(保留率 0.8 到 1.0)和距离相关的密度衰减,模拟不同反射率和雨雾条件。还可以做“模态丢弃”:以 10% 的概率整个丢掉雷达分支,强迫视觉分支独立学出可用的占据预测。这个技巧在部署时很有价值,万一雷达故障,系统还能降级运行。

提示:标定扰动的幅度不要贪大。超过 1 度的旋转噪声会让模型学到一个“模糊化”的退化解,正常标定下的精度反而下降。0.3 到 0.6 度是比较稳的区间。

4. 从零搭一版可跑的 OccFusion

4.1 数据与坐标系统一

开工第一步是把数据管线捋顺,这一步做不好后面全是玄学。我整理了一个检查清单,每次换数据集都过一遍:

  • 所有外参统一到自车坐标系,不要保留“雷达到相机”和“相机到雷达”两种方向的外参,混用必错。
  • 时间戳按硬件触发对齐,图像和点云的时间差控制在 5 毫秒内。
  • 每帧记录自车位姿(平移 + 四元数),时序融合直接用它。
  • 占据标签生成时,用雷达射线做自由空间标注:射线经过的体素标自由,射线终点标占用,没被任何射线碰到的体素标“忽略”。
  • 边界处理统一:范围外的点直接丢弃,不要 clamp,clamp 会把远处的东西挤到边界上造成假占用。

标签生成这块我要多说一句。很多开源实现只标占用,自由和未知靠网络自己推,结果就是在没有点云覆盖的区域(比如远处高空、被遮挡区域)网络乱猜。显式标注自由空间、并把未观测区排除在 loss 之外,是提升 mIoU 最直接的一招,成本也就是写一个射线遍历的脚本。

4.2 特征提取与栅格化

两路特征提取的骨干网不需要太深。我用的是一个轻量的二维骨干(下采样 16 倍)处理图像,一个基于体素稀疏卷积的三维骨干处理点云,两路的输出通道都对齐到 32。

点云栅格化的时候有几个参数值得说:

# 伪代码:点云体素化 voxel_size = (0.4, 0.4, 0.4) point_range = [-40, -40, -2.0, 40, 40, 4.4] # 1) 计算每个点所属体素索引 idx = floor((points_xyz - range_min) / voxel_size) # 2) 去掉越界点 # 3) 每个体素内做特征聚合:均值 + 最大值 + 点数 # 均值保留几何中心,最大值保留边缘,点数反映密度 voxel_feat = concat([mean(pts_per_voxel), max(pts_per_voxel), log1p(count)])

把“点数”单独作为一个通道是我反复验证过的有效 trick。它让网络能间接感知到“这个体素是实心还是被扫了个边”,对区分薄板、树枝这类穿透性目标有帮助。

图像分支到体素的转换我用的是深度分布加权的方式,前面写过。这里有个实操细节:射线生成不要用全分辨率,要在特征图上做(下采样 16 倍后的尺寸),否则光生成视锥就要几毫秒。特征图分辨率下采样后,每条射线对应的感受野反而更大,对小目标的鲁棒性更好。

4.3 融合模块实现

融合模块的结构前面给了伪代码,这里补充几个实操层面的决定。

通道数上,两路特征必须对齐到同一维度,我用 32。试过 64,mIoU 只涨 0.4 个点,显存翻倍,不划算;试过 16,细长目标召回掉得厉害。

融合的位置,我放在两路骨干各自下采样到 1/4 分辨率之后,而不是最后一层。太晚融合,两路的特征已经各自抽象到语义空间,空间对齐的精度不够;太早融合,特征还没成形,融合没意义。1/4 分辨率是个折中,此时体素特征的空间精度还够,语义信息也已经有了一些。

时序融合的 warp 实现要注意插值方式。三线性插值会让边缘变糊,我实测在细长目标上会损失 1 个点左右的 IoU。改成“最近邻采样 + 一个 3×3×3 的平滑卷积”,效果反而更好,因为平滑是在 warp 之后做的,不会把插值误差扩散开。

# 伪代码:时序 warp # prev_occ: [B, C, X, Y, Z],上一帧占据特征 # T: [B, 4, 4],上一帧到当前帧的位姿变换 # 1) 生成当前帧体素中心坐标 grid_cur [N, 3] # 2) 变换到上一帧坐标系:grid_prev = grid_cur @ T[:3,:3].T + T[:3,3] # 3) 归一化到 [-1, 1] 后做最近邻采样 # 4) 对采样结果做一次 3x3x3 平滑 # 5) 门控融合:fused = w * cur + (1-w) * prev_warped

4.4 训练配置与显存调度

训练这块的经验是:占据预测不是靠调超参调出来的,是靠把显存榨干、把 batch 撑上去撑出来的。batch 太小,focal loss 的梯度噪声极大,收敛非常不稳。

我的配置大致是这样:混合精度训练,优化器用带动量的 SGD 或 AdamW(AdamW 更稳一点,学习率 2e-4),warmup 500 步,余弦退火到 1e-5。梯度裁剪阈值设 5.0,不加的话前期偶尔会炸。

显存优化上用过几个手段,效果排序是:混合精度(省 40%)> 梯度检查点(省 30%,速度慢 20%)> 分阶段训练(先冻结视觉分支训融合,再联合训)。梯度检查点我一般只在最后冲高分辨率实验时开,平时不用,因为训练时间拉长太多影响迭代速度。

还有一个容易忽略的点:时序输入的采样必须按序列分桶。如果 batch 里混了不同序列的帧,warp 的时候位姿对不上,会引入完全错误的监督信号。我在数据加载器里加了断言,同一 batch 的所有样本必须来自同一序列的连续帧。

4.5 评测与指标口径

指标口径不统一是复现结果对不上的最大原因。我建议至少报告三组数:

第一是 mIoU,但一定要说明是在哪些类上平均的。把“自由”类算进去会让数字虚高,因为自由体素占比极高,随便猜都能拿到不错的 IoU。我一般报的是“占用类 IoU”和“占用 + 自由两类 mIoU”两个数。

第二是分距离段的 IoU:0-10 米、10-20 米、20-30 米、30-40 米各报一个。整体 mIoU 会把近场的优势平均掉,看不出远场的真实水平。这个拆解对定位问题极有帮助,我每次发现指标异常,第一件事就是看分段曲线,通常能立刻判断是深度分支的问题还是融合权重的问题。

第三是时序一致性,也就是相邻帧之间同一静态体素的预测翻转率。这个指标不在标准评测里,但工程上很有价值:翻转率高说明模型对噪声敏感,实际部署时会看到占据图“闪烁”,下游规划根本没法用。

指标计算方式参考目标(城区场景)主要用途
占用 IoU占用类交并比0.55 到 0.68主指标,反映整体水平
近场 IoU0-10 米范围0.75 以上泊车、低速场景关键
远场 IoU30-40 米范围0.30 到 0.45高速场景关键
时序翻转率相邻帧静态体素翻转比例低于 5%下游可用性
单帧延迟端到端推理耗时100 毫秒以内部署可行性

4.6 部署侧的几个硬约束

训练跑通和能上车是两码事。体素任务部署时最先撞上的就是算子支持问题:三维稀疏卷积在很多推理后端上支持不完整,往往要退化成稠密卷积,算力一下就上去了。

我的处理是把融合阶段的稀疏卷积换成“稠密卷积 + 掩码”,掩码在推理时由几何规则直接生成(相机射线命中范围 + 点云占用范围),不需要网络算。这样虽然算力比真稀疏卷积多一点,但完全走标准稠密算子,部署链路成熟得多。

第二个约束是显存带宽。体素特征虽然总容量不大(几十 MB),但融合模块的读写非常频繁,在嵌入式平台上很容易卡在带宽上而不是算力上。缓解办法是把通道数压到 32 以下、把时序融合放到最后一级、以及尽量减少中间张量的落地次数。

第三个是预处理。把体素中心坐标投影到各个相机这个操作,如果每帧都重算,纯 CPU 上可能就要几毫秒。做法是预处理阶段预计算好体素到各相机的投影索引表,运行时只做查表和采样。这招能省下 3 到 5 毫秒,在 100 毫秒的总预算里不算小。

5. 常见问题与排查实录

5.1 典型现象速查表

下面这些是我和团队实际遇到过、并且反复出现的现象,按“现象—定位—处置”整理:

现象大概率原因排查方法处置
整体 mIoU 正常但边界糊深度分布过软或者体素分辨率太粗看深度分布直方图是否过度平滑降 bin 数量、加深度 KL 监督
远处系统性漏检视觉射线有效长度不够、稀疏化阈值误剪统计 30 米外有效体素占比放宽稀疏判据、加远场 loss 权重
细长目标断裂解码头感受野过大对比不同卷积深度的细长类 IoU卷积核限 3×3×3,层数砍到 2
自由空间被误判为占用未标自由空间,模型靠外推检查标签里自由体素的数量补雷达射线自由空间标注
时序融合后反而变差warp 位姿误差累积、插值太软关掉时序看指标变化只挂一帧、改最近邻采样
夜晚 / 逆光精度骤降视觉分支深度估计失效分光照条件统计指标加光照增强、提高雷达门控权重
相邻帧结果闪烁缺少时序一致性约束统计静态体素翻转率加时序一致性 loss、扩大输入帧间隔

5.2 几个踩过的坑

第一个坑是“融合门控学成了常数”。训练日志里我发现门控输出的方差极小,几乎恒等于 0.5,等于没融合。原因是初始化的时候两路特征的数值尺度差太多——相机特征经过 softmax 加权后的量级偏小,雷达特征经过稀疏卷积后量级偏大,门控网络一开始就被大的一路主导,梯度推不动。解决办法是融合前对两路特征各做一次 LayerNorm,把尺度拉平,门控立刻就学出了区分度。

第二个坑是“时序 warp 用了错的位姿约定”。自车位姿有“车体到世界”和“世界到车体”两种约定,四元数的顺序也有 wxyz 和 xyzw 两种。我在一个项目里混用过,结果就是历史帧被镜像翻转地贴上来,指标看着还行(因为静态背景大致对称),但一遇到路口转弯就完全崩。现在我的做法是写一个单元测试:给定位姿变换,把一组已知点变换过去再变换回来,检查是否回到原点,误差超过 1e-4 就报错。

第三个坑是“数据增强把标签搞坏了”。三维旋转增强的时候,如果只转输入不转标签,模型会学到一个旋转不变但其实旋转相关的错误映射。这个错误很隐蔽,训练 loss 看着正常下降,验证集指标也不难看,但一换场景就崩。现在的做法是把增强参数写进样本字典,标签生成阶段统一应用,任何一处不一致就抛异常。

第四个坑相对冷门:辅助的可见性头会“抢戏”。我加了可见性预测之后,主占据头的收敛变慢了,因为两个任务共享了大部分特征,可见性任务更容易学(本质是个几何问题),梯度很快把共享特征拉向自己的方向。处置办法是给辅助头单独的低学习率(主头的 0.1 倍),并且用梯度缩放把它对共享特征的影响压下去。

提示:调试占据预测,最有效的工具不是看指标而是看可视化。把预测结果按高度切片,一层层看,问题往往一眼就能看出来——某一层整体偏移说明标定有问题,某一层全是噪点说明该高度的监督信号不足。

我个人在实际做这类融合方案时最深的体会是:模型结构上的创新带来的收益,往往比不上把坐标系、时间戳、标签生成这三件事做扎实。我见过太多项目在结构上反复折腾,最后发现问题出在相机外参的一个符号上。OccFusion 这个思路本身不复杂,难的是把它工程化到能稳定跑在车上的程度,而这部分工作几乎没有捷径,只能靠一层层的可视化自检和单元测试堆出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询