深度学习之目标检测yolo算法Ⅴ-YOLOv8

前言:

根据之前我关于YOLOv5的文章总结,本章我开始对YOLOv8进行原理->实践,从整体流程到单步流程。从前处理,网络结构到后处理正样本的筛选,函数的损失等一步一步来剖析目标检测算法YOLOv8的神秘面纱。

⭐深度学习之目标检测yolo算法Ⅲ-YOLOv5(1)-CSDN博客

⭐深度学习之目标检测yolo算法Ⅳ-YOLOv5(2)_yolo-parseq-CSDN博客

YOLOv8的创新

YOLOv5的创新有三个核心点

  • 引入C3模块

  • Mosaic数据增强

  • 自适应锚框

YOLOv8是基于YOLOv5的升级版本,属于集大成者

  • 用C2f替代C3模块(更高效的特征融合)
  • Anchor-Free + Anchor-Point
  • 解耦头(分类与回归分开)
  • TAL(任务对齐学习)让分类和回归的损失更匹配,提升精度

YOLOv8的模型结构

重要模块:

网络架构

⭐Backbone:特征提取的 "核心引擎"

1.c2f:特征分流+多径聚合。

源码路径(ultralytics/ultralytics/nn/modules/block.py)中的cf2模块

2.SPPF模块:空间金字塔池化,不同尺度特征融合,解决不同大小目标检测。

  • C2f : "分成两部分 + shortcut 拼接" 是核心逻辑,补充:C2f 是对 YOLOv5 C3 模块的轻量化改进,用Split(拆分)+ Concat(拼接) 替代了 C3 的 Bottleneck 串联结构,残差分支是多个Bottleneck2d(轻量残差块),相比 C3,在参数量 / 计算量增加有限的前提下,残差连接更密集,特征复用性更强,这也是其特征提取能力提升的关键。
  • SPPF :SPPF 是SPP 的快速优化版 (Spatial Pyramid Pooling Fast),用连续的 5×5 最大池化替代了 SPP 的 5×5、9×9、13×13 池化,实现了和 SPP 完全一致的多尺度特征融合效果,但计算效率大幅提升,这是 YOLOv5/8 保留 SPPF 而非原始 SPP 的原因。

Neck:特征融合的 "中转站"

1.FPN: 自顶向下,将高层的语义特征传递到底层。

2.PAN: 自底向上,把底层的几何特征传递到高层。

FPN+PAN =》同时让模型具备"识别目标"+"精准定位"的能力。

YOLOv8 去掉了 YOLOv5 中 Neck 部分上采样后的卷积连接层,进一步精简了参数量

Head:检测结果的 "输出端"(YOLOv8 最大创新之一)

**1.解耦头输出:**将分类任务和回归任务分为两个独立的分支,避免任务之间的相互干扰。

**2.Anchor-Free:**不预设锚框,直接中心点坐标 + [l,t,r,b] + 置信度,适配不同尺寸/比例目标

**3.TAL:**任务对齐学习,让分类分数与回归精度对齐。提升最终的mAP。

损失函数

YOLOv8 的损失由三部分组成:

  • 分类损失:BCEWithLogitsLoss(二分类交叉熵,适配多标签检测);
  • 回归损失:CIoULoss(考虑框的重叠面积、中心点距离、宽高比例,比 IoU Loss 更精准);
  • TAL(任务对齐学习):无单独损失,通过动态调整分类/回归损失的权重,让两者优化目标对齐,提升检测精度

回归损失并非纯 CIoULoss ,YOLOv8 采用的是 CIoULoss + DFL(分布焦点损失) 组合!

  • CIoULoss:如你所说,考虑了框的重叠面积、中心点距离、宽高比例,解决了纯 IoULoss 在框无重叠时梯度消失的问题;
  • DFL(Distribution Focal Loss):把边界框的坐标回归转化为 "分布预测",用离散的概率分布拟合坐标的连续值,解决了传统回归对边界框精细定位的不足,让框的回归精度更高。

对于YOLOv8的源码分析,其主要结构与V5类似,我们首先从推理阶段入手,准备好任何一个best.pt和一张需要处理的图片(640x640),我们就能够模拟完成推理阶段的代码实现。准确的讲我们以检测流程(推理阶段)进行深入剖析,之后再分析网络结构和损失函数等核心步骤。

