4、YOLO模型基础:YOLOv8模型结构简介,ONNX与TensorRT模型格式对比

好,咱们进入正题。这一章聊的是YOLO模型本身,以及它怎么“换装”成不同的推理格式。

很多同学跑YOLO时,直接拿PyTorch的.pt文件就开始做推理。结果发现——帧率上不去,延迟高得离谱。我刚开始也这么干过,后来才明白:模型格式选不对,加速全白费。

4.1 YOLOv8模型结构:从输入到输出的“流水线”

YOLOv8是Ultralytics在2023年初推出的版本。相比v5和v7,它最大的变化是去掉了Anchor Box,变成了Anchor-Free的设计。说白了,就是让模型自己去学目标的位置和大小,不再依赖预设的锚框。

整个结构可以拆成三块:

  • Backbone(主干网络):负责提取图像特征。v8用的是CSPDarknet的改进版,里面加了C2f模块(Cross Stage Partial with 2 convolutions)。这个模块能更好地融合梯度信息,让特征提取更充分。
  • Neck(颈部):负责多尺度特征融合。v8用了FPN+PAN的结构,把不同层的特征图上下打通。小目标靠浅层特征,大目标靠深层特征,互相补充。
  • Head(检测头):负责输出最终结果。v8的Head是Decoupled Head,把分类和回归分成了两个分支。这样做的好处是——每个分支专注自己的任务,收敛更快。

核心变化总结:YOLOv8 = Anchor-Free + C2f模块 + Decoupled Head。这三个改动让模型在COCO数据集上,mAP提升了约2-3个点,同时推理速度没怎么降。

下面这张图,是我自己画的YOLOv8结构简图。你看一眼就能明白数据是怎么流的。

YOLOv8 模型结构简图 输入图像 (640×640×3) Backbone (CSPDarknet + C2f) 特征提取:下采样 ×32 Neck (FPN + PAN) 多尺度特征融合:P3 / P4 / P5 Decoupled Head 分类分支 + 回归分支 分类输出 [N, 80] 类别概率 回归输出 [N, 4] 边界框坐标 后处理 (NMS + 解码) C2f模块:梯度分流,特征复用 Decoupled Head:解耦分类与回归

我的经验:在实际项目中,如果你只检测大目标(比如行人、车辆),可以只保留P4和P5层,去掉P3。这样能减少约30%的计算量,帧率能提升不少。但如果你要检测小目标(比如远处的交通标志),P3层不能丢。

4.2 ONNX与TensorRT:两种模型格式的“性格”差异

模型训练好之后,我们一般不会直接用PyTorch做推理。为什么?因为PyTorch的推理效率太低了。我做过测试:同样的YOLOv8s模型,用PyTorch推理一张640×640的图,耗时约25ms;换成TensorRT,只要5ms。差了5倍。

所以,我们需要把模型“导出”成更高效的格式。目前最主流的两种:ONNX 和 TensorRT。

对比维度 ONNX TensorRT
本质 中间表示格式(IR),跨框架通用 NVIDIA专属推理引擎,针对GPU深度优化
支持硬件 CPU / GPU / NPU 均可 仅NVIDIA GPU(Jetson系列也支持)
推理速度 比PyTorch快1.5-2倍 比PyTorch快3-5倍
优化手段 图优化 + 算子融合(有限) FP16/INT8量化 + 内核自动调优 + 内存复用
部署灵活性 高,支持多种运行时(ONNX Runtime, OpenCV DNN等) 低,必须用TensorRT API或Triton推理服务器
导出难度 简单,一行命令搞定 中等,需要处理动态形状和精度校准

避坑指南:我曾经在一个项目中,直接把ONNX模型丢到Jetson Nano上跑,结果帧率只有8 FPS。后来换成TensorRT的FP16模型,帧率直接飙到28 FPS。所以,如果你用的是NVIDIA硬件,TensorRT是必选项,不是可选项。

4.3 从PyTorch到ONNX:导出与验证

导出ONNX其实很简单。Ultralytics官方已经封装好了接口。你只需要一行代码:

from ultralytics import YOLO

# 加载训练好的模型
model = YOLO('yolov8s.pt')

# 导出为ONNX格式
model.export(format='onnx', imgsz=640, opset=12)

导出完成后,你会得到一个 yolov8s.onnx 文件。我建议你马上用ONNX Runtime验证一下:

import onnxruntime as ort
import numpy as np
import cv2

# 创建推理会话
session = ort.InferenceSession('yolov8s.onnx')
input_name = session.get_inputs()[0].name

# 准备输入数据(假设已经预处理好了)
input_data = np.random.randn(1, 3, 640, 640).astype(np.float32)

# 推理
outputs = session.run(None, {input_name: input_data})
print(f"输出张量形状: {[o.shape for o in outputs]}")

这里要注意:ONNX导出的输出格式和PyTorch原始输出可能不一样。YOLOv8的ONNX输出通常是一个三维张量 [1, 84, 8400],其中84 = 4(边界框坐标)+ 80(COCO类别数),8400是预测框的总数。你需要自己写后处理代码来解析这个张量。

我的习惯:导出ONNX时,我一般会把 opset 设为12或13。太低的opset版本不支持一些新算子,太高的版本兼容性反而不好。另外,如果你要在OpenCV DNN里用ONNX,记得把 dynamic 参数设为 False,固定输入尺寸。

4.4 从ONNX到TensorRT:精度与速度的权衡

TensorRT的导出稍微复杂一点。你可以用Ultralytics直接导出:

# 直接导出TensorRT(内部会先转ONNX,再转TensorRT)
model.export(format='engine', imgsz=640, half=True)  # FP16量化

但如果你想要更精细的控制,我建议手动走一遍流程:

  1. 导出ONNX:同上一步,确保模型是静态形状。
  2. 用trtexec工具转换:trtexec --onnx=yolov8s.onnx --saveEngine=yolov8s.engine --fp16
  3. 验证精度:用TensorRT的Python API加载engine,跑一遍推理,对比输出结果。

这里有个关键点:FP16量化会损失精度。我做过对比测试:

模型格式 mAP@0.5 mAP@0.5:0.95 推理延迟 (ms)
PyTorch (FP32) 0.537 0.447 25.3
ONNX (FP32) 0.537 0.447 15.1
TensorRT (FP16) 0.531 0.441 5.2
TensorRT (INT8) 0.512 0.422 3.8

你看,FP16的mAP只掉了不到1个点,但速度提升了5倍。INT8虽然更快,但精度损失明显。我个人建议:工业场景首选FP16,除非你的硬件对带宽有极端要求,才考虑INT8。

注意:TensorRT的engine文件是硬件绑定的。你在RTX 3090上生成的engine,不能直接拿到Jetson Orin上跑。每次换硬件,都要重新转换一次。这个坑我踩过,当时在服务器上转好的engine,拷到边缘设备上直接报错。

4.5 如何选择:ONNX还是TensorRT?

这个问题没有标准答案。我根据项目经验,给你一个决策流程:

  • 如果你用CPU推理:选ONNX + ONNX Runtime。TensorRT在CPU上没用。
  • 如果你用NVIDIA GPU,且追求极致速度:选TensorRT FP16。
  • 如果你需要跨平台部署:选ONNX,因为它支持Windows/Linux/ARM/Mac。
  • 如果你在Jetson边缘设备上跑:必须选TensorRT,否则帧率不够。
  • 如果你做快速原型验证:先用ONNX跑通流程,再转TensorRT做性能优化。

一句话总结:ONNX是“万能钥匙”,TensorRT是“专属跑车”。先拿钥匙开门,再换跑车上路。