十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLO26模型导出全攻略:从ONNX到TensorRT的完整避坑指南

YOLO26模型导出全攻略:从ONNX到TensorRT的完整避坑指南 【内容安全审查】通过YOLO26 模型导出从训练到部署的最后一公里我踩过的坑和最终方案做计算机视觉的同学应该都有这种经历训练时各种指标刷得飞起模型在 Colab 上跑得好好的loss 降得赏心悦目mAP 也说得过去。结果到了要交付的时候导师或者甲方一句“把模型给我我这边要接进系统”你才发现手里只有一个.pt文件拿去给对方的 C 工程师人家眉头一皱这玩意儿我们这边跑不了。这就是模型导出的意义。YOLO26 作为目前 YOLO 系列里热度一直居高不下的版本网上关于它训练、改进、结构图解析的内容已经很多了但“训练完之后怎么把模型导出成不同平台能吃的格式”这块系统性讲清楚的内容反而不多。不少做计算机视觉大作业的同学、做 YOLO26 人员入侵检测项目的人甚至很多已经在跑 YOLO26 训练自己数据集的工程师都卡在了导出这一步。这篇东西我想把它彻底讲透。你不需要是部署专家只要手里有一个训练好的 YOLO26 模型能看懂 Python按着下面的思路走一遍基本就能把 ONNX、OpenVINO、TensorRT 这些格式理清楚也知道每一步为什么要那么做。1. 导出不只是“换个后缀”模型的运行世界变了很多人第一次接触导出觉得就是把.pt转成.onnx本质上是文件格式转换。这个理解不算错但太浅了会导致后面遇到问题完全找不到方向。1.1 训练框架和推理引擎对“模型”的理解完全不同PyTorch 里的模型本质是一堆层结构加上一堆权重然后被 Python 的 runtime 动态解释执行。它灵活、方便调试但每跑一次前向都要经过 Python 层的调度图的优化也几乎为零。这在训练阶段完全没问题因为训练本身就要频繁改权重、算梯度本来就需要这种灵活性。但推理不一样。推理的时候模型结构是固定的权重也是固定的不存在反向传播也不存在动态建图的需求。推理引擎要的是“把计算图固化下来针对固定 shape 做极致优化”。这就是为什么业界要把训练好的模型从 PyTorch 里“搬”出来搬到一个中立的、描述计算图的格式里。ONNXOpen Neural Network Exchange就是这个“中立格式”。PyTorch 训练完的模型导出成 ONNX 后相当于把整张计算图固化成了标准化的描述文件。逻辑上它就是一个“计算图快照”任何支持 ONNX 的推理引擎ONNX Runtime、TensorRT、OpenVINO、Core ML 等都能读取并执行它。用个生活化的比喻PyTorch 模型像一位会临场发挥的厨师你告诉他“做一道鱼”他会根据今天有什么食材、灶台什么火力、自己当时的心情决定具体怎么做。而 ONNX 就像一份精确到克数的菜谱食材、步骤、火候全部写死。你把这菜谱给任何一个能看懂菜谱的厨房各种推理引擎都能做出一模一样的菜。1.2 ONNX、TensorRT、OpenVINO 各管哪一段路这是新手最容易懵的地方格式太多了分不清。我把它们按“离硬件有多近”排个序就清楚了ONNX最中立的计算图描述格式本身不做太多优化像一个通用的中间表示IR不太依赖具体硬件。它能跑的地方最广但优化的空间也最大。ONNX RuntimeORT微软出品的推理引擎直接吃 ONNX 文件。它在 CPU 和 GPU 上都能跑做了不少图优化和算子融合适合做“通用部署”。OpenVINOIntel 出品的推理工具套件针对 Intel CPU、集成显卡、Movidius 神经计算棒做了深度优化。如果你部署的目标机器是 Intel CPUOpenVINO 往往是性能最好的选择之一。它可以把 ONNX 进一步转成它自己的 IR 格式.xml.bin也可以直接读 ONNX。TensorRTNVIDIA 出品的推理优化器只在 NVIDIA GPU 上工作。它会针对你的目标 GPU 做层融合、精度校准、kernel 自动调优是“NVIDIA GPU 上推理性能天花板”级别的存在。它吃 ONNX转化后再序列化成 TensorRT engine.engine文件。Core MLApple 生态的模型格式给 iPhone、Mac 上用的。所以整个导出的链路一般是PyTorch (.pt) → ONNX → OpenVINO / TensorRT / Core ML / ONNX Runtime理解了这条链路你就明白为什么大家总说“先导出 ONNX”它是一个必经的中间站是 PyTorch 和各个硬件推理引擎之间的“通用语言”。不只是 YOLO26YOLOv5、v8、v11 这些全是这个套路。1.3 NMS 在导出流程里的真正地位提到 YOLO 导出有个绕不开的东西叫NMSNon-Maximum Suppression非极大值抑制。YOLO 模型输出的原始裸结果是一大堆候选框每张图可能有上万甚至几万个框其中绝大多数是重叠的、低置信度的“废框”。NMS 的作用就是把这些框过滤、合并最后留下每个目标位置上最合适的那个框。训练和验证的时候NMS 是在 PyTorch 代码里用纯 Python 或 C 扩展实现的这部分逻辑根本没进到模型的计算图里。而导出成 ONNX 后你拿到的是“模型输出原始框”这一步NMS 要不要一起导出就成了 YOLO 系列导出时最容易出现分歧的大问题。实际工程里主流做法是模型只导出到“原始输出”为止NMS 后处理放在推理代码里用目标语言C/C#/Java/Go自己写。原因后面会详述但你现在先记住导出 YOLO 系列模型基本默认不导出 NMS除非你的部署场景非常特殊比如目标检测的嵌入式端后处理写起来极其困难才有动力把 NMS 一起塞进模型里。2. 你的 YOLO26 是“哪一款”不同任务导出的策略不一样在具体动手前先别急着敲命令。你要回头看一下自己训练的 YOLO26 到底是“哪一款”模型。YOLO 系列从 v8 开始就不仅仅是目标检测器了它同时支持分类、分割、姿态估计这些任务。YOLO26 在这个基础上还衍生出了 depth深度估计等变体网上搜“yolo26 depth”“yolo26 姿态模型版本区别”就能看到很多人都在这块有疑问。不同任务类型模型输出的头head完全不同导出的注意事项也各不相同。2.1 常规目标检测模型的导出重点这是最简单、最常见的情况。你训练了一个 YOLO26 目标检测模型比如 YOLO26n、yolo26s、yolo26m 这些用来做人员入侵检测、安全帽检测、车辆识别等任务。这种情况导出时核心关注点有三个输入尺寸训练时用的 imgsz 是多少导出时就尽量保持一致。虽然 ONNX 支持动态输入但对推理性能不友好后面细说。输出格式YOLO 检测模型的原始输出一般是[1, 4 num_classes, 8400]这种形状具体数字取决于模型输入尺寸和 stride其中 4 是框坐标cx, cy, w, h 或 xyxynum_classes 是类别数8400 是不同尺度特征图上的候选框总数输入 640 时。导出时你会在输出节点上看到类似名字。要不要 NMS默认不要。让下游代码自己处理。如果你是用 Ultralytics 框架训练的 YOLO26框架本身已经把导出逻辑封装好了正常情况一句model.export()就能搞定。但如果你跑的是别人魔改的 YOLO26网上“yolo26 改进专栏”里这种特别多比如加了注意力机制、换了检测头就不能完全依赖框架自动导出要手动检查改动过的层能不能被 ONNX 算子集支持。这是最让人头疼的场景我后面会用一节专门讲怎么排查这类问题。2.2 depth 版本导出时处理额外输出头如果你用的是 YOLO26 depth 版本用于单目深度估计它的网络结构在检测头之外还挂了一个深度头输出的是一个和输入图像尺寸成一定比例的深度图。导出这种模型你要额外确认深度头的输出名称和 shape导出的 ONNX 文件中深度输出是一个独立的输出节点通常像[1, H, W]或[1, H/scale, W/scale]。深度值范围你训练时的深度标签是相对深度0~1还是绝对深度米模型输出的原始值范围是什么这些信息虽然不影响导出但会影响你部署后怎么处理深度图最好在导出的同时写进模型说明文档。后处理差异检测分支可能需要 NMS深度分支只需要插值缩放回原图尺寸并做可视化或后续计算。这决定了你在推理代码里要同时处理“框后处理”和“深度图后处理”两套逻辑导出本身没区别但部署代码要注意。姿态模型导出与模型版本差异YOLO26 的姿态模型pose输出就更特殊了。Ultralytics 框架里的 pose 模型训练好的.pt导出 ONNX 后输出通常是两个头的拼接或分开一个是检测框分支一个是关键点分支。以 COCO 姿态任务为例关键点通常是 17 个每个点有(x, y, confidence)三个值。所以关键点分支的输出维度一般是 batch 相关、通道数 17 * 3。检测框分支仍然输出框和类别置信度。这里必须注意“版本差异”。网上搜“yolo26 姿态模型版本区别”能看到很多人在问同一个 YOLO26 名字下有不同权重版本关键点位顺序、坐标约定是不是和 COCO 一致是否带了可见性 flagvisible flag这些细节不同训练脚本导出的结果形状就不一样。我的建议是导出姿态模型之前先拿一张测试图用 PyTorch 原始模型推理一遍打印出每个输出的 shape并人工检查关键点的坐标值是不是符合预期。只有确认了“PyTorch 阶段输出是正确的”你导出 ONNX 后去对比才有参照物。如果你连 PyTorch 阶段的输出都没确认过导出后出了 bug 你根本分不清是导出环节的问题还是模型本身的问题。2.4 基于 YOLO26 做人员入侵检测等项目时的导出选型结合很多做“YOLO26 人员入侵检测”这类计算机视觉项目的同学其实是为了交大作业或者做毕设。这种项目通常需要部署在一台普通的电脑上甚至可能是 CPU only 的机器。这种场景下我最推荐导出OpenVINO格式原因有两点Intel CPU 是个巨大的存量市场OpenVINO 对此的优化是出了名的猛经常能在不损失精度的情况下比纯 PyTorch CPU 推理快 2 到 4 倍。Ultralytics 框架原生支持导出 OpenVINO一条命令就能完成还会帮你生成配套的部署文件。如果项目方指定要用 NVIDIA GPU 跑推理那就直接上 TensorRT。虽然推理速度最快但因为它和具体显卡绑定注意是“绑定”不是“兼容”换了一张显卡型号甚至驱动版本都可能要重新导出在交付时要多留个心眼千万别只给客户一个 .engine 文件一定要把 ONNX 文件一起交付不然客户换台机器就跑不了。3. 直接抄作业我验证过的导出流程和参数配置理论铺垫完了现在进入实操。下面是我在自己的环境里跑通的完整导出流程包含了环境准备和导出命令以及每一步背后的选择逻辑。3.1 环境准备与版本对齐先说一个重要教训导出 YOLO26 前先确认你的 ultralytics 包版本和你下载/训练的模型来源一致。YOLO26 相关的代码和权重在社区里流传速度很快往往你今天下载的yolo26n.pt需要用某个特定版本的ultralytics包才能正常加载。如果你是在跑“yolo26 环境配置”时按网上的博客装了一个版本的包然后又从另一个来源下载了权重很可能加载时就报错或者行为异常。我本地的建议环境组合Python 3.9~3.11PyTorch 2.1 或以上ultralytics 包版本以你模型来源说明为准一般 8.2.x 之后的版本对 YOLO26 支持比较完整onnxruntime 1.17 以上用于导出后验证onnx 1.15 以上openvino 2024.x如果用 CPU 部署建议导出 OpenVINO 格式tensorrt 8.6 或以上如果用 NVIDIA GPU建议导出 TensorRT提示Torch、CUDA、TensorRT 三个版本要互相兼容这是老生常谈但永远有人在这上面翻车。TensorRT 对 CUDA 版本很敏感建议统一用 NVIDIA 官方文档里列的兼容矩阵来配。你可以在终端里检查版本python -c import torch, ultralytics, onnx, onnxruntime; print(torch.__version__, ultralytics.__version__, onnx.__version__, onnxruntime.__version__)如果输出里没有报错说明基础环境没问题。3.2 标准导出命令与超参选择如果是用 Ultralytics 框架训练的 YOLO26最笨但也最靠谱的导出代码长这样from ultralytics import YOLO # 加载你自己训练的权重 model YOLO(runs/detect/train/weights/best.pt) # 导出 ONNX model.export( formatonnx, # 导出格式 imgsz640, # 输入尺寸和训练时保持一致 opset12, # ONNX 算子集版本 dynamicFalse, # 是否允许动态输入尺寸 simplifyTrue, # 是否用 onnxsim 简化计算图 nmsFalse, # 是否导出 NMS强烈建议 False )这行代码执行完后会在权重同目录下生成一个best.onnx。你可能会问参数为什么这么设我逐个解释imgsz640你训练时如果是 640这里就填 640。如果训练时是 1280这里就得填 1280。模型的 backbone 和 head 对输入特征图的尺寸有依赖stride 通常是 8、16、32 的组合。输入尺寸必须是 32 的倍数或者至少是 stride 最小公倍数的倍数否则特征图会出问题。你导出时如果填了一个训练时完全没有用过的尺寸精度可能会莫名掉点因为模型从未在这个分辨率上充分适配过。opset12ONNX 的算子集版本。太低的版本不支持一些较新的算子太高的版本对老版本推理引擎不友好。YOLO 系列在这种 CNN 结构里opset 11~13 之间都够用。我默认选 12兼容性和功能都比较平衡。如果你后续要转 TensorRTTensorRT 8.6 以上的版本对 ONNX opset 的适配已经很好不需要额外操心。dynamicFalse这个我强烈建议。设成 True 虽然能让模型接受任意尺寸输入但代价是推理引擎没法做输入 shape 相关的固定优化比如 TensorRT 的某些层会根据固定尺寸做内存布局调优性能会有损。部署阶段如果你能保证输入尺寸固定就固定死不要开动态。但有个例外如果你的业务里输入图像尺寸不能预知且变化幅度极大又不想做 letterbox 填充把图像缩放并填充成固定尺寸那只能开 dynamic。这种情况你要意识到每换一个尺寸推理引擎可能会重新做 shape 推导延迟会高不少。simplifyTrue导出的 ONNX 会经过 onnxsim 工具做一遍常量折叠、冗余节点删除之类的优化。实测下来YOLO26 的模型开 simplify 后文件体积和推理时间都有改善而且不影响精度。但注意如果你的模型里有自定义算子simplify 可能会报错那就不用 onnxsim后面手动排查。nmsFalse前面讲过原因。官方导出选项里有 nms 这个参数默认是 False。我建议保持默认除非你非常明确自己的部署链路为什么需要模型内置 NMS。3.3 导出 ONNX 后我用这三种方式验证它没问题导出成功不等于导出正确。我见过太多人 export 时没有报错以为万事大吉结果接入部署代码后检测结果全乱套。以下三步每步都值得做第一步用 onnx.checker 检查模型结构合法性import onnx onnx_model onnx.load(best.onnx) onnx.checker.check_model(onnx_model) print(ONNX model check passed.)如果你看懂了 checker 的报错内容大多数结构性问题比如节点输入输出不匹配、维度信息缺失都能在这一步暴露出来。第二步用 onnxruntime 推理一张测试图和 PyTorch 输出对比这是最关键的一步。拿同一张测试图分别用原始.pt模型和导出的.onnx推理打印输出矩阵的 shape 和数值看看是否一致。注意不同框架的预处理方式可能有细微差别比如像素归一化方式是 0~1 还是 -1~1通道顺序是 RGB 还是 BGR。为了验证 ONNX 本身没导错你应该把 ONNX 的输入喂成 PyTorch 模型输入张量的完全相同的值。这在部署阶段做“预处理对齐”前的一个独立验证。import numpy as np import torch import onnxruntime as ort from ultralytics import YOLO # 用 PyTorch 模型推理得到输入张量 x 和输出 model YOLO(best.pt) x torch.randn(1, 3, 640, 640) # 这里可以放一张真实图片预处理后的张量 with torch.no_grad(): pt_outputs model.model(x) # 这是 PyTorch 原始输出不是后处理后的结果 # 用 ONNX Runtime 跑同一个输入 session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) ort_inputs {session.get_inputs()[0].name: x.numpy()} ort_outputs session.run(None, ort_inputs) # 对比每个输出的 shape 和数值 for i, pt_out in enumerate(pt_outputs): ort_out ort_outputs[i] print(fPyTorch output {i}: shape{pt_out.shape}) print(fONNX output {i}: shape{ort_out.shape}) print(fMax absolute diff: {np.abs(pt_out.numpy() - ort_out).max():.6f})如果 max diff 在 1e-4 这个量级甚至更小说明导出基本无损。如果 diff 很大比如超过 1e-2那说明计算图里有算子被错误替换了或权重对齐出了问题要回头查简化过程或者逐层对比。第三步可视化模型结构把导出的 ONNX 文件拖进 Netron 网页端https://netron.app认一遍从输入到到各分支输出的形状变化。这一步特别适合检查你用的是不是接了正确 head也适合给做 YOLO26 结构图展示的人用。这个习惯很值得养成导出不是验证的终点“导出完更要对齐再收工”省下的全是后面排查自己的时间。我在自己带团队做项目时甚至立过规矩没做输出对齐验证的模型不允许发给下游。3.4 继续导出 OpenVINO 和 TensorRTYOLO26 用 Ultralytics 导出 OpenVINO 很简单model.export(formatopenvino, imgsz640, halfFalse) # halfFalse 保持 FP32如果推理机器支持且对精度损失做了评估你可以设置halfTrue转成 FP16。很多 Intel CPU 的核显对 FP16 也算友好但如果你在普通 CPU 上做纯 CPU 推理FP32 其实更稳妥。导出 TensorRT 则这样model.export(formatengine, imgsz640, halfTrue, device0) # device 指定 GPUTensorRT 导出时务必指定在 NVIDIA GPU 上。halfTrue是 FP16 精度在主流 Ampere 及之后架构的卡上YOLO26 用 FP16 推理精度损失通常很小速度提升明显。如果你的模型是用 FP32 训练的在 NVIDIA 显卡上直接用 FP16 推理绝大多数情况下没有问题但你仍然要在导出完成后对同一批验证集图跑一遍 mAP确认精度下降不超过 1 个百分点才比较稳。目标平台推荐格式关键特征通用 CPUONNX ONNX Runtime跨平台安装简单Intel CPUOpenVINOCPU 推理优化充分易部署NVIDIA GPUTensorRT推理性能天花板绑定显卡Apple 设备Core ML生态私有格式跨平台原型验证ONNX先落地再决定正式优化4. 导出时最容易翻车的隐蔽环节与完整排查链路这一节我想专门讲“导出过程中报错”的处理思路。因为 YOLO26 的源码和公开资源太多太杂你下载的“YOLO26 源码下载”很可能是某个博主二次开发的版本结构图和官方不完全一样甚至已经加了各种改进模块。这种模型导出最容易在“自定义算子转 ONNX”这一步挂掉。4.1 先复现再排查处理导出报错的通用链路假设你在跑 export 时遇到了一个问题比如报错说某个算子不支持。不要急着搜“YOLO26 导出报错”这种宽泛的关键词按照下面的链路一步步缩小范围第一步精确定位到具体的层名和算子Ultralytics 的 export 日志通常会打印到哪个模块、哪个节点失败。如果日志里只给了统一错误用 traceback 定位到模型结构定义文件里的具体调用栈找到出错的模块名。第二步检查这个模块是不是新加的“改进模块”如果它来自你下载的“YOLO26 改进专栏”代码那么它大概率是社区里有人写的自定义模块内部可能用了不常规的 PyTorch 操作比如torch.cumsumscatter的组合有些注意力机制会这么写、用了可变循环长度等。这些操作要么 ONNX 算子集里没有直接对应要么对动态 shape 支持不好。第三步把该模块单独抠出来构造假数据导出单测这是非常高效的排查手段。你在一个新文件里单独定义一个只包含那个模块的小模型随机生成相同 shape 的输入对它单独做 torch.onnx.export看能不能成功。如果单独导出成功说明该模块本身没有算子问题问题出在和模型中其他模块的配合上如果单独导出失败那问题就锁定在这个模块内部。然后尝试把模块内疑似有问题的操作逐个替换成更基础的算子比如把某些变形操作用 reshape transpose 拆开再重新导出验证。第四步考虑将“改进模块”留在 PyTorch 中用前后处理承接如果自定义模块实在无法转 ONNX某些复杂的注意力机制和检测头结构确实会有问题可以考虑导出时屏蔽掉该模块把它放到部署代码前后处理阶段用别的方式补偿。比如注意力模块输出可以在 PyTorch 阶段预先计算好部署时把它的结果作为预处理的一部分。这听起来绕但在工程上是常见的妥协方案。第五步计算图分段导出再拼接如果模型主体是标准结构、只有个别模块导不出可以把这个模块从全图中拆出来单独导出成一个小 ONNX然后用 ONNX GraphSurgeon、onnx_graphsurgeon 或者 ONNX 官方工具库进行图拼接。这样做非常接近“模型主体 一系列子图”的部署方式比较考验对计算图结构的理解。一般不建议新手轻易尝试但它确实是高阶方案里最有效的。4.2 图像缩放方式造成的“精度翻车”是部署阶段最常见的坑导出时模型内部计算没问题但推理结果不对最常见的原因之一是图像预处理没有对齐训练时的逻辑。YOLO 系列训练和验证时有一套默认的预处理流程等比缩放 letterbox向边缘填充灰边 归一化 BGR/RGB 通道顺序约定。这套逻辑在 PyTorch 阶段是框架内部自动帮你做掉的但导出到 ONNX 后模型本身只负责“吃一个张量吐一个张量”预处理你必须自己在推理代码里实现。我见过无数人栽在这件事上直接用了 OpenCV 的cv2.resize(image, (640, 640))把原图拉变形了喂进模型导致小目标检测效果极差或者图像尺寸没做 letterbox 填充直接把非正方形图塞进去模型输出的候选框坐标在映射回原图时就全位移了。正确的做法是部署代码里复刻 ultralytics 的预处理函数。核心逻辑如下计算缩放比例、完成等比缩放、再用灰边 (114, 114, 114) 填充到目标尺寸、最后做归一化和维度调整。下采样映射回原图时再把 letterbox 填充的偏移量减去。这段代码不复杂但如果抄网上零散版本很容易漏掉 BGR/RGB 转换。建议统一从你训练的框架源码里把预处理函数抠出来翻译成目标语言的版本而不是自己重新“发明”一遍预处理。4.3 导出 ONNX 后输出和 PyTorch 输出差一点但不算离谱这类问题很隐蔽。报错倒是没有数值对比 diff 也在 1e-3 到 1e-2 量级。这种情况通常来源于两种因素一是 batch norm 层转 fold 时的数值误差。PyTorch 推理时 batch norm 是按x - running_mean/ sqrt(running_var eps) * weight bias 逐层计算的而导出到 ONNX 时推理引擎会尝试把 BN 融合到卷积层里变成 y x * scale offset。这个融合理论上精确但浮点运算顺序变化会引入微小误差。这种误差在 1e-4 量级通常不影响后处理结果。二是在 FP16 推理下引入的量化误差。如果你导出时开了 halfTrueFP32 权重转成 FP16 会损失一点精度输出 diff 到 1e-2 量级也是正常的。如果 diff 在这个量级而你的后处理结果比如检测框没有明显变化基本可以继续走但如果 diff 到了 1e-1 以上检测框都飘了就别以为是“正常误差”大概率是你某个参数导出时变了比如权重文件没正确加载。4.4 dynamicTrue 导出后动态 shape 导致的隐性 bug有的 YOLO26 模型以动态 shape 导出后某些自研模块在推导输入尺寸时依赖于“shape 的数值”做条件分支比如判断特征图宽度是否大于某阈值来决定是否执行某个操作。PyTorch 是实时执行没问题但 ONNX 导出时这种条件分支会被固化成静态的算子路径换了输入尺寸可能执行了错误的计算路径导致输出错乱。规避方案很直接不要开 dynamic。如果你的业务场景确实需要输入尺寸可变优先考虑在你的部署代码里做“多档位”策略比如固定支持三种输入尺寸分别导出三个 engine 或 ONNX 模型推理时按需调用。这在工程上比一个动态模型靠谱得多。4.5 CPU 和 GPU 推理的输出差异注意点同一个 ONNX 文件在 CPU 上用 ONNX Runtime 跑和在 GPU 上用 TensorRT 跑输出结果会有很小的差异原因在于浮点运算顺序不同、某些算子在不同后端有不同的实现策略。只要差异在正常范围内通常 mAP 差异不超过 1 个百分点都是合理的。如果差异明显优先检查是不是某个算子如Resize、GridSample在两种后端上的插值方式实现不同。YOLO 系列里Resize最常出问题因为框架默认的Resize模式不同版本有细微差别。5. 导出完成后模型要怎么接进真实系统导出只是第一步模型接进推理引擎跑起来才是完整闭环。这里讲一讲我在实际项目中用的最小可用推理链路以 ONNX Runtime 为例方便各位理解部署侧在做什么。5.1 一套简洁的 ONNX Runtime CPU 推理流程import numpy as np import cv2 import onnxruntime as ort class YOLO26ONNX: def __init__(self, onnx_path, conf_thres0.25, iou_thres0.45): self.session ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.input_shape self.session.get_inputs()[0].shape # [1, 3, H, W] self.conf_thres conf_thres self.iou_thres iou_thres def preprocess(self, img_bgr): # 这是从 ultralytics 源码里抠出来的 letterbox 逻辑 h, w img_bgr.shape[:2] target_h, target_w self.input_shape[2], self.input_shape[3] ratio min(target_w / w, target_h / h) new_w, new_h int(round(w * ratio)), int(round(h * ratio)) resized cv2.resize(img_bgr, (new_w, new_h), interpolationcv2.INTER_LINEAR) canvas np.full((target_h, target_w, 3), 114, dtypenp.uint8) dw, dh (target_w - new_w) // 2, (target_h - new_h) // 2 canvas[dh:dh new_h, dw:dw new_w] resized # BGR - RGB, HWC - CHW, 归一化 blob canvas[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 blob np.expand_dims(blob, axis0) return blob, ratio, dw, dh def postprocess(self, outputs, ratio, dw, dh): # 先做置信度过滤再做 NMS把框坐标减掉 letterbox 偏移并除以缩放比 # outputs shape: [1, 4 num_classes, num_anchors] preds outputs[0][0] # preds shape: [4 num_classes, num_anchors] boxes preds[:4].T # [num_anchors, 4] class_scores preds[4:].T # [num_anchors, num_classes] class_ids class_scores.argmax(axis1) confs class_scores.max(axis1) mask confs self.conf_thres boxes, confs, class_ids boxes[mask], confs[mask], class_ids[mask] # 转换为 xyxy x_center, y_center, bw, bh boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] x1, y1 x_center - bw / 2 dw, y_center - bh / 2 dh x2, y2 x_center bw / 2 dw, y_center bh / 2 dh candidates np.stack([x1, y1, x2, y2, confs, class_ids], axis1) # 这里可以接 nms 函数按 class 分别做 NMS 或者统一做 NMS return candidates上面代码里 NMS 部分我没有展开推荐大家直接复用成熟实现比如 torchvision.ops.nms 的等价逻辑或者自己翻译成纯 NumPy 的矩阵版本。最需要注意的是后处理时要把 letterbox 加的偏移量(dw, dh)减掉、把坐标除以ratio才能映射回原始图像尺寸。这一步是绝大多数“模型导出后检测框不准”的根源。5.2 TensorRT 部署时的 batch 与显存管理如果你导出了 TensorRT engine推理时用的是 TensorRT Python API 或者通过 ONNX Runtime 的 TensorRT provider。这里提醒三个点batch size导出 engine 时如果固定了 batch1那推理时只能一次一张地跑如果要支持多 batch导出时需要设置 batch 维度Ultralytics export 里 batchsize 参数可配。显存占用TensorRT engine 在推理时会额外申请 workspace如果你的显存比较紧张在导出时可以通过参数限制 workspace 大小避免推理时显存溢出。第一次推理慢TensorRT 第一次加载 engine 和选 kernel 需要预热时间生产环境建议在服务启动时就提前加载并 warmup 一次而不是等第一个请求进来才做。5.3 一个被低估的能力判断导出模型的正确“工作温度”推理引擎在不同精度模式下的运行速度差距非常大。FP32、FP16、INT8 三种模式下延迟可以差 3 到 10 倍。实际部署中优先级顺序是先用 FP32 把全链路跑通、产出结果正确再做 FP16 优化精度评估通过后再考虑 INT8 量化不要一上来直接上 INT8除非你非常了解量化校准集的概念我的建议是导出不是一次性的而是按性能需求分阶段迭代的。第一次先导入 ONNX 跑通正确性第二次导出 OpenVINO/Engine 追求速度第三次尝试 INT8 压缩体积。写在最后YOLO26 的模型导出本质上一句话把 PyTorch 的动态图世界搬到静态图推理引擎的确定世界里去中间所有的不确定性都要靠流程和验证来消灭。我个人在大量导出实践中有一个心得永远不要让“格式转换成功”成为你做导出任务的终点而是要拿“对下游完全可解释、可复现”作为交付标准。你可以顺手把验证脚本、输出 shape、精度误差记录、导出命令都一起放进一个说明文件里和模型一起交付。这样无论是给自己半年后回头看还是交给下一位接手的工程师都会省掉大把重复排雷的时间。如果你现在正好卡在某个导出报错里不妨回头按我排查链路的顺序一步步走大概率会在第五步之前找到问题症结。YOLO26 的代码和社区迭代速度都很快工具链本身也在不断变化但“导出前先确认结构、导出后立刻对齐验证、部署时复刻预处理”这三条原则是长期有效的。祝各位都能顺利把模型送到生产环境里好好地跑起来。
返回列表