YOLOv8推理阶段源码解析

推理阶段(Detect):(Head + 解码) +后处理

1.YOLOv8 Detect Head+解码

问题产生:

在我的初始认知里,神经网络的输入和输出都应该为Tensor才正常,但是在实际操作过程中,我发现我Detect阶段中的Head部分代码中将1张640x640的图片输入后经过前向传播(forward)输出的结果居然不是Tensor,而是一个Tuple。

拆解 tuple 的两个部分:

  • 第一部分:1x84x8400 的 tensor(concat 后的输出):
    • 维度解释:(batch×(num_classes+4)×h×w),COCO 是 80 类,这里要注意,**(我们将位置预测的 80 类概率的最大值作为该框的置信度,所以这里没有添加置信度,仅有84个值)**YOLOv8 的 8400 是 640 输入时:80×80=6400,40×40=1600,20×20=400,总和 8400)。
    • 作用:推理时直接对这个 tensor 做解码(将特征值转成检测框坐标、置信度、类别),方便后处理(NMS)。
  • 第二部分:包含三个头的 list:
    • 三个list的含义
    • 第一个 Tensor(1,144,80,80):对应大尺度特征图(80×80) → 负责检测小目标
    • 第二个 Tensor(1,144,40,40):对应中尺度特征图(40×40) → 负责检测中目标
    • 第三个 Tensor(1,144,20,20):对应小尺度特征图(20×20) → 负责检测大目标

我在head.py阶段代码中的forward的源码中发现了这一判断,如果是训练阶段进行前向传播则直接返回3个list用于计算损失及其后续内容。

由于此时是detect阶段,继续向下的代码实现了进一步的拼接和分割实现

⭐⭐⭐

回归的box_Tensor = (1,64,8400):一张图片有8400个bbox,每个bbox用64个数表示其位置,其本质是 「概率分布特征」,64个特征经过分布解码(加权求和)后得到ltrb,代表bbox相对于网格中心点(v8的锚点)的偏移。

分类的cls_Tensor = (1,80,8400):一张图片有8400个预测位置,每个预测位置用80个数表示分类原始特征值,经过Sigmoid激活后转化为(0-1)的类别概率,每个值代表对应类别的概率。

解码分两步骤:

1.概率分布->ltrb距离,16个数->1个数

2.ltrb->xywh/xyxy

回归的dbox经过解码,归一化处理后从64维变成4维,同时对分类的cls_tensor进行Sigmoid处理后两者合并得到(1x84x8400)

⭐基于Detect阶段中得到的1x84x8400的Tensor结果,此时仅完成了Head的前向传播+内部解码。

84 的本质:4(解码后的 xyxy 绝对坐标) + 80(Sigmoid 激活后的类别概率)

⭐⭐⭐yolov8和yolov5的区别(2点)

  • 分类方式不同
    • YOLOv5:用 Softmax 做互斥多分类(一个框仅属于一类)
    • YOLOv8:用 Sigmoid 做多标签非互斥分类(一个框可属于多类,更贴合实际场景)
  • 回归方式不同
    • YOLOv5:直接回归 x/y/w/h 偏移量(直接回归)
    • YOLOv8:用 DFL(分布焦点损失)建模坐标的概率分布,加权求和得到坐标(分布回归

回归分支解码(框架内部,基于Head原始144维输出):

Head原始输出(1×144×8400)的前64维是「DFL回归特征值」(4个坐标×reg_max=16)

解码逻辑:

  1. 生成 DFL 的候选坐标值(默认 reg_max=16,对应 0~15 的整数);

  2. 对每个坐标的16个bin特征值做 softmax,得到每个候选值的权重;

  3. 加权求和得到「偏移量」(dx, dy, dw, dh);

  4. 结合网格坐标+步长,计算实际框的 x/y/w/h:

x = (grid_x + sigmoid(dx)) × 步长

y = (grid_y + sigmoid(dy)) × 步长

w = exp(dw) × 步长

h = exp(dh) × 步长

注:代码中grid_xy已提前乘以步长,因此x = grid_xy[:,0] + sigmoid(dx),无需重复乘步长。 5. 格式转换:xywh → LTRB → xyxy(最终84维的前4维)。

源码步骤实现:

同时注意下述代码中的init中这16个值的权重是不更新梯度的,其值保持为[0-15]。

python 复制代码
self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)

