1. YOLOv5模型ONNX导出与TensorRT部署基础

任务目标

本案例完成YOLOv5s模型从PyTorch权重到ONNX再到TensorRT引擎的完整转换链路,并基于转换后的TensorRT引擎执行一次基础推理,验证检测结果与原始PyTorch模型输出的一致性。

环境与依赖

  • Ubuntu 20.04+ / Windows 10+,Python 3.8+
  • PyTorch 1.12+,CUDA 11.6+,cuDNN 8.4+
  • TensorRT 8.4+(需与CUDA版本匹配)
  • YOLOv5官方仓库(v6.0+):git clone https://github.com/ultralytics/yolov5
  • Python依赖:pip install onnx onnxruntime-gpu pycuda

实现步骤

1. 导出ONNX模型

在YOLOv5仓库根目录执行以下命令,将官方预训练权重yolov5s.pt导出为ONNX格式。导出时固定输入尺寸为640×640,并启用--simplify以优化计算图。

python export.py --weights yolov5s.pt --include onnx --img-size 640 --batch-size 1 --simplify

执行成功后生成yolov5s.onnx文件。可通过onnxruntime快速验证ONNX模型可正常加载并执行推理。

2. 构建TensorRT引擎

使用TensorRT的trtexec工具将ONNX模型转换为FP16精度的TensorRT引擎。FP16在保持精度的同时显著提升推理速度,适合自动驾驶场景的实时性要求。

trtexec --onnx=yolov5s.onnx --saveEngine=yolov5s_fp16.trt --fp16 --workspace=2048

转换完成后生成yolov5s_fp16.trt引擎文件。若需INT8量化,需额外提供校准数据集,本案例不展开。

3. TensorRT基础推理代码

以下代码实现TensorRT引擎的加载与推理。核心逻辑包括:创建运行时与引擎上下文、分配GPU显存、执行前向推理、将输出拷贝回CPU。输入为预处理后的640×640 RGB图像(归一化至[0,1]),输出为YOLOv5标准的3个检测头原始张量(形状分别为[1, 3, 80, 80, 85]、[1, 3, 40, 40, 85]、[1, 3, 20, 20, 85])。

import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit

class TRTInference:
    def __init__(self, engine_path):
        # 加载引擎
        with open(engine_path, 'rb') as f:
            engine_data = f.read()
        runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING))
        self.engine = runtime.deserialize_cuda_engine(engine_data)
        self.context = self.engine.create_execution_context()
        
        # 分配输入输出缓冲区
        self.inputs, self.outputs, self.bindings = [], [], []
        for binding in self.engine:
            shape = self.engine.get_binding_shape(binding)
            size = trt.volume(shape)
            dtype = trt.nptype(self.engine.get_binding_dtype(binding))
            host_mem = cuda.pagelocked_empty(size, dtype)
            device_mem = cuda.mem_alloc(host_mem.nbytes)
            self.bindings.append(int(device_mem))
            if self.engine.binding_is_input(binding):
                self.inputs.append({'name': binding, 'host': host_mem, 'device': device_mem, 'shape': shape})
            else:
                self.outputs.append({'name': binding, 'host': host_mem, 'device': device_mem, 'shape': shape})
    
    def infer(self, input_image):
        # 输入预处理:HWC->CHW,归一化
        img = input_image.astype(np.float32) / 255.0
        img = np.transpose(img, (2, 0, 1))[None]
        np.copyto(self.inputs[0]['host'], img.ravel())
        
        # 执行推理
        cuda.memcpy_htod(self.inputs[0]['device'], self.inputs[0]['host'])
        self.context.execute_v2(bindings=self.bindings)
        for out in self.outputs:
            cuda.memcpy_dtoh(out['host'], out['device'])
        
        # 返回三个检测头的输出
        return [out['host'].reshape(out['shape']) for out in self.outputs]

# 使用示例
engine_path = 'yolov5s_fp16.trt'
trt_infer = TRTInference(engine_path)

# 加载一张测试图像(需自行准备,此处仅演示接口)
# image = cv2.imread('test.jpg')
# image = cv2.resize(image, (640, 640))
# outputs = trt_infer.infer(image)
# print(f"检测头输出形状: {[o.shape for o in outputs]}")

4. 验证检测结果

验证分为两个层面:

  1. 输出一致性:使用同一张测试图像分别通过PyTorch模型(yolov5s.pt)和TensorRT引擎推理,对比输出张量的余弦相似度。余弦相似度大于0.99即认为转换精度损失可接受。
  2. 端到端检测:对TensorRT输出执行YOLOv5标准的NMS后处理(需复用YOLOv5仓库utils/general.py中的non_max_suppression函数),在图像上绘制检测框,确认目标被正确框出。

通过条件:TensorRT推理输出的检测框坐标与类别置信度与PyTorch模型结果基本一致(IoU > 0.9),且单帧推理延迟满足实时性要求(FP16下通常< 10ms,具体取决于GPU型号)。

常见问题

  • 导出ONNX时维度错误:确保--batch-size 1,动态batch需额外配置--dynamic参数。
  • TensorRT转换失败:检查ONNX是否包含不支持的操作(如部分上采样层),可尝试升级TensorRT版本或使用--fp16而非INT8。
  • 推理结果全零:检查输入图像是否已归一化至[0,1]且通道顺序为RGB(非BGR)。