news 2026/10/3 3:04:01

ONNX垃圾分类系统部署实战:从模型导入到Web服务避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ONNX垃圾分类系统部署实战:从模型导入到Web服务避坑指南

简介:这份资源面向深度学习入门者与计算机视觉方向的开发者,提供一套基于Python实现的垃圾分类识别项目,重点解决图像自动分类的落地问题,并采用ONNX格式导入模型以提升跨平台部署的灵活性。压缩包共6个文件,包含3个csv数据表、2个py源码文件及1个pyc编译文件,整体约12KB,csv多用于标签与历史记录存储,py文件承担模型推理与界面逻辑,结构精简便于快速理解项目脉络。项目围绕卷积神经网络对垃圾图像进行特征提取与类别预测,涵盖数据预处理、模型加载与结果输出等环节,适合作为课程设计或毕业设计的参考案例。目前已有116人学习下载,读者可从中获取完整的分类流程代码、ONNX模型导入思路以及轻量级项目目录组织方式,便于在此基础上二次开发或迁移到实际环保场景中验证。

1. 从一份 ONNX 垃圾分类包说起:它到底能不能直接跑起来

前阵子帮一个做环保设备的朋友看方案,他丢过来一个压缩包,名字叫「基于深度学习的垃圾分类系统(模型使用ONNX导入).zip」。他问得很直接:这东西能不能直接跑?我需不需要装 PyTorch?模型是不是还得自己训练?我拆开看了一遍,目录结构其实很典型——app.py是入口,rubbish.py里放着推理逻辑,static装前端资源,label.csv是类别映射,user_pwd.csv和history.csv分别管账号和识别记录,模型走 ONNX 导入。也就是说,它不是一个只给你看训练脚本的「课程作业」,而是一个带 Web 界面、能上传图片、能返回分类结果的完整小系统。

它解决的核心问题很明确:把「拍一张垃圾照片 → 判断属于哪一类 → 记录结果」这条链路做成可部署的形态。适合谁?一是做深度学习图像识别入门、想找一个能跑通前后端闭环的实战项目的人;二是手里有 ONNX 模型、想套一个轻量 Web 壳子做演示的工程师;三是做毕设或课程设计,需要一套结构清晰、依赖不夸张的代码参考。但要注意,它默认你已经有可用的 ONNX 模型文件,或者至少知道怎么把 PyTorch 模型转成 ONNX。如果你连.onnx怎么运行都还没概念,这篇会从环境、代码、参数到踩坑一条条拆开讲。

2. 拆开压缩包:目录结构、依赖与 ONNX 推理链路

2.1 文件清单与各自职责

先把包里的东西按角色分清楚,不然后面改代码容易找错地方。下面这张表是我按实际拆包习惯整理的,文件名来自项目正文,职责按这类 Flask + ONNX 项目的常见组织方式推断。

文件/目录类型作用
app.pyPython 入口启动 Web 服务,注册路由,串联上传、推理、返回结果
rubbish.pyPython 模块封装 ONNX 模型加载、图像预处理、推理、后处理
static/静态资源前端页面、CSS、JS、可能的上传占位图
label.csv数据文件类别索引到中文/英文标签的映射
user_pwd.csv数据文件简单账号密码存储,通常用于登录或演示权限
history.csv数据文件识别历史记录,追加写入
views/Python 包可能存放蓝图或视图函数,拆分路由逻辑
__pycache__/缓存目录Python 字节码缓存,部署时可删

这里最需要关注的是rubbish.py和label.csv的配合关系。推理输出的通常是类别索引或概率数组,必须通过label.csv映射成人类可读的垃圾类别,否则前端只会显示一个数字。

2.2 ONNX 推理链路:从图片到类别

ONNX 的价值在于跨框架互操作。训练时你可能用 PyTorch,部署时用onnxruntime加载,不需要把整个 PyTorch 塞进运行环境。常见做法是:训练脚本导出.onnx文件,推理端只装onnxruntime和图像处理库。下面这段代码是我按这类项目最常见的写法补全的,放在rubbish.py里,负责加载模型并跑一次推理。