分类分支解码(框架内部):

Head原始输出(1×144×8400)的后80维是「分类原始特征值」

解码逻辑:

  1. 对80维特征值做Sigmoid归一化,得到0-1的类别概率(非互斥);

  2. 取最大值作为该框的置信度,最大值对应的索引为类别ID。

python 复制代码
import torch

# ===================== 1. 模拟Head原始输出(1×144×8400) =====================
batch_size = 1
num_classes = 80  # COCO数据集
reg_max = 16      # YOLOv8默认reg_max
# Head原始输出:64维回归(4×16) + 80维分类 = 144维
pred_raw = torch.randn(batch_size, reg_max*4 + num_classes, 8400)  # 1×144×8400

# ===================== 2. 生成8400个位置的网格坐标+步长 =====================
strides = torch.tensor([8, 16, 32])  # 80×80/40×40/20×20对应的步长
grid_sizes = [80, 40, 20]            # 3个尺度的网格尺寸

grids = []
for i, (size, stride) in enumerate(zip(grid_sizes, strides)):
    grid_y, grid_x = torch.meshgrid(torch.arange(size), torch.arange(size), indexing='ij')
    grid = torch.stack([grid_x, grid_y], dim=-1).reshape(-1, 2) * stride  # 乘以步长得到原图坐标
    grids.append(grid)
grid_xy = torch.cat(grids, dim=0)  # 8400×2 (x, y)

# ===================== 3. 回归分支解码(DFL) =====================
# 提取64维回归特征(4×16×8400),reshape为(4, 16, 8400)
reg_pred = pred_raw[:, :reg_max*4, :].reshape(batch_size, 4, reg_max, -1)  # 1×4×16×8400

# 步骤3.1:生成DFL候选值(0~15)
range_tensor = torch.arange(reg_max, device=reg_pred.device)  # [0,1,...,15]

# 步骤3.2:对每个坐标的16个bin做softmax,得到权重
reg_softmax = torch.softmax(reg_pred, dim=2)  # 1×4×16×8400

# 步骤3.3:加权求和得到偏移量dx, dy, dw, dh(4×8400)
dxdy_dwdh = torch.matmul(reg_softmax, range_tensor).squeeze(0)  # 4×8400

# 步骤3.4:计算实际坐标(x/y/w/h)
# 按尺度分配步长(8400个框对应8/16/32步长)
stride_tensor = strides.repeat_interleave([80*80, 40*40, 20*20])  # 8400×1

x = grid_xy[:, 0] + torch.sigmoid(dxdy_dwdh[0])  # grid_xy已乘步长,无需重复乘
y = grid_xy[:, 1] + torch.sigmoid(dxdy_dwdh[1])
w = torch.exp(dxdy_dwdh[2]) * stride_tensor
h = torch.exp(dxdy_dwdh[3]) * stride_tensor

# 转成xyxy格式(最终84维的前4维)
x1 = x - w / 2
y1 = y - h / 2
x2 = x + w / 2
y2 = y + h / 2
boxes_xyxy = torch.stack([x1, y1, x2, y2], dim=-1)  # 8400×4(xyxy绝对坐标)

# ===================== 4. 分类分支解码 =====================
# 提取80维分类特征,做Sigmoid得到类别概率
cls_pred = pred_raw[:, reg_max*4:, :]  # 1×80×8400
cls_probs = torch.sigmoid(cls_pred).squeeze(0).T  # 8400×80(0~1的类别概率)

# 计算置信度和类别ID
confidence = cls_probs.max(dim=1)[0]  # 8400×1(每个框的最高置信度)
class_ids = cls_probs.max(dim=1)[1]   # 8400×1(每个框的类别ID)

# ===================== 5. 拼接成解码后的84维Tensor(1×84×8400) =====================
# 前4维:xyxy坐标;后80维:类别概率
pred_decoded = torch.cat([boxes_xyxy.T, cls_probs.T], dim=0).unsqueeze(0)  # 1×84×8400

