ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLO26模型导出实战:从ONNX到TensorRT的完整指南

2026/9/8 16:39:57 拓冰建站 浏览量
YOLO26模型导出实战:从ONNX到TensorRT的完整指南 我最早接触模型导出这个环节是在把 YOLO26 模型从训练机挪到部署机的时候。当时以为训练完就万事大吉结果在导出阶段连续折腾了两天环境版本对不上、opset 选错、动态尺寸导出后推理直接崩……后来才明白训练只是算法工作的一半导出才是把模型真正交到业务手里的“最后一公里”。尤其 YOLO26 这类新版本的检测框架很多同学跑通训练后卡在导出明明检测精度没问题部署后速度或兼容性却完全不是一回事。这篇文章就从模型导出这个具体技术点入手把环境准备、导出参数取舍、部署格式转换、推理验证和几个高频场景的配套处理完整过一遍适合那些正在做 YOLO26 目标检测落地、边缘设备部署、或者想优化检测速度的开发者参考。先说一个结论模型导出不是“点一下按钮就完成”的事它背后是训练框架、推理引擎、硬件平台三者之间的博弈。YOLO26 延续了 YOLO 系列在实时检测方向上的演进路线默认输入通常是 640×640输出为多维特征图需要经过解码与 NMS 后处理才能得到检测框。我在做导出时会始终把“导出后精度不下降、速度达到预期、目标平台能稳定跑起来”这三个标准放在一起权衡。接下来我把自己实际操作的路径和踩坑记录整理成下面的几个章节。1. 导出前最重要的一件事把环境钉死在能复现的状态1.1 环境一致性为什么是导出环节的头号敌人模型导出本质上是把 PyTorch 的算子图序列化成目标格式。PyTorch 版本不同某些算子的实现可能变化CUDA 版本不同部分自定义算子比如 Focus、C2f 等结构中的卷积组合在导出时走的底层库路径也不同onnx 版本不同算子集的映射表自然就不一样。这些版本细节叠加在一起很容易出现“训练机能导出部署机跑不起来”或者“同样一份代码两次导出的模型行为不一致”的情况。我经常打一个比方PyTorch 模型导出 ONNX 就像是把一段用方言拍的视频翻译成普通话。你说话的人PyTorch、翻译工具exporter、字幕标准opset三者必须匹配只要任何一方版本漂移翻译出来的内容就可能走样。很多人忽略这一点直接拿最新版本库去导旧模型然后花大量时间排查一个根本没有逻辑错误的“玄学问题”。所以我每次导出前第一件事不是写导出代码而是把当前环境完整记录一次。至少在项目目录下留一份 requirements.txt 或者 environment.yml自己能复现别人也能复现。这个习惯在后来我帮同事排查导出问题时帮了大忙。1.2 我常用的环境组合与依赖核对方法以我手头这份 YOLO26 导出实践为例环境是这样的组件版本说明Python3.10对 PyTorch 与 ONNX 生态兼容性较好PyTorch2.1.0cu121CUDA 12.1 编译版本兼顾训练与导出稳定性CUDA12.1与 PyTorch 编译版本对应cuDNN8.9与 CUDA 12.1 配套ultralytics8.3.x当前 YOLO 系列模型的统一训练/导出入口onnx1.16.0负责 ONNX 图结构读写onnxruntime-gpu1.17.0用于导出后的 CPU/GPU 推理验证onnx-simplifier0.4.36对导出图做化简你可能注意到我没有盲目追求“全部最新”。这是因为导出环节对稳定性的要求远高于对新功能的追求。ultralytics 官方代码对 YOLO26 的导出支持已经很成熟我只需要保证相关依赖在它要求的版本区间内即可。核对环境时我一般会跑这三条命令python -c import torch, ultralytics, onnx; print(torch.__version__, ultralytics.__version__, onnx.__version__) nvidia-smi python -c import onnxruntime as ort; print(ort.__version__, ort.get_available_providers())这三条分别确认深度学习框架、GPU 驱动和推理引擎的状态。如果 onnxruntime 的 providers 里没有 CUDAExecutionProvider那说明 CUDA 相关动态库没配对后边导出结果的验证环节会非常被动需要先解决环境问题再继续。1.3 模型权重源的选择官方权重还是自训练权重导出前还有一个容易忽略的问题你手里的 .pt 权重文件到底是官方发布的预训练权重还是自己在自定义数据集上训练出来的权重。二者导出流程一样但后续验证标准不同。如果是官方预训练权重比如 yolo26n.pt 或 yolo26s.pt我建议先跑一次官方示例图片确认权重在训练环境下能正常检出目标再进行导出。这个动作看似多余实际上能帮你把“权重本身是否损坏”和“导出过程是否出错”这两个变量隔离开。如果是自训练权重还要额外确认类别数、类别顺序与训练时的配置文件一致。YOLO 系列在导出后不会把类别名写进模型文件后处理时你需要自己维护一个类别索引表。我见过不止一次部署侧把类别顺序搞错导致检测结果完全对不上那和导出本身没有关系纯粹是使用方没有对齐元信息。2. 用 Ultraalytics 导出 ONNX一条命令背后的参数取舍2.1 导出命令与核心参数在 ultralytics 框架内导出 ONNX最直接的方式是命令行yolo export modelyolo26n.pt formatonnx opset17 imgsz640 simplifyTrue也可以写成 Python APIfrom ultralytics import YOLO model YOLO(yolo26n.pt) model.export( formatonnx, opset17, imgsz640, simplifyTrue, dynamicFalse, halfFalse, )这条命令跑完后同目录下会出现一个 yolo26n.onnx 文件。不要急着把它当成品用先理解一下命令里每个参数的含义。参数作用我的建议opsetONNX 算子集的版本决定了可用算子范围12 以上即可如果目标设备是老版本引擎先用 12否则用 17imgsz模型输入的固定尺寸建议与训练尺寸一致通常 640如果需要部署到高分辨率图像场景可以按实际推理尺寸导出simplify是否调用 onnx-simplifier 化简计算图通常开启减少冗余算子提升兼容性dynamic是否允许动态输入尺寸谨慎开启部分推理引擎对动态 shap e 支持不佳half是否导出 FP16 权重只在目标硬件支持 FP16 加速时开启2.2 imgsz、opset 与 dynamic三个最容易被忽视的坑先说 imgsz。YOLO 系列网络内部有多次下采样最终特征图尺寸与输入尺寸严格绑定。导出时你填的 imgsz 会写进 ONNX 的输入张量 shape 中。如果训练时用 640导出时填 416模型照样能导出但检测小目标的能力会明显下降反过来训练 640、导出 832速度又可能达不到预期。我通常的做法是训练尺寸与导出尺寸保持完全一致如果部署端要求不同的输入尺寸最好的路径是带着新尺寸重新训练或至少做充分的精度验证而不是直接改导出参数。再说 opset。ONNX 算子集版本可以理解为“翻译时允许使用的词汇表”。版本越低词汇越少兼容性越好但某些 YOLO26 模型中用到的算子如 SiLU 激活函数、部分变形卷积在低版本算子集中可能无法表达或表达方式不高效。我实际测试下来opset12 能覆盖绝大多数旧设备但性能通常不如 opset17 优化后的图opset 太高则可能遇到老版本 TensorRT、OpenVINO 不认的算子。一个稳妥策略是先按 opset17 导出在目标引擎上做一次完整验证如果报不认识算子再降到 12 重新导出。dynamic 参数是另一个大坑。开启 dynamic 后ONNX 输入变成类似 [None, 3, None, None] 的动态 shape听起来很灵活但它会让 TensorRT 的 engine 构建阶段无法做充分的尺寸专用优化OnnxRuntime 的某些优化 pass 也会失效实测推理延迟可能比固定 shape 高出不少。如果你的部署场景输入尺寸固定工业检测、安防摄像头通常都是固定分辨率不要开 dynamic直接用固定 shape 换来优化空间更划算。如果必须支持不同分辨率输入我宁愿按几个常用尺寸分别导出多份模型由调度层根据输入分辨率选择加载也不要在模型层做动态 shape。2.3 从 PyTorch 到 ONNX 的完整验证链路导出完成后第一件事不是马上转 TensorRT而是先在 ONNX Runtime 上验证导出前后结果是否一致。我习惯写一个简单的对比脚本核心逻辑分三步第一步用 PyTorch 模型对同一张测试图做推理记录所有检测框的坐标、置信度与类别。第二步用 onnxruntime 加载导出的 ONNX 文件做推理得到原始输出张量后走同样的后处理逻辑记录检测框。第三步计算检测框的 IoU 和置信度差。import cv2 import numpy as np import onnxruntime as ort from ultralytics import YOLO img cv2.imread(test.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # PyTorch 推理 pt_model YOLO(yolo26n.pt) pt_results pt_model.predict(img_rgb, imgsz640, conf0.25) # ONNX Runtime 推理 session ort.InferenceSession( yolo26n.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider], ) input_name session.get_inputs()[0].name input_shape session.get_inputs()[0].shape input_tensor cv2.resize(img_rgb, (640, 640)).astype(np.float32) / 255.0 input_tensor input_tensor.transpose(2, 0, 1)[None, ...] onnx_outputs session.run(None, {input_name: input_tensor})[0] print(ONNX output shape:, onnx_outputs.shape)这里输出的 onnx_outputs 就是模型的原始特征输出。以 YOLO26 当前在 ultralytics 中的导出实现为例输出通常是一个 [1, 4 num_classes, 8400] 形状的张量数值以你实际安装版本导出的 shape 为准8400 来自不同尺度特征图上的 anchor 网格总和需要继续做解码、置信度阈值过滤、NMS 才能得到最终检测框。写验证脚本时后面这部分后处理必须和 PyTorch 端用同一套代码否则比对不公允。我实测下来一次成功的导出验证PyTorch 与 ONNX 的结果应该高度一致绝大多数检测框 IoU 在 0.99 以上置信度偏差在 0.01 以内。如果偏差明显不要继续往下转 TensorRT先回到导出参数里找原因。3. 从 ONNX 到 TensorRT/OpenVINO生产环境部署格式的再优化3.1 TensorRT 导出的两种路径与精度取舍ONNX 是中间格式真正常见的部署目标之一是 NVIDIA 的 TensorRT。TensorRT 能比 ONNX Runtime 跑得更快核心原因是它会在构建 engine 阶段做层融合、内核自动调优、显存复用等优化。简单说ONNX 是“通用可读的翻译稿”TensorRT engine 则是“针对这台 GPU 专门排版好的成品”。在 ultralytics 中可以直接把 PyTorch 权重导出成 TensorRT engineyolo export modelyolo26n.pt formatengine device0 halfTrue也可以先用 ONNX 导出再通过 trtexec 构建 enginetrtexec --onnxyolo26n.onnx --saveEngineyolo26n.engine --fp16两条路径本质相同只是入口不同。关键在精度取舍上FP32 engine精度最接近原始模型但显存占用和速度收益有限。FP16 engine速度和显存都有明显改善检测精度几乎无损是常规首选。INT8 engine速度和体积进一步缩小但需要准备校准数据集精度可能下降适合对延迟极度敏感且精度冗余较大的场景。我实测 YOLO26n 在单张 RTX 3060 上FP16 相比 FP32 大约能快 30% 左右显存占用下降接近一半精度损失在千分位级别完全值得开启。INT8 我没有作为默认选项因为校准集的选取直接影响量化效果如果没有充分代表部署场景真实分布的图像很容易在个别类别上出现精度塌方排查成本很高。3.2 TensorRT 导出失败的常见原因与定位方法TensorRT 构建 engine 的过程不是每次都能一次通过。我遇到的失败类型大致有这几类这里整理成一个排查表现象常见原因处理方式报错 Unknown plugin / unsupported operatorONNX 图中含有 TensorRT 不支持的算子或者 opset 版本过高退回 opset12 重新导出或使用 ultralytics 提供的自动处理机制构建过程中显存不足batch size 设置过大或显卡显存有限减小 batch size或加 --maxWorkspaceSize 限制构建内存dynamic shape 相关的报错ONNX 输入是动态 shape而 engine 构建要求明确尺寸回到 ONNX 导出阶段关闭 dynamic固定 imgszFP16 导出后精度异常某些层对 FP16 敏感先试 FP32确认正常后再用 FP16或者用 --fp16 加 --stronglyTyped 限制部分层精度构建报 mismatch 版本错误TensorRT 版本与 CUDA/cuDNN 不匹配检查 TensorRT 官方版本对应表通常 TensorRT 8.6 需要 CUDA 12.x定位这些问题的通用思路是“逐步倒退”先导出 FP32、固定 shape、opset 最低如果能成功说明问题出在某一个优化选项上再逐项加回去直到复现问题为止。这个方法虽然笨但比对着报错猜要高效得多。3.3 OpenVINO 与轻量化部署思路如果不是 NVIDIA 设备而是 Intel CPU、集成显卡或边缘盒子那么 OpenVINO 是值得考虑的格式。导出命令同样简洁yolo export modelyolo26n.pt formatopenvinoOpenVINO 会生成 .xml 和 .bin 两个文件分别保存模型结构和权重。它的优势在于 Intel 平台的推理优化以及比 ONNX Runtime CPU 更低的延迟。再往深一层说如果你在边缘设备上做轻量化部署模型导出的格式选择往往和“如何在硬件算力与精度之间找平衡”绑定在一起。YOLO26n 本身就是轻量网络如果还是跑不动可以考虑导出 INT8 量化模型或者更换更小的输入尺寸。但这里要再次强调输入尺寸直接影响小目标检测能力不要为了速度把 imgsz 降到离谱的数值轻量化应该先从模型结构、量化和算子优化入手而不是牺牲输入信息。4. 导出不是终点部署侧的推理验证与踩坑清单4.1 ONNX Runtime 推理代码与输出解析导出成功后我建议先把 ONNX 文件直接作为正式推理入口写一个最小可用的推理脚本。这样既能验证导出质量也能为后续业务代码预留接口。import cv2 import numpy as np import onnxruntime as ort class YOLO26ONNX: def __init__(self, onnx_path, conf_thres0.25, iou_thres0.45): self.session ort.InferenceSession( onnx_path, providers[CUDAExecutionProvider, CPUExecutionProvider], ) self.conf_thres conf_thres self.iou_thres iou_thres self.input_name self.session.get_inputs()[0].name def preprocess(self, img, size640): h, w img.shape[:2] scale min(size / w, size / h) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(img, (new_w, new_h)) canvas np.full((size, size, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] resized tensor canvas.astype(np.float32) / 255.0 tensor tensor.transpose(2, 0, 1)[None, ...] return tensor, scale, new_w, new_h def postprocess(self, output, scale, new_w, new_h, orig_shape): # output shape: [1, 4 num_classes, num_anchors] preds output[0] # 转置为 [num_anchors, 4 num_classes] preds preds.transpose(1, 0) boxes preds[:, :4] class_scores preds[:, 4:] class_ids class_scores.argmax(axis1) confidences class_scores.max(axis1) mask confidences self.conf_thres boxes, class_ids, confidences boxes[mask], class_ids[mask], confidences[mask] # 将 cxcywh 转换为 xyxy并映射回原图尺寸 boxes[:, 0] (boxes[:, 0] - boxes[:, 2] / 2) / scale boxes[:, 1] (boxes[:, 1] - boxes[:, 3] / 2) / scale boxes[:, 2] (boxes[:, 0] boxes[:, 2]) / scale boxes[:, 3] (boxes[:, 1] boxes[:, 3]) / scale return boxes, class_ids, confidences def __call__(self, img): tensor, scale, new_w, new_h self.preprocess(img) output self.session.run(None, {self.input_name: tensor})[0] return self.postprocess(output, scale, new_w, new_h, img.shape[:2])这段代码有两个细节值得注意。第一预处理时的 letterbox 要保证等比缩放并做 padding否则缩放后图像变形检测精度会受影响。第二后处理中把 cxcywh 转 xyxy 并除以 scale 回映射到原图坐标时如果原图与画布之间存在 padding 偏移还需要减去 padding 的偏移量。我这里为了简洁省略了 padding 偏移实际生产代码里一定要加上否则检测框在原图上的位置会有系统性偏差。这个坑我在早期部署时踩过跑出来的框总是偏向右下角排查了很久才发现是 padding 没有还原。4.2 导出前后精度不一致的排查方向如果导出前后精度有明显差异不要急着怀疑导出工具本身按下面这个顺序排查效率最高。第一确认预处理完全相同。YOLO 系列通常用 /255 归一化但不同工程里 letterbox 的填充值、缩放方式、BGR 与 RGB 的通道顺序都有可能不同。两套推理流程只要有一个像素值不一样结果就会偏离。第二确认后处理参数完全一致。置信度阈值、NMS 阈值、类别数、坐标解码方式任何一个不同最终检测框集合就不同。第三确认 ONNX 输出解析正确。导出的输出张量可能有多个输出节点如果你只取了第一个而模型实际把 box 分支和 class 分支分开了结果自然对不上。看一眼 session.get_outputs() 的输出数量和 shape再对照导出日志确认统计信息。第四确认运行时是否走了不同精度。onnxruntime 的 CPUExecutionProvider 默认 FP32GPU 默认 FP32但如果你显式开启了 FP16 或者某些图优化精度就可能有细微变化。4.3 常见报错与应对我把导出和部署阶段遇到的典型报错整理成一个速查表方便直接对照报错信息含义处理方法No operator named ... is registeredONNX 图中有当前 runtime 不支持的算子升级 runtime或降低 opset 重新导出Got invalid dimensions for input推理时输入的 shape 与导出模型不匹配在预处理时严格使用导出时设定的 imgszCUDA driver version is insufficient驱动版本过低升级 GPU 驱动或改用 CPU 推理验证NMS is not supported in this engineTensorRT/OpenVINO 等引擎对部分 NMS 节点支持不友好把 NMS 从模型图中移出回到后处理代码中实现File is not a zip file错误地加载了一个非权重文件确认文件路径不要误把 .pth 或 .pt 当 .onnx 加载最后一条看起来很蠢但在多人协作的项目里模型文件命名混乱导致加载错误的事情真的不少见值得在团队流程里加一道文件校验。5. 热搜词背后的真实场景低光、测距、轻量化对导出链路的影响5.1 低光环境检测预处理与模型输出的联动很多人搜“yolo26 低光环境检测”时以为只要模型够强就能解决。实际部署中低光图像往往对比度极低模型即使能检出目标置信度也很低。我的经验是在导出模型旁边配套一个预处理模块先做 CLAHE 或 Gamma 矫正再把增强后的图像送入 ONNX/TensorRT 推理。这样模型本身不需要重新训练部署侧只是多了一个预处理步骤。这个方案对导出有一个隐含要求模型输入尺寸、归一化方式必须与预处理输出完全匹配。也就是说你在验证阶段用什么增强参数导出的模型就应该按照同样的参数去推理。如果验证阶段用了 CLAHE部署阶段却直接换成了原始图那导出的模型再准也没有意义。另外低光场景下的另一个思路是把“低光增强网络 检测网络”融合成一个 ONNX 图前向时一次推理完成增强和检测。这样做可以避免中间结果的传输开销提升整体速度但需要把增强网络的权重和检测网络一起导出修改点比较多。如果刚开始接触我建议先用独立预处理模块跑通全链路后再考虑融合。5.2 单相机测距输出距离导出模型的配套后处理“yolo26 单相机测距 输出距离”是另一个典型热搜。很多人误以为模型能直接输出“距离”这个值实际上 YOLO26 作为 2D 目标检测模型输出的是检测框而距离来自几何后处理和模型本身没有直接关系。常规做法是先做相机标定得到内参矩阵和畸变系数再用 YOLO26 检测出目标框通常取检测框底边中心点作为目标与地面的接触点最后基于针孔成像模型或地面平面单应性矩阵计算该点在世界坐标系中的距离。这一步对模型导出的影响在于距离计算依赖检测框的稳定性。导出后我用 TensorRT FP16 推理时检测框比 FP32 稍有抖动反映到远距离测量上就是几个厘米到十几厘米的误差。如果你的测距精度要求很高建议导出 FP32 格式的 engine并且在后处理里加一个基于历史帧的平滑滤波。这个经验可能不在任何官方文档里但对实际项目很重要。5.3 轻量化与改进复现从哪里开始动手热搜里还有“yolo26轻量化”“yolo26改进”这类词。围绕模型导出展开轻量化最直接的方式是选择合适的变体比如 yolo26n 就是为轻量部署设计的。如果这个还不够再考虑导出 INT8 TensorRT engine或者用 OpenVINO 在 CPU 侧跑。“改进复现”则要提醒一点很多人会在 YOLO26 主干网络上加注意力模块之类的小改动这本身没问题但改动后一定要把导出兼容性纳入测试范围。有一次我在主干中加了一个自定义算子在 PyTorch 里前向完全正常导出 ONNX 时却报算子不受支持最后不得不把那个算子改写成标准卷积组合才能导出。这个经验给我的教训是做结构改进时不仅要想精度怎么提升还要想算子是否容易被导出工具转换。生产环境不是实验室模型最终要能走出训练脚本。6. 从一次完整导出操作说起把经验固化成脚本讲到这里我想再把完整的导出操作串起来当作一个顺手可用的模板。这个模板我实际用了很多次基本每次都能减少反复试错的成本。第一步准备验证图片。从训练集和真实业务场景中各抽 10 张代表性图片固定下来后续每次导出都用这 20 张图做精度对比。第二步记录环境版本。前面提到的 requirements.txt 或 environment.yml 一定要生成本如果换机器部署直接按这个文件重建环境。第三步执行训练尺寸下的 ONNX 导出。opset 先按 17simplify 开启固定 imgsz。第四步写一个对比脚本把 PyTorch 推理结果和 ONNX 推理结果做 IoU 与置信度比对。第五步确认无误后再转向目标平台的 engine 导出比如 TensorRT 或 OpenVINO。第六步在目标平台上做端到端推理测试包括加载速度、单帧延迟、显存占用和精度回归。整个流程里我最大的心得是“把结果可视化”。不要只看日志里的 accuracy 数字直接把 PyTorch 的检测框和 ONNX 的检测框画在同一张图上输出一眼就能看出有没有框偏移、漏检、误检。视觉问题用视觉的方式检验往往比盯着一堆数字更高效。还有一个小技巧每次导出时把命令、参数、环境版本、精度对比结果都写进一个 Markdown 文件存档。听起来繁琐但当项目迭代几轮后你会深刻体会到这种“导出日志”的价值——你永远需要知道当前正在部署的模型是用什么配置导出来的否则回滚和排查都无从下手。7. 导出之外的最后一层思考模型交付不仅是文件交接模型导出做到最后你会发现它其实是一项“交付工程”。训练好的模型无论精度多高如果不能稳定、高效地在目标设备上跑起来对业务来说就是零。模型导出则是这道交付流水线的核心枢纽它决定了后续部署的顺利程度。我自己在项目组里推广过一个小规范所有模型交付必须附带一份简短的部署说明内容包括环境版本、导出参数、验证图片、精度对比结果、常见报错处理五部分。这个规范一旦建立团队里新人接手模型时就不必从零开始摸索导出参数也极大减少了“换个人就导出失败”的尴尬局面。回到 YOLO26 本身作为一个新版本检测模型导出工具链已经相当成熟但越是这样我们越要把基础原理搞清楚。理解 imgsz 如何影响特征图网格理解 opset 如何影响算子兼容性理解不同推理引擎的优化策略这些知识才是你在任何一个检测模型上都能复用的资产。下一次当你面对一个新的 YOLO 版本时导出这一步就不会再让你手足无措。