ARTICLE DETAIL

资讯详情

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

垃圾分类模型ONNX部署实战:从PyTorch转换到ONNX Runtime推理

垃圾分类模型ONNX部署实战:从PyTorch转换到ONNX Runtime推理 简介面向垃圾分类场景的深度学习实践资源基于Python与ONNX模型导入实现图像自动识别旨在提升垃圾分类效率与准确性适合深度学习初学者、环保类应用开发者以及课设、竞赛项目参考。压缩包共六个文件包含两个Python脚本、一个pyc缓存及三个CSV数据文件整体仅12KB结构精简。其中py脚本承担主程序与辅助逻辑csv文件存放标签映射及历史记录pyc为编译缓存便于快速定位核心代码。目前已有116人学习下载。资源体量虽小但覆盖数据预处理、模型调用、预测输出等关键环节并体现CNN等网络在图像识别中的应用以及使用ONNX跨平台部署的思路。借助CSV数据可理解类别标注与交互记录适合作为理解垃圾分类系统原理与快速部署的入门参考。1. 把训练好的垃圾分类模型转成 ONNX 再部署不是炫技是部署的刚需手头已经有一个训练好的垃圾分类模型要接到摄像头或服务端上最顺手的做法不是拿 PyTorch 直接跑而是先把模型转成 ONNX再用 ONNX Runtime 导入推理。基于深度学习的垃圾分类系统里这个 ONNX 导入环节往往决定了部署能多快落地。因为生产环境的机器不一定会装 PyTorch也可能没有 GPUONNX 作为中间格式既能隔离训练框架又能让同一份模型在 CPU、GPU、甚至边缘设备上跑得足够快。这篇文章就围绕“模型使用 ONNX 导入”这条主线讲清楚从 PyTorch 训练好的分类器到 ONNX Runtime 推理全流程怎么做、参数怎么调、哪些地方会翻车。这里说的是最常见的落地场景你训练了一个垃圾图像分类模型类别可能有可回收垃圾、厨余垃圾、有害垃圾和其他垃圾也可能细分成塑料、玻璃、纸张等几十类。训练阶段你用的是 PyTorch但最终要部署到摄像头端或者给别人调用这时候 ONNX 就是那个“后悔药”加“万能插头”。新手照着下面的步骤能跑通熟手可以直接跳去第 5 章看避坑和边界。2. 先把模型训练到能导出分类模型选型与 ONNX 转换前置条件2.1 为什么垃圾分类分类模型首选轻量 CNN垃圾分类本质上是图像分类任务不是目标检测。虽然很多人一听“垃圾分类系统”就想起 YOLO 去识别垃圾桶里的瓶子但实际落地时如果只判断“这张图属于哪一类垃圾”一个轻量 CNN 分类器就够了。常见做法是用 ResNet18、MobileNetV2 或者 EfficientNet-Lite这些网络在 ImageNet 上预训练过迁移到垃圾分类数据上只需要替换最后的全连接层。相比 YOLO 这类目标检测网络轻量分类模型参数量小、导出 ONNX 后占体积小摄像头端推理帧率也更容易跑上去。选型理由要结合部署目标说如果垃圾分类系统是放在小区垃圾桶旁边跑在树莓派或者低功耗 Mini PC 上那么 MobileNetV2 比 ResNet50 实际得多。ResNet50 精度高几个点但 ONNX Runtime 在 CPU 上跑单帧可能需要 80 到 150 毫秒而 MobileNetV2 压到 30 毫秒左右。垃圾分类不是医学影像不需要 99% 的准确率能稳定区分纸箱、易拉罐、剩饭这三类轻量模型完全够用。我一般建议先拿 MobileNetV2 起步跑通 ONNX 导入流程后再换大模型整个过程只改一行权重文件路径。2.2 PyTorch 训练与导出前的检查点输入尺寸、预处理、BatchNorm 融合ONNX 导出不是最后一步才想的事训练阶段就要埋好伏笔。最关键的一项是输入尺寸。很多人在 PyTorch 里训练时用 224×224但测试或部署时又随手 resize 成 256×256导致 ONNX 模型和实际输入不匹配。推荐做法是训练、验证、导出的预处理统一写成一个函数包括 resize 尺寸、归一化均值标准差、通道顺序。这些参数一旦不一致模型在 ONNX Runtime 上跑出来就是一片乱码式的分类结果。第二个是 BatchNorm。PyTorch 训练时 BatchNorm 层统计的是批内均值方差导出 ONNX 时最好先用训练好的模型跑一次验证模式把 BatchNorm 的 running_mean 和 running_var 固定下来再执行 torch.onnx.export。严格来说只要调用了 model.eval()PyTorch 就会切换到推理模式BatchNorm 不再用 batch 内的统计量但很多新手在 model.train() 状态下直接导出导出的 ONNX 模型里 BatchNorm 行为是错的。这是个非常隐蔽的坑后面第 5 章会再提到。第三个是输入张量是否要加 batch 维。PyTorch 分类模型的输入形状通常是 [N, C, H, W]导出 ONNX 时可以把它固定为 [1, 3, 224, 224]也可以设为动态维度。为了让部署端少写代码我习惯导出时固定 batch1因为摄像头单帧推理几乎不会用到 batch1。动态 batch 看上去灵活但 ONNX Runtime 里做动态维度要额外处理输入 shape性价比很低。2.3 用 torch.onnx.export 导出分类模型的完整代码以下是 PyTorch 分类模型导出 ONNX 的最小实现假设你已经训练好一个 MobileNetV2 并保存了权重文件。导出过程不依赖 GPUCPU 就可以完成。import torch import torchvision.models as models from torchvision import transforms # 1. 加载模型并切换到推理模式 model models.mobilenet_v2(pretrainedFalse) model.classifier[1] torch.nn.Linear(model.classifier[1].in_features, 4) # 4类垃圾分类 state_dict torch.load(garbage_mobilenetv2.pth, map_locationcpu) model.load_state_dict(state_dict) model.eval() # 2. 构造一个与训练时完全一致的预处理管道 preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) # 3. 构造示例输入并导成 ONNX dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, garbage_mobilenetv2.onnx, input_names[input], output_names[logits], dynamic_axes{input: {0: batch}, logits: {0: batch}}, opset_version12, ) print(导出完成garbage_mobilenetv2.onnx)逻辑说明先加载训练好的权重强制把模型放到 CPU 上避免 GPU 上导出留下额外的设备状态。第二步构造的预处理管道必须和你训练时一致否则后面 ONNX Runtime 的输入数据分布对不上模型。第三步是真正的导出dynamic_axes 这里只动态化了 batch 维H、W 还是固定的 224。参数说明opset_version 是 ONNX 算子集版本PyTorch 官方支持和 Runtime 支持有个交集。选择 opset_version12 是一个比较稳的折中因为 ONNX Runtime 1.7 以上的版本都支持到 12而 PyTorch 1.9 以上导出 12 也很成熟。如果你后面要做 INT8 静态量化可能把 opset 调到 13 或 14 以获得更好的量化支持但务必先确认目标 ONNX Runtime 版本支持。如果导出时遇到“Unsupported opset”报错优先降低 opset_version而不是去升级 Runtime。3. 用 ONNX Runtime 在本地跑通推理从加载到垃圾分类输出3.1 选定 ONNX Runtime 的 Python 接口与 Session 配置ONNX 导入这一步核心工作就是让 ONNX Runtime 把.onnx文件加载成推理会话。ONNX Runtime 提供onnxruntime.InferenceSession指定providers可以选择 CPU 或 GPU 执行环境。对于垃圾分类这种分类模型最常见的部署是 CPU 推理所以先不要急着上 CUDA直接用 CPU 跑通再优化。下面是加载并检查模型的代码。import onnxruntime as ort import numpy as np # 加载 ONNX 模型 session ort.InferenceSession( garbage_mobilenetv2.onnx, providers[CPUExecutionProvider], ) # 打印模型的输入输出信息确认张量名称、形状和类型 for inp in session.get_inputs(): print(输入:, inp.name, inp.shape, inp.type) for out in session.get_outputs(): print(输出:, out.name, out.shape, out.type)这里的providers是一个列表ONNX Runtime 会按顺序选择可用的执行提供程序。如果只写[CPUExecutionProvider]那就是纯 CPU 跑。后面想用 GPU可以在列表里加上CUDAExecutionProvider但要保证onnxruntime-gpu包和 CUDA 版本匹配否则会在初始化时静默回退到 CPU表现是日志里出现一行警告很多人没注意到。get_inputs()拿到的shape通常是动态的例如[batch, 3, 224, 224]这个 batch 就是导出时设置的动态维度。运行代码前先确认模型用的是 NCHW 顺序ONNX 里图像基本都是 NCHW这一点和第 5 章要讲的数据预处理直接挂钩。3.2 跑通第一帧垃圾分类输入预处理与后处理代码模型加载没问题之后就要喂一张真实图片进去。这里最容易出问题的地方不是模型而是把图片转成网络输入的代码。下面给出一段完整的推理脚本读者可以直接把图片路径替换成自己的垃圾照片。import onnxruntime as ort import numpy as np from PIL import Image from torchvision import transforms # 定义类别标签顺序要和训练时一致 labels [可回收垃圾, 厨余垃圾, 有害垃圾, 其他垃圾] # 预处理管道 preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) # 加载图片 img Image.open(test_plastic_bottle.jpg).convert(RGB) tensor_img preprocess(img) # 形状 [3, 224, 224] # 增加 batch 维并转为 numpy float32 input_data tensor_img.unsqueeze(0).numpy() # 形状 [1, 3, 224, 224] # 运行推理 session ort.InferenceSession(garbage_mobilenetv2.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name logits session.run(None, {input_name: input_data})[0] # 形状 [1, 4] # 后处理取最大 logits 对应的类别 pred_idx np.argmax(logits[0]) confidence logits[0][pred_idx] print(f分类结果: {labels[pred_idx]}, logits{confidence:.4f})逻辑说明图片先经过和导出时一致的预处理管道得到[3, 224, 224]的归一化张量然后unsqueeze(0)在 batch 维上变成[1, 3, 224, 224]。ToTensor()会把 PIL 图片的 HWC 转成 CHW并自动除以 255这个顺序不要手动改。.numpy()转成 float32 是 ONNX Runtime 的标准输入格式。参数说明session.run(None, {input_name: input_data})里的第一个None表示返回所有输出节点的结果。对于分类模型输出是 logits不是概率。如果你训练时用了CrossEntropyLoss这个 loss 内部已经包含了 softmax所以模型输出是未经过 softmax 的 logits。想要输出 0 到 1 的概率需要自己加一步scipy.special.softmax或者用np.exp手工计算这里先取 logits 最大值来做分类是完全没问题的因为 softmax 是单调递增的不影响argmax结果。3.3 性能参数怎么设CPU 线程数、GPU EP 与执行模式ONNX Runtime 在 CPU 上的表现很大程度取决于会话配置。默认情况下InferenceSession会使用机器的全部逻辑核但部署在摄像头端时往往不希望它把 CPU 吃满因为同一个设备上还要跑别的服务。这时要主动设置线程数。import onnxruntime as ort import os # 限制 ONNX Runtime 使用 4 个线程 sess_options ort.SessionOptions() sess_options.intra_op_num_threads 4 sess_options.inter_op_num_threads 1 sess_options.execution_mode ort.ExecutionMode.ORT_SEQUENTIAL session ort.InferenceSession( garbage_mobilenetv2.onnx, sess_optionssess_options, providers[CPUExecutionProvider], )参数说明intra_op_num_threads是每个算子内部并行线程数inter_op_num_threads是算子之间并行线程数。模型推理时一个算子的张量乘法内部用 intra多个算子之间的流水并行用 inter。对于单张图片推理建议intra_op_num_threads4, inter_op_num_threads1因为分类模型算子之间有强依赖并行流水意义不大。ORT_SEQUENTIAL表示顺序执行算子可以避免跨算子多线程竞争单次推理延迟更稳定。如果你想在 GPU 上跑最简单的方式是安装onnxruntime-gpu然后提供[CUDAExecutionProvider, CPUExecutionProvider]。第一个是首选第二个是兜底。不过要提醒CPU 和 GPU 的输入输出数据格式没有区别但 GPU 要不断拷数据单张小图反而可能比 CPU 更慢。垃圾分类摄像头场景如果不是特别大的模型CPU 多线程完全够用。4. 从单张图片到实时摄像头垃圾分类系统的集成路径4.1 摄像头推理与帧率控制队列、跳帧、缓冲区单张图片推理跑通后很多人直接跳到摄像头视频流发现画面卡成幻灯片。这是因为他们把每一帧都送进模型推理而分类模型单帧推理可能需要 30 到 100 毫秒再加上读取摄像头和解码帧率自然上不去。常规做法是引入跳帧机制每读取 3 帧只推理 1 帧其余帧直接沿用上一次的推理结果。这样视觉上几乎无感但 CPU 压力直接降到三分之一。更稳的方案是生产者-消费者队列。摄像头线程只负责读帧并放入队列推理线程从队列取帧并推理两边的速度解耦。这里用 OpenCV 读取摄像头并用一个简单的队列实现。import cv2 import queue import threading import numpy as np import onnxruntime as ort from torchvision import transforms from PIL import Image frame_queue queue.Queue(maxsize4) def camera_thread(rtsp_url): cap cv2.VideoCapture(rtsp_url) while True: ret, frame cap.read() if not ret: break # 丢帧策略队列满时直接丢弃新帧 if frame_queue.full(): continue frame_queue.put(frame) cap.release() def inference_thread(session, preprocess): while True: frame frame_queue.get() rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil_img Image.fromarray(rgb) input_data preprocess(pil_img).unsqueeze(0).numpy() logits session.run(None, {input: input_data})[0] label_id np.argmax(logits[0]) # 这里把分类结果画到帧上再显示或推流 cv2.putText(frame, str(label_id), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(inference, frame) if cv2.waitKey(1) 0xFF ord(q): break逻辑说明frame_queue.maxsize4是缓冲区大小放在这里是因为推理速度比读取摄像头慢队列一旦堆满新帧会被丢弃避免内存疯涨。camera_thread里那个 Full 判断就是一种跳帧策略比固定丢帧更智能它等推理线程有空位时再喂下一帧。inference_thread里先做 BGR 到 RGB 转换因为 OpenCV 读出来是 BGR而训练时用的是 RGB这种通道顺序错误也是垃圾分类系统里特别常见的坑。参数说明frame_queue.maxsize不宜过大4 到 8 比较合适。太大导致延迟增加看到的是几秒钟前的垃圾太小又容易让摄像头线程空转。跳帧丢帧策略的核心目标是让队列稳定在非满非空的状态那意味着推理速度和输入速度基本匹配。4.2 集成到 Flask/FastAPI 服务模型实例与请求并发如果垃圾分类系统要作为 API 服务给小程序或后台调用最常见的做法是启动一个 FastAPI 服务对外暴露一个图片分类接口。这里必须注意ONNX Runtime 的InferenceSession是线程安全的可以在多线程请求下复用同一个 session不需要每次请求都重新加载模型。正确做法是在服务启动时创建一个全局 session。from fastapi import FastAPI, UploadFile import onnxruntime as ort import numpy as np from PIL import Image import io from torchvision import transforms app FastAPI() # 全局初始化只加载一次模型 session ort.InferenceSession( garbage_mobilenetv2.onnx, providers[CPUExecutionProvider], ) preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) app.post(/classify) async def classify(file: UploadFile): img_bytes await file.read() img Image.open(io.BytesIO(img_bytes)).convert(RGB) input_data preprocess(img).unsqueeze(0).numpy() logits session.run(None, {input: input_data})[0] label_id int(np.argmax(logits[0])) return {label_id: label_id, logits: logits[0].tolist()}参数说明session是模块级全局变量FastAPI 的每个请求协程都会复用这个 session。ONNX Runtime 内部会在不同线程上排队执行所以不用担心并发写冲突。如果对于并发量非常高的场景可以创建多个 session每个 session 绑定特定数量的线程再用连接池分配请求到不同 session。但垃圾分类 API 通常不会达到这种压力单 session 足够。4.3 模型升级与 ONNX 版本兼容性管理部署系统运行一段时间后一定会遇到模型升级新增了垃圾类别或者换了更大的训练集。升级 ONNX 模型不是简单替换文件因为你训练脚本里的类别顺序、预处理参数可能已经改了。我踩过这样的坑新模型把第 0 类从“可回收垃圾”改成了“干电池”但服务端类别列表没更新导致所有分类结果都错位。常规做法是把模型版本和目录绑定例如models/garbage_v1.onnx、models/garbage_v2.onnx每个版本旁边放一个labels.json记录类别顺序和预处理参数。代码里加载模型时同时加载这个 json 文件保证模型和元数据同步。模型的opsets版本也要记录在案升级 ONNX Runtime 之后再重新验证一次模型不要盲目跨版本升级。5. 垃圾分类 ONNX 部署避坑6 个真实翻车场景与排查方法5.1 输入张量格式不对NCHW 与 HWC 的混用现象推理结果完全错乱某些类别概率固定为 0换一张图结果也一样错。原因ONNX 模型期望的输入是 NCHW 顺序即[1, 3, 224, 224]而你在预处理时用了 OpenCV 的numpy数组直接reshape成[1, 224, 224, 3]。形状是让模型接受了但通道顺序对不上模型看到的每个像素都是按 BGR 或 RGB 错位重排的乱数据。解决记住一句话ONNX Runtime 只认你导出模型时定义的输入形状。打开模型查看session.get_inputs()[0].shape然后构造输入时直接对照。用 PyTorch 的transforms.ToTensor()最不容易错因为它会帮你把 HWC 转 CHW 并归一化。手写 numpy 转换时一定要先np.transpose(img, (2, 0, 1))。5.2 预处理只做了缩放没做归一化垃圾图片识别全乱现象模型在测试集上准确率 94%导出 ONNX 后丢给服务端真实图片全分类到同一类。原因训练时你用 ImageNet 的 mean/std 做了归一化但部署代码里只resize后直接转成 float32 喂给模型。模型训练时看到的是中心化后的分布你在部署端给它未经中心化的数据分布偏移非常大。解决把归一化写进预处理管道严格复用训练时的参数。推荐在项目里单独放一个preprocess.py训练和部署都从同一个模块导入避免两边各写各的。如果模型输入范围是 0 到 1 而不是 -1 到 1也要保证一致ONNX 模型本身不会记录这些信息全看导出端怎么约定。5.3 模型输出是 logits 不是概率分类阈值怎么定现象分类结果大多数时候正确但某种垃圾的置信度忽高忽低设定 0.5 阈值后召回很低。原因ONNX 导出的是 logits不是 softmax 后的概率。logits 的取值范围可以到几十甚至几百直接套 0.5 阈值当然出错。垃圾分类场景经常需要区分“可回收垃圾”和“其他垃圾”如果只取 argmax你会发现所有垃圾都被分到可回收因为模型 bias 偏向训练样本多的类别。解决先对 logits 做 softmax 再判断。日志里打印 softmax 后的数值看看它们的分布。如果某一类的概率总是大于 0.8而对其他类总是 0.2说明类别不平衡。可以调整全连接层的 bias或者干脆用 logits 上手动加一个阈值先用验证集求出每个类别的召回率和精确率的平衡点。这个阈值不是 0.5通常要调到 0.7 甚至 0.9。5.4 ONNX 算子版本过高ONNX Runtime 不认现象使用更高精度模型导出后CPU 上直接报错“No Op found for GatherElements”或者“Unsupported operator”。原因新模型用 PyTorch 较新的版本导出opset_version 被设成了 17 或 18但部署端的 ONNX Runtime 还是 1.12只支持到 opset 14。算子集版本向下兼容反过来不行。解决导出时把opset_version固定为部署端 Runtime 支持的最高版本。比如 ONNX Runtime 1.14 支持 opset 19但没有必要用那么新12 到 14 基本都是性能不错的区间。检查方法是onnxruntime.__version__查看版本再对照官方算子集支持表。旧 Runtime 不想升级的话把导出代码里的 opset_version 调低即可。5.5 量化后精度崩了哪些层不适合 INT8现象对 ONNX 做 INT8 量化后模型体积降了四倍但垃圾分类准确率从 93% 掉到 70%完全不可用。原因量化对激活值的动态范围非常敏感。分类模型的最后一层和开头的卷积层通常不适合直接量化因为这些层的激活值范围过大INT8 表达不下。解决不要做全量化用混合量化。ONNX Runtime 的quantize_static接口支持指定不量化的算子通常采用per_channel的方式量化卷积层保留最后的线性层为 FP32。推荐的做法是先做动态量化也就是只量化权重不量化激活精度损失通常能控制在 2% 以内然后用校准数据集做静态量化最后对精度不达标的部分逐层检查。量化是一个“可接受精度损失”的工程判断不是零成本压缩。5.6 摄像头推理卡顿单帧延迟与吞吐量不是一回事现象摄像头画面一卡一卡的但单张图片推理只要 40 毫秒换成摄像头后就变成 200 毫秒。原因单帧延迟测的是模型推理时间而摄像头端真正影响观感的是端到端延迟包括读取摄像头、颜色转换、队列排队、推理、画框显示。如果你的推理线程落后于摄像头线程队列会持续堆积decode 的帧永远排不上队观感就是越来越卡。解决优先测端到端延迟而不是模型延迟。用time.time()包住从读取到显示的全过程找到耗时超过 10 毫秒的阶段。摄像头读取方式也要注意cap.read()默认会等下一个帧如果同时开太多 OpenCV 窗口也会拖累显存。减少跳帧把frame_queue的 maxsize 降下来一般能解决。6. 把 ONNX 模型再推进一步INT8 量化与端侧部署验证6.1 用 onnxruntime.quantization 做 INT8 动态量化前面避坑章节提到量化可能崩精度但不要因此放弃。对于垃圾分类这种资源受限场景把模型压到四分之一体积推理速度提升一两倍很值得尝试。最安全的切入点是动态量化它不需要标注数据集直接产出一个 INT8 权重模型。下面是核心代码。from onnxruntime.quantization import quantize_dynamic, QuantType, QuantizationMode # 只量化权重保留激活为 FP32 quantize_dynamic( model_inputgarbage_mobilenetv2.onnx, model_outputgarbage_mobilenetv2_int8.onnx, weight_typeQuantType.QInt8, ) print(动态量化完成输出: garbage_mobilenetv2_int8.onnx)参数说明weight_typeQuantType.QInt8一般优于QUInt8在某些 CPU 上 INT8 的执行速度更快。动态量化不涉及激活值的校准所以精度损失通常比较温和。如果这个模型跑起来后准确率还在 90% 以上那基本满足部署需求。如果不行再考虑静态量化需要准备几百张校准图片。6.2 量化前后精度对比与可接受阈值量化完成后不要急着部署先做一个量化前后对比脚本。用同一批验证集图片分别跑原模型和量化模型统计 logits 的 argmax 一致率。分类任务里常见的一致率在 95% 以上就可以认为是无损否则回到 5.5 节的混合量化方案。我在项目里习惯把对比脚本写成固定函数输出一张表格类别、原模型置信度、量化模型置信度、是否一致。这个表格同时用于验收也会记录在项目 README 里方便后来者判断模型是否适合继续压缩。垃圾分类这类任务中用户真正在意的往往只有少数高风险类别比如“有害垃圾”不能分错只要这些类别一致率高其他类别可以接受。6.3 我的习惯每版模型固定一个 ONNX 算子集版本号最后分享一个我自己的教训。早期我导出模型时总是不填 opset_version让 PyTorch 用默认值。后来模型从 PyTorch 1.8 换到 2.0导出的算子集版本也跟着升级部署端还在用老 Runtime结果出现各种算子不支持的报错。现在我做垃圾分类系统每个模型文件名都带上算子集版本例如garbage_v3_op13.onnx同时写进部署配置。这样即使后来 Runtime 全面升级我也能知道每个模型是在哪个算子集上验证过的。验证方法也不难用一个 python 脚本读取 ONNX 文件打印模型信息包括算子上报的 opset_import 版本再把推理脚本做成参数化同一个测试图片分别跑 FP32 和 INT8 模型输出对比结果。这套流程走完你就可以放心把基于深度学习的垃圾分类系统交出去因为 ONNX 导入这条链路已经验证过后续只需要维护好类别列表和阈值。至于要不要上更高级的静态量化或者 TensorRT建议先跑通动态量化让整个部署链路稳定再按真实设备的性能需求优化。老实说我见过太多垃圾分类项目在训练阶段追求千分之几的准确率最后却因为 ONNX 导入后输入格式错误导致上线回滚。先固定输入尺寸、预处理和算子集版本这三个变量你的系统就不再是一门玄学。希望这次的完整拆解能帮到你也希望你早点把自己的 ONNX 模型跑起来。本文还有配套的精品资源点击获取
返回列表