# ===================== 6. 解码结果输出 =====================
print("解码后的84维Tensor形状:", pred_decoded.shape)  # torch.Size([1, 84, 8400])
print("解码后的检测框坐标(前5个):\n", boxes_xyxy[:5])
print("解码后的类别概率(第一个框前5类):\n", cls_probs[0, :5])
print("解码后的置信度(前5个):\n", confidence[:5])
print("解码后的类别ID(前5个):\n", class_ids[:5])

# 注:官方输出的1×84×8400就是上述pred_decoded,无需再做任何解码,直接用于后处理(NMS)。

核心原理:

**训练阶段:**Detect 头输出3 个尺度的 list(1,144,80,80/40,40/20,20) → 分尺度计算总损失【分类损失(Focal Loss) + 回归损失(DFL损失 + CIoU损失)】;

**推理阶段:**Detect头输出3个尺度的list(1,144,80,80/40,40/20,20)→ 框架内部拼接展平(1×144×8400)→ 内部解码(DFL转坐标 + Sigmoid转类别概率)→ 官方默认输出1×84×8400 Tensor → 取类别概率最大值作为置信度 → 后处理(NMS);

注:DFL 解码直接得到 dx/dy/dw/dh 偏移量 → 先转 xywh(中心坐标 + 宽高) → 再转 ltrb(可选) → 最终转 xyxy;官方输出的 84 维前 4 维已是 xyxy 绝对坐标。


2.YOLOv8 Detect 后处理

基于上述解码完成后得到 8400 个框(包含大量低置信度 / 重复框),下一步就是非极大值抑制(NMS)

python 复制代码
# 后处理核心目标:过滤低置信度框 + 去除重复框(NMS)
conf_thres = 0.25  # 低置信度阈值(低于此值的框直接丢弃)
iou_thres = 0.45   # NMS的IoU阈值(高于此值的重复框仅保留最高分)

# 1. 过滤低置信度框(关键:boxes需用解码后的xyxy格式)
mask = confidence > conf_thres
boxes_filtered = boxes_xyxy[mask]  # 用boxes_xyxy(而非boxes),对应前面解码的xyxy坐标
cls_probs_filtered = cls_probs[mask]
confidence_filtered = confidence[mask]
class_ids_filtered = class_ids[mask]

# 2. 执行NMS(注:YOLOv8实际用batched_nms,支持多类别;以下是简化版单类别NMS)
# 关键要求:boxes_filtered必须是xyxy格式,且与confidence_filtered数据类型一致(如float32)
boxes_filtered = boxes_filtered.float()  # 补充:确保数据类型正确
confidence_filtered = confidence_filtered.float()
indices = torch.ops.torchvision.nms(boxes_filtered, confidence_filtered, iou_thres)

# 3. 最终有效框(仅保留NMS筛选后的结果)
final_boxes = boxes_filtered[indices]
final_conf = confidence_filtered[indices]
final_cls = class_ids_filtered[indices]

print("\nNMS后剩余的有效框数量:", len(final_boxes))
print("最终有效框坐标:\n", final_boxes)

3.推理阶段的总结+知识补充

阶段 输入 输出 关键说明
Detect Head(推理) 3 个尺度特征图 1×84×8400(4+80 维) 框架自动完成 "前向传播 + 拼接展平 + 解码",输出为解码后的结果:4 个 xyxy 绝对坐标 + 80 个 Sigmoid 类别概率;144 维原始特征仅出现在训练阶段,对用户不可见
后处理(NMS) 1×84×8400(xyxy + 概率) 少量有效框(如 10×4+10×1) 取 80 类概率的最大值作为置信度,过滤低置信度框,再做 NMS 去重,保留高置信度、非重复框

重点知识补充:IOU系列和DFL和Focal Loss的区别和联系

  • 三者均为目标检测训练阶段的损失组件,推理阶段均不参与,最终模型总损失 = **BCEWithLogitsLoss(YOLOv8默认分类损失,等价于Sigmoid+BCE)** + 加权系数 ×(CIoU + DFL)(回归)
  • IoU 系列与 DFL 为回归分支强协同关系,无互斥性,二者结合实现 "精准找坐标(DFL)+ 标准画框(IoU)" 的 1+1>2 效果。
  • Focal Loss 与 IoU/DFL 为任务分工关系,分别负责 "认对目标" 和 "框准目标",共同支撑模型检测性能。

