YOLOv8 目标检测完整学习笔记

YOLOv8 目标检测完整学习笔记

------ 从整体结构、Anchor-Free、DFL、置信度到训练与推理流程

适合已经听说过 YOLO、但希望真正理解 YOLOv8 工作流程的读者。

本文主要讨论经典的 YOLOv8 Detect 目标检测模型 。实例分割、姿态估计和旋转框检测是在检测模型基础上的扩展,不是本文重点。

Ultralytics 的主分支会持续演进,因此源码中可能同时出现后续模型的兼容逻辑。学习 YOLOv8 时,先抓住本文整理的核心路径,再阅读源码会轻松很多。


目录

  1. 先建立整体认识
  2. 目标检测到底要解决什么问题
  3. [YOLOv8 的整体网络结构](#YOLOv8 的整体网络结构)
  4. [Backbone、Neck 和 Detect Head 分别做什么](#Backbone、Neck 和 Detect Head 分别做什么)
  5. [C2f 模块:YOLOv8 为什么替换 YOLOv5 的 C3](#C2f 模块:YOLOv8 为什么替换 YOLOv5 的 C3)
  6. [多尺度检测:P3、P4、P5 是什么](#多尺度检测:P3、P4、P5 是什么)
  7. [从 YOLOv5 的 Anchor-Based 讲起](#从 YOLOv5 的 Anchor-Based 讲起)
  8. [YOLOv8 的 Anchor-Free 到底是什么意思](#YOLOv8 的 Anchor-Free 到底是什么意思)
  9. [YOLOv8 的参考点为什么位于 Cell 中心](#YOLOv8 的参考点为什么位于 Cell 中心)
  10. [YOLOv8 如何表示边界框](#YOLOv8 如何表示边界框)
  11. [DFL:为什么每条边预测 16 个离散位置](#DFL:为什么每条边预测 16 个离散位置)
  12. [DFL 推理解码:如何从 16 个 Logit 得到连续距离](#DFL 推理解码:如何从 16 个 Logit 得到连续距离)
  13. [DFLoss 训练损失:如何监督离散分布](#DFLoss 训练损失:如何监督离散分布)
  14. [为什么默认使用 reg_max = 16](#为什么默认使用 reg_max = 16)
  15. [YOLOv8 的置信度怎么算](#YOLOv8 的置信度怎么算)
  16. [Task-Aligned Assigner:正样本如何动态分配](#Task-Aligned Assigner:正样本如何动态分配)
  17. [YOLOv8 的损失函数](#YOLOv8 的损失函数)
  18. 训练流程完整串联
  19. 推理流程完整串联
  20. [为什么 ONNX 输出经常是 1 × 84 × 8400](#为什么 ONNX 输出经常是 1 × 84 × 8400)
  21. [YOLOv5 与 YOLOv8 对比表](#YOLOv5 与 YOLOv8 对比表)
  22. 常见误区
  23. 源码阅读顺序
  24. 最终记忆卡片
  25. 参考资料

1. 先建立整体认识

YOLOv8 是一个单阶段目标检测器。输入一张图像后,它会一次前向传播产生大量候选框,再经过置信度过滤和 NMS,输出最终检测结果。

可以先把它看成下面这条流水线:

text 复制代码
输入图像
   ↓
预处理:缩放、补边、归一化
   ↓
Backbone:提取不同层次的图像特征
   ↓
Neck:融合浅层细节与深层语义
   ↓
Detect Head:预测类别分数与边界框
   ↓
边界框解码
   ↓
置信度阈值过滤
   ↓
NMS 去除重复框
   ↓
输出最终检测框、类别和置信度

如果你学过 YOLOv5,可以先记住 YOLOv8 的四个关键变化:

text 复制代码
1. C3 → C2f
2. Anchor-Based → Anchor-Free
3. 耦合检测头 → 分类与回归分离的检测头
4. Anchor 尺寸匹配 → Task-Aligned 动态正样本分配

此外,YOLOv8 的边界框回归使用了 DFL:

text 复制代码
直接回归一个距离
        ↓
预测距离的离散概率分布
        ↓
通过期望值恢复连续距离

2. 目标检测到底要解决什么问题

目标检测需要同时回答两个问题:

text 复制代码
问题 1:图像里有什么?
问题 2:目标在哪里?

例如,对一张街道图片进行检测:

text 复制代码
类别:car
边界框:[x1, y1, x2, y2]
置信度:0.91

其中:

text 复制代码
x1, y1:边界框左上角坐标
x2, y2:边界框右下角坐标

YOLOv8 的检测头也自然分成两条路径:

text 复制代码
分类分支:
这个候选框属于哪个类别?

回归分支:
这个候选框的四条边在哪里?

3. YOLOv8 的整体网络结构

标准 YOLOv8 Detect 使用三个尺度的特征图:

text 复制代码
P3:stride = 8
P4:stride = 16
P5:stride = 32

假设模型输入尺寸为:

text 复制代码
640 × 640 × 3

整体结构可以简化为:

text 复制代码
输入图像:640 × 640
        │
        ▼
Conv,stride = 2
320 × 320
        │
        ▼
Conv,stride = 2 + C2f
160 × 160
        │
        ▼
Conv,stride = 2 + C2f
80 × 80                     ← P3,stride = 8
        │
        ▼
Conv,stride = 2 + C2f
40 × 40                     ← P4,stride = 16
        │
        ▼
Conv,stride = 2 + C2f + SPPF
20 × 20                     ← P5,stride = 32
        │
        ▼
上采样 + Concat + C2f
        │
        ▼
继续上采样 + Concat + C2f
        │
        ▼
再向下采样 + Concat + C2f
        │
        ▼
Detect(P3, P4, P5)

官方 yolov8.yaml 中明确列出了 Backbone 中的 C2fSPPF,Head 中的上采样、拼接和三尺度 Detect 输出。


4. Backbone、Neck 和 Detect Head 分别做什么

4.1 Backbone:提取特征

Backbone 可以理解为"视觉信息提取器"。

浅层特征更关注:

text 复制代码
边缘
纹理
颜色
局部形状

深层特征更关注:

text 复制代码
目标整体结构
语义信息
类别相关信息

随着网络逐渐下采样,特征图尺寸越来越小,但每个特征点看到的输入图像区域越来越大,也就是感受野逐渐增大。


4.2 Neck:融合不同层次的信息

小目标需要细节,大目标需要较强的语义信息。

因此,YOLOv8 不会只使用最深层特征,而是将浅层、中层和深层特征融合起来。

可以粗略理解为:

text 复制代码
深层特征:语义强,细节少
       ↓ 上采样
与中层特征拼接
       ↓
继续上采样
与浅层特征拼接
       ↓
再向下聚合
       ↓
输出 P3、P4、P5 三个尺度

这种设计与 FPN 和 PAN 的思想相近:让信息在不同层级之间双向流动。


4.3 Detect Head:产生最终预测

对于每一个特征图位置,YOLOv8 的 Detect Head 分成两条主要分支:

text 复制代码
输入特征
  ├── Box 分支:预测边界框的距离分布
  └── Cls 分支:预测每个类别的 Logit

标准设置下:

text 复制代码
reg_max = 16
Box 分支输出维度 = 4 × 16 = 64
Cls 分支输出维度 = nc

其中:

text 复制代码
4:左、上、右、下四个方向
16:每个方向的离散距离档位数量
nc:类别数量

5. C2f 模块:YOLOv8 为什么替换 YOLOv5 的 C3

5.1 YOLOv5 的 C3

YOLOv5 中常见的 C3 模块大致可以理解为:

text 复制代码
输入 x
 ├── 分支 1:Conv → Bottleneck × N
 └── 分支 2:Conv
                  ↓
               Concat
                  ↓
                Conv

重点是:

text 复制代码
一个较深的加工分支
+
一个较浅的旁路分支

最后将二者拼接。


5.2 YOLOv8 的 C2f

C2f 会保留更多中间结果:

text 复制代码
输入 x
   ↓
Conv
   ↓
拆分为 y0、y1
        │
        └── y1 → Bottleneck → y2
                            ↓
                         Bottleneck → y3
                                    ↓
                                  ...
   ↓
Concat(y0, y1, y2, y3, ...)
   ↓
Conv
   ↓
输出

伪代码:

python 复制代码
y0, y1 = split(conv1(x))

y2 = bottleneck_1(y1)
y3 = bottleneck_2(y2)
y4 = bottleneck_3(y3)

out = conv2(concat(y0, y1, y2, y3, y4))

C2f 的直观优势是:

text 复制代码
保留多个中间阶段的特征
增加梯度传播路径
增强特征复用

需要注意:模型 FLOPs 较低不代表在所有设备上都一定更快。边缘端部署时,SplitConcat、内存访问和算子融合情况也会影响真实速度。


6. 多尺度检测:P3、P4、P5 是什么

假设输入为:

text 复制代码
640 × 640

那么三个检测尺度为:

特征层 Stride 特征图尺寸 参考点数量 整体倾向
P3 8 80 × 80 6400 更适合细节丰富的小目标
P4 16 40 × 40 1600 更适合中等目标
P5 32 20 × 20 400 更适合较大目标

总参考点数量:

text 复制代码
80 × 80 + 40 × 40 + 20 × 20
= 6400 + 1600 + 400
= 8400

这里的"小、中、大"只是倾向,不是强制规则。

YOLOv8 不会编写类似这样的硬编码:

python 复制代码
if area < 32 * 32:
    assign_to_P3()
elif area < 96 * 96:
    assign_to_P4()
else:
    assign_to_P5()

实际正样本由动态分配机制决定。


7. 从 YOLOv5 的 Anchor-Based 讲起

理解 YOLOv8 的 Anchor-Free 之前,先回顾经典 YOLOv5。

7.1 YOLOv5 使用预设 Anchor Box

YOLOv5 会为 P3、P4、P5 预先设置不同大小的 Anchor:

yaml 复制代码
anchors:
  - [10, 13, 16, 30, 33, 23]       # P3
  - [30, 61, 62, 45, 59, 119]      # P4
  - [116, 90, 156, 198, 373, 326]  # P5

每层有 3 个宽高模板。

对于 640 × 640 输入:

text 复制代码
P3:80 × 80 × 3 = 19200
P4:40 × 40 × 3 =  4800
P5:20 × 20 × 3 =  1200
--------------------------
总计:25200 个候选 Anchor

每一个 Anchor 预测:

text 复制代码
tx, ty, tw, th, objectness, class_1, class_2, ...

7.2 YOLOv5 的 Anchor 是人为固定的吗

准确答案是:

text 复制代码
有预设先验,但不是完全僵硬的人为分组。

默认 Anchor 会提前写在模型配置文件中。自定义数据集训练时,经典 YOLOv5 还可以使用 AutoAnchor 检查默认 Anchor 是否适合数据集,并在必要时重新聚类生成更匹配的 Anchor。

但是,训练正式开始后,Anchor 通常不会像卷积核权重那样随每个 Batch 反向传播更新。


7.3 YOLOv5 是否强制小目标只能交给 P3

不是。

YOLOv5 不会直接按面积将 GT 框硬切分到某一个层,而是比较:

text 复制代码
GT 的宽高
与
Anchor 的宽高

如果比例差异在允许范围内,则对应 Anchor 可以成为正样本。

因此,一个 GT 可能同时匹配:

text 复制代码
P3 的某些 Anchor
+
P4 的某些 Anchor
+
邻近 Cell

P3、P4、P5 形成的是一种由 Anchor 尺寸自然诱导出的分工,不是绝对规则。


7.4 YOLOv5 的解码方式

YOLOv5 中心点解码公式可以简化为:

text 复制代码
x = [2 × sigmoid(tx) - 0.5 + grid_x] × stride
y = [2 × sigmoid(ty) - 0.5 + grid_y] × stride

宽高解码:

text 复制代码
w = [2 × sigmoid(tw)]² × anchor_w
h = [2 × sigmoid(th)]² × anchor_h

这里:

text 复制代码
grid_x, grid_y

可以理解为 Cell 左上角对应的整数网格索引。

但这不代表预测框中心固定在 Cell 左上角。网络会继续预测中心偏移量。

当:

text 复制代码
tx = 0
ty = 0

由于:

text 复制代码
sigmoid(0) = 0.5

预测中心恰好落在 Cell 中心。


8. YOLOv8 的 Anchor-Free 到底是什么意思

YOLOv8 取消了 YOLOv5 那种带宽高先验的 Anchor Box。

对于 640 × 640 输入:

text 复制代码
P3:80 × 80 = 6400
P4:40 × 40 = 1600
P5:20 × 20 =  400
------------------------
总计:8400 个参考点

它不再为每个 Cell 绑定 3 个 Anchor 宽高模板。

对比:

text 复制代码
YOLOv5:
每个 Cell × 3 个 Anchor Box

YOLOv8:
每个 Cell × 1 个中心参考点

8.1 Anchor-Free 是否等于完全没有 Anchor

不是。

阅读 YOLOv8 源码时,你仍然会看到:

python 复制代码
make_anchors(...)
anchor_points

这里的 anchor_points 是参考点,不是 YOLOv5 中带宽高的 Anchor Box。

需要分清:

text 复制代码
YOLOv5 Anchor Box:
带宽高先验,例如 (10, 13)、(16, 30)

YOLOv8 Anchor Point:
只有位置坐标,例如 (0.5, 0.5)、(1.5, 0.5)

因此,YOLOv8 的 Anchor-Free 更准确的含义是:

text 复制代码
取消预设宽高模板
保留网格参考点

9. YOLOv8 的参考点为什么位于 Cell 中心

YOLOv8 生成参考点时,默认使用:

python 复制代码
grid_cell_offset = 0.5

因此,特征图上的参考点坐标为:

text 复制代码
(0.5, 0.5)
(1.5, 0.5)
(2.5, 0.5)
...

而不是:

text 复制代码
(0, 0)
(1, 0)
(2, 0)
...

假设 P3 层:

text 复制代码
stride = 8

第一个参考点映射到输入图像后:

text 复制代码
(0.5 × 8, 0.5 × 8)
= (4, 4)

示意图:

text 复制代码
输入图像上的第一个 8 × 8 区域

(0, 0) ┌──────────────┐
       │              │
       │      ●       │
       │    (4, 4)    │
       │              │
       └──────────────┘ (8, 8)

●:YOLOv8 的固定参考点

YOLOv8 的参考点位于 Cell 中心,但最终预测框中心不一定与它重合。


10. YOLOv8 如何表示边界框

YOLOv8 不直接预测:

text 复制代码
x_center, y_center, width, height

而是预测参考点到四条边的距离:

text 复制代码
l:参考点到左边界的距离
t:参考点到上边界的距离
r:参考点到右边界的距离
b:参考点到下边界的距离

示意图:

text 复制代码
                    t
                    ↑
          ┌────────────────────┐
          │                    │
          │        ● A         │
      l ← │       参考点        │ → r
          │                    │
          └────────────────────┘
                    ↓
                    b

假设参考点为:

text 复制代码
A = (ax, ay)

则边界框为:

text 复制代码
x1 = ax - l
y1 = ay - t
x2 = ax + r
y2 = ay + b

例如:

text 复制代码
参考点 = (10.5, 8.5)

l = 2
t = 1
r = 4
b = 3

得到:

text 复制代码
x1 = 10.5 - 2 = 8.5
y1 =  8.5 - 1 = 7.5
x2 = 10.5 + 4 = 14.5
y2 =  8.5 + 3 = 11.5

注意:

text 复制代码
预测框中心 ≠ 固定参考点

只有:

text 复制代码
l = r
并且
t = b

时,两者才重合。


11. DFL:为什么每条边预测 16 个离散位置

如果直接回归,可以让网络输出:

text 复制代码
l = 3.6
t = 1.8
r = 5.2
b = 4.1

但是 YOLOv8 不直接输出四个普通浮点数,而是为每一个方向预测一组离散分布。

标准设置:

text 复制代码
reg_max = 16

每条边对应 16 个 Logit:

text 复制代码
0, 1, 2, 3, ..., 15

四条边总共:

text 复制代码
4 × 16 = 64 个回归 Logit

拆开来看:

text 复制代码
l:16 个 Logit
t:16 个 Logit
r:16 个 Logit
b:16 个 Logit

11.1 为什么四个方向分别预测

不同边界的清晰程度可能不同。

例如,一个人站在桌子后面:

text 复制代码
左边缘:清晰
右边缘:被另一个人遮挡
上边缘:清晰
下边缘:被桌子遮挡

模型对四条边的不确定性并不一致。

所以,更合理的方式是:

text 复制代码
分别预测 l、t、r、b 的分布

11.2 16 个档位不是 16 个框

错误理解:

text 复制代码
每个参考点预测 16 个边界框

正确理解:

text 复制代码
每条边预测 16 个距离档位的相对可能性

对于一个参考点,完整输出仍然对应一个框,只是这个框的四条边使用概率分布表示。


11.3 不是在 Cell 内再划分 16 个小格子

错误理解:

text 复制代码
将每个 Cell 再切成 16 份

正确理解:

text 复制代码
16 个档位代表距离为:
0、1、2、...、15 个特征图单位

假设:

text 复制代码
stride = 8

则:

text 复制代码
档位 0 → 0 像素
档位 1 → 8 像素
档位 2 → 16 像素
...
档位 15 → 120 像素

但模型最后不是只能输出 2432 像素。因为它会进行概率加权求和,所以仍然可以得到:

text 复制代码
3.62 × 8 = 28.96 像素

12. DFL 推理解码:如何从 16 个 Logit 得到连续距离

以左侧距离 l 为例。

模型先输出 16 个 Logit:

text 复制代码
z0, z1, z2, ..., z15

这些 Logit 经过 Softmax:

text 复制代码
p0, p1, p2, ..., p15

满足:

text 复制代码
p0 + p1 + p2 + ... + p15 = 1

最终距离通过期望值计算:

text 复制代码
l = Σ(k × pk), k = 0 ... 15

数学形式:

text 复制代码
l = 0 × p0 + 1 × p1 + 2 × p2 + ... + 15 × p15

例如:

档位 概率
2 0.02
3 0.38
4 0.56
5 0.04
其余 接近 0

则:

text 复制代码
l ≈ 2 × 0.02
   + 3 × 0.38
   + 4 × 0.56
   + 5 × 0.04

  ≈ 3.62

所以最终距离仍然是连续数。


12.1 推理阶段的 DFL 模块

源码中有一个类叫:

python 复制代码
DFL

虽然名字里有 Loss,但这个模块在前向过程中主要负责积分解码:

text 复制代码
64 个回归 Logit
   ↓
拆成 l、t、r、b 四组
   ↓
每组执行 Softmax
   ↓
使用 [0, 1, 2, ..., 15] 加权求和
   ↓
得到 4 个连续距离

这一步之后,再结合参考点恢复边界框。


13. DFLoss 训练损失:如何监督离散分布

推理时使用 DFL 模块解码。

训练时还需要一个真正的损失函数:

python 复制代码
DFLoss

二者不是同一个东西。


13.1 真实距离是浮点数怎么办

假设 GT 框相对于参考点的左侧距离为:

text 复制代码
l_gt = 3.6

离散档位只有整数:

text 复制代码
0, 1, 2, 3, 4, ..., 15

不能粗暴地四舍五入:

text 复制代码
3.6 ≈ 4

否则会丢失细节。

DFLoss 会将监督信号分配给相邻两个整数。


13.2 将 3.6 拆分到 3 和 4

text 复制代码
左侧档位:
floor(3.6) = 3

右侧档位:
3 + 1 = 4

权重:

text 复制代码
档位 3 的权重:
4 - 3.6 = 0.4

档位 4 的权重:
3.6 - 3 = 0.6

理想监督:

档位 目标权重
3 0.4
4 0.6
其余 0

期望值:

text 复制代码
3 × 0.4 + 4 × 0.6
= 3.6

DFLoss 可以简化写成:

text 复制代码
Loss_DFL =
- 0.4 × log(p3)
- 0.6 × log(p4)

这样,模型会被鼓励将概率集中到真实值左右两个邻近档位。


14. 为什么默认使用 reg_max = 16

先给出结论:

text 复制代码
16 不是数学上唯一正确的答案。
它是表达能力、距离范围与计算成本之间的工程折中。

标准 YOLOv8 Detect 中:

text 复制代码
reg_max = 16

每条边有 16 个档位:

text 复制代码
0 ~ 15

14.1 reg_max 太小的问题

假设:

text 复制代码
reg_max = 4

那么档位只有:

text 复制代码
0, 1, 2, 3

在 P3 层:

text 复制代码
stride = 8

单条边能表达的距离范围非常有限:

text 复制代码
大约 3 × 8 = 24 像素

很容易不足以覆盖较大的目标。


14.2 reg_max 太大的问题

假设:

text 复制代码
reg_max = 256

边界框分支的输出通道数:

text 复制代码
4 × 256 = 1024

标准设置只需要:

text 复制代码
4 × 16 = 64

增大 reg_max 会提高:

text 复制代码
输出通道数
计算量
显存占用
内存带宽压力
部署成本

而且,由于期望值已经能够恢复连续坐标,继续无限增大档位数量未必能带来明显收益。


14.3 reg_max 主要影响范围,不是简单的定位精度

当档位间隔固定为 1 个特征图单位时,增大 reg_max 首先扩大的是:

text 复制代码
单条边能够表达的最大距离范围

标准 YOLOv8 中:

text 复制代码
档位:0 ~ 15

大致对应:

检测层 Stride 单边可表达距离量级
P3 8 约 15 × 8 = 120 像素
P4 16 约 15 × 16 = 240 像素
P5 32 约 15 × 32 = 480 像素

如果参考点位于目标内部较中心的位置,那么框宽或框高的覆盖量级可以达到单边距离的约两倍。

这些是帮助理解的近似值,不是人为设置的严格目标尺寸边界。


14.4 为什么源码会限制到 reg_max - 0.01

训练时,需要为真实距离找到左右两个相邻档位:

text 复制代码
tl = floor(target)
tr = tl + 1

假设:

text 复制代码
target = 15

则:

text 复制代码
tl = 15
tr = 16

但档位只有:

text 复制代码
0 ~ 15

索引 16 越界。

因此,训练时会将目标距离限制为:

text 复制代码
小于 reg_max

源码中常见形式:

python 复制代码
dist.clamp_(0, reg_max - 0.01)

15. YOLOv8 的置信度怎么算

这是 YOLOv8 与经典 YOLOv5 的另一处重要差异。


15.1 回顾 YOLOv5

经典 YOLOv5 每个 Anchor 会输出:

text 复制代码
边界框参数
+
objectness
+
类别分数

推理时,某个类别的最终分数通常为:

text 复制代码
confidence =
objectness × class_probability

例如:

text 复制代码
objectness = 0.8
person_probability = 0.9

最终 person 置信度:
0.8 × 0.9 = 0.72

15.2 YOLOv8 没有独立 Objectness 通道

标准 YOLOv8 Detect 的检测头输出维度为:

text 复制代码
nc + 4 × reg_max

而不是:

text 复制代码
nc + 1 + 4 × reg_max

没有额外的 +1,因此没有经典 YOLOv5 式独立 Objectness 分支。

YOLOv8 的两条主要输出路径为:

text 复制代码
Box 分支:4 × reg_max
Cls 分支:nc

15.3 分类分支先输出 Logit

假设有 3 个类别:

text 复制代码
person
car
dog

某个参考点的分类分支输出:

text 复制代码
person logit =  2.0
car    logit = -1.0
dog    logit = -2.2

这些不是最终置信度,需要分别经过 Sigmoid:

text 复制代码
sigmoid(z) = 1 / (1 + exp(-z))

得到:

text 复制代码
person score ≈ 0.881
car    score ≈ 0.269
dog    score ≈ 0.100

默认单标签路径中,通常取最大类别分数:

text 复制代码
confidence = max(class_scores)
class_id   = argmax(class_scores)

因此:

text 复制代码
最终类别 = person
最终置信度 = 0.881

15.4 为什么使用 Sigmoid 而不是 Softmax

Softmax 会强制:

text 复制代码
所有类别概率之和 = 1

Sigmoid 则是独立判断:

text 复制代码
像不像 person?
像不像 car?
像不像 dog?

每个类别单独得到一个分数。


15.5 DFL 是否直接参与置信度计算

不会。

标准 YOLOv8 推理路径中:

text 复制代码
DFL 分布
   ↓
解码边界框坐标

分类 Logit
   ↓
Sigmoid
   ↓
类别置信度

不会计算:

text 复制代码
confidence = class_score × DFL_peak

也不会计算:

text 复制代码
confidence = class_score × DFL_entropy

所以:

text 复制代码
DFL 负责框画在哪里
Cls 分支负责分数有多高

但是在训练阶段,由于正样本分配同时考虑分类质量和定位质量,二者会间接关联。


16. Task-Aligned Assigner:正样本如何动态分配

训练目标检测器时,需要回答:

text 复制代码
对于一个 GT 框,哪些预测位置应该负责学习它?

YOLOv8 使用 Task-Aligned Assigner,简称 TAL。


这一步是 YOLOv8 和 YOLOv5 的重要差异。

在训练阶段,模型必须回答:

对于一个真实目标,哪些预测位置应该负责学习它?

YOLOv8 使用 TaskAlignedAssigner,简称 TAL

官方文档将其描述为:结合分类信息和定位信息,为真实目标分配正样本。 (Ultralytics Docs)

它的思路可以简化为四步。

16.1 第一步:筛选落在真实框内部的参考点

如果某个 Anchor Point 位于 Ground Truth 框外部,它通常不会成为该目标的候选正样本。

text 复制代码
GT 框
┌───────────────────┐
│   ×       ×       │
│       ×           │
│            ×      │
└───────────────────┘

框内的参考点进入候选集合

16.2 第二步:计算分类和定位对齐程度

对于候选参考点,计算一个对齐指标:

text 复制代码
alignment_metric = score^α × IoU^β

其中:

  • score:预测为真实类别的分数;
  • IoU:预测框与真实框的重叠程度;
  • α:分类分数的权重;
  • β:定位质量的权重。

当前官方 v8DetectionLoss 中,常见设置为:

text 复制代码
alpha = 0.5
beta  = 6.0
topk  = 10

(GitHub)

由于 β 较大,IoU 会产生较强影响。模型倾向于选择:

text 复制代码
分类判断较可靠
并且
边界框位置较准确

的预测点作为正样本。

16.3 第三步:为每个 GT 选择 Top-K 候选点

每一个真实框会从候选点中挑选对齐程度较高的若干个点。

16.4 第四步:解决冲突

某个参考点可能同时落入两个真实框内。此时,分配器会根据重叠情况选择更合适的 GT。

官方实现中包含 select_highest_overlaps() 逻辑。 (GitHub)

16.5 与 YOLOv5 的区别

text 复制代码
YOLOv5:
GT 与 Anchor 宽高模板是否匹配?

YOLOv8:
当前哪些参考点的分类与定位表现更适合负责该 GT?

YOLOv8 的正样本分配会随着模型状态变化,更加动态。


17. YOLOv8 的损失函数

YOLOv8 Detect 的损失主要包括三部分:

text 复制代码
Loss =
λbox × Loss_box
+
λcls × Loss_cls
+
λdfl × Loss_dfl

17.1 Box Loss

Box Loss 关注:

text 复制代码
预测框与 GT 框整体是否接近

通常使用 IoU 类损失,例如 CIoU。

可以粗略理解为:

text 复制代码
预测框与 GT 框重叠越好
Loss 越小

17.2 Cls Loss

分类损失关注:

text 复制代码
预测类别是否正确

YOLOv8 使用基于 Logit 的二分类交叉熵形式:

text 复制代码
BCEWithLogitsLoss

注意:正样本的分类监督不是只包含简单硬标签。TAL 还会让定位质量影响目标分数,使类别分数在训练后包含一定的定位质量信息。

因此,YOLOv8 推理时直接使用类别分数,但它不应被机械理解成严格校准后的统计学概率。


17.3 DFL Loss

DFL Loss 关注:

text 复制代码
l、t、r、b 四条边的距离分布
是否集中在真实距离附近

它与 IoU 类 Box Loss 互补:

text 复制代码
IoU Loss:
从整体上约束完整边界框

DFL Loss:
细化四条边的位置分布

18. 训练流程完整串联

下面将 YOLOv8 的训练流程完整走一遍。


18.1 输入图像与标签

输入图像经过缩放、补边等预处理。

YOLO 常见标签格式:

text 复制代码
class_id center_x center_y width height

这些坐标通常会归一化到:

text 复制代码
[0, 1]

训练时再转换到模型需要的坐标系。


18.2 Backbone 与 Neck 提取多尺度特征

对于 640 × 640 输入:

text 复制代码
P3:80 × 80
P4:40 × 40
P5:20 × 20

总参考点:

text 复制代码
8400

18.3 检测头输出两类结果

对于每个参考点:

text 复制代码
Box 分支:
64 个 Logit = 4 × 16

Cls 分支:
nc 个类别 Logit

假设 COCO 数据集:

text 复制代码
nc = 80

则每个参考点训练阶段的主要原始输出维度:

text 复制代码
64 + 80 = 144

18.4 生成 Cell 中心参考点

参考点:

text 复制代码
(0.5, 0.5)
(1.5, 0.5)
...

不同检测层还有对应 Stride:

text 复制代码
P3:8
P4:16
P5:32

18.5 使用 DFL 将分布解码为连续距离

对于每个方向:

text 复制代码
16 个 Logit
   ↓ Softmax
16 个概率
   ↓ 加权求和
连续距离

得到:

text 复制代码
l, t, r, b

18.6 恢复预测框

text 复制代码
x1 = ax - l
y1 = ay - t
x2 = ax + r
y2 = ay + b

18.7 TAL 分配正样本

根据:

text 复制代码
参考点是否在 GT 内
分类质量
预测框与 GT 的 IoU
Top-K 筛选
冲突处理

确定哪些参考点为正样本。


18.8 计算损失并反向传播

text 复制代码
Box Loss
+
Cls Loss
+
DFL Loss

然后进行反向传播,更新卷积层等可学习参数。


19. 推理流程完整串联

下面以一张图像推理为例。


19.1 图像预处理

常见步骤:

text 复制代码
读取图像
   ↓
Letterbox 缩放与补边
   ↓
BGR → RGB
   ↓
HWC → CHW
   ↓
归一化到 [0, 1]
   ↓
增加 Batch 维度

19.2 网络前向传播

得到三尺度特征:

text 复制代码
P3、P4、P5

检测头产生:

text 复制代码
Box 分支:距离分布
Cls 分支:类别 Logit

19.3 DFL 解码边界框

每个方向:

text 复制代码
16 个 Logit
   ↓
Softmax
   ↓
期望值
   ↓
连续距离

然后结合参考点与 Stride 恢复边界框。


19.4 分类分支计算置信度

text 复制代码
class_scores = sigmoid(class_logits)

默认单标签情况下:

text 复制代码
confidence = max(class_scores)
class_id = argmax(class_scores)

19.5 阈值过滤

过滤掉:

text 复制代码
confidence < conf_threshold

的候选框。


19.6 NMS 去除重复框

标准 YOLOv8 Detect 仍然需要 NMS。

NMS 逻辑:

text 复制代码
选择置信度最高的框
   ↓
计算它与其他框的 IoU
   ↓
删除高度重叠的重复框
   ↓
在剩余框中继续重复

最终输出:

text 复制代码
边界框
类别
置信度

20. 为什么 ONNX 输出经常是 1 × 84 × 8400

对于:

text 复制代码
输入尺寸:640 × 640
类别数量:80
Batch Size:1

参考点数量:

text 复制代码
80 × 80 + 40 × 40 + 20 × 20
= 8400

DFL 在模型图中完成解码后,每个参考点通常包含:

text 复制代码
4 个边界框参数
+
80 个类别分数
=
84

因此,常见 ONNX 输出:

text 复制代码
[1, 84, 8400]

不同导出工具或后端也可能输出:

text 复制代码
[1, 8400, 84]

二者只是维度顺序不同。


20.1 为什么不是 144

训练阶段原始预测:

text 复制代码
64 个回归 Logit
+
80 个分类 Logit
=
144

但是常见导出图通常已经包含:

text 复制代码
64 个回归 Logit
   ↓
DFL 解码
   ↓
4 个边界框参数

所以最终变成:

text 复制代码
4 + 80 = 84

21. YOLOv5 与 YOLOv8 对比表

对比项 经典 YOLOv5 YOLOv8 Detect
检测范式 Anchor-Based Anchor-Free
参考对象 带宽高先验的 Anchor Box Cell 中心 Anchor Point
每个 Cell 的候选数量 通常 3 个 Anchor 1 个参考点
640 × 640 输入候选数量 25200 8400
Backbone 核心模块 C3 C2f
SPPF
多尺度输出 P3、P4、P5 P3、P4、P5
检测头 耦合头 分类与回归分离
独立 Objectness 无经典 YOLOv5 式独立通道
边界框参数化 中心点偏移 + 宽高缩放 点到四条边距离 ltrb
是否依赖 Anchor 宽高
正样本分配 GT 与 Anchor 尺寸匹配 TAL 动态分配
框损失 IoU 类 Loss IoU 类 Loss + DFL
常见置信度 objectness × class_score sigmoid(class_logit)
常见 ONNX 输出 [1, 25200, 85] [1, 84, 8400][1, 8400, 84]
后处理 NMS NMS

22. 常见误区

误区 1:Anchor-Free 就是完全没有 Anchor

错误。

YOLOv8 没有 Anchor Box,但仍然有 Anchor Point。

text 复制代码
没有宽高先验
≠
没有参考位置

误区 2:YOLOv8 的参考点就是预测框中心

错误。

参考点只是计算 l、t、r、b 的起点。

只有:

text 复制代码
l = r
t = b

时,参考点才与预测框中心重合。


误区 3:reg_max = 16 表示预测 16 个框

错误。

text 复制代码
每条边预测 16 个距离档位
四条边共 64 个 Logit
最终仍然恢复一个框

误区 4:16 个档位表示只能输出整数距离

错误。

通过概率期望:

text 复制代码
distance = Σ(k × pk)

模型仍然可以输出:

text 复制代码
3.62
7.18
11.47

这样的连续距离。


误区 5:DFL 是在 Cell 内继续划分 16 个小格子

错误。

档位代表到边界的距离:

text 复制代码
0 ~ 15 个特征图单位

不是 Cell 内部二次划分。


误区 6:YOLOv8 置信度仍然是 obj × cls

错误。

经典 YOLOv5 才使用:

text 复制代码
objectness × class_probability

标准 YOLOv8 Detect 没有独立 Objectness 通道。


误区 7:DFL 分布越尖锐,最终置信度一定越高

不一定。

推理时:

text 复制代码
DFL → 解码框
Cls → 计算置信度

两者不会直接相乘。


误区 8:P3 只能检测小目标,P5 只能检测大目标

错误。

P3、P4、P5 具有不同的倾向,但正样本分配不是面积硬切分。


误区 9:YOLOv5 的 Anchor 在训练过程中不断更新

通常不是。

AutoAnchor 可以在训练开始前检查或重新生成 Anchor,但正式训练中 Anchor 一般保持固定。


23. 源码阅读顺序

学习 YOLOv8 时,不建议一开始遍历整个仓库。

推荐顺序:

顺序 文件 重点
1 ultralytics/cfg/models/v8/yolov8.yaml Backbone、Neck、P3/P4/P5、Detect 输入
2 ultralytics/nn/modules/block.py C2f、SPPF、DFL 积分解码模块
3 ultralytics/nn/modules/head.py Detect Head、Box 分支、Cls 分支、reg_max
4 ultralytics/utils/tal.py make_anchors()dist2bbox()、TAL
5 ultralytics/utils/loss.py DFLossBboxLossv8DetectionLoss
6 导出的 ONNX 图 [1, 84, 8400] 的来源

对照 YOLOv5 时,可阅读:

文件 重点
models/yolov5s.yaml Anchor Box 配置
models/yolo.py YOLOv5 Detect Head 与解码公式
utils/loss.py Anchor 匹配、build_targets()
utils/autoanchor.py AutoAnchor

24. 最终记忆卡片

24.1 用一句话描述 YOLOv8

text 复制代码
YOLOv8 使用 C2f Backbone、类似 FPN/PAN 的多尺度融合结构和 Anchor-Free 解耦检测头;
它以 Cell 中心为参考点,为每个位置预测类别分数与 l、t、r、b 四条边的离散距离分布,
通过 DFL 解码连续边界框,并通过 TAL 动态分配正样本。

24.2 用一张流程图记住推理

text 复制代码
输入图像
   ↓
Backbone + Neck
   ↓
P3、P4、P5 三尺度特征
   ↓
8400 个 Cell 中心参考点
   ↓
┌──────────────────────────────┐
│ Box 分支:每点 4 × 16 个 Logit │
│ Cls 分支:每点 nc 个 Logit     │
└──────────────────────────────┘
   ↓                         ↓
DFL:Softmax + 期望值        Sigmoid
   ↓                         ↓
l、t、r、b                  类别分数
   ↓                         ↓
结合参考点与 Stride           置信度过滤
   ↓                         ↓
边界框坐标 ───────────────→ NMS
                             ↓
                       最终检测结果

24.3 三条公式

边界框解码:

text 复制代码
x1 = ax - l
y1 = ay - t
x2 = ax + r
y2 = ay + b

DFL 距离期望:

text 复制代码
distance = Σ(k × softmax(logits)k)

YOLOv8 类别置信度:

text 复制代码
confidence_c = sigmoid(class_logit_c)

24.4 三个关键词

text 复制代码
Anchor-Free
DFL
Task-Aligned Assigner

真正吃透这三部分后,YOLOv8 的训练、推理、ONNX 输出与后处理逻辑就能自然串起来。


25. 参考资料

以下资料适合配合本文阅读:

  1. Ultralytics YOLOv8 官方说明:

    https://docs.ultralytics.com/models/yolov8/

  2. YOLOv8 网络 YAML:

    https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/models/v8/yolov8.yaml

  3. Detect Head 源码文档:

    https://docs.ultralytics.com/reference/nn/modules/head/

  4. C2f 与 DFL 模块源码文档:

    https://docs.ultralytics.com/reference/nn/modules/block/

  5. TAL、make_anchors()dist2bbox() 源码文档:

    https://docs.ultralytics.com/reference/utils/tal/

  6. DFLoss、BboxLoss、v8DetectionLoss 源码文档:

    https://docs.ultralytics.com/reference/utils/loss/

  7. Generalized Focal Loss 论文:

    https://arxiv.org/abs/2006.04388

  8. 经典 YOLOv5 仓库:

    https://github.com/ultralytics/yolov5

相关推荐
星越华夏2 小时前
深度学习项目实战:基于PyTorch的图像分类与目标检测(YOLOv8)
pytorch·深度学习·yolo·分类
hans汉斯18 小时前
【计算机科学与应用】YOLO-Apple:一种用于苹果幼果检测的改进型目标检测方法
人工智能·yolo·目标检测·计算机视觉·目标跟踪·数据·病虫害检测
动物园猫18 小时前
外墙裂缝目标检测数据集分享(适用于YOLO系列深度学习分类检测任务)
深度学习·yolo·目标检测
stsdddd1 天前
YOLO系列目标检测数据集大全【第七期】
yolo·目标检测·目标跟踪
YOLO数据集集合1 天前
无人机低空安防巡检AI落地方案|航拍小目标人员入侵检测、多场景跨领域目标检测数据集与YOLO算法工程实战
人工智能·yolo·目标检测·无人机
Ricky05531 天前
基于对比学习的卫星影像目标检测领域适应方法(2024年美国研究)
人工智能·学习·目标检测
钓了猫的鱼儿1 天前
基于深度学习+AI的茶叶病害目标检测与预警系统(Python源码+数据集+UI可视化界面+YOLOv11训练结果)
人工智能·深度学习·目标检测
YOLO数据集集合1 天前
YOLOv11+DeepSeek多技术融合电网缺陷巡检平台|绝缘子破损瓷瓶故障AI识别、前后端一体化电力运维管理系统落地开发
运维·人工智能·yolo
Jumbuck_101 天前
从零实现《三角洲行动》手游自动跑刀脚本:ADB 直控 + OpenCV 视觉识别 + 固定点位搜刮)三角洲自动跑刀教程
嵌入式硬件·yolo·目标检测·自动化·自动驾驶·三角洲·自动跑刀