13 YOLOv8的ONNX输出结构——9个张量如何变成检测框

13-YOLOv8的ONNX输出结构------9个张量如何变成检测框

一句话总结:YOLOv8 导出为 ONNX 后,输出 9 个张量------3 个检测头 × 3 个分支(box、cls、DFL)。每个张量是一个 4 维数组,6400+1600+400 个网格各装一个"信息包",解码后得到框坐标和类别置信度。


目录

  • 13-YOLOv8的ONNX输出结构------9个张量如何变成检测框
    • 目录
    • [前置知识:ONNX 导出时发生了什么](#前置知识:ONNX 导出时发生了什么)
    • [第一层:9 个输出的来源](#第一层:9 个输出的来源)
      • [3 个检测头](#3 个检测头)
      • [3 个分支](#3 个分支)
      • [9 个输出一览](#9 个输出一览)
    • 第二层:每个张量的形状和含义
      • [box 特征 → [1, 64, H, W]](#box 特征 → [1, 64, H, W])
      • [cls 特征 → [1, 80, H, W]](#cls 特征 → [1, 80, H, W])
      • [DFL 特征 → [1, 64, H, W]](#DFL 特征 → [1, 64, H, W])
    • [第三层:NCHW vs NHWC------怎么理解更直观](#第三层:NCHW vs NHWC——怎么理解更直观)
    • 第四层:一个网格的完整信息包
    • [第五层:从 9 个张量到最终检测框](#第五层:从 9 个张量到最终检测框)
      • [以网格 (10, 20) 为例的完整解码](#以网格 (10, 20) 为例的完整解码)
    • [第六层:为什么 box 和 DFL 是两个独立输出](#第六层:为什么 box 和 DFL 是两个独立输出)
    • 自测
    • 一句话记住

前置知识:ONNX 导出时发生了什么

YOLOv8 的 PyTorch 模型导出为 ONNX 时,ONNX 图包含 Backbone、Neck 和检测头的卷积部分。DFL 解码、NMS 等后处理逻辑不在 ONNX 图中,而是在推理代码中完成。

这种"后处理外置"的方式是工业界常用的部署方式------ONNX 只负责特征提取和原始预测,推理代码灵活可控。


第一层:9 个输出的来源

复制代码
3 个检测头 × 3 个分支 = 9 个输出

3 个检测头

检测头 特征图尺寸 网格数 负责
P3 80×80 6400 小目标(鸟、硬币)
P4 40×40 1600 中目标(人脸、猫)
P5 20×20 400 大目标(汽车、建筑)
合计 --- 8400 ---

3 个分支

分支 通道数 含义
box 64 框坐标(4 条边 × 16 个 bin)
cls 80 类别(COCO 80 个类别)
DFL 64 与 box 相同,标记走 DFL 解码链

9 个输出一览

复制代码
检测头 P3(小目标,80×80 网格):
  ├── 输出1: box特征   [1, 64, 80, 80]   ← 64 = 4条边 × 16个bin
  ├── 输出2: cls特征   [1, 80, 80, 80]   ← 80 = COCO 80个类别
  └── 输出3: DFL特征   [1, 64, 80, 80]   ← 和box一样

检测头 P4(中目标,40×40 网格):
  ├── 输出4: box特征   [1, 64, 40, 40]
  ├── 输出5: cls特征   [1, 80, 40, 40]
  └── 输出6: DFL特征   [1, 64, 40, 40]

检测头 P5(大目标,20×20 网格):
  ├── 输出7: box特征   [1, 64, 20, 20]
  ├── 输出8: cls特征   [1, 80, 20, 20]
  └── 输出9: DFL特征   [1, 64, 20, 20]

第二层:每个张量的形状和含义

box 特征 → [1, 64, H, W]

复制代码
64 个通道 = 4 条边 × 16 个 bin

通道 0~15:   left   的 16 个 bin → 候选偏移 0.0, 0.5, 1.0, ..., 7.5
通道 16~31:  top    的 16 个 bin
通道 32~47:  right  的 16 个 bin
通道 48~63:  bottom 的 16 个 bin

每个通道的值是原始分数(未归一化),正负无穷都行,没有范围限制。

以 P3 检测头网格 (10, 20) 为例:

复制代码
left:   [2.0, 5.0, 1.0, 0.5, 0.2, 0.1, 0.0, ..., 0.0]  ← 16 个 bin
top:    [1.5, 4.0, 2.0, 0.3, 0.1, 0.0, ..., 0.0]
right:  [3.0, 6.0, 0.5, 0.1, 0.0, 0.0, ..., 0.0]
bottom: [2.5, 5.5, 1.5, 0.2, 0.1, 0.0, ..., 0.0]

cls 特征 → [1, 80, H, W]

复制代码
80 个通道 = COCO 80 个类别

通道 0:  person       分数 9.5   ← 最高,是人!
通道 1:  bicycle      分数 0.1
通道 2:  car          分数 0.2
通道 3:  motorcycle   分数 0.0
通道 4:  airplane     分数 0.0
...
通道 15: cat          分数 0.3
通道 16: dog          分数 0.1
...
通道 79: toothbrush   分数 0.0

每个通道的值也是原始分数,需要 Sigmoid 变成 0~1 的置信度。

DFL 特征 → [1, 64, H, W]

和 box 特征的数据完全相同。为什么导出两个?因为推理代码需要知道"这 64 个通道是 DFL 格式的,要走 Softmax→Conv(dfl)→Sub→Div 这条解码链"。


第三层:NCHW vs NHWC------怎么理解更直观

ONNX 默认用 NCHW 排列(PyTorch 惯例),但理解的时候完全可以脑补成 NHWC

复制代码
NCHW [1, 64, 80, 80]:  先按通道分,每个通道里是 80×80 平面
                       像 64 张纸叠在一起

NHWC [1, 80, 80, 64]:  先按空间分,每个格子里是 64 个值
                       像 6400 个格子,每个格子存 64 个数

NHWC 的思维模型更直观

复制代码
输出1 box:   [1, 80, 80, 64]  ← 6400个格子,每个格子 64 个值(框在哪)
输出2 cls:   [1, 80, 80, 80]  ← 6400个格子,每个格子 80 个值(框里是啥)
输出3 DFL:   [1, 80, 80, 64]  ← 和box一样,标记走DFL链

第四层:一个网格的完整信息包

网格 (10, 20) 的"信息包":

复制代码
┌──────────────────────────────────────────────────────┐
│  网格 (10, 20) 的信息包                               │
│                                                      │
│  box: 64 个值 → 4条边 × 16个bin                      │
│    left  [2.0, 5.0, 1.0, 0.5, ...]                   │
│    top   [1.5, 4.0, 2.0, 0.3, ...]                   │
│    right [3.0, 6.0, 0.5, 0.1, ...]                   │
│    bottom[2.5, 5.5, 1.5, 0.2, ...]                   │
│                                                      │
│  cls: 80 个值 → 80个类别                              │
│    [9.5, 0.1, 0.2, 0.0, ..., 0.3, 0.1, ..., 0.0]    │
│                                                      │
│  DFL: 64 个值 → 和box一样                             │
│                                                      │
│  共 64 + 80 = 144 个值                                │
└──────────────────────────────────────────────────────┘

6400 个格子 × 144 个值 = 921,600 个数字,这就是 P3 检测头的全部输出。


第五层:从 9 个张量到最终检测框

复制代码
第1步:解码
  8400 个网格(3 个检测头)
  每个网格:
    64 个值 → DFL 解码 → 框坐标 (x1, y1, x2, y2)
    80 个值 → Sigmoid → 80 个置信度

第2步:过滤
  8400 个候选框 → 去掉置信度 < 0.25 的 → 剩几百个

第3步:NMS
  几百个框 → 去掉重叠的 → 剩几个最终框

第4步:画框
  最终框 → 在原图上画矩形 + 类别标签

以网格 (10, 20) 为例的完整解码

复制代码
ONNX 输出(P3 头):
  box  [1,64,80,80] → 取网格(10,20) → 64个值
  cls  [1,80,80,80] → 取网格(10,20) → 80个值
  DFL  [1,64,80,80] → 取网格(10,20) → 64个值
          │
          ▼
┌─────────────────────────────────────────────────────┐
│  对 DFL 的 64 个值:                                 │
│                                                     │
│  left 16个bin → Softmax → Conv(dfl) → 偏移量        │
│  top  16个bin → Softmax → Conv(dfl) → 偏移量        │
│  right 16个bin → Softmax → Conv(dfl) → 偏移量       │
│  bottom 16个bin → Softmax → Conv(dfl) → 偏移量      │
│                                                     │
│  → Sub → Div → 得到 4 个坐标值                       │
└─────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────┐
│  对 cls 的 80 个值:                                 │
│                                                     │
│  80个值 → Sigmoid → 80个置信度                       │
│  取最大值: person 置信度 0.9997                      │
└─────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────┐
│  最终结果:                                           │
│                                                     │
│  检测到: person                                      │
│  框坐标: (80, 60, 320, 400)  ← 像素坐标              │
│  置信度: 0.9997                                      │
└─────────────────────────────────────────────────────┘

第六层:为什么 box 和 DFL 是两个独立输出

复制代码
检测头的最后几层卷积:

特征图 ──┬── Conv(box) ──→ [1, 64, H, W]  ← 回归分支
         │
         └── Conv(cls) ──→ [1, 80, H, W]  ← 分类分支

box 和 DFL 数据完全一样,为什么导出两个?

原因 说明
语义标记 DFL 输出告诉推理代码"这 64 个通道需要走 DFL 解码链"
ONNX 规范 ONNX 图中每个输出节点有独立的名字,推理代码通过名字识别
灵活性 推理代码可以分别处理 box 和 DFL,比如做剪枝或优化

实际上,推理代码拿到 DFL 特征后,走 Softmax→Conv(dfl)→Sub→Div 这条解码链,box 特征通常和 DFL 特征合并处理。


自测

Q1:YOLOv8 的 ONNX 为什么输出 9 个张量?
点击查看答案

3 个检测头(P3/P4/P5)× 3 个分支(box/cls/DFL)= 9 个。每个检测头用自己的特征图尺寸(80×80/40×40/20×20),每个分支输出不同含义的数据。

Q2:[1, 64, 80, 80] 中的 64 个通道分别代表什么?
点击查看答案

通道 0~15: left 的 16 个 bin;通道 16~31: top 的 16 个 bin;通道 32~47: right 的 16 个 bin;通道 48~63: bottom 的 16 个 bin。4 条边 × 16 个 bin = 64。

Q3:一个网格最终输出多少个值?分别是什么?
点击查看答案

144 个值。box/DFL 64 个值(框坐标的分布信息)+ cls 80 个值(80 个类别的原始分数)。

Q4:从 9 个张量到最终画框,经历了哪几步?
点击查看答案

  1. 解码:64 个值 → DFL 解码 → 框坐标;80 个值 → Sigmoid → 置信度
  2. 过滤:去掉置信度 < 0.25 的候选框
  3. NMS:去掉重叠的框
  4. 画框:在原图上画矩形 + 类别标签

一句话记住

9 个输出 = 3 个检测头 × 3 个分支。每个检测头输出 6400/1600/400 个网格,每个网格装一个"信息包"(64 个框值 + 80 个类别值),解码后得到框坐标和置信度,过滤 + NMS 后画框。

相关推荐
FriendshipT4 小时前
Ultralytics:解读SPPF模块
人工智能·pytorch·python·深度学习·目标检测
是Dream呀7 小时前
YOLOv8-QSD: 革新自动驾驶视觉感知的小目标检测算法
yolo·目标检测·自动驾驶
FriendshipT1 天前
Ultralytics:解读PSA模块
人工智能·pytorch·python·深度学习·目标检测
2601_962851742 天前
计算机毕业设计之基于SSM的网络安全课程资源分享平台
vue.js·算法·安全·web安全·yolo·目标检测·课程设计
AI棒棒牛3 天前
YOLO26 全网独家改进创新:ECCV2026 PriorEye 主干网络改进,引入视觉-空间先验门控 Backbone,复杂场景涨点新思路!
人工智能·神经网络·目标检测·yolo26
吃鱼不卡次3 天前
YOLO26目标检测网络结构
yolo·目标检测·yolo26
动物园猫3 天前
可回收垃圾目标检测数据集:5类别、13,000张图像 | 目标检测
人工智能·目标检测·计算机视觉
向哆哆3 天前
高质量人体检测与行人识别数据集分享(适用于YOLO系列深度学习分类检测任务)
深度学习·yolo·目标检测·分类
YOLO数据集集合4 天前
无人机松材线虫病树监测:早期与晚期感染阶段目标检测数据集全解析10125期
人工智能·目标检测·无人机