训练阶段的 DFL(DFL Loss)+ CIoU,和推理阶段解码中的 DFL,虽然都基于「DFL 离散分布」的核心思想,但作用、场景、目标完全不同。

维度 训练阶段(DFL Loss + CIoU) 推理阶段(解码中的 DFL)
核心作用 约束模型学习,让模型输出的离散分布逼近真实值 还原连续坐标,把模型输出的特征值转成实际偏移量
场景 反向传播,计算损失值(标量),更新模型参数 正向推理,纯计算(无参数更新),生成检测框
输入 模型输出的 **144 维中 64 维 DFL 特征**(4×16) + 真实框坐标 模型输出的 **144 维中 64 维 DFL 特征**(4×16)(框架内部处理,用户不可见)
输出 损失值(标量,用于反向传播) 连续偏移量 dx/dy/dw/dh(用于计算实际框坐标)
与 CIoU 的关系 CIoU 是主回归损失,DFL Loss 是辅助优化 无 CIoU 参与,纯 DFL 解码逻辑

本质差异

  • 训练 DFL:是「损失函数」,作用是 "约束模型学习"(反向传播);
  • 推理 DFL:是「解码逻辑」,作用是 "还原连续坐标"(正向计算);

小小的注意点:

我们团队在实验过程中发现有两种不同的情况,在阈值都一样的情况下输出的结果有些许不同,经过分析后发现不同的数据前处理过程的算法也会造成不同的影响。我们最后分析得到最有可能的情况是resize使用的算法不同(Pil算法和Cv2算法)导致其结果不同。


YOLOv8前向传播源码解析

总共两个逻辑

1.YOLOv8.yaml->self.model

2.For self.model

YOLOv8的前向传播算子的yolov8.yaml文件,每一层的算子意义是:

