4. YOLOv8 ONNX 导出实战

好,咱们进入实战环节。前面讲了那么多理论,说白了都是为了这一步——把训练好的 YOLOv8 模型转成 ONNX,为后面的 TensorRT 部署铺路。

我个人习惯把 ONNX 导出看作是模型部署的“中间人”。它不负责最终推理,但没它,TensorRT 就吃不下你的模型。嗯,这里要注意,导出过程其实挺容易踩坑的,尤其是你第一次搞的时候。

核心目标:将 PyTorch 训练的 YOLOv8 模型(检测 & 分割)导出为 ONNX 格式,并验证其正确性。

4.1 导出 YOLOv8 检测模型

YOLOv8 官方其实已经把导出接口封装得很简单了。你不需要自己写复杂的 torch.onnx.export 脚本。直接用 CLI 或者 Python API 就行。

方法一:命令行导出(推荐新手)

yolo export model=yolov8n.pt format=onnx imgsz=640

就这么一行。它会自动处理输入输出、动态轴这些。我在项目中第一次用的时候,说实话,有点不敢相信这么简单。

方法二:Python API 导出(更灵活)

from ultralytics import YOLO

# 加载模型
model = YOLO('yolov8n.pt')

# 导出 ONNX
model.export(format='onnx', imgsz=640, dynamic=True, simplify=True)

这里我解释几个关键参数:

参数 作用 我的建议
imgsz 输入图像尺寸 保持与训练时一致,通常是 640
dynamic 动态 batch 尺寸 部署时建议开启,方便批量推理
simplify ONNX 模型简化 强烈建议开启,能去掉冗余算子

小技巧:如果你不确定导出是否成功,可以在命令行加个 --verbose 参数,它会打印详细的导出日志。我曾经靠这个日志定位过一次算子不兼容的问题。

4.2 导出 YOLOv8 分割模型

分割模型的导出和检测模型几乎一样,只是模型文件换成了分割版的。YOLOv8 的分割模型名字里带个 -seg。

# 命令行
yolo export model=yolov8n-seg.pt format=onnx imgsz=640

# Python API
model = YOLO('yolov8n-seg.pt')
model.export(format='onnx', imgsz=640, dynamic=True, simplify=True)

你可能会问,检测和分割导出的 ONNX 有什么区别?

区别在输出层。检测模型输出的是边界框和类别概率。分割模型除了这些,还会输出一个原型掩码(prototype masks)和掩码系数。嗯,这个后面讲 TensorRT 后处理时会细说。

注意:分割模型的 ONNX 文件通常比检测模型大 2-3 倍。因为多了掩码分支。导出时如果内存不够,可以试试 imgsz=480 或者关闭 dynamic。

4.3 验证导出的 ONNX 模型

导出完了别急着高兴。一定要验证一下。我见过太多人导出后直接拿去部署,结果推理结果全是乱的。

验证步骤:

  1. 检查模型结构:用 onnx.shape_inference 或 Netron 可视化工具看一眼输入输出。
  2. 数值一致性验证:用同一张图在 PyTorch 和 ONNX 上跑,对比输出差异。

下面这个脚本是我常用的验证方法:

import onnx
import onnxruntime as ort
import numpy as np
import torch
from ultralytics import YOLO

# 1. 检查 ONNX 模型结构
onnx_model = onnx.load('yolov8n.onnx')
onnx.checker.check_model(onnx_model)
print("ONNX 模型结构检查通过")

# 2. 数值一致性验证
# PyTorch 推理
pt_model = YOLO('yolov8n.pt')
img = np.random.randn(1, 3, 640, 640).astype(np.float32)
pt_results = pt_model(img, verbose=False)

# ONNX Runtime 推理
ort_session = ort.InferenceSession('yolov8n.onnx')
ort_inputs = {ort_session.get_inputs()[0].name: img}
ort_results = ort_session.run(None, ort_inputs)

# 对比输出(这里只对比第一个输出的最大差异)
diff = np.max(np.abs(pt_results[0].cpu().numpy() - ort_results[0]))
print(f"PyTorch vs ONNX 最大差异: {diff:.6f}")
if diff < 1e-3:
    print("✅ 验证通过!")
else:
    print("❌ 差异过大,请检查导出参数")

关键指标:PyTorch 和 ONNX 的输出差异通常应该在 1e-3 到 1e-5 之间。如果差异大于 1e-2,说明导出有问题。

4.4 常见导出错误与解决

这部分是我最想跟你分享的。我在项目中踩过的坑,你大概率也会遇到。

错误 1:算子不支持

报错信息类似 Unsupported operator: torchvision::nms。YOLOv8 的后处理里用了非标准算子。

解决:导出时加上 opset=12 或者 opset=13,老版本的算子集更稳定。

model.export(format='onnx', opset=12)

错误 2:动态轴设置失败

报错 RuntimeError: Only batches with 1 image are supported。这通常是因为你开启了 dynamic=True,但模型里某些层不支持动态 batch。

解决:先关掉 dynamic,导出静态 batch 的模型。或者手动指定动态轴:

model.export(format='onnx', dynamic={'input': {0: 'batch'}})

错误 3:内存溢出

尤其是导出分割模型时,显存不够直接崩。

解决:降低 imgsz,或者用 CPU 导出(虽然慢,但稳)。

model.export(format='onnx', imgsz=480, device='cpu')

错误 4:简化失败

onnxsim 有时候会报错,说模型里有不支持的节点。

解决:先不简化,导出后再手动用 onnxsim 工具简化。

python -m onnxsim yolov8n.onnx yolov8n_simplified.onnx

我曾经踩过的坑:有一次我导出的 ONNX 模型在验证时差异很小,但部署到 TensorRT 后精度暴跌。查了两天才发现,是因为导出时忘了设置 half=False,模型自动用了 FP16,导致精度损失。所以,如果你要部署 FP16,导出时一定要用 FP32,让 TensorRT 自己去转精度。

4.5 本章知识结构图

下面这张图总结了 YOLOv8 ONNX 导出的完整流程和关键节点。你可以把它当作一个检查清单。

YOLOv8 ONNX 导出流程 PyTorch 模型 导出方式 CLI: yolo export ... | Python API: model.export() 检测模型 (Detect) 分割模型 (Segment) 关键参数 imgsz=640 | dynamic=True | simplify=True 验证步骤 结构检查 | 数值一致性对比 ONNX 模型 💡 导出后务必验证数值一致性,差异应 < 1e-3。常见错误:算子不支持、动态轴失败、内存溢出。

4.6 本章小结

说白了,YOLOv8 的 ONNX 导出并不复杂。核心就三步:选对模型、设对参数、做对验证。你只要记住我上面说的那几个坑,基本不会出大问题。

我个人建议,每次导出后都跑一遍数值验证脚本。别嫌麻烦。这个习惯能帮你省下后面 TensorRT 部署时的大量调试时间。