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标签文件。
数据准备步骤我总结了一下:
- 收集图片:分辨率别太离谱,我建议统一到640x640附近
- 标注:用LabelImg或CVAT,导出YOLO格式
- 划分数据集:train/val/test,比例7:2:1比较稳
- 写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文件拖进去就行。
可视化主要看三点:
- 输入输出节点:输入是不是
images,shape对不对(1x3x640x640) - 输出结构:YOLOv8的输出是1x84x8400,其中84=4(bbox)+80(COCO类),8400是检测头网格点总数
- 有没有奇怪的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,每一步的关键节点都在里面:
从图上可以看得很清楚:整个流程分训练和导出两大阶段。训练阶段我们用PyTorch,灵活但速度一般;导出成ONNX后,它就变成了一个中立的中间表示,可以跨框架使用;最后到了TensorRT,才是真正的高性能部署。