简介:本资源面向计算机视觉方向的开发者与研究人员,提供YOLO11目标检测从训练到TensorRT加速部署的完整实战方案,帮助读者打通模型训练、格式转换与推理优化的全链路,适合具备一定深度学习基础、希望掌握工程化落地技巧的中高级学习者。压缩包共67个文件,约2.82MB,以Python脚本、Shell执行脚本、模型配置yaml、示例图片及Git版本文件为主,涵盖训练、预测、ONNX导出、TensorRT引擎构建与推理等模块,并附README流程说明。已有1286人学习下载。读者可依据教程逐步完成coco_minitrain_10k数据集上的模型训练,借助一键脚本完成ONNX导出与onnx2trt转换,最终获得TensorRT优化后的加速推理效果,同时理解层融合、混合精度等优化思路,为自动驾驶、视频监控等实时视觉应用积累可复用的工程经验。
1. YOLO11 训练到 TensorRT 部署:一条能跑通的工程链路长什么样
很多人第一次接触 YOLO11,是在一台装了 4060 的 Windows 机器上,跟着教程把yolo detect train跑通,看到 loss 曲线往下掉,觉得目标检测不过如此。然后到了部署环节,把best.pt丢给推理代码,发现单帧要 40 毫秒,产线要求 10 毫秒以内,于是开始翻 TensorRT 的文档,翻到一半被 ONNX 导出、动态 shape、INT8 校准、插件版本这一堆东西劝退。这个标题讲的,就是把「训练」和「TensorRT 部署」这两段接起来的那条工程链路——不是教你调包,而是让你知道每一步为什么这么做、参数怎么定、哪里会翻车。
适合两类人看:一类是已经会用 Ultralytics 跑训练,但部署还停留在 PyTorch 推理阶段的算法工程师;另一类是拿到一个检测需求,需要从数据集准备一路做到 TensorRT 引擎落地、并且要能复现的工程同学。整条链路的核心节点是:数据 → 训练 → 验证 → 导出 ONNX → 构建 TensorRT 引擎 → 推理封装 → 精度与速度对齐。下面按这个顺序拆开讲,重点放在那些教程里通常一笔带过、但实际会卡住你的地方。
2. 训练前的数据与配置:别让标注问题拖到部署才暴露
2.1 数据集目录结构与 YOLO 格式的硬性约束
YOLO11 沿用 Ultralytics 的数据组织方式,目录结构必须是固定的三段式,images和labels平行,且文件名(不含扩展名)严格一一对应。常见做法是这样:
dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── 0002.jpg │ └── val/ │ ├── 0101.jpg │ └── 0102.jpg ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── 0002.txt │ └── val/ │ ├── 0101.txt │ └── 0102.txt └── data.yamldata.yaml里三个字段必须写对:path指向数据集根目录,train和val写相对路径,names用字典或列表都可以,但索引必须从 0 开始连续。这里有个血泪经验:如果names写成{1: cat, 2: dog},训练不会报错,但类别索引会整体偏移,验证时 mAP 看着正常,部署后所有类别标签全错一位。标注文件每行是class_id cx cy w h,坐标必须是归一化到 0~1 的浮点数,不是像素值。用 LabelImg 或 X-AnyLabeling 导出时选 YOLO 格式,导出后随手抽三个文件核对一下数值范围,能省掉后面几小时的排查。
2.2 训练参数怎么定:从 yolov8 迁移过来的配置要改哪里
YOLO11 的官方权重分 n/s/m/l/x 五档,参数量和精度递增。选型上,如果部署端是 Jetson 或 RK3588 这类边缘设备,n 或 s 是现实选择;如果是服务器 T4/A10,m 起步。训练命令本身不复杂:
yolo detect train \ model=yolo11s.pt \ data=dataset/data.yaml \ epochs=200 \ imgsz=640 \ batch=16 \ device=0 \ workers=8 \ patience=50 \ cache=ram \ pretrained=True \ optimizer=auto \ cos_lr=True \ close_mosaic=10逐项说:imgsz=640是训练分辨率,也是后面导出 ONNX 和 TensorRT 的输入尺寸基准,训练和部署必须一致,否则精度会掉。batch=16在 8G 显存上跑 s 模型基本够用,显存不够就降到 8 并开amp=True。patience=50是早停,200 epoch 里如果 50 轮没提升就停,避免过拟合。cache=ram把图片缓存进内存,数据集小于 20G 时强烈建议开,能省掉大量 IO 等待。close_mosaic=10是最后 10 轮关闭 mosaic 增强,让模型在接近真实分布的图像上收敛,这个参数对最终精度影响比想象中大,别省。
从 yolov8 迁移过来的配置,主要改两处:一是模型权重换成yolo11*.pt,二是 YOLO11 的 C3k2 模块对学习率更敏感,optimizer=auto会自动选,但如果你手动设了lr0,建议从 0.01 降到 0.005 起步。训练过程中重点看metrics/mAP50-95和val/box_loss两条曲线,如果 box_loss 震荡剧烈,先把lr0砍半。
2.3 训练完先别急着导出:验证集上的三个必查项
训练结束,runs/detect/train/weights/下会有best.pt和last.pt。导出前必须做三件事。第一,用yolo detect val model=best.pt data=dataset/data.yaml跑一遍验证,确认 mAP 和训练日志里最后一轮的数字对得上,对不上说明验证集路径或类别配置有问题。第二,抽 20 张验证集图片做可视化推理,肉眼看框的位置和类别,模型在验证集上的数字好看但框飘的情况并不少见,尤其是小目标。第三,检查best.pt的输入尺寸,用model.model.args打印出来,确认imgsz是 640 而不是训练中途被覆盖成别的值。这三步做完,再进入导出环节,否则后面 TensorRT 精度对不上时,你分不清是训练的问题还是部署的问题。
3. 从 pt 到 ONNX:导出参数里藏着部署精度的第一道坎
3.1 导出 ONNX 的最小命令与 opset 选择
Ultralytics 把导出封装得很简单,一行命令:
yolo export \ model=runs/detect/train/weights/best.pt \ format=onnx \ imgsz=640 \ opset=17 \ simplify=True \ dynamic=False \ half=Falseopset=17是当前 TensorRT 8.x/10.x 都稳定支持的版本,别贪新用 19,部分 TensorRT 版本对高 opset 的算子映射不完整,会在构建引擎时报Unsupported ONNX op。simplify=True会调用 onnx-simplifier 做常量折叠和冗余节点消除,能减少后续 TensorRT 解析的负担,但注意它偶尔会把某些动态分支简化掉,如果简化后推理结果异常,把这项关掉重导。dynamic=False表示固定 batch=1、固定输入尺寸,这是部署场景最常用的配置,动态 shape 虽然灵活但会限制 TensorRT 的优化空间,除非你的业务真的需要变长输入,否则别开。half=False在导出阶段保持 FP32,量化留到 TensorRT 构建时做,这样精度损失可控。
3.2 导出后的 ONNX 自检:用 onnxruntime 对齐 PyTorch 输出
导出完不能直接拿去构建引擎,先用 onnxruntime 跑一遍,和 PyTorch 的输出做数值对齐。这一步是排查精度问题的分水岭:
import numpy as np import onnxruntime as ort import torch from ultralytics import YOLO # PyTorch 推理 model = YOLO("runs/detect/train/weights/best.pt") img = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8) pt_out = model.predict(img, imgsz=640, verbose=False)[0].boxes.data.cpu().numpy() # ONNX Runtime 推理 sess = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) input_name = sess.get_inputs()[0].name blob = img.astype(np.float32) / 255.0 blob = blob.transpose(2, 0, 1)[None, ...] # 1x3x640x640 onnx_out = sess.run(None, {input_name: blob})[0] print("PyTorch boxes:", pt_out.shape) print("ONNX output shape:", onnx_out.shape) print("Max abs diff:", np.abs(pt_out[:5, :4] - onnx_out[0, :5, :4]).max())逻辑说明:PyTorch 侧用 Ultralytics 的predict拿到解码后的框,ONNX 侧拿到的是原始输出张量,形状通常是[1, 84, 8400](84 = 4 框坐标 + 80 类,8400 是候选框数)。两者不能直接逐元素比,但可以比前几个框的坐标量级。如果 max abs diff 超过 1e-2,说明导出过程有问题,优先检查opset和simplify。参数上,providers先用 CPU 跑通,确认数值没问题再换 CUDA,避免把 GPU 环境问题混进来。
3.3 动态 shape 与静态 shape 的取舍
静态 shape 的 ONNX 在 TensorRT 里能获得最好的优化,kernel 可以针对固定尺寸做特化。动态 shape 的好处是同一引擎支持多种输入分辨率,但代价是 TensorRT 需要为每个维度范围保留优化空间,实际推理速度通常比静态慢 10%~20%。我一般会这样做:如果业务输入尺寸固定(比如摄像头固定 1080p 裁切成 640),就用静态;如果输入来自不同设备、尺寸不一,先用静态跑通,再评估是否值得为动态 shape 牺牲速度。动态 shape 导出时把dynamic=True打开,并在 TensorRT 构建时用--minShapes、--optShapes、--maxShapes指定范围,这三个值设不好会直接导致构建失败或推理越界。
4. TensorRT 引擎构建:版本、精度与显存的三角博弈
4.1 TensorRT 安装与版本匹配的坑
TensorRT 的版本必须和 CUDA、cuDNN、显卡驱动四者对齐。常见组合是 CUDA 11.8 + TensorRT 8.6,或 CUDA 12.x + TensorRT 10.x。这里有个高频翻车点:TensorRT 10.x 对 GTX 10 系显卡的支持是有限的,GTX 1070 这类 Pascal 架构在 TensorRT 10 下部分 FP16/INT8 算子会回退到 FP32,甚至直接不支持。如果你手上是 1070/1080,建议锁在 TensorRT 8.6 + CUDA 11.8 这套组合,别追新。安装方式上,pip 安装tensorrt包最省事,但要注意 pip 包里的libnvinfer.so版本必须和系统 CUDA 匹配,用python -c "import tensorrt; print(tensorrt.__version__)"确认。
4.2 用 trtexec 构建引擎:一条命令与四个关键参数
trtexec 是 TensorRT 自带的命令行工具,构建引擎最直接:
trtexec \ --onnx=best.onnx \ --saveEngine=best_fp16.engine \ --fp16 \ --workspace=4096 \ --minShapes=images:1x3x640x640 \ --optShapes=images:1x3x640x640 \ --maxShapes=images:1x3x640x640 \ --verbose--fp16开启半精度,在支持 FP16 的显卡上(图灵架构及以后)能带来接近翻倍的速度提升,精度损失通常在 0.5% mAP 以内。--workspace=4096是构建时允许使用的显存上限,单位 MB,设太小会导致某些层无法选择最优 kernel,设太大浪费,4096 对 640 输入的 YOLO11 足够。三个 shape 参数在静态导出时都写同一个值,动态导出时才需要拉开范围。--verbose会打印每一层的构建信息,第一次构建建议开着,能看到哪些层被 FP16 化、哪些回退到 FP32。构建完成后,trtexec 会输出推理耗时统计,重点看GPU Compute Time的 mean 值,这是纯推理时间,不含前后处理。
4.3 INT8 量化:校准集怎么选、精度掉多少算正常
INT8 能再提速 30%~50%,但需要校准。校准集的选取原则是:从训练集里抽 200~500 张有代表性的图片,覆盖所有类别和典型场景,不要只用验证集,验证集分布太窄会导致校准偏差。用 Ultralytics 直接导出 INT8 引擎:
yolo export \ model=best.pt \ format=engine \ imgsz=640 \ int8=True \ data=dataset/data.yaml \ workspace=4 \ batch=1data参数指向data.yaml,Ultralytics 会自动从训练集里抽图做校准。workspace=4单位是 GB。INT8 之后必须重新跑验证集,对比 FP16 的 mAP,掉 1% 以内算正常,掉超过 2% 说明校准集代表性不够,需要手动指定校准图片目录。另外注意,INT8 对小目标的精度影响通常比大目标大,如果你的场景里小目标占比高,INT8 要谨慎。
4.4 引擎推理封装:前后处理必须和训练对齐
拿到.engine文件后,推理代码的前后处理必须和训练时完全一致。前处理是 letterbox 缩放 + 归一化 + BGR→RGB(如果训练用的是 RGB),后处理是解码 + NMS。常见错误是推理时用了直接 resize 而不是 letterbox,导致框的位置整体偏移。下面是一个最小封装:
import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit import numpy as np import cv2 def letterbox(img, new_shape=640, color=(114, 114, 114)): shape = img.shape[:2] r = min(new_shape / shape[0], new_shape / shape[1]) new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r))) dw, dh = new_shape - new_unpad[0], new_shape - new_unpad[1] dw, dh = dw // 2, dh // 2 img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) top, bottom = dh, new_shape - new_unpad[1] - dh left, right = dw, new_shape - new_unpad[0] - dw img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) return img, r, (dw, dh) # 加载引擎 logger = trt.Logger(trt.Logger.WARNING) with open("best_fp16.engine", "rb") as f, trt.Runtime(logger) as runtime: engine = runtime.deserialize_cuda_engine(f.read()) context = engine.create_execution_context() # 分配显存 input_shape = (1, 3, 640, 640) output_shape = (1, 84, 8400) d_input = cuda.mem_alloc(int(np.prod(input_shape)) * 4) d_output = cuda.mem_alloc(int(np.prod(output_shape)) * 4) stream = cuda.Stream() def infer(img_bgr): img, ratio, pad = letterbox(img_bgr, 640) blob = img[:, :, ::-1].astype(np.float32) / 255.0 # BGR->RGB blob = blob.transpose(2, 0, 1)[None, ...] cuda.memcpy_htod_async(d_input, blob, stream) context.execute_async_v3(stream_handle=stream.handle) cuda.memcpy_dtoh_async(np.empty(output_shape, dtype=np.float32), d_output, stream) stream.synchronize() return output逻辑说明:letterbox保持长宽比缩放并填充灰边,记录缩放比r和填充量pad,后处理时用这两个值把框坐标映射回原图。execute_async_v3是 TensorRT 8.5+ 的异步接口,比同步接口吞吐更高。参数上,output_shape必须和 ONNX 输出严格一致,不同模型版本可能是[1, 84, 8400]或[1, 8400, 84],用engine.get_tensor_shape打印确认,写错会直接段错误。
5. 部署避坑:那些让精度和速度同时翻车的细节
5.1 现象:TensorRT 推理结果框全偏,mAP 掉 30 个点
原因:前处理用了直接 resize 而不是 letterbox,或者 BGR/RGB 通道顺序和训练不一致。Ultralytics 训练时默认把图片转成 RGB,推理时如果喂 BGR,颜色通道错位会让模型把红色当蓝色,框的位置和类别都会乱。
解决:在推理代码里显式做img[:, :, ::-1]转 RGB,并用 letterbox 替代 resize。验证方法很简单:拿一张训练集里的图,分别用 PyTorch 和 TensorRT 推理,把两张结果图画出来叠在一起看,框重合就说明前处理对了。
5.2 现象:trtexec 构建引擎时报Unsupported ONNX op: NonMaxSuppression
原因:导出 ONNX 时 Ultralytics 默认会把 NMS 也导出成 ONNX 算子,但 TensorRT 对 NMS 的支持依赖插件,部分版本不内置。这个报错在 opset 17 + TensorRT 8.6 组合下出现频率较高。
解决:导出时加nms=False,把 NMS 放到后处理里用 Python 或 CUDA 实现。代价是后处理耗时增加,但换来的是引擎构建稳定。如果一定要把 NMS 放进引擎,需要确认 TensorRT 版本自带EfficientNMS_TRT插件,并在导出时指定nms=True且 opset 不低于 16。
5.3 现象:INT8 引擎推理速度没提升,反而比 FP16 慢
原因:显卡不支持 INT8 的 DP4A 指令,或者 TensorRT 在构建时因为校准数据不足,把大部分层回退到了 FP32。GTX 10 系及更早的显卡没有 INT8 硬件加速,强行开 INT8 只会增加量化/反量化开销。
解决:先确认显卡架构,图灵(RTX 20 系)及以上才有可用的 INT8 加速。确认支持后,检查校准集是否覆盖了所有类别,校准图片少于 100 张时 TensorRT 的校准器容易给出保守的 scale,导致层回退。把校准集加到 300 张以上再试。
5.4 现象:同一引擎在不同机器上推理结果不一致
原因:TensorRT 引擎和构建时的显卡架构、驱动版本、CUDA 版本绑定。在 A100 上构建的引擎拿到 4090 上跑,可能因为 SM 版本不同导致 kernel 选择差异,数值结果有微小偏差,极端情况下直接报错。
解决:引擎在目标部署机器上构建,或者用trtexec的--saveEngine时加上--timingCacheFile把 timing cache 一起保存,迁移时带上。跨架构部署时,老老实实在目标机器上重新构建,别图省事直接拷引擎文件。
5.5 现象:batch 从 1 改成 4 后,显存溢出或速度不升反降
原因:静态导出的 ONNX 固定了 batch=1,TensorRT 引擎也只能跑 batch=1。强行喂 batch=4 的数据会报 shape 不匹配。如果导出时用了动态 batch,TensorRT 需要为最大 batch 预留显存,--maxShapes设太大就会 OOM。
解决:需要多 batch 时,导出 ONNX 用dynamic=True并指定batch维度为动态,构建引擎时--minShapes=images:1x3x640x640 --optShapes=images:4x3x640x640 --maxShapes=images:8x3x640x640,optShapes 设成实际业务最常用的 batch,TensorRT 会针对这个值做优化。显存不够就降 maxShapes,别硬撑。
6. 把整条链路串成一个可复现的脚本:我的收尾习惯
走到这里,训练、导出、构建、推理四段都通了,但每次手动敲命令容易漏参数。我一般会写一个run_all.sh把整条链路串起来,关键是用变量控制路径和参数,换数据集时只改开头几行:
#!/bin/bash set -e DATA=dataset/data.yaml MODEL=yolo11s.pt IMGSZ=640 EPOCHS=200 BATCH=16 NAME=yolo11s_exp # 1. 训练 yolo detect train model=$MODEL data=$DATA imgsz=$IMGSZ epochs=$EPOCHS batch=$BATCH name=$NAME # 2. 验证 yolo detect val model=runs/detect/$NAME/weights/best.pt data=$DATA imgsz=$IMGSZ # 3. 导出 ONNX yolo export model=runs/detect/$NAME/weights/best.pt format=onnx imgsz=$IMGSZ opset=17 simplify=True dynamic=False # 4. 构建 FP16 引擎 trtexec --onnx=runs/detect/$NAME/weights/best.onnx \ --saveEngine=runs/detect/$NAME/weights/best_fp16.engine \ --fp16 --workspace=4096 \ --minShapes=images:1x3x${IMGSZ}x${IMGSZ} \ --optShapes=images:1x3x${IMGSZ}x${IMGSZ} \ --maxShapes=images:1x3x${IMGSZ}x${IMGSZ} # 5. 构建 INT8 引擎(可选) yolo export model=runs/detect/$NAME/weights/best.pt format=engine imgsz=$IMGSZ int8=True data=$DATA workspace=4 batch=1set -e让脚本在任一步失败时立即停止,避免带着错误往下跑。变量集中在开头,换实验时只改NAME和DATA。INT8 那步单独注释掉,因为不是每次都需要,而且校准耗时较长。
脚本跑通之后,我习惯做一次端到端对齐验证:从验证集里抽 50 张图,分别用 PyTorch、ONNX Runtime、TensorRT FP16、TensorRT INT8 四个后端推理,把每张图的检测框数量、类别分布、平均置信度记到一张表里。如果四个后端的框数量差异在 5% 以内、类别分布一致,这条链路就算稳了。这个习惯帮我省过好几次「训练没问题、部署精度崩了」的排查时间——有一次就是 INT8 校准集里缺了一个稀有类别,导致该类别的框在 INT8 引擎里全部消失,表格一拉出来就定位到了。
最后说一个参数上的个人偏好:imgsz我通常不会为了提速降到 416 或 320,除非业务明确允许小目标漏检。YOLO11 在 640 下的精度和速度平衡最好,降到 416 速度提升约 40%,但小目标 mAP 可能掉 10 个点以上,这笔账要提前算清楚。部署这件事,快不是唯一目标,快且准才是。希望帮到你。
本文还有配套的精品资源,点击获取