恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
【YOLO】YOLOv8原理
首页
资讯中心
/
【YOLO】YOLOv8原理
【YOLO】YOLOv8原理
发布时间:2026/10/10 2:54:56
===== 【相关资源获取途径】 =====1.CSDN:YOLO专栏2.---------: XiaoJ1234567目录1. YOLOv8 概述2. YOLOv8 网络结构1)Backbone + Neck + Head:检测器三段式2)YOLOv8 整体结构3)yaml 结构代码解读4)CBS:基础卷积块5)Bottleneck 模块6)C2F 模块7)SPPF 模块3. YOLOv8参数1)YOLOv8 网络整体参数2)模型尺度:n / s / m / l / x 是怎么来的3)计算尺寸和通道4)参数计算:Backbone 主干网络5)参数计算:Head(颈部 + 头部网络)4. YOLOv8 的 Anchor-Free1)Anchor-Based:基于锚框2)Anchor-Free:不基于锚框3)YOLOv8 Anchor-Free 核心机制:(DFL 思想)(1)核心思想(2)距离预测机制(DFL 思想)(3)解码到原图(4)完整示例5. YOLOv8 正负样本匹配1)前置概念:IoU 与 CIoU2)为什么需要正负样本分配3)初筛:落在真实框 GT 内的观测点4)精筛:Top-k 质量分 + 去重6. YOLOv8 损失函数1)BCE(Binary Cross-Entropy,二元交叉熵)2)CIoU(Complete IoU,完全交并比)3)DFL(Distribution Focal Loss,分布焦点损失)(1)概念(2)公式(3)计算步骤(以 y = 3.2 为例)(4)推理时的解码7. YOLOv8 项目代码1)仓库根目录2)核心包 `ultralytics/`3)源码阅读入口8. YOLOv8 模型训练、验证、预测1)CLI 命令格式2)训练3)验证4)预测5)导出9. 小结本文从零讲清 YOLOv8模型。主要包含网络结构、网络参数、损失函数、项目整体代码、模型的训练验证预测导出等。1. YOLOv8 概述目标检测(Object Detection):给一张图,找出所有物体的位置(用边界框( x 1 , y 1 , x 2 , y 2 ) (x_1,y_1,x_2,y_2)(x1,y1,x2,y2)框住)和类别(是猫、是车还是人)。训练集里人工标好的「正确答案」叫GT(Ground Truth,真值框),模型要学会让预测尽量接近 GT。检测网络分两派:两阶段(如 R-CNN)先生成候选框再逐个分类,精度高但慢;单阶段(如 YOLO、SSD)一次前向直接出全部框,快。YOLO = You Only Look Once,把检测压缩成「一次前向 + 一次 NMS(非极大值抑制,推理时去重复框的后处理)」。YOLO 系列的出框方式演进:版本出框方式v1无锚框,格子直接回归 (x, y, w, h)v2–v5预设 Anchor,学偏移量v8回到无锚框,但观测点更密(三尺度 8400 点)+ DFL 把距离学成分布YOLOv8由 Ultralytics(YOLOv5 同一团队)于2023 年 1 月发布。它是单阶段检测器:一张图一次前向,同时给出位置、类别、置信度;同一套结构还可做检测、分类、分割、跟踪、姿态、旋转框(OBB)六大任务。2. YOLOv8 网络结构1)Backbone + Neck + Head:检测器三段式现代检测网络通常分三段:Backbone(主干)从图像提取特征,Neck(颈部)融合多个分辨率的特征,Head(检测头)在特征图上预测框和类别。输入图像 │ ▼ Backbone(骨干) ── 提取特征,逐级下采样得到多尺度特征 │ ▼ Neck(颈部) ── 融合多尺度特征(深层语义 + 浅层细节) │ ▼ Head(头部) ── 在各尺度上输出「类别 + 位置 + 置信度」 │ ▼ 后处理(NMS 等)──► 最终检测框卷积一层层把图像变成「特征图」(H×W×C 的张量)。特征图 1 格对应原图stride个像素(如 stride=32 表示原图 32×32 压成 1 格)——stride 是后面把距离还原回原图的关键。网络越深感受野越大、适合大物体但小物体信息易丢失;浅层分辨率高、适合小物体但语义不足,所以要在多个尺度上同时预测。YOLOv8 用三个尺度:层级特征图尺寸(输入 640)stride负责P380×808小物体P440×4016中物体P520×2032大物体2)YOLOv8 整体结构YOLOv8 正是标准的三段式:Backbone 提特征 → Neck 多尺度融合 → Head 三尺度输出。图中Head模块,包含颈部+头部;要点:Backbone:CBS 下采样 + C2f 提特征 + SPPF 收尾,逐级下采样到 20×20×1024;Neck:上采样 + Concat 自顶向下融合,再下采样 + Concat 自底向上融合,得到 80×80 / 40×40 / 20×20 三个尺度特征;Head:三个尺度各接一个解耦头,输出张量分别为 80×80×(4×16+nc)、40×40×(4×16+nc)、20×20×(4×16+nc),其中 nc 为类别数;4×16+nc就是「回归分支 + 分类分支」的通道拼在一起。检测头要回答两个问题——两者性质不同,所以 YOLOv8 把它们分开处理:1.「格子里是什么」 → 分类(classification):输出各类别的分数 2.「框的边界在哪」 → 回归(regression):输出框的位置(四边距离 l/t/r/b)早期检测器让两者共用一条分支,称为耦合头(Coupled Head);YOLOv8 让它们各走一条独立分支,称为解耦头(Decoupled Head)——分开后两个任务互不干扰,精度更高、收敛更快。回归分支为什么输出 64 个通道,而不是 4 个?因为 YOLOv8 不直接回归一个距离值,而是对每条边(左 l / 上 t / 右 r / 下 b,共 4 条)各输出16 个打分,再用加权求和算出距离——这个 16 就是 reg_max(默认 16)。所以回归分支输出 4×16 =64通道。3)yaml 结构代码解读YOLOv8 沿用 Ultralytics「改结构 = 改 yaml」的设计:网络结构写在 yaml 里,不用改 Python 代码。每行格式为[from, repeats, module, args]:字段含义from该层输入来自哪一层(-1 表示上一层)repeats该模块重复次数module模块名称(Conv / C2f / SPPF / Concat / Detect 等)args模块参数(输出通道、卷积核、步幅等)yaml文件内容:这里Conv相当于CBS模块;# Parameters nc: 80 # number of classes scales: # [depth, width, max_channels] n: [0.33, 0.25, 1024] # YOLOv8n s: [0.33, 0.50, 1024] # YOLOv8s m: [0.67, 0.75, 768] # YOLOv8m l: [1.00, 1.00, 512] # YOLOv8l x: [1.00, 1.25, 512] # YOLOv8x backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 3, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 6, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 6, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 3, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] # 9 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 3, C2f, [512]] # 12 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 3, C2f, [256]] # 15 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 12], 1, Concat, [1]] # cat head P4 - [-1, 3, C2f, [512]] # 18 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 9], 1, Concat, [1]] # cat head P5 - [-1, 3, C2f, [1024]] # 21 (P5/32-large) - [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)关键:Neck + Head 在 yaml 里合并成了一个head:段。该段实际包含两部分:head: 段 = ① Neck(特征融合,第 10~21 层) + ② Head(检测头,最后 1 层) ① Neck:Upsample + Concat + C2f ×2 组(自顶向下,把深层语义传给浅层) Conv(s2) + Concat + C2f ×2 组(自底向上,把浅层细节传给深层) → 产出 P3(80×80) / P4(40×40) / P5(20×20) 三个融合后的特征 ② Head:Detect(P3, P4, P5) → 在上述三个尺度上输出「类别 + 位置 + 置信度」所以「Neck + Head 合并成 head 模块」指的是配置与代码层面的组织方式:网络结构上 Neck 依然真实存在(负责多尺度融合),只是没有被单独拆成一个 yaml 段,而是和 Detect 一起归在head:下。4)CBS:基础卷积块CBS = Conv + BN + SiLU,是整个网络的基本积木:Conv:卷积,提取特征;BN(Batch Normalization,批归一化):把每批数据特征分布拉回均值 0、方差 1,加速收敛、提升训练稳定性;SiLU:激活函数,引入非线性。CBS(k, s):X ──► Conv(k×k, stride=s) ──► BN ──► SiLU ──► 输出常用三种组合:CBS(1,1)(1×1 卷积,升降通道)、CBS(3,1)(3×3 卷积,提特征、尺寸不变)、CBS(3,2)(3×3 卷积步幅 2,下采样)。5)Bottleneck 模块Bottleneck(瓶颈)先用 1×1 卷积降维、再用 3×3 卷积升维,减少计算量的同时保持表达能力。两种形态:不带残差:输入依次过两个 CBS 后直接输出;带残差:两个 CBS 的输出再与输入相加(需 shortcut=True 且输入输出通道相等)——相当于给输入开了一条「直达通道」:即使中间的卷积没学到东西,原始信息也能原样送到后面,网络堆得再深也不容易训坏。不带残差(shortcut=False 或 c1≠c2): X ──► CBS(1,1) ──► CBS(3,1) ──► 输出 带残差(shortcut=True 且 c1==c2): X ──┬──► CBS(1,1) ──► CBS(3,1) ───┐ │ ⊕ ──► 输出 └─────────────────────────────┘ (恒等映射:输入直接跳到末尾,与卷积结果逐元素相加)条件(shortcut=True 且 c1==c2):为什么残差要求「输入输出通道相等」?因为图中的 ⊕ 是逐元素相加:两个张量必须形状完全相同(H、W、C 都一致),才能一位对一位地相加。输入通道是 c1、输出通道是 c2,若 c1 ≠ c2,通道数不同就没法相加(类比:两排格子数量不同,无法一一对应相加)。所以代码里用一个布尔量同时判断两件事:self.add = shortcut and c1 == c2 # └─ 想不想加 └─ 能不能加 # (人为开关) (形状硬约束)shortcut=True 只是"想不想加",c1 == c2 才是"能不能加"。形状对不上时框架会自动跳过残差、走不带残差的分支,不会报错——这也是为什么 yaml 里可以随意写True/False,不必担心维度冲突。Bottleneck 模块代码:class Bottleneck(nn.Module): """Standard bottleneck.""" def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): super().__init__() c_ = int(c2 * e) # hidden channels self.cv1 = Conv(c1, c_, k[0], 1) self.cv2 = Conv(c_, c2, k[1], 1, g=g) self.add = shortcut and c1 == c2 def forward(self, x): return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))6)C2F 模块C2F(Cross Stage Partial bottleneck with 2 convolutions,跨阶段部分双卷积瓶颈模块):一种特征提取模块,通过跨阶段连接与瓶颈结构,把特征拆成两半,一半不计算直接保留,另一半过一串 Bottleneck 且每一级输出都单独引出,最后全部拼接再用 1×1 卷积融合——用更低的计算量聚合"由浅到深"的多层级特征。C2f 名字:C= Cross Stage Partial(跨阶段局部:把特征拆成两半、跨阶段拼接,源自 CSP);2= 头部与尾部各一个 1×1 卷积;F= faster(比之前更快的实现);相关介绍:CSP(Cross Stage Partial Network,跨阶段局部网络)——源自 CSPNet,做法是把特征在通道维拆成两部分,一部分跳过当前阶段的计算直接连到后面,另一部分正常做卷积,最后再拼接;用更少的计算换得更丰富的梯度路径与特征复用。ELAN(Efficient Layer Aggregation Network,高效层聚合网络)——就是"高效地做层聚合"(Efficient + Layer Aggregation),做法是让输入分成并行支路、其中一条展开成卷积链,并把链上每一级的输出都收集起来拼接,从而高效聚合不同深度的特征。C2f 正是参考了 ELAN「多分支拼接、逐级收集特征」这一思想。C2f 为什么这么设计?网络越深越强,但直接堆卷积有两个麻烦:计算量暴涨、原始信息在层层传递中易丢失。拆一半不计算,就省了一半开销还保住原信息;另一半逐层加深并把每一级都收集起来,就同时拿到了浅层和深层特征。# C2F 模块 X │ CBS(1×1) ── 输出 H×W×C │ Split │ ┌────┴─────┐ │ │ H×W×C/2 H×W×C/2 │ │ ──────┐ ← 右半直接引出到 concat(同时进 Bottleneck) │ ▼ │ │ Bottleneck │ │ │ ──────┤ ← 每级输出都单独引出到 concat │ ▼ │ │ Bottleneck │ │ │ ──────┤ │ ▼ │ │ Bottleneck │ (图中画 3 个,实际 n 个) │ │ ──────┤ ▼ ▼ ▼ ┌────────────────────┐ │ concat │ ← 共 2+n 份:左半 + 右半 + n 级输出 └────────────────────┘ │ CBS(1×1) │ 输出要点:参与最后拼接的共 2+n(左半 + 右半 + n 级输出) 份——既有原样(浅),也有逐层加深的结果(深)。这也对应代码里cv2的输入通道为(2+n)×c。C2F 模块代码:class C2f(nn.Module): """Faster Implementation of CSP Bottleneck with 2 convolutions.""" def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): super().__init__() self.c = int(c2 * e) # hidden channels self.cv1 = Conv(c1, 2 * self.c, 1, 1) self.cv2 = Conv((2 + n) * self.c, c2, 1) self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) def forward(self, x): y = list(self.cv1(x).chunk(2, 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, dim=1)) def forward_split(self, x): y = list(self.cv1(x).split((self.c, self.c), 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, dim=1))7)SPPF 模块SPP(Spatial Pyramid Pooling,空间金字塔池化)——出自 SPPNet(2015),做法是对同一特征图并行用多个不同大小的池化核(如 5×5、9×9、13×13)分别池化,再把各结果拼接,从而在同一层内融合多种感受野,并能输出固定长度的特征。SPPF(Spatial Pyramid Pooling - Fast,快速空间金字塔池化)——SPP 的加速版:把"并行多个不同池化核"改成串联 3 个 5×5 最大池化并复用中间结果,效果与 SPP 等价但计算更快;再把每一级输出与原始特征 concat 后用 CBS 融合,作用是在同一层内融合多种感受野,增强对不同尺度目标的适应能力。X ──► CBS ──► Maxpool(5) ──► Maxpool(5) ──► Maxpool(5) │ │ │ │ └──────────┴─────────────┴─────────────┴──► concat ──► CBS ──► 输出class SPPF(nn.Module): """Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher.""" def __init__(self, c1, c2, k=5): super().__init__() c_ = c1 // 2 # hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c_ * 4, c2, 1, 1) self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2) def forward(self, x): y = [self.cv1(x)] y.extend(self.m(y[-1]) for _ in range(3)) return self.cv2(torch.cat(y, dim=1))3. YOLOv8参数1)YOLOv8 网络整体参数YOLOv8 整体由Backbone(主干)+ Head(含 Neck 与检测头)两部分组成(Neck 已合并进 head)。参数代码块(通道数按 yaml 标称值列出,输入 640×640):这里Conv相当于CBS模块;输入 640×640×3 ── Backbone ── Conv 3×3×64, s2 → 320×320×64 │ P1/2 Conv 3×3×128, s2 → 160×160×128 │ P2/4 C2f ×3 → 160×160×128 Conv 3×3×256, s2 → 80×80×256 │ P3/8 ← 接 Head C2f ×6 → 80×80×256 Conv 3×3×512, s2 → 40×40×512 │ P4/16 ← 接 Head C2f ×6 → 40×40×512 Conv 3×3×1024, s2 → 20×20×1024 │ P5/32 ← 接 Head C2f ×3 → 20×20×1024 SPPF 5×5 → 20×20×1024 │ 扩大感受野,尺寸不变 ── Neck + Head(yaml 中合并为 head 段)── Upsample ×2 → 40×40×1024 │ 自顶向下:深层放大 Concat ← P4(512) → 40×40×1536 │ 拼接 backbone 第 6 层 C2f ×3 → 40×40×512 # 12 Upsample ×2 → 80×80×512 Concat ← P3(256) → 80×80×768 │ 拼接 backbone 第 4 层 C2f ×3 → 80×80×256 # 15 P3/8 小目标 Conv 3×3×256, s2 → 40×40×256 │ 自底向上:浅层下采样 Concat ← #12(512) → 40×40×768 C2f ×3 → 40×40×512 # 18 P4/16 中目标 Conv 3×3×512, s2 → 20×20×512 Concat ← #9(1024) → 20×20×1536 C2f ×3 → 20×20×1024 # 21 P5/32 大目标 Detect [15,18,21] → 8400 个预测点 │ 80²+40²+20²=84002)模型尺度:n / s / m / l / x 是怎么来的YOLOv8 提供 5 种规格,由 yaml 里scales的三个缩放系数控制,共用同一份结构定义。(1)scales 三要素depth(深度):控制 C2F / Bottleneck 的堆叠次数;width(宽度):控制每层卷积的通道数;max_channels:通道数的上限封顶。型号depth(深度)width(宽度)max_channels(最大通道)n0.330.251024s0.330.501024m0.670.75768l1.001.00512x1.001.25512(2)max_channels:输出通道数限制YOLOv8l:最大输出通道数 = 宽度 1.00 × 最大通道 512 =512;YOLOv8x:最大输出通道数 = 宽度 1.25 × 最大通道 512 =640。所以使用 YOLOv8l 时,虽然 yaml 主干里写的是 1024,但会被 max_channels 限制到最大输出 512。(3)depth:控制 C2f 的重复次数C2f 实际重复次数 = 深度 depth × yaml 中的 repeats。以 yaml 中[ -1, 3, C2f, [1024, True] ]层(repeats = 3)为例:YOLOv8l:m = 1.00 × 3 = 3,即C2f_1_3;YOLOv8m:m = 0.67 × 3 ≈ 2,即C2f_1_2。要点:width 管"每层多宽"、depth 管"堆多少层",两者共同把一套 yaml 缩放出五档模型——即复合缩放(compound scaling)。3)计算尺寸和通道YOLOv8 每一层的输出有两个维度要算:空间尺寸(高×宽)和通道数。两者算法不同——尺寸由卷积公式算,通道数由 yaml 配置决定。(1) 空间尺寸公式:(总长 + 两边填充 − 卷积核占的第一刀) ÷ 步幅 + 起点那1次O H = H + 2 P − K H S + 1 , O W = W + 2 P − K W S + 1 OH = \frac{H + 2P - KH}{S} + 1,\qquad OW = \frac{W + 2P - KW}{S} + 1OH=SH+2P−KH+1,OW=SW+2P−KW+1符号含义备注H , W H,\ WH,W输入特征图的高、宽即上一层的输出尺寸O H , O W OH,\ OWOH,OW输出特征图的高、宽本层计算结果K H , K W KH,\ KWKH,KW卷积核的高、宽YOLOv8 常用 3×3,故二者均为 3P PPPadding(填充)YOLOv8 的 Conv 自动取P = ⌊ K / 2 ⌋ P=\lfloor K/2\rfloorP=⌊K/2⌋,3×3 即P = 1 P=1P=1S SSStride(步幅)1 = 尺寸不变,2 = 尺寸减半使用说明:结果向下取整:( H + 2 P − K H ) (H+2P-KH)(H+2P−KH)不能被S SS整除时向下取整——最后一步滑不完就丢弃,不补半格。auto-pad(自动填充):YOLOv8 的 Conv 默认自动补P = ⌊ K / 2 ⌋ P=\lfloor K/2\rfloorP=⌊K/2⌋,所以 3×3 卷积取S = 1 S=1S=1时尺寸不变、取S = 2 S=2S=2时尺寸减半。非卷积层的尺寸:Upsample 放大 2 倍(尺寸 ×2,通道不变);Concat 拼接(尺寸不变,通道相加);C2f / SPPF 不改尺寸。(2) 通道数怎么来的通道数不由卷积公式决定,而是由 yaml 配置直接指定,再按模型尺度缩放。通道数公式:实际通道 = min(标称通道, 最大通道上限) × 宽度系数C 实际 = min ( C 标称 , C m a x ) × w i d t h C_{实际} = \min(C_{标称},\ C_{max}) \times widthC实际=min(C标称,Cmax)×width符号含义备注C 标称 C_{标称}C标称yaml 中args的第一个值即该层「标称」输出通道,如[64, 3, 2]中的 64C m a x C_{max}Cmax该型号的通道上限由scales的max_channels给出,如 YOLOv8l 为 512w i d t h widthwidth该型号的宽度系数由scales的width给出,控制每层通道的缩放比例C 实际 C_{实际}C实际该层真正的输出通道数训练/推理时实际使用的通道以第 0 层[-1, 1, Conv, [64, 3, 2]]为例(C 标称 = 64 C_{标称}=64C标称=64):型号w i d t h widthwidthC m a x C_{max}Cmax计算C 实际 C_{实际}C实际YOLOv8n0.251024min(64, 1024) × 0.2516YOLOv8s0.501024min(64, 1024) × 0.5032YOLOv8l1.00512min(64, 512) × 1.0064YOLOv8x1.25512min(64, 512) × 1.2580说明和注意点:标称值 vs 实际值:yaml 里写的是标称值(骨架的「设计图」),实际通道要再乘 width。下面的验算表统一按标称值列出,方便理解结构骨架;具体型号的实际通道按上表换算。max_channels 的封顶作用:当C 标称 × w i d t h C_{标称} \times widthC标称×width超过C m a x C_{max}Cmax时,先取 min 再乘 width。例如 yaml 写 1024、YOLOv8l 的 max_channels=512,则实际通道 = min(1024, 512) × 1.00 =512,而非 1024。非卷积层的通道:Concat 是通道相加(如 1024+512=1536);Upsample / SPPF 通道不变;C2f 的输出通道 =args[0