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结构简图。你看一眼就能明白数据是怎么流的。
我的经验:在实际项目中,如果你只检测大目标(比如行人、车辆),可以只保留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量化
但如果你想要更精细的控制,我建议手动走一遍流程:
- 导出ONNX:同上一步,确保模型是静态形状。
- 用trtexec工具转换:
trtexec --onnx=yolov8s.onnx --saveEngine=yolov8s.engine --fp16 - 验证精度:用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是“专属跑车”。先拿钥匙开门,再换跑车上路。