10 KiB
10 KiB
BEV特征尺度详解 - Phase 4A Stage 1配置
📋 目录
🔄 BEV特征生成流程
阶段1: 多模态编码 → 单尺度BEV
Camera分支 (多尺度图像 → 单尺度BEV)
输入图像 (6×3×256×704)
↓
[SwinTransformer Backbone]
├─ out_indices: [1, 2, 3] # 输出3个尺度
├─ Stage 1: (B, 192, H/4, W/4)
├─ Stage 2: (B, 384, H/8, W/8)
└─ Stage 3: (B, 768, H/16, W/16)
↓
[GeneralizedLSSFPN Neck]
├─ 将3个尺度融合到统一通道
└─ 输出: 3个尺度×256通道
↓
[DepthLSSTransform]
├─ 3D几何变换 (Lift-Splat-Shoot)
├─ xbound: [-54.0, 54.0, 0.2] # Stage 1: 0.2m分辨率
├─ ybound: [-54.0, 54.0, 0.2]
├─ downsample: 2
└─ 输出: **单个BEV特征** (B, 80, 360, 360)
^^^^^^^^^^^^^^^^
关键:多尺度图像特征被投影到单一BEV平面
LiDAR分支 (3D点云 → 单尺度BEV)
点云输入
↓
[Voxelization]
└─ Voxel Size: [0.075, 0.075, 0.2]
↓
[SparseEncoder Backbone]
├─ 多尺度稀疏卷积
├─ encoder_channels: [[16,16,32], [32,32,64], [64,64,128], [128,128]]
└─ 输出: **单个BEV特征** (B, 256, 360, 360)
^^^^^^^^^^^^^^^^
关键:3D稀疏特征被压缩到2D BEV
融合阶段 (多模态 → 统一BEV)
Camera BEV (B, 80, 360, 360) ┐
├─→ [ConvFuser] → (B, 256, 360, 360)
LiDAR BEV (B, 256, 360, 360) ┘ ^^^^^^^^^^^^^^
统一的单尺度BEV特征
重要结论1️⃣:
从骨干网络多尺度形成的BEV是【单个尺度】
虽然Camera和LiDAR编码器内部使用多尺度特征提取,但最终输出的BEV特征是统一的单一尺度 (360×360 @ 0.2m分辨率)。
阶段2: Decoder处理 → 多尺度再生成
SECOND Backbone (单尺度输入 → 多尺度输出)
配置:
in_channels: 256 # 输入单尺度BEV
out_channels: [128, 256] # 输出2个尺度
layer_nums: [5, 5]
layer_strides: [1, 2] # 第1层stride=1,第2层stride=2
融合BEV (B, 256, 360, 360)
↓
[Block 1: stride=1]
├─ 5层卷积
└─ 输出: (B, 128, 360, 360) ← Scale 1 (原分辨率)
↓
[Block 2: stride=2]
├─ 5层卷积 + 下采样
└─ 输出: (B, 256, 180, 180) ← Scale 2 (1/2分辨率)
SECONDFPN Neck (多尺度融合 → 单尺度输出)
配置:
in_channels: [128, 256] # 输入2个尺度
out_channels: [256, 256] # 每个尺度输出256通道
upsample_strides: [1, 2] # Scale1不变,Scale2上采样2倍
Scale 1: (B, 128, 360, 360) → Conv → (B, 256, 360, 360)
↓
Scale 2: (B, 256, 180, 180) → Deconv(×2) → (B, 256, 360, 360)
↓
Concat
↓
最终输出: (B, 512, 360, 360)
^^^^^^^^^^^^^^^^^^^^
单尺度BEV特征 (通道拼接)
重要结论2️⃣:
Decoder输出【单个尺度】的BEV特征
SECONDFPN将多尺度特征上采样到统一分辨率后concat,输出512通道的单尺度BEV。
🎯 任务头输入分析
3D检测头 (TransFusionHead)
# bevfusion.py L346
pred_dict = head(x, metas) # x是单个tensor
# 配置
heads:
object:
type: TransFusionHead
in_channels: 512 # ← 接收512通道的单尺度BEV
输入: (B, 512, 360, 360) - 单个尺度的BEV特征
处理流程:
(B, 512, 360, 360)
↓
[Heatmap Branch]
└─ 生成检测热图 (B, num_classes, H, W)
↓
[Transformer Decoder]
├─ Query-based检测
└─ 精细化Bbox预测
BEV分割头 (EnhancedBEVSegmentationHead)
# bevfusion.py L349
losses = head(x, gt_masks_bev) # x是单个tensor
# 配置
heads:
map:
type: EnhancedBEVSegmentationHead
in_channels: 512 # ← 接收512通道的单尺度BEV
decoder_channels: [256, 256, 128, 128] # 内部4层decoder
grid_transform:
input_scope: [[-54.0, 54.0, 0.75], [-54.0, 54.0, 0.75]] # 360×360
output_scope: [[-50, 50, 0.167], [-50, 50, 0.167]] # 600×600
输入: (B, 512, 360, 360) - 单个尺度的BEV特征
处理流程:
(B, 512, 360, 360)
↓
[Grid Transform] # 双线性插值
└─ 从360×360上采样到384×384 (padding到540×540范围)
↓
[ASPP多尺度特征提取]
├─ 1×1卷积
├─ 3×3空洞卷积 (rate=6)
├─ 3×3空洞卷积 (rate=12)
└─ 3×3空洞卷积 (rate=18)
↓
[注意力机制]
├─ Channel Attention (SE)
└─ Spatial Attention
↓
[4层Decoder上采样]
├─ Layer 1: 256通道 → 上采样
├─ Layer 2: 256通道 → 上采样
├─ Layer 3: 128通道 → 上采样
└─ Layer 4: 128通道 → 上采样到600×600
↓
[Per-class分类器]
└─ 输出: (B, 6, 600, 600) # 6个类别的分割结果
重要结论3️⃣:
检测头和分割头都接收【单个尺度】的BEV特征
两个任务头共享同一个512通道的BEV特征 (360×360),然后各自进行任务特定的处理。
❓ 关键问题解答
Q1: 从骨干多尺度形成的BEV是一个尺度还是多个?
答案:单个尺度 (360×360)
详细解释:
- Camera编码器内部使用多尺度特征 (SwinTransformer的3个输出)
- LiDAR编码器内部使用多尺度稀疏卷积
- 但最终投影到BEV空间时,都被统一到单一分辨率:
- Camera: 通过DepthLSSTransform投影 → 360×360 @ 0.2m
- LiDAR: 通过Voxelize+压缩 → 360×360 @ 0.2m
- 融合后仍是单尺度:256通道 × 360×360
原因:
- BEV空间的物理分辨率是统一的 (xbound/ybound定义)
- 多模态融合需要空间对齐,必须是同一尺度
Q2: 3D检测头和分割头的输入是单个BEV尺寸还是多个?
答案:单个尺寸 (512×360×360)
详细解释:
-
输入统一:两个头都接收decoder输出的同一个tensor
x = self.decoder["neck"](x) # (B, 512, 360, 360) # 检测头 pred_dict = self.heads["object"](x, metas) # 分割头 losses = self.heads["map"](x, gt_masks_bev) -
内部处理不同:
- 检测头:保持360×360,在这个尺度上做heatmap和transformer
- 分割头:上采样到600×600 (通过grid_transform)
-
为什么不直接输入多尺度?
- 架构设计简洁:统一的BEV特征更易于多任务学习
- 计算效率:避免多尺度特征的重复计算
- 特征共享:两个任务共享前面提取的高层语义
📐 尺度变化全流程总结
阶段 | 特征尺度数 | 具体尺寸 | 说明
------------------|-----------|--------------------------|-------------------
Camera Backbone | 3个尺度 | H/4, H/8, H/16 | 多尺度图像特征
LiDAR Backbone | 1个尺度 | 1440×1440×41 (3D) | 稀疏体素特征
BEV投影 | 1个尺度 | 360×360 @ 0.2m | ★统一到BEV平面
Fuser融合 | 1个尺度 | 256×360×360 | ★多模态统一
Decoder Backbone | 2个尺度 | 360×360, 180×180 | 短暂的多尺度
Decoder Neck | 1个尺度 | 512×360×360 | ★上采样+concat
检测头输入 | 1个尺度 | 512×360×360 | ★共享BEV特征
分割头输入 | 1个尺度 | 512×360×360 | ★共享BEV特征
分割头输出 | 1个尺度 | 6×600×600 | 上采样到GT分辨率
核心要点:
- ✅ BEV空间是单尺度的:由xbound/ybound统一定义
- ✅ 多尺度只存在于Decoder内部:SECOND backbone输出2个尺度,但SECONDFPN立即融合回单尺度
- ✅ 任务头共享单一BEV特征:都是512×360×360
- ✅ 分割头内部有多尺度decoder:4层逐步上采样到600×600
🎯 Phase 4A配置验证
当前训练配置摘要
# Camera BEV生成
vtransform:
xbound: [-54.0, 54.0, 0.2] # 360×360
ybound: [-54.0, 54.0, 0.2]
downsample: 2
# Decoder
decoder:
backbone:
out_channels: [128, 256] # 2个尺度
neck:
in_channels: [128, 256] # 输入2个尺度
out_channels: [256, 256] # 输出拼接→512
# 任务头
heads:
object:
in_channels: 512 # 单尺度输入
map:
in_channels: 512 # 单尺度输入
grid_transform:
output_scope: [[-50, 50, 0.167], [-50, 50, 0.167]] # 600×600
特征尺寸验证
# 从训练日志验证
Epoch [1][10700/15448]
├─ memory: 18893 MB
├─ loss/map/...: 正常训练 ✅
└─ loss/object/...: 正常训练 ✅
# 说明:
# 1. 两个任务头都正常工作 → 输入尺寸正确
# 2. 显存占用18.9GB → 符合单尺度BEV特征的预期
# 3. 360×360的BEV特征足够支持600×600的分割输出
📚 参考代码位置
| 组件 | 文件路径 | 关键代码行 |
|---|---|---|
| BEVFusion主流程 | mmdet3d/models/fusion_models/bevfusion.py |
L332-340 (fusion+decoder) |
| SECOND Backbone | mmdet3d/models/backbones/second.py |
L84-97 (输出多尺度) |
| SECONDFPN Neck | mmdet3d/models/necks/second.py |
L83-99 (concat成单尺度) |
| 检测头forward | mmdet3d/models/fusion_models/bevfusion.py |
L346 |
| 分割头forward | mmdet3d/models/fusion_models/bevfusion.py |
L349 |
| 分割头实现 | mmdet3d/models/heads/segm/enhanced.py |
L192-246 |
🔬 实验建议
如果未来想要尝试多尺度BEV方案,可以考虑:
-
方案A:多尺度BEV生成
- 在DepthLSSTransform阶段生成多个分辨率的BEV (如180×180, 360×360)
- 但需要修改融合逻辑,增加显存和计算量
-
方案B:保持当前单尺度设计
- 优点:架构简洁,特征共享好,显存友好
- 当前设计已经能支持600×600的高分辨率分割
- 推荐方案 ✅
文档创建时间: 2025-11-03
当前训练阶段: Phase 4A Stage 1 (FP32, Epoch 1, Iter 10700/15448)
BEV分辨率: 360×360 @ 0.2m (Stage 1)
目标分割分辨率: 600×600 @ 0.167m