2. YOLOv8模型训练与导出:从源码到ONNX的完整链路

YOLOv8出来也有一段时间了,我最早接触它是在一个工业质检项目里。当时客户要求模型必须能在Jetson上跑,而且精度不能掉太多。嗯,这就逼着我把训练到导出的整条链路都摸透了。今天咱们就从头捋一遍,从源码结构到ONNX导出,每一步我都会把项目里踩过的坑讲清楚。

本章核心目标:掌握YOLOv8的源码组织方式,学会用自定义数据集训练模型,理解评估指标的含义,最后导出并验证ONNX模型。

2.1 YOLOv8源码结构解析

Ultralytics的代码库设计得挺清爽的。我第一次打开的时候,说实话有点惊讶——比我想象中简洁得多。核心代码就几个文件夹:

目录/文件 作用 我常用的操作
ultralytics/ 核心代码包 改模型结构时主要动这里
ultralytics/nn/ 网络模块定义 调Backbone、Head就在这里
ultralytics/data/ 数据加载与增强 自定义数据集时改这里
ultralytics/engine/ 训练、验证、预测引擎 调超参、改训练逻辑
ultralytics/cfg/ 默认配置与模型yaml 模型结构yaml文件在这
ultralytics/models/ 各版本模型入口 v8、v9等不同版本

我个人习惯先看 ultralytics/nn/tasks.py,因为那里定义了整个模型的组装逻辑。你想想看,DetectionModel类里怎么把Backbone、Neck、Head串起来的,搞懂了这个,后面改模型结构就顺手了。

小技巧:YOLOv8的模型结构是用yaml文件定义的,在 ultralytics/cfg/models/v8/ 下。想改层数、通道数,直接改yaml就行,不用动Python代码。我经常这么干来快速实验不同规模的模型。

2.2 自定义数据集训练

做部署项目,十有八九要用自己的数据。YOLOv8支持的数据集格式是标准的COCO风格,说白了就是一张图片配一个同名的txt标签文件。

数据准备步骤我总结了一下:

  1. 收集图片:分辨率别太离谱,我建议统一到640x640附近
  2. 标注:用LabelImg或CVAT,导出YOLO格式
  3. 划分数据集:train/val/test,比例7:2:1比较稳
  4. 写data.yaml:告诉YOLO你的数据在哪、有几类

data.yaml长这样:

# dataset.yaml
path: /home/user/my_dataset  # 数据集根目录
train: images/train           # 训练集图片路径
val: images/val               # 验证集图片路径

nc: 3                         # 类别数
names: ['cat', 'dog', 'bird'] # 类别名称

训练命令很简单:

yolo train model=yolov8n.pt data=dataset.yaml epochs=100 imgsz=640 batch=16

这里有个坑——batch size。我曾经在2080Ti上直接用了默认的16,结果显存爆了。后来发现YOLOv8默认开启了自动混合精度(AMP),但如果你显存紧张,可以调小batch或者加上 --amp 参数手动控制。

注意:预训练权重 yolov8n.pt 会自动下载。如果你在内网环境,记得提前下载好放到项目目录下。我吃过这个亏,现场没网,干等了半小时才发现。

2.3 模型评估与指标解读

训练完第一件事就是看指标。YOLOv8训练完会自动跑验证,输出类似这样的结果:

指标 含义 我一般看多少算合格
mAP@0.5 IoU阈值0.5时的平均精度 工业场景0.85以上
mAP@0.5:0.95 IoU从0.5到0.95的平均精度 0.5以上算不错
Precision 预测为正样本中真正正确的比例 0.9左右
Recall 真实正样本中被正确找出的比例 0.85以上
F1-score Precision和Recall的调和平均 0.85+

为什么会关注mAP@0.5:0.95?因为部署到TensorRT后,精度往往会掉一点。如果你训练时mAP@0.5:0.95只有0.4,导出后可能掉到0.35,那就没法用了。我一般要求训练时这个指标至少0.5以上,才敢往下走部署流程。

经验之谈:别只看mAP。如果你的场景里漏检代价很高(比如安检),那Recall比Precision重要。反过来,如果误报很烦人(比如智能门铃),那就优先保Precision。调confidence阈值可以平衡这两者。

2.4 导出ONNX模型

模型训练好了,下一步就是导出。YOLOv8导出ONNX就一行命令:

yolo export model=runs/train/exp/weights/best.pt format=onnx imgsz=640

导出后你会得到一个 best.onnx 文件。这里有几个参数我经常调:

  • --opset:ONNX算子集版本,默认12,TensorRT一般支持到13或14,我习惯用13
  • --dynamic:动态batch,部署时很有用,但TensorRT里处理起来稍微麻烦点
  • --simplify:用onnx-simplifier简化模型,我每次都加

完整命令:

yolo export model=best.pt format=onnx imgsz=640 opset=13 simplify=True dynamic=True

避坑指南:我曾经导出时忘了加 --simplify,结果ONNX模型里有一堆Shape和Gather节点,TensorRT解析时直接报错。加了simplify之后,模型干净多了,转换也顺利了。

2.5 ONNX模型可视化与验证

导出ONNX之后,我习惯先看一眼模型长什么样。用Netron是最方便的——直接把.onnx文件拖进去就行。

可视化主要看三点:

  1. 输入输出节点:输入是不是 images,shape对不对(1x3x640x640)
  2. 输出结构:YOLOv8的输出是1x84x8400,其中84=4(bbox)+80(COCO类),8400是检测头网格点总数
  3. 有没有奇怪的op:比如NonMaxSuppression如果在ONNX里,TensorRT可能不支持

验证模型正确性也很简单,用ONNX Runtime跑一下:

import onnxruntime as ort
import numpy as np

session = ort.InferenceSession("best.onnx")
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name

# 构造随机输入
dummy_input = np.random.randn(1, 3, 640, 640).astype(np.float32)
outputs = session.run([output_name], {input_name: dummy_input})

print(outputs[0].shape)  # 应该输出 (1, 84, 8400)

如果shape对了,说明导出没问题。我还会拿一张真实图片跑一下,看看检测结果是不是合理——这一步虽然简单,但能避免很多低级错误。

小技巧:用 onnxruntime 验证时,记得把输入图片做同样的预处理(归一化、resize)。我见过有人直接拿原图塞进去,结果输出全是nan,还以为是模型坏了。

本章知识体系总览

下面这张图把本章的核心流程串起来了,从源码到ONNX,每一步的关键节点都在里面:

YOLOv8训练导出全流程 YOLOv8 训练 → 导出 ONNX 全流程 1. 源码结构 nn/ data/ engine/ cfg/ 2. 自定义数据集 标注 → data.yaml 3. 模型训练 yolo train ... 4. 评估 mAP指标 5. 导出ONNX yolo export ... 6. 可视化验证 Netron + ONNX Runtime 关键输出产物 best.pt(训练权重) → best.onnx(部署格式) → 验证通过的ONNX模型 训练阶段(PyTorch) 灵活、易调试 导出阶段(ONNX) 中间表示、跨框架 部署阶段(TensorRT) 高性能推理

从图上可以看得很清楚:整个流程分训练和导出两大阶段。训练阶段我们用PyTorch,灵活但速度一般;导出成ONNX后,它就变成了一个中立的中间表示,可以跨框架使用;最后到了TensorRT,才是真正的高性能部署。