import onnxruntime as ort import numpy as np from PIL import Image # 加载 ONNX 模型,指定 CPU 执行提供者 # 如果机器有 CUDA,可换成 ['CUDAExecutionProvider', 'CPUExecutionProvider'] session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"]) # 读取类别标签,假设 label.csv 每行是 "索引,标签" def load_labels(csv_path="label.csv"): labels = {} with open(csv_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue idx, name = line.split(",", 1) labels[int(idx)] = name return labels labels = load_labels() def preprocess(image_path, input_size=(224, 224)): # 打开图片并转 RGB,避免灰度或 RGBA 导致通道数不匹配 img = Image.open(image_path).convert("RGB") # 缩放到模型输入尺寸,常见是 224x224 img = img.resize(input_size) # 转 numpy 并归一化到 [0,1],再按 ImageNet 均值方差标准化 arr = np.asarray(img, dtype=np.float32) / 255.0 mean = np.array([0.485, 0.456, 0.406], dtype=np.float32) std = np.array([0.229, 0.224, 0.225], dtype=np.float32) arr = (arr - mean) / std # HWC -> CHW,并增加 batch 维度 arr = np.transpose(arr, (2, 0, 1)) arr = np.expand_dims(arr, axis=0) return arr def predict(image_path): input_tensor = preprocess(image_path) # 获取模型输入名,避免硬编码 input_name = session.get_inputs()[0].name outputs = session.run(None, {input_name: input_tensor}) # 假设第一个输出是 logits 或 probabilities logits = outputs[0][0] # 如果输出未经过 softmax,这里手动做一次 exp = np.exp(logits - np.max(logits)) probs = exp / exp.sum() pred_idx = int(np.argmax(probs)) return labels.get(pred_idx, "unknown"), float(probs[pred_idx])

逻辑说明:ort.InferenceSession是onnxruntime的标准入口,providers决定用 CPU 还是 GPU。预处理里的均值和方差必须和训练时一致,否则精度会掉得莫名其妙。session.get_inputs()[0].name比硬编码"input"稳,因为不同导出工具生成的输入名可能不同。后处理里手动 softmax 是因为有些导出模型输出的是 logits,不是概率。

参数说明:input_size必须和模型训练时的输入尺寸一致,常见是 224×224 或 299×299。label.csv的索引必须和训练时类别顺序一致,错一位结果全错。providers如果写 CUDA 但机器没装对应运行库,会直接报错,建议先用 CPU 跑通再换。

2.3 Web 入口与路由怎么串

app.py通常做三件事:初始化 Flask、注册路由、启动服务。上传图片的接口一般长这样:

from flask import Flask, request, jsonify, render_template import os from rubbish import predict app = Flask(__name__) UPLOAD_FOLDER = "static/uploads" os.makedirs(UPLOAD_FOLDER, exist_ok=True) @app.route("/") def index(): return render_template("index.html") @app.route("/api/classify", methods=["POST"]) def classify(): file = request.files.get("image") if not file: return jsonify({"error": "no file"}), 400 save_path = os.path.join(UPLOAD_FOLDER, file.filename) file.save(save_path) label, score = predict(save_path) # 追加历史记录,注意并发写入问题 with open("history.csv", "a", encoding="utf-8") as f: f.write(f"{file.filename},{label},{score:.4f}\n") return jsonify({"label": label, "score": score}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)

逻辑说明:request.files.get("image")对应前端表单字段名,字段名不一致会拿到None。保存文件后再推理,避免直接读内存流时某些图像库解析失败。history.csv用追加模式写入,简单但并发高时会出问题,后面避坑章节会讲。

参数说明:host="0.0.0.0"让局域网内其他机器能访问,只在本机跑可以写127.0.0.1。debug=False在生产环境必须关,否则会暴露调试信息。端口 5000 是 Flask 默认,冲突就换。

3. 环境配置与模型导入:从零把服务跑起来

3.1 Python 环境与依赖安装

这类项目对 Python 版本不挑,3.8 到 3.11 都能跑,但onnxruntime和numpy的版本要匹配。我一般用 conda 建一个干净环境,避免和系统里的包打架。

conda create -n refuse-cls python=3.10 -y conda activate refuse-cls pip install flask onnxruntime pillow numpy

逻辑说明:onnxruntime是推理引擎,不装 PyTorch 也能跑 ONNX 模型,这是这个项目最省事的地方。pillow负责图像读写,numpy负责数组运算。如果要用 GPU 推理,把onnxruntime换成onnxruntime-gpu,并确认 CUDA 和 cuDNN 版本匹配。

参数说明:python=3.10是保守选择,3.12 有些老版本onnxruntime还没轮子。-y是自动确认,脚本里常用。如果你用requirements.txt,常见内容是flask、onnxruntime、pillow、numpy四行,版本号可以不锁,但生产环境建议锁。

