尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLOv11从PyTorch训练到ONNX跨平台部署与INT8量化

YOLOv11从PyTorch训练到ONNX跨平台部署与INT8量化 简介这份《零基础入门YOLOv11——从PyTorch训练到ONNX跨平台部署全流程》PDF文档面向希望系统掌握目标检测的零基础读者与进阶开发者围绕YOLOv11的单阶段检测优势展开覆盖从环境搭建、数据准备到模型训练、转换与跨平台部署的完整链路可应对安防监控、自动驾驶、工业检测等场景的入门与落地需求。文档共45页以1个PDF文件交付压缩包约2.29MB支持目录章节跳转、阅读器左侧大纲显示与章节快速定位查阅体验较为顺畅。目前已有77人学习下载。内容按引言、YOLOv11架构、环境搭建、数据准备、PyTorch训练、模型评估与优化、ONNX格式转换、ONNX跨平台部署及常见问题解决方案等模块展开既讲清骨干网络、颈部网络与检测头等核心概念也给出训练参数配置、评估指标计算、模型量化与部署排错思路适合边学边查、按目录复盘帮助读者少走弯路地完成从训练到部署的实践闭环。1. 零基础把 YOLOv11 从 PyTorch 训练跑到 ONNX 跨平台部署真正卡住的地方在哪很多人第一次接触这条链路时以为最难的是一行yolo train怎么拼参数。真上手才发现训练反而是最顺的一段装好 PyTorch数据摆对目录模型就开始掉 loss 了。真正让人返工的是训练之前的数据格式校验、训练之中的 batch 和 imgsz 组合以及训练之后的 ONNX 导出——导出的张量到底是什么形状、有没有内置 NMS、归一化在模型里还是在自己代码里这几件事任何一件没对齐部署端就会给出「框全是乱的」或者「一个框都没有」的结果。这条路线适合两类人一类是要把检测能力塞进自己已有系统的后端工程师PyTorch 训练只是手段Java 或 C 里跑 onnxruntime 才是目的另一类是需要快速验证一个视觉想法的人希望从零到能跑通的最小闭环尽量短。下面的内容按「环境与数据 → PyTorch 训练 → ONNX 导出与跨平台推理 → 小目标与部署细节」推进每一段都落到可复制的命令和参数上。2. PyTorch 训练环境搭建与 YOLO 格式数据集的准备环境这一步的目标很明确拿到一个能torch.cuda.is_available()返回 True 的独立环境并且知道数据集里每一条标注都合法。这两件事做完后面的训练基本不会有玄学问题。2.1 用 Anaconda 配 PyTorch 训练环境的最小命令序列先建一个干净的虚拟环境别往 base 里装东西。conda 负责隔离 Python 版本PyTorch 的安装命令一定要去 PyTorch 官网按自己的显卡驱动选因为 wheel 包是按 CUDA 版本分的装错了会出现「能 import torch但 cuda 不可用」这种最费时间的假成功。# 独立的训练环境Python 版本选 3.10 这类主流且轮子齐全的 conda create -n yolo11 python3.10 -y conda activate yolo11 # 先看驱动能撑到哪个 CUDA 版本再回 pytorch 官网复制对应那条 pip 命令 nvidia-smi # cuXXX 换成官网上和你驱动匹配的那串不要手抄别人的 pip install torch torchvision --index-url https://download.pytorch.org/whl/cuXXX # 训练框架、导出与推理运行时 pip install ultralytics onnx onnxruntime # 如果要用 GPU 推理装 GPU 版运行时它能和 CPU 版共存但优先级要自己指定 pip install onnxruntime-gpu参数上有两点容易踩--index-url必须指向 PyTorch 自己的源用国内镜像装默认版大概率拿到 CPU-only 的包onnxruntime和onnxruntime-gpu同时装在一个环境里时InferenceSession的providers顺序决定最终用哪个执行器建议显式写成[CUDAExecutionProvider, CPUExecutionProvider]而不是依赖默认值。装完立刻验证不要等到训练脚本报错才回头查python -c import torch, ultralytics; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))输出里True和显卡型号都出现这一步就算过了。只出现版本号而is_available()是 False说明装的是 CPU 包或者驱动太旧回到官网重新选 CUDA 版本。2.2 YOLO 格式数据集目录结构与标注合法性自检YOLO 系列读数据的约定很固定一张图配一个同名 txttxt 每行是类别 id 中心x 中心y 宽 高后四个值都是相对图片宽高的 01 归一化数值。用 labelimg 打标时如果保存成了 Pascal VOC 的 xml要转成 YOLO 格式再放进 labels 目录转换脚本网上很多但转完一定要自己校验一遍因为坐标越界不会让训练直接崩只会让某一类永远学不会。目录按下面这样摆data.yaml里的路径写绝对路径最稳dataset/ images/train/ images/val/ labels/train/ labels/val/ data.yaml# data.yaml path: /data/dataset train: images/train val: images/val names: 0: plate 1: car接着跑一段校验脚本把「有图无标签」「坐标越界」「类别 id 超出 names 数量」这三类问题一次性挑出来import os from pathlib import Path from PIL import Image root Path(/data/dataset) num_cls 2 # 与 data.yaml 里的类别数保持一致 bad [] for split in (train, val): img_dir, lbl_dir root / images / split, root / labels / split for img_path in img_dir.iterdir(): w, h Image.open(img_path).size lbl lbl_dir / (img_path.stem .txt) if not lbl.exists(): bad.append((img_path.name, 缺标签文件)) continue for i, line in enumerate(lbl.read_text().strip().splitlines()): parts line.split() if len(parts) ! 5: bad.append((img_path.name, f第{i}行字段数为{len(parts)})) continue cid int(parts[0]) vals [float(v) for v in parts[1:]] if cid 0 or cid num_cls: bad.append((img_path.name, f第{i}行类别越界 {cid})) if any(v 0 or v 1 for v in vals): bad.append((img_path.name, f第{i}行坐标越界 {vals})) print(f共 {len(bad)} 条问题) for name, why in bad[:20]: print(name, why)这段脚本只做静态检查不读像素内容所以几万张图也能秒级跑完。发现坐标越界通常是打标工具框拖到了图片外或者转换时忘了做归一化除法。类别越界多半是names的 id 从 1 开始写了YOLO 要求从 0 开始连续编号。提示划分 train/val 时按「场景」分而不是随机分。同一个视频抽出来的连续帧随机分到两边验证集 mAP 会虚高十几个点上线后立刻现原形。2.3 训练环境常见报错的定位表现象大概率原因处理方式CUDA out of memorybatch 或 imgsz 超过显存先降 batch再考虑降 imgsz两者不是等价缩放训练启动即报找不到标签path写成相对路径且工作目录变了改成绝对路径loss 一直是 nan学习率过大或标注坐标超过 1先跑校验脚本再把 lr0 降到 1e-3 以下训练速度极慢没开cache每轮都在读盘内存够就cacheram不够用cachediskGPU 占用低workers太小数据加载成瓶颈按 CPU 核心数的 0.60.8 倍给3. 用 PyTorch 跑 YOLOv11 训练命令、关键参数与指标解读环境通了以后训练本身就是一条命令加一组参数。这一章的重点不是「怎么开始训练」而是每个参数改了以后会发生什么、看什么曲线判断该停还是该改。3.1 一次完整训练的命令与参数含义yolo detect train \ modelyolo11n.pt \ data/data/dataset/data.yaml \ epochs150 \ imgsz640 \ batch16 \ device0 \ workers8 \ cacheram \ patience30 \ optimizerauto \ lr00.01 \ cos_lrTrue \ close_mosaic10 \ pretrainedTrue \ projectruns/plate \ nameexp1model传的是官方预训练权重文件名形如yolo11n.ptn/s/m/l/x 对应从小到大。零基础场景一律从 n 或 s 起步先跑通再换大模型否则调参的时间成本会翻几倍。imgsz是训练分辨率也是推理时的默认输入尺寸小目标多的话这个值比换更大模型更有效但它和显存占用大致呈平方关系。batch与imgsz要一起调。显存不够时优先降 batch 到 8 或 4因为 Ultralytics 会在 batch 变化时自动调整学习率相关策略一上来就把 imgsz 砍到 320等于把网络能看到的细节先砍掉一半。patience30指 30 轮没有提升就早停配合epochs150用能省下大量空转时间。close_mosaic10表示最后 10 轮关闭 mosaic 增强——mosaic 把四张图拼成一张能显著提升泛化但拼出来的目标普遍偏小且边界被裁切训练末尾关掉它让模型在接近真实分布的图上收尾mAP 通常还能再涨一截。注意想复用已有模型只微调头部可以加freeze10冻结前 10 层。这和 LLM 里常见的 LoRA 微调思路不同检测模型做的是迁移学习加冻结策略不是低秩适配别把两套概念混在一起。3.2 训练曲线该看哪几条什么时候该动手改终端输出的三组 loss 分工很明确box_loss管边框回归cls_loss管分类dfl_loss是分布焦点损失用于让边界预测更细。三条都在稳定下降就是健康状态。真正该看的是验证集指标metrics/mAP50快速上升后趋平说明模型已经学到主要特征metrics/mAP50-95明显落后于 mAP50说明定位精度不够方向是提高 imgsz 或加数据而不是猛加轮数训练 loss 继续降、验证 loss 开始升就是过拟合该做的是加数据增强、加数据量或者提前停。断点续训直接加resumeTrue复用上次的last.pt它会连优化器状态和轮次一起恢复比重新--weights last.pt训练更接近中断前的状态。3.3 训练完成后保存推理结果与批量导出验证阶段最实用的动作是拿best.pt在测试集上跑一遍并保存带框的图和 txtyolo detect predict \ modelruns/plate/exp1/weights/best.pt \ source/data/test \ imgsz640 \ conf0.25 \ iou0.7 \ saveTrue \ save_txtTrue \ save_confTrue \ projectruns/pred \ nametest1conf是置信度阈值框的得分低于它就丢弃iou是 NMS 的重叠阈值两个框 IoU 超过它就去掉得分低的那个。这两个值在验证集上做网格搜索比凭感觉定要靠谱把 conf 从 0.1 扫到 0.5看 F1 曲线峰值落在哪就把那里定成部署默认值。save_txtTrue输出的 txt 与训练标注格式一致可以直接和标注文件做 diff快速找出漏检的那批图属于哪个场景。4. YOLOv11 导出 ONNX 与跨平台推理落地从 PyTorch 走到 ONNX本质是把训练时那套 Python 依赖丢掉让推理端只需要一个.onnx文件和一个运行时。跨平台的价值就在这里——Java 服务、C 客户端、移动端都能用同一份模型。4.1 yolo export 导出 ONNX 的参数与输出张量布局yolo export \ modelruns/plate/exp1/weights/best.pt \ formatonnx \ imgsz640 \ opset12 \ simplifyTrue \ dynamicFalse \ halfFalse \ nmsFalseopset是 ONNX 算子集版本选 12 是兼容性和算子支持之间的平衡点太低有些算子表达不了太高则部分运行时和边缘芯片工具链还不认。simplifyTrue会调用 onnxsim 做常量折叠能砍掉一批冗余节点对推理速度有实际收益。dynamicFalse表示输入固定为 1×3×640×640固定形状的模型在各家运行时上优化最充分确实需要变分辨率时再开 dynamic但要接受速度损失。nmsFalse是关键决策点。不开 NMS 时导出模型的输出形状通常是[1, 4类别数, 预测框数量]其中前 4 个通道是cx, cy, w, h后面是各类别得分且归一化已经包含在模型内部——导出后的模型直接吃 01 的 float32 输入。开了 NMS 则输出变成三个张量框、得分、类别模型体积和计算图都变复杂边缘工具链支持度更差。跨平台部署我一般选不开把 NMS 放到语言侧实现可控性更强。4.2 用 onnxruntime 在 Python 与 Java 端加载模型先用一段最小代码确认输入输出形状不然后面所有预处理都是猜的import numpy as np, onnxruntime as ort, cv2 sess ort.InferenceSession( best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider], # 无 GPU 时自动落到 CPU ) inp sess.get_inputs()[0] print(输入:, inp.name, inp.shape, inp.type) img cv2.imread(test.jpg) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 训练用的是 RGBOpenCV 读出来是 BGR x cv2.resize(rgb, (640, 640)).astype(np.float32) / 255.0 x np.transpose(x, (2, 0, 1))[None] # HWC - NCHW并补 batch 维 out sess.run(None, {inp.name: x})[0] print(输出:, out.shape) # 形如 (1, 4nc, N)Java 端的调用结构几乎一样差别在预处理要自己写OrtEnvironment env OrtEnvironment.getEnvironment(); OrtSession sess env.createSession(best.onnx, new OrtSession.SessionOptions()); float[] data new float[3 * 640 * 640]; // 自己按 NCHW 顺序填值域 0~1 long[] shape {1, 3, 640, 640}; OnnxTensor tensor OnnxTensor.createTensor(env, FloatBuffer.wrap(data), shape); try (OrtSession.Result r sess.run(Collections.singletonMap(images, tensor))) { float[][][] pred (float[][][]) r.get(0).getValue(); // [1, 4nc, N] // 在 [4nc, N] 上逐列取最大类别分再按 cx,cy,w,h 还原到原图坐标 }Java 里最容易出错的两处一是通道顺序Java 没有np.transpose这种便利循环里就得写成data[c*640*640 y*640 x]二是坐标还原ONNX 输出的是相对 640 的归一化坐标要乘回原图宽高如果预处理用了 letterbox 补边还得把补边偏移减掉。4.3 INT8 量化静态量化的校准流程与掉点控制FP32 模型在边缘设备上跑不动时INT8 是首选方案。静态量化需要一批校准图来统计激活值分布通常从训练集里抽 200500 张覆盖各场景即可import glob, cv2, numpy as np from onnxruntime.quantization import quantize_static, CalibrationDataReader, QuantFormat, QuantType class CalibReader(CalibrationDataReader): def __init__(self, pattern/data/calib/*.jpg, imgsz640, nameimages): self.files, self.i, self.imgsz, self.name glob.glob(pattern), 0, imgsz, name def _prep(self, p): im cv2.cvtColor(cv2.imread(p), cv2.COLOR_BGR2RGB) im cv2.resize(im, (self.imgsz, self.imgsz)).astype(np.float32) / 255.0 return np.transpose(im, (2, 0, 1))[None] def get_next(self): if self.i len(self.files): return None x self._prep(self.files[self.i]); self.i 1 return {self.name: x} quantize_static( best.onnx, best_int8.onnx, calibration_data_readerCalibReader(), quant_formatQuantFormat.QDQ, # QDQ 格式对各家运行时的兼容性最好 weight_typeQuantType.QInt8, activation_typeQuantType.QInt8, per_channelTrue, # 逐通道量化精度损失明显小于逐张量 )QuantFormat.QDQ会在图上插入量化/反量化节点好处是能直接被 TensorRT、部分 NPU 工具链识别per_channelTrue对卷积权重的每一路输出通道单独算缩放因子是小模型量化不掉点的关键开关。校准集一定要包含真实场景的极端情况——夜间的图、过曝的图、目标极小的图否则校准出来的缩放因子会把动态范围压得太窄量化后小目标直接消失。指标FP32INT8 静态量化经验量级模型体积基准约为 1/4CPU 端单帧延迟基准常见降到 1/2 到 1/3mAP50 掉点—校准集覆盖好时 02 个点覆盖差时可达 5 点以上边缘工具链支持一般明显更好注意量化只在 CPU 或专用加速器上有收益。如果推理仍然走 GPU 的 FP16 路径INT8 反而会因为插入反量化节点而变慢先确认目标设备的执行器再决定要不要量化。5. 小目标优化与跨平台部署的几个落地技巧模型能跑通之后剩下的差距几乎都在细节里。小目标检测和预处理对齐是返工率最高的两块。5.1 小目标检出率分辨率、切片与增强的组合拳小目标在 640 分辨率下可能只占十几个像素网络下采样到最后一层时特征基本被抹平。三条路按性价比排序先提高imgsz到 960 或 1280代价是显存和延迟同步上升再开高分辨率训练时配套的增强参数scale调大一点、mosaic保留更久让模型见过更多小尺度样本如果目标在原图里本来就只占很小一块而整图很大那更该用切片推理——把大图切成带重叠的子图分别送进模型再在整图坐标系里做一次全局 NMS 合并。切片推理有两个必调参数切块大小和重叠比例。重叠太小跨切块的目标会被切断两边各出一个半截框重叠太大同一目标在多个切块里被重复检出全局 NMS 的iou阈值就得相应调低。经验起步值是重叠 20%iou从 0.5 开始往上试。5.2 部署侧预处理必须和训练侧逐项对齐推理精度对不上九成是预处理不一致。要逐项核对的只有四件事通道顺序RGB 还是 BGR、输入值域01 还是 0255、缩放方式直接 resize 还是 letterbox 补边、插值算法。训练侧 Ultralytics 用的是 letterbox 加双线性插值、RGB、01部署侧只要照抄这四项输出坐标的还原公式就是可推导的。用直接 resize 代替 letterbox 会改变宽高比框会系统性偏移而且是那种「看起来能用、仔细看每个框都偏一点」的偏移最难排查。验证不要靠肉眼看图用同一张图做端到端回归PyTorch 侧跑一次拿到框坐标ONNX 侧跑一次拿到框坐标两者做 diff最大偏差应该在 1 个像素量级。超过这个数量级就说明预处理或坐标还原有问题而不是模型精度问题。5.3 用一张固定图做长期的推理回归基线部署完成后把上面那张对比图和相关脚本固定下来每次换 ONNX 版本、换运行时版本、重量化之后都跑一遍。基线图要选一张目标数量中等、包含边界目标的图输出保存成 txt 提交到代码仓库。这样某次升级导致输出布局从[1, 4nc, N]变成别的形状、或者量化后某个类别整类消失时diff 会立刻暴露出来比上线后从业务侧反馈发现要早得多。本文还有配套的精品资源点击获取
返回列表