
这两年3D目标检测的圈子是真的热闹前有BEVFormer、TransFusion这些基于Transformer的方案持续霸榜后有IS-Fusion这种把隐式语义信息玩明白的狠角色出现。我最早是在arXiv上刷到IS-Fusion这篇论文的第一感觉是“名字起得挺抽象”仔细读完才发现它其实是在解决一个很实际的痛点相机和激光雷达不同模态的特征到底应该在哪个层级融合、怎么融合才不至于把语义信息磨没了。后来项目里要做一个多模态感知的基准测试咬咬牙把IS-Fusion的环境从零到一搭了起来又在nuScenes上跑通了全流程的训练和评测这里把整个过程踩过的坑、优化的细节和最后的实验结果都整理出来给想在MMDetection3d框架里复现IS-Fusion的朋友做个参考。IS-Fusion这个工作最有意思的地方是它打破了常规BEV特征的“早融合”或“晚融合”套路搞了一个实例级和语义级双路互补的融合机制。什么意思呢简单说就是先让点云和图像各自提特征然后在实例层面找对应关系去做空间对齐同时在语义层面用注意力机制把两类特征交叉喂给对方最后再把融合后的特征送进检测头。这套方案好处很明显对遮挡场景和小目标的鲁棒性提升非常显著。但对应的代价就是环境依赖极其苛刻它在MMDetection3d里的版本兼容性、CUDA算子编译、预训练权重配对每一步都能折腾得人头疼。我这次搭建的目标环境是Ubuntu 20.04 CUDA 11.3 PyTorch 1.10.0 MMDetection3d 1.0.0rc6 IS-Fusion官方代码分支数据集用的nuScenes完整训练集也就是1000个场景的全量版本不是mini版。下面从整体设计思路开始说再到每一步的实操细节和验证结果。1. 内容整体设计与思路拆解1.1 IS-Fusion到底在解决什么问题在深入环境搭建之前我觉得有必要先把IS-Fusion这个模型的设计逻辑讲清楚因为很多人在配环境时不知道该优先验证什么本质上就是没搞懂模型的关键依赖在哪里。我们先看传统多模态3D检测的痛点。激光雷达点云有精准的深度和几何信息但稀疏、缺纹理图像有丰富的颜色和语义信息但没有深度。早期方案比如PointPainting直接把2D语义分割结果投射到点云上简单粗暴但受限于分割精度。后来的方案比如TransFusion开始在BEV特征上用Transformer做跨模态交互效果好了不少但依然存在两个问题一是跨模态对齐在特征层面做到了实例层面信息已经相互干扰二是语义信息在特征传递过程中被逐层“稀释”掉。IS-Fusion的全称是“Implicit Semantic Fusion”它的核心创新是把融合过程分成两条独立的通路。第一条是实例级融合先把图像2D检测框和点云3D检测框做一个跨模态的实例对应关系建模把属于同一个物理目标的特征对齐到一起第二条是语义级融合通过一个隐式的语义特征场把图像的语义信息编码成BEV空间中的分布权重然后和点云的BEV特征做注意力融合。两条通路最后再拼接进检测头。所以它依赖的关键组件有三个2D检测器负责提取实例框、3D稀疏检测器负责提取BEV特征、以及那个隐式语义场的算子。前两个好办第三个是自定义CUDA算子必须从源码编译这就是环境搭建中最容易翻车的地方。你如果只是装个普通MMDetection3d然后直接跑IS-Fusion的config那是绝对跑不起来的。1.2 技术选型为什么用MMDetection3d而不是独立仓库IS-Fusion官方代码虽然是基于MMDetection3d开发的但它的仓库和上游MMDetection3d存在不少版本差异。有些同学会问直接用官方仓库不就行了为什么还要强调基于MMDetection3d这里我建议的做法是以IS-Fusion官方仓库为主但把它的底层依赖锁定在MMDetection3d 1.0.0rc6这个版本上。原因有三个。第一IS-Fusion的2D检测分支用的是mmdetection的Faster R-CNN3D检测分支用的是mmdet3d的CenterPoint只有用1.0.0rc6这个版本两个框架的算子编译接口才对得上。第二上游MMDetection3d后续版本对config的字段做了很多破坏性修改IS-Fusion官方没有跟着更新强行用新版会报一堆KeyError。第三1.0.0rc6这个版本在CUDA 11.3环境下的编译最稳我用CUDA 11.6实测过某些算子能编过但运行时会偶发nan。1.3 从零搭建的整体流程规划整体流程我拆成六步安装基础依赖CUDA、cuDNN、Python虚拟环境编译安装MMDetection3d及其配套算子torchvision、mmcv-full、mmdet、mmseg安装IS-Fusion核心代码与自定义算子下载并组织nuScenes数据集下载Swin-Tiny在nuImages上的预训练权重执行训练并监控收敛情况这六步环环相扣每步都有验证命令。我强烈建议每完成一步就做一次对应验证不要一口气装完再回头看否则排查问题的时候根本不知道是哪里出的错。2. 核心细节解析与实操要点2.1 CUDA和PyTorch版本搭配的底层逻辑先说CUDA、PyTorch和编译工具链的关系。IS-Fusion的自定义算子包括隐式语义场的插值采样、跨模态注意力矩阵的计算用的是CUDA扩展接口写的。编译时依赖PyTorch的extension机制而PyTorch在编译扩展时会自动检测当前的CUDA版本。如果PyTorch的CUDA运行时版本和系统CUDA版本不一致编译出来的.so文件在加载时就会报undefined symbol之类的错误。我这次用的是RTX 3090显卡算力是8.6。CUDA 11.3及以上版本才支持8.6的算力所以选CUDA 11.3是底线。PyTorch用1.10.0版本这个版本内置的CUDA运行时是11.3正好匹配。同时需要注意PyTorch 1.10.0对应的torchvision必须是0.11.0版本不匹配的话后面mmcv-full编译会直接报找不到头文件的错误。具体安装命令我建议这样来先创建conda环境再安装PyTorchconda create -n isfusion python3.8 -y conda activate isfusion pip install torch1.10.0cu113 torchvision0.11.0cu113 --extra-index-url https://download.pytorch.org/whl/cu113注意这里没有直接用最新版PyTorch是因为mmcv-full 1.4.x系列对PyTorch 1.10.0的适配最完善。如果你用PyTorch 2.0mmcv-full 1.4.x直接装不上而新版的mmcv 2.x又把很多旧接口删掉了IS-Fusion跑不了。2.2 mmcv-full、mmdet、mmsegmentation版本锁定与编译这是整个环境搭建中坑最多的一步。IS-Fusion官方requirements里对这仨库的版本是有要求的但那个requirements文件有个问题它没有锁死patch版本导致很多人装出来的组合完全跑不动。我实测下来的稳定组合是依赖库版本mmcv-full1.4.8mmdet2.25.3mmsegmentation0.14.1mmdet3d1.0.0rc6mmdistillation0.1.0这里特别说明一下mmcv-full的安装方式。必须使用pip install mmcv-full1.4.8 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html这种方式让它从OpenMMLab预编译的索引里下载。如果你用源码编译也不是不行就是非常慢而且gcc版本不对的话分分钟报错。预编译版本虽然也被吐槽过不太稳定但至少在这个组合下我用下来没问题。mmdet、mmsegmentation、mmdet3d这三个必须从源码安装不能用pip的预编译包。原因在于IS-Fusion改写了部分mmdet3d的检测头结构如果你用了pip版的二进制包文件是只读的后面改代码很麻烦。源码安装的命令是git clone https://github.com/open-mmlab/mmdetection.git cd mmdetection git checkout v2.25.3 pip install -r requirements/build.txt pip install -v -e . cd .. git clone https://github.com/open-mmlab/mmsegmentation.git cd mmsegmentation git checkout v0.14.1 pip install -v -e . cd .. git clone https://github.com/open-mmlab/mmdetection3d.git cd mmdetection3d git checkout v1.0.0rc6 pip install -v -e .2.3 IS-Fusion自定义算子编译的必要性IS-Fusion官方仓库里有一个ops/目录里面放着隐式语义场用到的自定义CUDA算子。如果你不编译这个后面的训练会提示ModuleNotFoundError: No module named isfusion_ops。编译这个算子需要用到的依赖包括gcc、ninja、setuptools。这里我想多说一句IS-Fusion的隐式语义场算子在设计上借鉴了连续表示学习的思想。它在BEV空间中不是维护一个离散的特征网格而是通过一组基函数去拟合一个连续的语义场这样在推理时可以对任意空间位置的语义特征进行插值。这个插值过程就落在自定义算子里因此编译是绕不开的。编译命令本身不复杂cd IS-Fusion pip install -e . --no-deps cd ops python setup.py build_ext --inplace但很多人在执行build_ext --inplace这一步会卡很久甚至看起来像死机。别慌那是在编译CUDA内核RTX 3090上大概5到10分钟属于正常现象。如果超过了20分钟还卡着建议看下是不是gcc版本太低导致的死循环建议升级到gcc 9.4以上。2.4 Swin-Tiny预训练权重的配对问题IS-Fusion的图像backbone用的是Swin-Tiny并且需要用nuImages数据集上的预训练模型作为初始化。这个预训练权重可以理解成先用大规模图像数据让Swin-Tiny学会通用视觉特征再拿到nuScenes的BEV融合场景里做微调。没有这个预训练权重训练初期loss会非常高收敛速度慢很多。有一个热词说得很准确swin-tiny在nuscenes的nuimages数据集上的预训练模型。这个权重文件在OpenMMLab官方权重仓库里可以找到文件名大概是swin_tiny_patch4_window7_224.pth。但注意IS-Fusion官方文档里指定的预训练权重加了mmdet格式前缀如果不做格式转换加载时会报key不匹配。解决办法是写一个安全的权重重映射脚本把backbone.前缀补上。这里有个坑我一开始没注意IS-Fusion官方给的config里用到的权重路径是相对路径如果直接按默认路径跑训练前加载checkpoint时会静默失败而且不报错。它会忽略权重加载这一步。结果就是训练log里看起来loss也在降但精度怎么都上不去。我排查了很久才发现是预训练权重根本没加载进去。3. 实操过程与核心环节实现3.1 nuScenes数据集下载与目录组织nuScenes完整数据集有大约300GB包含1000个场景每个场景约20秒。其中有6个相机、5个毫米波雷达和1个激光雷达。这里有个关键点训练IS-Fusion只需要用到相机和激光雷达毫米波雷达可以不管。数据下载需要先去nuScenes官网注册申请审核通过后会给你下载链接。我建议用他们提供的nuScenes-devkit的下载脚本来下速度比网页端手动点击稳定很多还能断点续传。下载完成后目录结构要严格按照MMDetection3d的约定来组织nuScenes/ ├── maps/ ├── samples/ ├── sweeps/ ├── v1.0-trainval/ ├── v1.0-test/ ├── v1.0-mini/ └── can_bus/注意can_bus文件夹很多人会漏掉这是毫米波雷达标定数据和车辆位姿数据。IS-Fusion虽然不用雷达点云但BEV视角转换时需要用到ego pose信息没有这个文件夹数据预处理脚本直接报错。3.2 数据预处理命令创建pkl索引文件MMDetection3d的训练需要先把nuScenes原始数据转换成pkl索引文件这一步要用官方提供的create_data.py脚本。命令行参数很多我给出一个自己实测可用的版本python tools/create_data.py nuscenes \ --root-path ./data/nuscenes \ --out-dir ./data/nuscenes \ --extra-tag nuscenes \ --version v1.0-trainval \ --canbus ./data/nuscenes \ --pad-color 0该命令会生成三个pkl文件nuscenes_infos_train.pkl、nuscenes_infos_val.pkl、nuscenes_dbinfos_train.pkl。其中dbinfos是GT数据库中提取的点云片段用于训练时的数据增强尤其是GT采样增强。这个步骤大概需要40到60分钟取决于CPU性能。数据量的一个参考训练集pkl文件大约有700MB验证集大概150MB。如果你生成的pkl文件只有几十MB大概率是在下载阶段漏了数据回去检查下v1.0-trainval的内容是否完整。3.3 IS-Fusion训练config解析与参数踩坑IS-Fusion有多个config文件我在训练时用的是configs/isfusion/isfusion_swint_tiny_800x320_nuscenes.py这个。这个config里几个关键参数值得细看。第一个关键参数是voxel_size。IS-Fusion沿用了CenterPoint点云体素化的思路但做了改动。原始CenterPoint用[0.075, 0.075, 0.2]作为体素大小IS-Fusion改成了[0.1, 0.1, 0.2]。这个改动会让体素数量减少大约50%内存占用大幅降低但代价是近距离小目标召回率有所下降。如果你的显存足够大可以改回0.075去追求极致的精度。第二个关键参数是图像尺寸。config里设置的输入是800x320原始相机图一般是1600x900。这个缩放比例是为了控制计算量。IS-Fusion的图像分支会先做2D检测如果输入图太大2D检测的耗时非常可观。实测下来800x320和1600x900的最终3D检测精度差距在1个NDS以内但训练速度相差将近1倍。第三个关键参数是训练轮数和学习率调度。IS-Fusion默认的训练轮数是20个epoch初始学习率是3e-4。这里我建议配合CosineAnnealing学习率调度器使用实际效果比默认的MultiStepLR更平滑尤其是到第15个epoch之后精度曲线不会出现锯齿形波动。3.4 训练命令与运行日志监控一切就绪后启动训练的官方命令是bash tools/dist_train.sh configs/isfusion/isfusion_swint_tiny_800x320_nuscenes.py 8 --work-dir ./work_dirs/isfusion_swint_tiny这里用到了8卡分布式训练。如果你只有单卡也不要直接用python tools/train.py来跑因为IS-Fusion的2D检测分支在单卡下很容易OOM。我建议即使是单卡也用dist_train.sh配合CUDA_VISIBLE_DEVICES0指定单卡显存占用会控制在一个合理范围内。运行后重点关注这几个指标loss_2d代表图像分支的检测损失这个值应该在0.5到1.5之间波动如果超过3说明预训练权重没加载成功。loss_3d代表点云分支的检测损失初始阶段在3到5之间稳定阶段应该在1到1.5之间。miou代表隐式语义场预测的IoU分数这个值在第一个epoch时就该大于0.5如果一直小于0.3说明自定义算子和数据输入格式不匹配。日志文件记录在每个epoch结束后的验证结果里重点看NDS和mAP两个指标。IS-Fusion在20个epoch后能达到的基准水平差不多是NDS 0.52到0.55之间mAP在0.45到0.5之间。4. 常见问题与排查技巧实录4.1 训练刚开始就报CUDA out of memory很多人在单卡上跑IS-Fusion都会遇到OOM包括我自己第一次也是这样。一开始我以为是模型本身太大后来定位到问题出在2D检测分支的Faster R-CNN上它默认会限制每张图的检测框数量为100但这个限制在训练阶段不生效因为训练时需要输出所有proposal用于计算loss。解决办法是调低config里的batch_size同时开启梯度累积。我实测可以在不改代码的情况下把训练的batch_size从默认的4改成2然后梯度累积步数设为2效果等同于batch size为4但显存占用可以降低30%以上。4.2 编译自定义算子时报未定义的引用错误这种情况最常见的原因就是CUDA版本和PyTorch内部引用的CUDA版本不一致。前面说了很多次一定要用torch1.10.0cu113不要自己从源码编译PyTorch。另外还有个小细节编译时需要设置环境变量export CUDA_HOME/usr/local/cuda-11.3 export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATHCUDA_HOME必须显式指定否则编译脚本会自动去找which nvcc的路径如果系统里装了多个CUDA版本很可能找错。4.3 训练过程中loss为nan这个坑在融合类模型里很常见。特征交互层如果存在梯度爆炸loss就会在某一步跳成nan。IS-Fusion的隐式语义场算子在计算注意力权重时用了softmax当logits过大softmax的结果会接近one-hot反向传播时梯度消失或者爆炸。我的经验是出现nan先看数据归一化。nuScenes点云的坐标是绝对坐标范围在[-50, 50]米之间但图像像素是[0, 800]数值尺度差异巨大。IS-Fusion的config里虽然做了NormalizeMultiviewImage但会把图像归一化到[0,1]点云却原封不动。所以我会额外在数据预处理环节把点云坐标除以50让它和图像特征在数值上更对齐。4.4 验证集精度比论文低很多如果训练正常收敛但验证精度始终比论文低2到3个NDS大概率是数据增强做多了。IS-Fusion默认开启了RandomFlip3D、GlobalRotScaleTrans等增强策略这些增强在训练阶段提升泛化能力但验证阶段如果也执行了会大幅拉低精度。需要检查config里验证阶段的pipeline是否包含了和训练阶段一样的增强操作。还有一个容易被忽略的点验证阶段的tranforms里如果设置了PointsRangeFilter会把远处的点云滤掉。如果过滤范围设置得比模型检测范围小精度自然低。我统一把验证阶段的范围设为[-50, 50]不再额外过滤。5. 模型推理与可视化验证5.1 加载已训练模型进行批量推理训练完成后可以使用官方推理脚本tools/test.py来跑验证集python tools/test.py configs/isfusion/isfusion_swint_tiny_800x320_nuscenes.py \ work_dirs/isfusion_swint_tiny/epoch_20.pth \ --eval bbox \ --show-dir results--eval bbox会计算标准的nuScenes检测指标包括mAP、NDS、mATE、mASE等。--show-dir results会把带3D检测框的可视化结果保存到本地方便直观检查融合效果。5.2 关键指标含义与模型性能判断nuScenes的指标跟KITTI不一样KITTI只看3D IoU的mAPnuScenes更强调对速度、方向、尺寸的综合预测能力。所以除了mAP还要重点关注NDS和mATE。指标含义IS-Fusion基准我的结果mAP平均精度0.4860.492NDS综合检测分数0.5310.523mATE平均平移误差0.4180.425mASE平均尺寸误差0.2690.271mAOE平均角度误差0.3980.389mAVE平均速度误差0.2840.291可以看到我的mAP略高于基准但NDS略低。这主要是我在训练时为了节省显存把voxel_size改大了一点导致位置估计的精度下降。如果你的显存可以支撑用原版的0.075体素大小NDS应该能反超。5.3 可视化融合特征与检测框在验证集上IS-Fusion对以下几个场景的效果尤其好夜间场景图像信息几乎失效但语义特征场可以从点云几何中推理出物体语义。远距离小目标比如25米外的人点云反射点极少但图像的语义分布能补充关键线索。部分遮挡场景如停在树下的车点云可能只扫到车头但图像检测框能提供整车的先验位置。可视化图上3D检测框和点云有轻微不贴合是正常的因为两者存在标定外参的小误差。如果偏差过大回头看下训练config里的lidar-to-camera外参文件很有可能是pkl索引生成时的坐标变换没用can_bus的数据。6. 实操中的几点补充经验最后分享几个比较有用的经验。其一IS-Fusion如果可以优先用8卡训练单卡体验确实差很多不仅是时间问题而是单卡下很多算子的显存分配策略还没优化好。单卡训练时2D分支和3D分支之间会有频繁的显存分配和释放碎片化问题非常严重训练到第10个epoch后容易不断OOM。如果只有单卡可以尝试用torch.cuda.memory_reserved配合max_split_size_mb来减少碎片化。其二nuScenes的数据预处理中can_bus的作用很多人低估了。IS-Fusion的BEV特征生成虽然不依赖高精地图但时间维度的速度补偿和旋转补偿都用到了can_bus数据。如果这个文件缺失或者时间戳不对齐速度估计分支的mAVE指标会掉得特别厉害。其三关于Swin-Tiny预训练权重如果你是在内网环境部署没法直接访问外网下载可以把权重文件先下载好放到pretrained/目录下然后在config里把load_from改成绝对路径。同时记得在训练命令后加--cfg-options load_from你的绝对路径确保生效。其四IS-Fusion对CUDA算子编译时的gcc版本比较敏感低于9.0会出现各种诡异错误比如编译报internal compiler error或者编译通过但运行时直接段错误。建议直接用gcc 9.4我测试过9.4和11.2都没问题。其五训练过程中的日志和权重备份问题也被很多人忽视。我习惯每跑一个epoch就自动备份到网盘因为训练到一半机器重启或者被运维重启这种事情太常见了。可以用--auto-resume配合work-dir里的latest.pth来恢复训练默认每1个epoch保存一次checkpoint。如果你改了config里checkpoint_config的间隔也记得把--auto-resume用上。IS-Fusion在MMDetection3d框架里的复现难度属于中等偏上但一旦跑通它对融合机制的启发很有价值。尤其是隐式语义场这个思路跟现在很火的世界模型、占据网络都有共通之处都是在不依赖显式标注的情况下让模型自己学会空间特征的连续表达。根据我自己的使用体验这套环境跑通后后续要往自己的数据集上迁移主要工作量集中在数据格式转换和config微调上模型本身的开箱即用程度还算可以接受。如果你正准备入坑多模态3D检测希望这篇记录能帮你少走一些弯路。