3.2 模型文件放哪、怎么确认输入输出

ONNX 模型文件通常叫model.onnx或best.onnx,放在项目根目录或models/下。放好后先别急着启动 Web,用一段脚本确认输入输出形状,这一步能省掉后面大量调试时间。

import onnxruntime as ort session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"]) for inp in session.get_inputs(): print("input:", inp.name, inp.shape, inp.type) for out in session.get_outputs(): print("output:", out.name, out.shape, out.type)

逻辑说明:inp.shape如果是[1, 3, 224, 224],说明输入是 batch×通道×高×宽,预处理必须按这个来。如果通道数是 1,说明模型吃灰度图,预处理要转L模式。输出形状如果是[1, N],N 就是类别数,必须和label.csv行数一致。

参数说明:providers先用 CPU,确认能跑通再换 GPU。如果报InvalidGraph或NoSuchFile,先检查路径和文件完整性,ONNX 文件损坏时也会报类似错误。

3.3 启动服务与第一次识别

确认模型没问题后,直接启动:

python app.py

浏览器打开http://127.0.0.1:5000,上传一张图片,看返回的标签和置信度。如果页面能打开但上传报错,先看终端堆栈,再看static/uploads目录有没有写权限。第一次识别建议用训练集里出现过的类别图片,确认链路通了再换陌生图片。

提示:如果启动时报Address already in use,说明 5000 端口被占,换port=5001或杀掉占用进程。

4. 避坑与排查:ONNX 垃圾分类部署里最容易翻车的五件事

4.1 现象:推理结果全是同一类,置信度还很高

原因:预处理和训练时不一致,最常见的是归一化参数写错,或者图片通道顺序搞反。另一个可能是label.csv索引和训练类别顺序错位。

解决:先打印预处理后的数组形状和数值范围,确认是[1,3,224,224]且数值在合理区间。再拿一张训练集图片跑,如果训练集也错,基本是标签映射问题。把label.csv和训练时的classes.txt逐行对比,顺序必须完全一致。

4.2 现象:onnxruntime报CUDAExecutionProvider不可用

原因:装了 CPU 版onnxruntime,或者 CUDA/cuDNN 版本和onnxruntime-gpu不匹配。

解决:先pip list | grep onnxruntime看装的是哪个包。要用 GPU 就卸掉 CPU 版,装onnxruntime-gpu,并确认 CUDA 版本符合官方对照表。实在搞不定就先用 CPU,垃圾分类这种小模型 CPU 推理一张图通常几十毫秒,演示够用。

4.3 现象:上传中文文件名图片后保存失败或乱码

原因:Flask 默认对文件名处理不友好,中文文件名在某些系统上会出问题。

解决:保存时用uuid重命名,保留原始文件名只写进history.csv。这样既避免路径问题,也防止同名文件覆盖。

import uuid ext = os.path.splitext(file.filename)[1] save_name = f"{uuid.uuid4().hex}{ext}" save_path = os.path.join(UPLOAD_FOLDER, save_name)

4.4 现象:history.csv偶尔丢记录或写入错乱

原因:多用户同时上传时,多个进程/线程同时追加写同一个文件,没有加锁。

解决:演示环境可以接受,生产环境建议换成 SQLite 或加文件锁。最低成本改法是用threading.Lock()包住写入操作,或者把历史记录写进数据库。

4.5 现象:模型文件换了一个,服务启动就报错

原因:新模型的输入尺寸、输入名或输出格式和旧代码不匹配。

解决:每次换模型都重新跑一遍 3.2 节的输入输出检查脚本,把input_size、input_name、输出后处理逻辑对应改掉。不要假设所有 ONNX 模型都长一样,导出工具和版本不同,差异很常见。

5. 进阶技巧:用 ONNX Runtime 做批量推理与置信度过滤

把单张推理跑通之后,真正影响体验的是两件事:批量处理速度和低置信度结果的处理。我一般会在rubbish.py里加一个批量推理函数,一次处理多张图片,减少 session 调用开销。

def predict_batch(image_paths, input_size=(224, 224), threshold=0.5): tensors = [preprocess(p, input_size) for p in image_paths] batch = np.concatenate(tensors, axis=0) input_name = session.get_inputs()[0].name outputs = session.run(None, {input_name: batch}) logits = outputs[0] exp = np.exp(logits - np.max(logits, axis=1, keepdims=True)) probs = exp / exp.sum(axis=1, keepdims=True) results = [] for i, prob in enumerate(probs): idx = int(np.argmax(prob)) score = float(prob[idx]) label = labels.get(idx, "unknown") if score < threshold: label = "待人工确认" results.append((image_paths[i], label, score)) return results

