MV2DFusion 详细讲解教程
核心思想:3D Detection = 图像分支 ⊕ 点云分支 ⊕ Fusion Transformer
主要代码文件 :mv2dfusion.py·mv2dfusion_head.py·mv2dfusion_transformer.py·image_distribution_query_generator.py·point_cloud_query_generator.py·detector_wrapper.py
目录
-
[2D 检测与 Proposal 生成](#2D 检测与 Proposal 生成)
-
[RoIAlign 多尺度选层](#RoIAlign 多尺度选层)
-
[Image Query 生成(25 个 3D 候选点)](#Image Query 生成(25 个 3D 候选点))
-
[Fusion Head:Query 组装](#Fusion Head:Query 组装)
-
[Fusion Transformer Decoder](#Fusion Transformer Decoder)
-
[附录:关键概念 FAQ](#附录:关键概念 FAQ)
1. 整体架构与训练流程
MV2DFusion 是一个多模态 3D 目标检测框架,三路并行:
| 分支 | 输入 | 输出 | 核心机制 |
|------|------|------|----------|
| 图像分支 | 多视图 RGB | 图像 Query(深度分布) | Faster R-CNN + 射线采样 |
| 点云分支 | LiDAR 点云 | 点云 Query(精确 3D) | SparseConv + Vote + FSDV2 |
| 融合分支 | 双分支 Query | 3D BBox | 6 层 Transformer Decoder |
1.1 训练主链路
入口在 mv2dfusion.py:
MV2DFusion.forward_train
├─ extract_img_feat → 图像特征(P2~P6 + P4 时序)
└─ forward_pts_train(每帧)
├─ forward_roi_head_train → 2D 检测训练(有梯度)
├─ forward_roi_head → 2D 推理出 proposal(no_grad)
├─ complement_2d_gt → GT 补框
├─ extract_roi_feats → RoIAlign
├─ img_query_generator → 图像 Query(25 depth bins)
├─ pts_backbone
│ └─ pts_query_generator → 点云 Query
└─ fusion_bbox_head → 6 层 Transformer → 3D 预测
Fusion Head 实现在 mv2dfusion_head.py ,Transformer 实现在 mv2dfusion_transformer.py。
1.2 架构示意图
┌─────────────────────────────────────┐
│ 数据输入 │
│ 多视图图像 + LiDAR + 3D/2D GT │
└──────────────┬──────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ │
┌───────────────┐ ┌───────────────┐ │
│ 图像分支 │ │ 点云分支 │ │
│ ResNet + FPN │ │ FSDV2 │ │
│ 2D 检测 │ │ Vote + Seg │ │
│ ImageQueryGen │ │ PtsQueryGen │ │
└───────┬───────┘ └───────┬───────┘ │
└────────┬───────────┘ │
▼ │
┌─────────────────┐ │
│ mv2dfusion_head │ ← Fusion Transformer│
└────────┬────────┘ │
▼ │
3D BBox 输出 │
2. 数据输入
典型多模态 3D 检测数据集需包含:
| 字段 | 说明 |
|------|------|
| img | 多视图图像 [B, T, V, 3, H, W] |
| points | LiDAR 点云,每帧 [N_pts, C] |
| gt_bboxes_3d / gt_labels_3d | 3D GT |
| gt_bboxes / gt_labels | 2D GT(每相机一个 list) |
| depths | 2D GT 深度(相机坐标系 Z,用于 depth loss) |
| intrinsics / extrinsics / lidar2img | 标定 |
示例规模(7 相机、640×1600、单帧):
| 参数 | 典型值 |
|------|--------|
| batch_size B | 2~8 |
| 相机数 V | 6~7 |
| pc_range | 约 ±50m × ±25m × ±4m |
| queue_length T | 1(可扩展多帧) |
depths 来源:
-
2D 标注中的
cam_depth/depth字段 -
或由 3D 框投影脚本生成:8 角点投影到图像后,取相机坐标系 Z 均值
3. 图像分支
3.1 特征提取
文件 :mv2dfusion.py → extract_img_feat
img [B*V, 3, H, W]
→ GridMask(可选)
→ img_backbone(如 ResNet50)→ C2~C5
→ img_neck(FPN)→ P2~P6,统一 256 通道
两个返回值:
| 返回值 | 内容 | 用途 |
|--------|------|------|
| img_feats | 单层(position_level 指定,通常 P4) | 时序 memory |
| img_feats_for_det | P2~P6 全层 | 2D 检测 + Fusion Cross-Attn |
FPN 层与尺寸 (以输入 640×1600、position_level=2 即 P4 为例):
| 层级 | stride | 空间尺寸(约) | 用途 |
|------|--------|---------------|------|
| P2 | 4 | 160×400 | 2D RPN/RoI、RoIAlign |
| P3 | 8 | 80×200 | Fusion Cross-Attn |
| P4 | 16 | 40×100 | 时序 memory + Fusion Cross-Attn |
| P5 | 32 | 20×50 | Fusion Cross-Attn |
| P6 | 64 | 10×25 | Fusion Cross-Attn |
3.2 Fusion Head 如何使用图像特征
文件 :mv2dfusion_head.py → forward
使用 P3~P6(跳过 P2),每层经 MLN 做相机内外参对齐后 flatten:
feat_flatten_img: [B*V, Σ(Hi×Wi), 256]
MLN 输入 14 维 = 内参 fx,fy + 外参 3×4 矩阵。
4. 2D 检测与 Proposal 生成
4.1 数据准备
文件 :mv2dfusion.py → prepare_detection_data
将 [B, V, ...] 展平为 B×V 个样本,供 Faster R-CNN 逐 view 处理。
4.2 训练分支(有梯度)
文件 :mv2dfusion.py → forward_roi_head_train
包装 :detector_wrapper.py → TwoStageDetectorWrapper.forward_train_w_feat
RPN
-
多层 FPN 上生成 anchor,预测 fg/bg + bbox delta
-
采样 256 个 anchor 算 loss
-
产出约 1000 proposals / view
RoI Head
-
RoIAlign 7×7
-
采样 512 个 RoI
-
多类分类 + bbox 回归
4.3 RPN 的 fg/bg 详解
RPN 只有二分类,没有类别。
detector_wrapper.py 中 RPN 调用时 gt_labels=None,只用 gt_bboxes:
| | RPN | RoI Head |
|--|-----|----------|
| 分类 | fg/bg(1 sigmoid) | num_classes 类 |
| 用 GT 类别? | 否 | 是 |
| fg 判定 | IoU ≥ 0.7 | IoU ≥ 0.5 |
best_iou ≥ 0.7 → fg
best_iou < 0.3 → bg
0.3 ~ 0.7 → ignore
4.4 推理分支(no_grad)
文件 :mv2dfusion.py → forward_pts_train
python
losses_det2d = forward_roi_head_train(...) # 有梯度
self.eval()
with torch.no_grad():
dets = forward_roi_head(...) # 推理
dets = complement_2d_gt(dets, gt) # GT 补框
rois = bbox2roi(dets)
self.train()
| 设计 | 原因 |
|------|------|
| 训练有梯度 | 2D 检测器持续优化 |
| proposal 无梯度 | NMS 不可微;避免噪声干扰 Query/Fusion |
| GT 补框(IoU<0.6) | 2D 漏检仍保留 3D 监督 |
4.5 空 GT 保护(多卡训练)
文件 :mv2dfusion.py → forward_roi_head_train
全 batch 无 2D GT 时,用假框 forward 并将 loss×0,保证 DDP 各 rank 计算图一致。
5. RoIAlign 多尺度选层
文件 :mv2dfusion.py → extract_roi_feats
实现 :MMDet SingleRoIExtractor
scale = sqrt((x2-x1) * (y2-y1))
level = floor(log2(scale / 56)) # finest_scale=56
→ 在对应 P2~P5 层做 RoIAlign → [N, 256, 7, 7] → AvgPool → [N, 256]
| level | stride | scale(像素) | 典型目标 |
|-------|--------|--------------|---------|
| 0 | 4 | < 112 | 小目标 |
| 1 | 8 | 112~224 | 中等 |
| 2 | 16 | 224~448 | 车辆 |
| 3 | 32 | ≥ 448 | 大目标 |
6. Image Query 生成(25 个 3D 候选点)
文件 :image_distribution_query_generator.py(基类:image_singple_point_query_generator.py)
典型配置 :prob_bin=25, depth_range=[0.1, 90]
6.1 流程
RoI feat [N, 256]
→ + 内参 MLP 融合
→ fc_center → [N, 25, 3] (每 bin: xy offset + depth logit)
→ softmax(depth_logits) → depth_prob [N, 25]
→ center2lidar(xy, d_bins) → [N, 25, 3]
→ cat(..., depth_prob) → dyn_query [N, 25, 4]
6.2 反投影
文件 :image_singple_point_query_generator.py → center2lidar
python
center_lidar = inv(K @ extrinsic) @ [xy*d, d, 1]
每个 bin 使用网络预测的 xy + 固定 深度网格 d_bins,权重来自 depth_prob。
6.3 gt_mono_loss 双路径
文件 :mv2dfusion.py → forward_pts_train
| 路径 | proposals | 用途 |
|------|-----------|------|
| 第一次 | 检测框 + GT 补框 | Fusion 前向 |
| 第二次 | 纯 GT 2D 框 | depth / 3D 辅助 loss |
7. 深度监督机制
文件 :image_distribution_query_generator.py → depth_loss
7.1 监督对象
| 量 | 直接监督? | 方式 |
|----|-----------|------|
| depth_logits | ✅ | bin 分类 CE(soft label) |
| xy offset | ❌ | 间接:3D 辅助 loss + Fusion loss |
| 反投影 3D 点 | ❌ | 间接:Fusion 端到端 |
7.2 depth_loss 流程
1. proposal 与 GT 2D 框 IoU≥0.6 一对一匹配
2. 取 GT cam_depth
3. 连续深度 → bin 索引(如 45.3m → bin 12.3)
4. Soft label:bin12 权重 0.7,bin13 权重 0.3
5. 加权 CE on depth_logits [N, 25]
文件 :mv2dfusion.py → loss 汇总为 imgqg.d_loss,通常再乘 loss_weight_3d。
8. 点云分支(FSDV2)
8.1 流程
点云 → 体素化 → SparseConv UNet
→ VoteSegHead(分割 + 投票)
→ 虚拟体素 → FSDV2Head → BEV NMS
→ point_cloud_query_generator.py
8.2 输出
| 输出 | 用途 |
|------|------|
| pts_query_center | 点云 Query 3D 中心 |
| pts_query_feat | 点云 Query 特征 |
| pts_feat / pts_pos | Cross-Attn 的 BEV KV |
8.3 与图像 Query 对比
| | 图像 Query | 点云 Query |
|--|-----------|-----------|
| 位置精度 | 低(深度不确定) | 高 |
| 特征来源 | RoI 视觉 | FSD 几何 |
| 数量 | 多(~数百) | 少(~数十) |
9. Fusion Head:Query 组装
文件 :mv2dfusion_head.py → forward
9.1 拼接
点云 query 在前 ,图像 query 在后:
python
reference_points = cat([pts_ref, img_ref], dim=1)
tgt = cat([pts_tgt, img_tgt], dim=1)
9.2 每个 Query 的组成
| 组件 | 变量 | 含义 |
|------|------|------|
| 内容特征 | tgt | 256 维语义(RoI / FSD 特征注入) |
| 3D 坐标 | reference_points | 锚点位置 |
| 位置编码 | query_pos | sin/cos 3D 编码;图像 query 额外编码 25-bin 深度分布 |
图像 query 动态 query 组装见 gen_dynamic_query ;点云见 gen_pts_query。
10. Fusion Transformer Decoder
文件 :mv2dfusion_transformer.py
Head 调用 :mv2dfusion_head.py → self.transformer(...)
6 层,每层:Self-Attn → Norm → Cross-Attn → Norm → FFN → Norm
10.1 Self-Attention
文件 :mv2dfusion_transformer.py → MV2DFusionTransformerDecoderLayer
Q = content + query_pos
K = content + query_pos
V = content
所有 img/pts query 同一序列,位置近则 attention 权重大,交换 256 维特征。
10.2 Cross-Attention(MixedCrossAttention)
13 个 key_points
配置 :num_pts=13
python
key_points = reference_point + Linear(instance_feature) # [B, n_q, 13, 3]
13 个可学习 3D 采样点,用于:
-
图像侧:投影到多相机 FPN → Deformable Attention
-
点云侧:投影 BEV → PETR Attention
深度迭代更新(图像 query)
文件 :mv2dfusion_transformer.py → MV2DFusionTransformerDecoder
每层 decoder 后:
python
dyn_q_logits += dyn_q_prob_branch[layer](query)
dyn_q_probs = softmax(dyn_q_logits)
reference_points[dyn_q_mask] = probs @ dyn_q_coords
10.3 预测
文件 :mv2dfusion_head.py → cls_branches / reg_branches
每层输出分类 + 8D bbox,6 层中间监督。
11. 加强与抑制机制
非硬编码规则,由 Attention + 匈牙利匹配 + NMS 共同产生。
11.1 加强
位置相近的 img_q 与 pts_q 在 Self-Attn 中互传 content 特征;Cross-Attn 从双模态特征图取证。
11.2 抑制
| 机制 | 文件 | 效果 |
|------|------|------|
| 匈牙利匹配 | mv2dfusion_head.py | 1 GT ↔ 1 query,其余 background |
| Focal Loss | 同上 | 未匹配 query 分数压低 |
| BEV NMS | mv2dfusion_head.py | 推理去重 |
同一物体若 img_q 与 pts_q 都靠近,通常只有一个匹配 GT,另一个被标 background。
12. 损失函数汇总
文件 :mv2dfusion.py → forward_pts_train(loss 汇总)
Total ≈ L_fusion × w_3d
+ L_pts × w_pts × w_3d
+ L_det2d
+ L_imgqg × w_3d
| 前缀 | 来源模块 | 内容 |
|------|----------|------|
| fusion.* | mv2dfusion_head.py | 3D cls + bbox(匈牙利) |
| pts.* | FSDV2 backbone | vote + seg + det |
| det2d.* | detector_wrapper.py | RPN + RoI |
| imgqg.d_loss | image_distribution_query_generator.py | depth bin CE |
| imgqg.loss_cls/bbox | 同上 | 初步 3D 辅助 |
13. 典型配置参数速查
以下为一组常见实验配置,可按数据集调整:
| 参数 | 典型值 |
|------|--------|
| img backbone | ResNet50 + FPN |
| position_level | 2(P4) |
| num_cams | 6~7 |
| prob_bin | 25 |
| depth_range | [0.1, 90] m |
| fusion num_query | 100~300(静态上限,实际动态) |
| decoder num_layers | 6 |
| embed_dims | 256 |
| num_pts(cross-attn) | 13 |
| num_levels(cross-attn) | 4 |
| loss_weight_3d | 0.1~1.0 |
| gt_mono_loss | True(推荐) |
| post_bev_nms_thr | 0.2 |
| DN scalar | 10 |
| memory_len | 6×256 ~ 7×256 |
14. 维度追踪速查表
以 B=2, V=6, H=640, W=1600 为例:
图像
| 阶段 | Shape |
|------|-------|
| img | [12, 3, 640, 1600] |
| FPN P4 | [12, 256, 40, 100] |
| roi_feats | [N_rois, 256] |
| dyn_query[b] | [N_b, 25, 4] |
| feat_flatten_img | [12, ~21250, 256] |
点云
| 阶段 | Shape |
|------|-------|
| pts_query | [B, N_pts_q, 256] |
| pts_feat(KV) | [B, max_bev, 256] |
Fusion
| 变量 | Shape |
|------|-------|
| tgt | [B, num_query, 256] |
| reference_points | [B, num_query, 3] |
| key_points | [B, n_q, 13, 3] |
| outs_dec | [6, B, num_query, 256] |
附录:关键概念 FAQ
Q:RPN fg 和类别有关吗?
A:无关。RPN 仅 fg/bg;类别在 RoI Head 分。
Q:proposal 为何 no_grad?
A:NMS 不可微;避免 2D 检测噪声干扰 Query/Fusion。
Q:深度监督监督什么?
A:depth_logits 的 25-bin 分类,不是 3D 坐标 L1。
Q:Self-Attn 的 query 是位置还是特征?
A:两者:tgt=内容特征,query_pos=位置编码。
Q:13 个 key_points 是什么?
A:Cross-Attn 的 3D 采样点,不是 Self-Attn 的 query。
Q:加强/抑制是硬规则吗?
A:否。Attention 软融合 + 匈牙利 1-to-1 + NMS。
Q:相关文件一览?
| 模块 | 文件 |
|------|------|
| 主检测器 | mv2dfusion.py |
| Fusion Head | mv2dfusion_head.py |
| Transformer | mv2dfusion_transformer.py |
| 图像 Query | image_distribution_query_generator.py |
| 点云 Query | point_cloud_query_generator.py |
| 2D 检测包装 | detector_wrapper.py |
| Query 基类 | image_singple_point_query_generator.py |
❤ 喜欢帮忙点赞 + 收藏,感谢!