11 KiB
YOLO26-2D3D 检测任务模型设计
本文基于当前仓库实现整理,主要对应以下代码:
ultralytics/cfg/models/26/yolo26-3d.yamlultralytics/nn/modules/head.pyultralytics/utils/plotting_3d.pyultralytics/data/ground3d_augment.pyultralytics/data/dataset.pyultralytics/cfg/datasets/mono3d_ground.yaml
1. 任务概览
当前任务是在 YOLO26 检测框架上扩展出的联合 2D+3D 检测任务,核心特征是:
- 2D 检测仍然走 YOLO26 的 P3/P4/P5 三层检测头。
- 在 2D 框与分类之外,额外输出 3D 属性分支
preds_3d和可见边采样分支preds_edge。 - 模型既支持完整 3D 类,也支持四面可见面建模类,还兼容纯 2D 类。
按当前 mono3d_ground.yaml:
face_3d_classes = [0..8],主要是车辆类,带四个面的 3D 标注。complete_3d_classes = [9..12],主要是行人/两轮/三轮类,只有整框 3D。13..16这些类当前只参与 2D 检测,不参与 3D 有效监督。
因此,分类头覆盖的是全量类别,而 3D 头是否真正参与监督,取决于类别分组。
2. 模型输入设计
2.1 图像输入
模型最终输入是标准张量:
- 形状:
[B, 3, H, W] - 通道:RGB/BGR 三通道图像
- 当前训练脚本默认
--imgsz 704,352,即宽高目标尺寸通常为704 x 352
但输入尺寸不是写死的,代码允许矩形输入,只要满足 stride 对齐即可。
2.2 ROI 与相机预处理
当前数据集是鱼眼地面 3D 数据,输入前会做 ROI/虚拟相机处理:
- 原图尺寸:
ori_img_size = [1920, 1080] roi0:- ROI 大小
1920 x 880 - 以图像中心 x 和消失点 y 为裁剪中心
- ROI 大小
roi1:- ROI 大小
768 x 352 - 以消失点 x、y 为裁剪中心
- ROI 大小
预处理流程大致为:
- 读取原图和标定。
- 按 ROI 裁剪,或走 virtual camera 变换。
- resize 到目标
imgsz。 - 同步修正
calib。 - 将 3D 深度按
virtual_fx做归一化。
因此,模型实际看到的是“ROI/虚拟相机坐标系”下的图像与标签,而不是原图直接输入。
2.3 标签输入
原始标签支持三种格式:
6列:纯 2D19列:完整 3D51列:带四面信息的 face-3D
加载后统一映射为内部 48 维格式,再拆成:
- 2D 标签:
cls + xywh - 3D 标签:
labels_3d,形状(N, 42)
labels_3d 的 42 维布局为:
-
0-2x3d, y3d, z3d -
3-5l, h, w -
6rot_y -
7-8xc, yc,整框 3D 中心投影 -
9alpha -
10-17front face -
18-25rear face -
26-33left face -
34-41right face
每个 face 的 8 维 GT 信息是 face 级 3D/投影/可见性描述,供 face 分支与 edge 分支监督使用。
2.4 训练时额外输入
除了图像和标签,batch 中还会携带:
calib:当前样本经过 ROI crop / virtual-camera 变换后的最终相机内参- 把 3D 点投影到 2D 图像上;
- 把 2D 点 + depth 反投影回 3D;
- 训练/验证/可视化时恢复 depth 真实尺度;
- 给 edge yaw、3D box decode、TensorBoard 可视化用
edge_faces_points_2d:(n, 4, 5, 2),对于一个车的前后左右,如果某个面可见,就在这个面的底边上均匀采5个点,然后投影到图片上,作为edge分支的监督目标。- n:当前图片里的目标数,
- 4:四个face,通常是front/rear/left/right,
- 5:每个 face 的底边采样 5 个点。
- 2:每个点的 2D 坐标 (u, v)。
edge_faces_depths:(n, 4, 5) 和 edge_faces_points_2d 一一对应,表示那 5 个 2D 采样点各自的 3D depth。edge_faces_valid:(n, 4):标记每个目标的每个 face 是否有有效 edge GTedge_partial_points_2d:(n, 5, 2),目标被图像边缘或 ROI 裁掉了一部分,比如车只露出半个头或半个尾。edge_partial_depthsedge_partial_face_type:表示这个 partial edge 属于哪个 face。 loss 里需要知道该拿模型 preds_edge 里的哪一个 face 分支来对齐这个 partial GT。
这些主要服务于 edge 辅助监督和可见面 yaw 解码。
3. 模型结构设计
3.1 主干与颈部
模型配置在 yolo26-3d.yaml 中,整体结构仍是 YOLO26 风格:
- Backbone:
Conv + C3k2 + SPPF + C2PSA - Neck:上采样 + 拼接 +
C3k2 - 输出层:P3/8、P4/16、P5/32 三层检测特征
即:
P3/8小目标层P4/16中目标层P5/32大目标层
3.2 检测头
最终 head 不是普通 Detect,而是 Detect3D:
- 继承自
Detect - 在 2D
box与cls之外,新增:cv4:41 通道 3D 属性头cv5:60 通道 edge 辅助头
3.3 End-to-End 模式
当前模型配置 end2end: True,因此会同时维护两套 head:
one2manyone2one
训练时两套都会输出;推理时主要使用 one2one。
推理阶段不是传统 IoU-NMS,而是:
- 对所有 anchor 的类别分数做
sigmoid - 直接选 top-k
- 输出
[box, score, class] - 再把同一批 top-k 对应的
preds_3d / preds_edge / anchors / strides一起选出来
所以它是“top-k 选择 + 端到端检测”的风格,不是经典 YOLO 的后置 NMS 路线。
4. 输出设计
4.1 每层 feature map 的逻辑输出
对每个检测层(P3/P4/P5),Detect3D 都会产生 4 路输出:
| 分支 | 通道数 | 当前任务通道数 | 含义 |
|---|---|---|---|
| 2D box head | 4 * reg_max |
4 |
2D 框回归 |
| 2D cls head | nc |
17 |
类别分类 |
| 3D head | 固定 41 |
41 |
3D 属性 |
| edge head | 固定 60 |
60 |
4 个面的边采样点 |
说明:
- 当前
reg_max = 1,所以 2D box head 只有4个通道,DFL 实际上关闭。 - 当前数据集唯一类别 ID 为
0..16,因此nc = 17。 yolo26-3d.yaml里的nc: 80只是默认占位值,真正建模时会被 trainer 用数据集类别数覆盖。- 所以当前任务单个 anchor 的原始输出总通道数为:
4 + 17 + 41 + 60 = 122
如果只看最终推理对外最直接的输出:
- 2D 检测结果:
[x1, y1, x2, y2, conf, cls] - 以及与 top-k 对齐的:
preds_3d_selected:(k, 41)preds_edge_selected:(k, 60)anchors_selected:(2, k)strides_selected:(k,)
4.2 2D box head 输出
通道定义
- 通道数:
4 - 语义:anchor 到框四边的距离回归
激活与后处理
- 训练前向:不加激活,保持线性输出
- 推理前向:
reg_max=1,因此DFL = Identity- 直接通过
dist2bbox(...)解码 - 再乘以 stride 还原到像素坐标
备注
- 这一分支没有显式
sigmoid/tanh/softmax - 2D box 的可用性主要依赖训练约束和
dist2bbox解码
4.3 2D cls head 输出
通道定义
- 通道数:
nc - 当前任务:
17
激活与后处理
- 训练时:输出 raw logits
- 推理时:对分类分数做
sigmoid - 然后与 box 一起进入 top-k 选择
4.4 3D head 输出
preds_3d 每个 anchor 固定 41 通道,布局如下。
4 个 face 分支,共 24 通道
| 通道范围 | face | 含义 |
|---|---|---|
0-5 |
front | z3d, du, dv, size0, size1, visible_score |
6-11 |
rear | z3d, du, dv, size0, size1, visible_score |
12-17 |
left | z3d, du, dv, size0, size1, visible_score |
18-23 |
right | z3d, du, dv, size0, size1, visible_score |
其中:
- front/rear 的
size0,size1对应h,w - left/right 的
size0,size1对应l,h
也就是说,face 分支并不直接回归完整 l,h,w,而是回归当前面最相关的两个尺寸。
whole-box 分支,共 17 通道
| 通道范围 | 含义 |
|---|---|
24 |
whole z3d |
25-26 |
whole du, dv |
27-29 |
whole l, h, w |
30-33 |
yaw bin logits,4 个方向 bin |
34-37 |
yaw residual,表示 sin(delta) |
38-40 |
cut class logits,3 类:normal / cut_in / cut_out |
3D head 的激活/反归一化
这一分支在 Detect3D.forward_head() 中会立刻做一次“反归一化”,变成更接近物理量的形式:
-
z3d- 通道:
0, 6, 12, 18, 24 - 变换:
raw * z3d_scale + z3d_offset - 单位:米
- 通道:
-
du, dv- 通道:
1-2, 7-8, 13-14, 19-20, 25-26 - 变换:
sigmoid(raw) * 16 - 8 - 含义:相对 anchor 的网格偏移,范围约
[-8, 8]
- 通道:
-
face 尺寸与 whole 尺寸
- face 通道:
3-4, 9-10, 15-16, 21-22 - whole 通道:
27-29 - 变换:
raw * size_scale + size_offset - 单位:米
- face 通道:
-
yaw residual
- 通道:
34-37 - 变换:
tanh(raw) - 含义:限制到
[-1, 1],表示sin(delta)
- 通道:
-
yaw bin logits
- 通道:
30-33 - 不加激活,保持 raw logits
- 通道:
-
cut logits
- 通道:
38-40 - 不加激活,保持 raw logits
- 通道:
-
face visible score
- 通道:每个 face 的第 6 维,即
5, 11, 17, 23 - 不加激活,保持原值
- 通道:每个 face 的第 6 维,即
3D 语义解码
-
whole yaw 解码:
- 先取
yaw_cls_logits[30:34]的argmax得到最佳 bin - 再取同一 bin 的
yaw_residual_sin - 最终
yaw = asin(residual_sin) + yaw_bin_offset
- 先取
-
cut 状态解码:
- 对
cut_logits[38:41]做argmax - 得到
normal / cut_in / cut_out
- 对
-
visible face 选择:
- 当前实现直接对 face 的
visible_score与阈值比较 - 这里不是 sigmoid 概率,而是“反归一化后直接比较”的分数
- 当前实现直接对 face 的
当前实现需要特别注意的一点
visible_score 当前没有走 sigmoid,而是直接作为一个原始连续值参与阈值判断与监督。这意味着:
- 它更接近“回归分数”而不是标准概率
- 文档或报告里如果把它理解成概率,会和实现不完全一致
4.5 edge head 输出
preds_edge 每个 anchor 固定 60 通道:
4个 face- 每个 face
5个采样点 - 每个采样点
3个值:du, dv, z
即:
60 = 4 * 5 * 3
每个 face 的 15 通道布局
以某个 face 为例:
- 点 1:
du1, dv1, z1 - 点 2:
du2, dv2, z2 - ...
- 点 5:
du5, dv5, z5
激活与反归一化
-
du, dv- 变换:
sigmoid(raw) * 16 - 8 - 含义:相对 anchor 的网格偏移
- 变换:
-
z- 变换:
raw * z3d_scale + z3d_offset - 单位:米
- 变换:
作用
这一分支不直接参与最终 2D 检测输出,而主要用于:
- 可见底边几何恢复
- edge-based yaw 解码
- cut object 的 partial side edge 重建
- edge 辅助监督
5. 输出在不同类别上的使用方式
5.1 face_3d_classes
这类目标会同时使用:
- 2D box/class
- face 级 3D 分支
- whole 3D 分支
- cut 分类
- edge 分支
5.2 complete_3d_classes
这类目标主要使用:
- 2D box/class
- whole 3D 分支
face 分支与 edge 分支虽然网络上也会输出,但在监督/评测上通常不作为有效主信号。
5.3 2D-only classes
这类目标只关心:
- 2D box
- 2D class
3D 分支对它们没有有效语义约束。
6. 一句话总结
当前 YOLO26-2D3D 模型本质上是一个三层 YOLO26 检测器,每个 anchor 同时输出:
4维 2D 框nc=17维分类41维 3D 属性60维 edge 几何
其中最关键的设计点是:
- whole 分支负责整框 3D 位置/尺寸/yaw/cut
- face 分支负责面中心 3D 属性与可见性
- edge 分支负责基于几何的 yaw 与可见边重建
- 推理阶段使用 end-to-end top-k 选择,而不是传统 NMS