逻辑说明:np.concatenate把多张图的张量拼成一个 batch,ONNX Runtime 对 batch 推理有优化,比循环单张快。threshold是置信度阈值,低于阈值的输出「待人工确认」,避免把模棱两可的结果直接当结论。这个策略在垃圾分类场景里很实用,因为有些图片本身模糊或混合多种垃圾。

参数说明:threshold=0.5是保守起点,实际可以按业务调。如果误判成本高,调到 0.7 甚至 0.8;如果只是辅助提示,0.3 也能用。input_size必须和模型一致,批量推理时所有图片会被缩放到同一尺寸。

另一个技巧是给onnxruntime开图优化和线程数:

options = ort.SessionOptions() options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL options.intra_op_num_threads = 4 session = ort.InferenceSession("model.onnx", options, providers=["CPUExecutionProvider"])

逻辑说明:ORT_ENABLE_ALL让运行时做算子融合等优化,intra_op_num_threads控制单算子并行线程数,CPU 推理时适当调大能提升吞吐。但线程不是越多越好,超过物理核数反而会因调度开销变慢。

验证方法很简单:拿 20 张测试图,分别用单张循环和批量推理跑一遍,对比总耗时和结果一致性。如果结果不一致,优先检查预处理是否对每张图都独立执行了 resize 和归一化。我自己的习惯是,每次换模型或改预处理,都强制跑一遍这 20 张的对比,确认没有回归再上线。从那以后我每次导入新 ONNX 模型都先跑输入输出检查和批量一致性验证,省掉了很多半夜排查的麻烦。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 3:03:45

Java企业人事管理系统毕设:从设计到答辩的完整实战指南

每年到了毕设季和课程设计季&#xff0c;Java方向出镜率最高的项目类型里&#xff0c;“企业人事管理系统”绝对能排进前三。这个名字听起来不复杂&#xff0c;但真拿到手你会发现&#xff1a;涉及的角色多、业务流程长、要交付的东西也不只是代码——文档、PPT、答辩演示一样都…

作者头像 李华
网站建设 2026/10/3 3:02:44

大连理工openGauss上机作业全流程:从建库到查询的实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 3:02:42

驱动管理四合一:扫描、更新、备份、还原实操指南

1. 为什么驱动管理是刚需&#xff0c;而不是“偶尔想起来才做的事”你肯定遇到过这样的场景&#xff1a;电脑用着用着&#xff0c;突然没声音了&#xff1b;插上U盘或者手机&#xff0c;系统提示“无法识别的设备”&#xff1b;玩个游戏&#xff0c;画面卡成一帧一帧的&#xf…

作者头像 李华
网站建设 2026/10/3 3:02:20

Python Web订单系统毕设源码拆解:分层架构与部署实战

简介&#xff1a;一个基于Python Web开发的简易订单系统源码包&#xff0c;面向计算机专业学生与Python Web入门开发者&#xff0c;可用于理解从零构建一个包含用户认证、订单创建与管理等核心流程的完整Web应用。压缩包共95个文件&#xff0c;约216KB&#xff0c;主要包含31个…

作者头像 李华
网站建设 2026/10/3 3:01:59

北邮编译原理词法分析器实战:手写DFA与Token生成

简介&#xff1a;本资源是北京邮电大学计算机学院《编译原理》课程配套的词法与语法分析器实践项目&#xff0c;面向高校计算机专业学生及编译技术初学者&#xff0c;聚焦编译前端核心能力训练——从源码中识别token并构建抽象语法树。压缩包共12个文件&#xff0c;含4个C/C源码…

作者头像 李华
网站建设 2026/10/3 3:01:28

RubyMotion iOS开发实战:纯代码UIKit布局、真机调试与签名发布指南

1. 为什么第二篇要绕开语法糖&#xff0c;专攻 UIKit 和签名RubyMotion 的 iOS 开发系列写到第二篇&#xff0c;我默认你已经过了motion create demo的兴奋期&#xff0c;也知道了rake能编出原生 App。但你很可能卡在下一个路口&#xff1a;页面怎么写&#xff1f;控件怎么布局…

作者头像 李华