[输入来源索引, 重复次数, 算子类别, 模块参数]

  • 第一个值(如 -164 :这不是 "输入通道",而是输入来源的层索引-1 表示直接来自上一层;6 表示来自配置文件中第 6 层的输出;[15,18,21] 表示来自第 15、18、21 层的多输入。

  • ⭐第二个值(如 13 :这是模块的重复次数(循环次数) 比如 3 代表这个模块会被连续执行 3 次。(注意:不同的算子其第二个值含义不一样,普通算子确实是模块执行次数,而像c2f等特殊算子:parse_model 会只创建 1 个 C2f 模块,但把第二个值3传入 C2f 的初始化参数,作为其内部 bottleneck 的数量。)

  • 第三个值(如 nn.UpsampleC2fConcatDetect :这是算子 / 模块的类别

  • 第四个值(如 [None, 2, "nearest"][512][1][nc] :这是模块的参数列表 ,不是统一的 "输出通道"。

    • C2fConv 这类卷积模块,参数里的数字是输出通道数(如 [512] 代表输出通道为 512)。
    • nn.Upsample,参数是上采样倍数和插值方式(如 [None, 2, "nearest"] 代表 2 倍上采样,用 nearest 插值)。
    • Concat,参数是拼接维度(如 [1] 代表在通道维度拼接)。
    • Detect,参数是类别数 [nc]

1.YOLOv8.yaml->self.model

该阶段属于模型构建阶段:

  • 加载 YOLOv8.yaml 配置文件 → 转成字典格式
  • 调用parse_model函数 → 把 yaml 字典解析成nn.Module列表
  • 赋值给self.model → 完成 "yaml→self.model"

2.For self.model

前向传播阶段 :你图中的这段代码(_predict_once/_forward_once方法),是遍历已经构建好的self.model模块列表,逐层执行前向计算

小小注意点:如果不指定相应的参数则其默认执行n模型

YOLOv8正样本选择+损失函数解析

我们的核心目的是训练神经网络找到所需的预测框 Pre_box,好的网络效果是 Pre_box 和图像中真实的 gt_box 一致或误差极小,此时网络收敛、效果最优。

YOLOv8 的网络输出为 1×84×8400,其输出并非简单的 Pre_box 坐标,而是包含坐标预测、类别预测、置信度预测的融合特征,只有框回归、分类、置信度三类损失均收敛,Pre_box 才是 "又准又对" 的。因此正样本的选择尤为重要,YOLOv5 与 YOLOv8 的正样本选择及损失函数设计差异显著,核心总结如下:

1. 正样本的选择差异

知识回顾:YOLOv5(Anchor-Based + 静态匹配)

YOLOv5 的前向传播结果是 1×85×25200:

  • 特征层:3 个尺度(80×80、40×40、20×20);
  • 锚框设计:每个特征层的每个网格点预设 3 个锚框(anchor);
  • 总预测框数:80×80×3 + 40×40×3 + 20×20×3 = 25200;
  • 维度含义:85 = 4(坐标)+ 1(独立置信度)+ 80(COCO 类别)。

YOLOv5 的正样本选择(静态锚框匹配)

  1. 先将 gt_box 的宽高与预设锚框的宽高计算比值,匹配 "最接近" 的锚框,锁定锚框类型;
  2. 找到 gt_box 中心点落在的网格点,该网格点的匹配锚框为核心正样本
  3. 为增加正样本比例加快收敛,将该网格点上下左右相邻网格点(通常 3 个)的同类型锚框也作为正样本;
  4. 最终每个 gt_box 匹配多个预测框 作为正样本,核心是人为增加样本数量

YOLOv8(Anchor-Free + 动态匹配,核心:TAL 任务对齐分配器)

核心修改:Anchor-free + 动态匹配,输出维度与正样本选择逻辑完全重构。

(1)YOLOv8 的输出维度(1×84×8400)

  • 特征层:与 v5 一致,3 个尺度(80×80、40×40、20×20);
  • 无锚框设计:每个网格点仅预测 1 个框,无需预设锚框;
  • 总预测框数:80×80×1 + 40×40×1 + 20×20×1 = 8400;
  • 维度含义:84 = 4(坐标)+ 80(COCO 类别),置信度融合到分类分支,无独立维度
  • 补充:框回归的 Box 分支实际输出64 通道(4 坐标维度 ×16 个离散锚点),为 DFL 离散分布预测做准备。

(2)YOLOv8 的正样本选择(粗筛→精选→确认,三层动态匹配)

并非直接全局 IoU 匹配,而是以 Task-Aligned Assigner(TAL)为核心的最优匹配,步骤如下:

步骤 1:初选(粗筛候选框,减少计算量)

  • 目标:快速排除无关预测框,仅保留 "有潜力" 的候选;
  • 操作:筛选出中心点落在 gt_box 内部的 Pre_box 作为候选样本;
  • 逻辑:中心不在 gt_box 内的 Pre_box 大概率是背景,无需参与后续精细匹配。

步骤 2:复选(TAL 任务对齐,精选 top-K 最优候选)

核心筛选环节,结合分类 + 定位双维度评分,避免单维度最优的无效样本:

  1. 计算对齐分数:对齐分数分类置信度(默认 α=1,β=6);
    • 分类置信度:Pre_box 预测对应 gt 类别的概率;
    • IoU:Pre_box 与 gt_box 的重叠度;
  2. 筛选 top-K 候选:对每个 gt_box,按对齐分数排序,取前 K 个(默认 K=10)作为 "准正样本";
  3. 冲突处理:若 1 个 Pre_box 被多个 gt_box 选中,仅保留与该 Pre_box IoU 最大的匹配关系。

步骤 3:全局 IoU 双向匹配(最终确认正样本)

基于复选后的候选框,通过 IoU 矩阵完成精准匹配:

  1. 构建 IoU 矩阵:行 = gt_box,列 = 候选 Pre_box,值 = 两者 IoU
  2. 单向匹配:
    • 按行找最大:每个 gt_box 选该行 IoU 最大的 Pre_box(gt 的 "最优匹配框");
    • 按列找最大:每个 Pre_box 选该列 IoU 最大的 gt_box(Pre_box 的 "最优匹配 gt");
  3. 双向匹配:仅当 "gt 选的 Pre_box,恰好也选了该 gt" 时,该 Pre_box 才定为该 gt 的正样本;
  4. 兜底规则:若某个 gt 的最大 IoU 低于阈值(如 0.2),直接选取 gt 中心点所在特征层网格对应的 Pre_box 作为正样本,避免无匹配。

2. 损失函数(YOLOv8:多任务加权求和,与正样本强绑定)

YOLOv8 的损失函数为多任务损失的加权求和,核心目标是让预测框坐标贴近 gt、类别预测准确、能精准区分前景 / 背景,最终通过最小化总损失推动网络收敛。

总损失公式

TotalLoss = λbox​ × (CIoULoss+DFLLoss) + λcls​×ClassLoss + λobj​×ObjLoss

  • 核心规则:仅正样本参与 BoxLoss 和 ClassLoss 计算,所有 8400 个预测框参与 ObjLoss 计算

(1)BoxLoss(框回归损失):CIoU Loss + DFL Loss(二者权重均为 1)

YOLOv8 的框回归为离散分布预测,DFL 与 CIoU 协同工作,过程监督 + 结果监督双重保障:

  • CIoU Loss结果监督,衡量预测框与 gt_box 的重叠度、中心点距离、长宽比偏差,让预测框整体贴合 gt;
  • DFL Loss(分布焦点损失)过程监督,监督框坐标的离散分布预测,让模型更精准回归框的边界坐标;

YOLOv8 框回归完整流程(DFL+CIoU):

  1. 网络输出 Box 分支特征(64 通道):4 坐标维度 ×16 个离散锚点;
  2. 对 Box 分支做 softmax,得到 16 个锚点的概率分布(和为 1);
  3. 计算分布的数学期望,得到最终的预测坐标(x1/y1/x2/y2);
  4. 计算 DFL Loss:用真实坐标构建二值离散标签,监督预测分布与真实标签的偏差;
  5. 计算 CIoU Loss:用步骤 3 的最终预测坐标,与 gt_box 计算 CIoU Loss;
  6. 求和得到 BoxLoss:BoxLoss=CIoULoss+DFLLoss。

(2)ClassLoss(分类损失):BCEWithLogitsLoss

  • 核心原理:采用带 sigmoid 激活的二元交叉熵损失,适配多标签分类逻辑,数值稳定性更高;
  • 计算对象:仅正样本,预测类别概率(未激活)vs 真实类别 one-hot 编码;
  • 公式:BCELoss=−[y×log(p)+(1−y)×log(1−p)](y=1 为真实类别,p 为预测概率)。

(3)ObjLoss(置信度损失):BCEWithLogitsLoss

  • 核心原理:YOLOv8 无独立置信度分支,取分类分支所有类别概率的最大值作为置信度预测值,本质是区分 "有物体" 和 "无物体";
  • 标签设计:正样本置信度标签 = 1,负样本 = 0;
  • 计算对象:所有 8400 个预测框(正 + 负);
  • 核心目标:让正样本置信度趋近 1,负样本趋近 0,避免模型预测大量无效空框。

知识补充:

1.YOLOv5 vs YOLOv8 核心差异

对比维度 YOLOv5(Anchor-Based) YOLOv8(Anchor-Free)
匹配规则 静态:锚框匹配 + 相邻网格点(人为增样本) 动态:初选 + TAL 复选 + 全局 IoU 双向匹配(数据选最优)
正样本数量 每个 gt 匹配多个正样本(3 个左右) 每个 gt 匹配 1 个最优正样本(少数兜底 1 个)
输出维度 1×85×25200(3 特征层 × 每网格 3 锚框) 1×84×8400(3 特征层 × 每网格 1 框)
BoxLoss 设计 单一 CIoU/MSE Loss(直接预测连续坐标) CIoU Loss+DFL Loss(离散分布预测坐标)
置信度分支 独立置信度分支(占 1 维) 融合到分类分支(取类别概率最大值)
核心目标 靠增加样本数量加快收敛 靠选 "分类 + 定位双优" 样本提升精度
核心优势 训练速度快,对固定宽高比目标适配性好 框回归精度高,对宽高比 / 尺度多变目标适配性好

适用场景选择

  • 优先选 YOLOv5 :目标宽高比相对固定、对训练速度要求高、场景简单(如固定视角的目标检测);
  • 优先选 YOLOv8 :目标宽高比多变、尺度差异大(如小目标 + 大目标混合)、对检测精度要求高、场景复杂(如户外通用目标检测)。

2.自定义数据增强

在yolo算法训练的过程中,数据增强是最常用的手段之一。现在补充一下自定义数据增强的方法。

1.新增增强类:ultralytics/data/augment.py 中实现你的自定义增强逻辑

2.新增超参数:hyp.yaml 中添加增强的开关 / 权重参数

3.集成到增强的流水线:build_transforms 中初始化并添加你的增强

案例:

1.新增增强类:ultralytics/data/augment.py 中实现你的自定义增强逻辑

python 复制代码
# ------------------- 自定义增强类 -------------------
class RandomBrightnessContrast(BaseTransform):
    """随机调整图像的亮度和对比度(自定义增强)"""
    def __init__(self, prob=0.5, brightness_delta=0.2, contrast_delta=0.2):
        super().__init__()
        self.prob = prob  # 增强触发概率
        self.brightness_delta = brightness_delta  # 亮度变化范围
        self.contrast_delta = contrast_delta  # 对比度变化范围

    def apply_image(self, img):
        """对单张图像应用增强"""
        if random.random() < self.prob:
            # 调整亮度
            brightness = 1 + random.uniform(-self.brightness_delta, self.brightness_delta)
            img = cv2.convertScaleAbs(img, alpha=brightness, beta=0)
            # 调整对比度
            contrast = 1 + random.uniform(-self.contrast_delta, self.contrast_delta)
            img = cv2.convertScaleAbs(img, alpha=contrast, beta=0)
        return img

    def __repr__(self):
        return f'{self.__class__.__name__}(prob={self.prob}, brightness_delta={self.brightness_delta}, contrast_delta={self.contrast_delta})'

2.新增超参数:hyp.yaml 中添加增强的开关 / 权重参数

打开训练用的 hyp.yaml(通常在 ultralytics/cfg/default.yaml 或你的自定义配置文件),在 augmentation 部分添加。

python 复制代码
# 新增自定义增强的超参数
brightness_contrast: 0.5  # 触发概率(0.0~1.0)
brightness_delta: 0.2     # 亮度变化幅度
contrast_delta: 0.2       # 对比度变化幅度

3.集成到增强的流水线:build_transforms 中初始化并添加你的增强

python 复制代码
        # ------------------- 新增:添加自定义增强 -------------------
        from .augment import RandomBrightnessContrast
        if hyp.get('brightness_contrast', 0.0) > 0.0:
            transforms.append(
                RandomBrightnessContrast(
                    prob=hyp.brightness_contrast,
                    brightness_delta=hyp.get('brightness_delta', 0.2),
                    contrast_delta=hyp.get('contrast_delta', 0.2)
                )
            )
        # ----------------------------------------------------------

总结:

本章笔者主要总结了我学习yolov8时的一些知识笔记,和学习源码的一些小细节,以及一些小技巧,希望给大家带来帮助。

相关推荐
智驱力人工智能2 小时前
货车违规变道检测 高速公路安全治理的工程实践 货车变道检测 高速公路货车违规变道抓拍系统 城市快速路货车压实线识别方案
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算
2501_941652772 小时前
改进YOLOv5-BiFPN-SDI实现牙齿龋齿检测与分类_深度学习_计算机视觉_原创
深度学习·yolo·分类
zy_destiny3 小时前
【工业场景】用YOLOv26实现8种道路隐患检测
人工智能·深度学习·算法·yolo·机器学习·计算机视觉·目标跟踪
铁手飞鹰3 小时前
[深度学习]Vision Transformer
人工智能·pytorch·python·深度学习·transformer
weixin_395448913 小时前
average_weights.py
pytorch·python·深度学习
香芋Yu3 小时前
【深度学习教程——02_优化与正则(Optimization)】09_为什么Dropout能防止过拟合?正则化的本质
人工智能·深度学习
皮肤科大白3 小时前
超轻量SAM模型部署:ONNX量化与Transformer剪枝全攻略
深度学习·transformer
Loo国昌3 小时前
【大模型应用开发】第三阶段:深度解析检索增强生成(RAG)原理
人工智能·后端·深度学习·自然语言处理·transformer
yuanyuan2o24 小时前
【深度学习】AlexNet
人工智能·深度学习