简介:这份资源面向深度学习入门者与计算机视觉方向的开发者,提供一套基于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.py | Python 入口 | 启动 Web 服务,注册路由,串联上传、推理、返回结果 |
rubbish.py | Python 模块 | 封装 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 模型都先跑输入输出检查和批量一致性验证,省掉了很多半夜排查的麻烦。希望帮到你。
本文还有配套的精品资源,点击获取