基于 YOLOv8 的端到端一体式二次分类检测实现思路

目录

前言

[一、方案选型:两阶段 vs 一体式](#一、方案选型:两阶段 vs 一体式)

[1、方案一:两阶段(检测 + 分类)](#1、方案一:两阶段(检测 + 分类))

2、方案二:一体式二次分类

3、方案对比小结

二、类别体系设计

1、粗分类与细分类的定义

2、粗细分类映射关系

3、推荐设计原则

三、数据集制作

1、数据目录结构

2、标签文件格式

[3、data.yaml 配置](#3、data.yaml 配置)

[4、是否先用粗分类模型裁剪 ROI](#4、是否先用粗分类模型裁剪 ROI)

[四、YOLOv8 原始检测头简述](#四、YOLOv8 原始检测头简述)

1、检测头输出形式

2、改造目标

五、检测分类头改造

1、结构对比

2、输出通道变化

3、改造思路(伪代码)

4、源码改造步骤

5、两种检测头设计

6、配套修改清单

[六、Loss 函数改造](#六、Loss 函数改造)

[1、原始 Loss](#1、原始 Loss)

[2、改造后 Loss](#2、改造后 Loss)

3、单标签任务

4、多标签任务(进阶)

[5、Loss 权重建议](#5、Loss 权重建议)

七、数据读取改造

1、原始读取逻辑

2、改造后读取逻辑

3、数据流示意

八、训练流程

1、训练步骤

2、分阶段训练建议

3、评估指标

九、推理流程

1、推理步骤

2、粗细分类不一致的处理

十、常见问题与注意事项

1、数据与标注

2、模型与训练

3、部署与选型


前言

在目标检测任务中,YOLOv8 默认每个目标只输出一个类别。但实际业务里,往往需要两层语义:

|-----|------|------------------|
| 层级 | 含义 | 示例 |
| 粗分类 | 大类 | 水果、动物、车辆 |
| 细分类 | 具体类别 | 苹果、香蕉、猫、狗、小汽车、卡车 |

希望模型一次推理直接输出:检测框 + 粗分类 + 细分类 + 置信度。

YOLOv8的网络结构和详情见YOLOv8 及其目标检测/实例分割应用的完整知识体系-CSDN博客

一、方案选型:两阶段 vs 一体式

1、方案一:两阶段(检测 + 分类)

流程:YOLOv8 粗分类检测 → 裁剪 ROI → 细分类模型识别

|-------------|------------|
| 优点 | 缺点 |
| 实现简单,适合快速落地 | 两个模型,推理链路长 |
| 细分类可单独优化 | 非真正端到端 |
| 新手容易上手 | 部署和维护成本高 |

2、方案二:一体式二次分类

流程:一个 YOLOv8 模型同时输出:bbox + coarse_cls + fine_cls

|------------|------------------|
| 优点 | 缺点 |
| 一次前向传播,速度快 | 需改检测头、标签、Loss、推理 |
| 部署简单,模型统一 | 实现难度略高 |
| 端到端联合优化 | 需理解 YOLOv8 源码 |

3、方案对比小结

(1)两阶段:易实现,适合验证想法、快速上线

(2)一体式:难实现,适合追求速度与部署简洁的场景

二、类别体系设计

1、粗分类与细分类的定义

(1)粗分类(coarse):0: 水果 (fruit)、1: 动物 (animal)、2: 车辆 (vehicle)

(2)细分类(fine):0: 苹果 (apple)、1: 香蕉 (banana)、2: 猫 (cat)、3: 狗 (dog)、4: 小汽车 (car)、5: 卡车 (truck)。

2、粗细分类映射关系

建立 fine_id → coarse_id 映射表:

fine_to_coarse = {

0: 0, # 苹果 → 水果

1: 0, # 香蕉 → 水果

2: 1, # 猫 → 动物

3: 1, # 狗 → 动物

4: 2, # 小汽车 → 车辆

5: 2, # 卡车 → 车辆

}

3、推荐设计原则

原则:标签文件只存细分类 id,粗分类由映射自动生成。

  • 标注格式保持 YOLO 标准:fine_id x y w h
  • 训练时根据 fine_to_coarse 得到 coarse_id
  • 改动最小,与 YOLOv8 原生格式兼容

三、数据集制作

1、数据目录结构

沿用 YOLOv8 标准检测数据集结构:

dataset/

├── images/

│ ├── train/ # 训练集图片

│ ├── val/ # 验证集图片

│ └── test/ # 测试集图片

├── labels/

│ ├── train/ # 训练集标签(与图片同名 .txt)

│ ├── val/

│ └── test/

└── data.yaml # 数据集配置文件

2、标签文件格式

每张图对应一个同名 .txt 文件,每行一个目标:fine_id x_center y_center width height

  • 坐标均为归一化值(0~1)
  • fine_id 为细分类 id

示例(一张图中有苹果和猫):

0 0.512 0.433 0.210 0.180

2 0.720 0.500 0.160 0.220

3、data.yaml 配置

复制代码
path: dataset
train: images/train
val: images/val
test: images/test

# 细分类
nc_fine: 6
fine_names:
  0: apple
  1: banana
  2: cat
  3: dog
  4: car
  5: truck

# 粗分类
nc_coarse: 3
coarse_names:
  0: fruit
  1: animal
  2: vehicle

# 细分类 → 粗分类映射
fine_to_coarse:
  0: 0
  1: 0
  2: 1
  3: 1
  4: 2
  5: 2

说明nc_finenc_coarsefine_to_coarse 需在自定义数据读取和模型构建时使用,YOLOv8 原生不识别这些字段。

4、是否先用粗分类模型裁剪 ROI

推荐流程:粗分类 YOLO 检测原图 → 生成候选框 → 人工校验 → 补充细分类 → 转为 fine_id x y w h → 训练一体式模型

|-----------|----------------------------------|
| 用途 | 说明 |
| 辅助标注 | 粗分类 YOLO 检测 → 裁剪 ROI → 人工补充细分类标签 |
| 数据清洗 | 剔除误检、模糊、难例 |
| 训练独立细分类模型 | 作为两阶段方案的中间数据 |
| 替代检测框数据 | ❌ 不可行,端到端训练仍需原图检测框 |

辅助细分类标注或构建分类数据集:

复制代码
from ultralytics import YOLO
import cv2

model = YOLO('yolov8n.pt')  # 粗分类模型
results = model('val/image.jpg')
img = cv2.imread('val/image.jpg')

for r in results:
    for det in r.boxes:
        x1, y1, x2, y2 = map(int, det.xyxy[0])
        crop = img[y1:y2, x1:x2]
        save_path = f"cropped/{det.cls}.jpg"
        cv2.imwrite(save_path, crop)

注意: ROI 裁剪适合辅助标注、清洗和训练独立细分类器;端到端一体式训练仍需原图检测框标签,不能只用 ROI 分类数据替代。

四、YOLOv8 原始检测头简述

1、检测头输出形式

原始 YOLOv8 检测头可理解为:bbox 分支 + cls 分支。每个预测位置输出:box_distribution + class_logits。其中class_logits 通道数 = nc(类别数)、若 6 类检测:cls_logits 为 6 维。

2、改造目标

将单分类头改为双分类头:bbox 分支 + coarse_cls 分支 + fine_cls 分支

五、检测分类头改造

1、结构对比

原始 YOLOv8

Detect Head

├── bbox branch

└── cls branch

改造后

Detect Head

├── bbox branch

├── coarse cls branch

└── fine cls branch

2、输出通道变化

假设 nc_coarse = 3nc_fine = 6

|--------|------------------------------|
| 版本 | 输出组成 |
| 原始 | bbox + 6 维 cls |
| 改造后 | bbox + 3 维 coarse + 6 维 fine |

概念示意

原始: box, cls_0, cls_1, cls_2, cls_3, cls_4, cls_5

改造: box, coarse_0, coarse_1, coarse_2, fine_0, fine_1, fine_2, fine_3, fine_4, fine_5

3、改造思路(伪代码)

在 YOLOv8 检测头基础上,增加细分类预测分支,使一个模型同时预测每个框的粗类和细类。

改造思路

  • 保留原有边框回归分支 cv2

  • 新增粗分类分支 cv3_coarse,输出 nc_coarse 通道

  • 新增细分类分支 cv3_fine,输出 nc_fine 通道

    class DetectCoarseFine(nn.Module):
    def init(self, nc_coarse, nc_fine, ch):
    super().init()
    self.cv2 = nn.ModuleList([...]) # 边框回归分支
    # 粗分类分支
    self.cv3_coarse = nn.ModuleList(
    nn.Conv2d(c3, nc_coarse, 1) for c3 in ch_out
    )
    # 细分类分支
    self.cv3_fine = nn.ModuleList(
    nn.Conv2d(c3, nc_fine, 1) for c3 in ch_out
    )

    复制代码
      def forward(self, feats):
          boxes = [conv(f) for conv, f in zip(self.cv2, feats)]
          coarse_scores = [conv(f) for conv, f in zip(self.cv3_coarse, feats)]
          fine_scores = [conv(f) for conv, f in zip(self.cv3_fine, feats)]
          return {
              'boxes': boxes,
              'coarse': coarse_scores,
              'fine': fine_scores
          }

损失函数:L = L_bbox + α·L_c + β·L_f

  • L_bbox:边框回归损失(含 DFL)
  • L_c:粗分类损失
  • L_f:细分类损失
  • αβ:权重,常取 1.0,细分类更重要时可设 β > α

优点 : 单模型、推理快、端到端联合优化

缺点: 需改网络与标签;细类多或样本极不平衡时训练更难

4、源码改造步骤

  1. 定位 YOLOv8 的 Detect 模块
  2. 保留原有 box 分支
  3. 将原 cls 分支拆为 coarse_branchfine_branch
  4. coarse_branch 输出 nc_coarse 通道
  5. fine_branch 输出 nc_fine 通道
  6. forward 同时返回 box、coarse、fine
  7. Loss 中分别计算 coarse_loss、fine_loss
  8. Predict 中分别解析 coarse、fine 分数

5、两种检测头设计

|----------|-----------------------------|-----------|
| 方案 | 结构 | 适用 |
| 方案 A(推荐) | 全局 fine_cls + 全局 coarse_cls | 新手首选,实现简单 |
| 方案 B | 每个粗类下独立 fine 分支 | 层级更强,实现复杂 |

6、配套修改清单

仅改 Head 不够,需同步修改:

  1. 标签读取
  2. Loss 计算
  3. 模型输出解析
  4. NMS 后处理
  5. 验证指标
  6. 推理结果显示

六、Loss 函数改造

1、原始 Loss

loss = box_loss + cls_loss + dfl_loss

2、改造后 Loss

loss = box_loss + dfl_loss + coarse_cls_loss + fine_cls_loss

3、单标签任务

每个目标一个粗类、一个细类:

coarse_loss = classification_loss(coarse_pred, coarse_target)

fine_loss = classification_loss(fine_pred, fine_target)

total_loss = box_loss + dfl_loss + coarse_loss + fine_loss

4、多标签任务(进阶)

一个目标可属多个粗/细标签时,使用 multi-hot + BCEWithLogitsLoss:

coarse_target = 1, 0, 1

fine_target = 0, 1, 0, 1, 0, 0

5、Loss 权重建议

初期可设a = 0.5b = 1.0(细分类通常更难、更重要)

total_loss = box_loss + dfl_loss + a * coarse_loss + b * fine_loss

七、数据读取改造

1、原始读取逻辑

class_id x y w h

2、改造后读取逻辑

  1. 读取 fine_id 和 bbox
  2. 通过 fine_to_coarse[fine_id] 得到 coarse_id
  3. 送入 Loss 的目标:bbox_targetfine_targetcoarse_target

3、数据流示意

读取图片

→ 读取 labels/*.txt

→ 得到 fine_id、bbox

→ 根据 fine_to_coarse 得到 coarse_id

→ 数据增强(Mosaic、MixUp 等)

→ 送入模型

→ 计算 box_loss、coarse_loss、fine_loss

Mosaic、MixUp 等增强后,只要 bbox 和 fine_id 正确,coarse_id 可随时由 fine_id 重新生成。

八、训练流程

1、训练步骤

  1. 准备原图检测数据集,每框标注细分类 id

  2. 编写 fine_to_coarse 映射

  3. 修改 YOLOv8 Detect Head

  4. 修改数据读取,生成 coarse_id

  5. 修改 Loss,增加 coarse_loss、fine_loss

  6. 加载 YOLOv8 预训练权重

  7. 新增分类头随机初始化

  8. 开始训练

  9. 验证粗分类准确率、细分类准确率、检测 mAP

方案 A:单阶段一体化训练
复制代码
from ultralytics import YOLO

model = YOLO('yolov8n.yaml')  # 自定义含双分类头的 YAML
model.train(
    data='data.yaml',
    epochs=100,
    imgsz=640,
    batch=16,
    lr0=0.01
)

需修改 ultralytics/cfg/models/ 下模型 YAML,在 head 增加 coarse/fine 输出通道,并调整 Loss。

方案 B:两阶段训练

粗分类检测:

复制代码
yolo detect train data=coarse_data.yaml model=yolov8s.yaml epochs=50 imgsz=640

细分类器:

复制代码
yolo classify train data=fine_data.yaml model=yolov8s-cls.yaml epochs=50

2、分阶段训练建议

|--------|----------------------|
| 阶段 | 操作 |
| 第一阶段 | 冻结 backbone,只训练 head |
| 第二阶段 | 解冻全网络微调 |
| 第三阶段 | 根据验证集调整 loss 权重 |

3、评估指标

  • 检测 mAP(框定位)
  • 粗分类准确率
  • 细分类准确率
  • 粗细一致性(细分类映射出的粗类是否与预测粗类一致)

九、推理流程

1、推理步骤

输入图片

→ YOLOv8 前向推理

→ 解码 bbox

→ 得到 coarse logits、fine logits

→ 计算 coarse score、fine score

→ NMS

→ 输出 bbox、coarse label、fine label、score

2、粗细分类不一致的处理

可能出现:粗分类预测「动物」,细分类预测「苹果」。

方式一:以细分类为准

fine = apple → coarse = fine_to_coarseapple = fruit

方式二:以粗分类为约束(推荐),更符合「先粗后细」的二次分类逻辑。

coarse = animal → fine 仅在 cat、dog 中选择最高分

3、模型导出

ONNX:

复制代码
yolo export model=best.pt format=onnx imgsz=640 dynamic=True simplify=True

TorchScript:

复制代码
yolo export model=best.pt format=torchscript imgsz=640

TensorRT:

复制代码
yolo export model=best.pt format=engine imgsz=640 dynamic=True fp16=True

4、TensorRT 加速要点

  1. ONNX 转 TensorRT:trtexec 或 Python API
  2. FP16/INT8:FP16 常用;INT8 需校准集(约 100--200 张)
  3. 动态 Shape:设置 min/opt/maxShapes
  4. NMS:可在导出时 nms=False,部署端自行 NMS

十、常见问题与注意事项

1、数据与标注

  • 粗类与细类映射需稳定,避免一个细类对应多个粗类(除非明确做多标签)
  • 细分类样本尽量平衡,防止偏向大类
  • 端到端训练必须用原图检测框,不能只用 ROI 分类数据

2、模型与训练

  • 训练与推理时 fine_to_coarse 必须一致
  • 细分类难区分时,可提高输入分辨率
  • 细分类依赖局部细节时,可考虑 ROIAlign 或局部分支(实现更复杂)

3、部署与选型

  • 追求速度:一体式双头更优
  • 追求准确率:两阶段方案有时更好(细分类模型专注 ROI)
相关推荐
云空2 小时前
《洪水应急场景下基于 YOLOv8 的目标检测与实例分割模型训练方案》
人工智能·yolo·计算机视觉
2601_962851744 小时前
计算机毕业设计之基于YOLOV10的低光照目标检测算法研究与实现
大数据·算法·yolo·目标检测·信息可视化·cnn·课程设计
想会飞的蒲公英5 小时前
TF-IDF + 随机森林中文文本分类全链路实战:从训练脚本到 Flask API + Streamlit 前端
人工智能·pytorch·python·随机森林·分类·flask·tf-idf
FriendshipT6 小时前
Ultralytics:解读C3模块
人工智能·pytorch·python·深度学习·目标检测
懂AI的老郑8 小时前
YOLOv26联手大模型,开启智能视觉新时代
人工智能·yolo·目标跟踪
画中有画14 小时前
使用yolov8实现自动化脚本
运维·yolo·自动化
动物园猫1 天前
车位与车牌目标检测数据集:4类别 | 目标检测
人工智能·目标检测·计算机视觉
sali-tec1 天前
C# 基于OpenCv的视觉工作流-章93-YOLO手部姿态
图像处理·人工智能·数码相机·opencv·yolo·计算机视觉
FriendshipT1 天前
Ultralytics:解读PSABlock模块
人工智能·pytorch·python·深度学习·目标检测