news 2026/10/1 4:24:37

基于Python的垃圾分类系统毕业设计:从CNN模型训练到Flask部署全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python的垃圾分类系统毕业设计:从CNN模型训练到Flask部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的Python垃圾分类系统毕业设计资源,包含完整源码与部署指南,适合作为期末课程设计、大作业或毕设参考,也便于基础较好的学习者在此基础上二次开发。资源包共126个文件,约66.06MB,涵盖20个Python脚本、13个Jupyter Notebook、12个Vue前端组件、18张运行截图及若干JSON、HTML、Markdown、PDF、Word文档与ONNX模型文件,从算法训练、前后端实现到部署配置均有覆盖,并附有参考报告与课程实践模板,目录结构清晰,便于按模块查阅。目前已有104人学习下载。整体项目经过调试测试,答辩评审分达98分,读者可据此快速理解垃圾分类识别流程、模型调用方式与系统部署思路,减少从零搭建的时间成本,对撰写论文与准备答辩具有较高的借鉴价值。

1. 从一张宿舍楼下的垃圾桶照片说起:垃圾分类系统到底在做什么

去年帮学弟看毕业设计,他选题是"基于 Python 的垃圾分类系统",开题报告写了八页,代码却只有两百行——一个 Flask 页面加一个调模型接口的函数,连数据集长什么样都没见过。答辩前一周他来找我,说老师问"你的模型在哪些类别上翻车最多",他答不上来。这不是个例,每年计算机毕业设计里,垃圾分类系统是被选得最多、也最容易被做浅的题目之一。

这个标题背后其实是一条完整的工程链路:图像采集、数据清洗、模型训练、推理服务、前端交互、部署上线。它解决的核心问题是——把一张随手拍的垃圾照片,映射到"可回收物 / 厨余垃圾 / 有害垃圾 / 其他垃圾"四分类中的一类,并且给出足够快的响应。适合谁做?适合想拿一个能跑通、能演示、能讲清楚技术细节的计算机毕业设计的学生,也适合想练一遍"从数据集到 Web 服务"完整流程的 Python 入门者。下面我按自己带过几届毕设的经验,把这条链路拆开讲,源码结构和部署步骤都会落到能直接抄的程度。

2. 垃圾分类系统的技术选型:为什么是 CNN 而不是规则匹配

2.1 图像分类任务的本质与模型选型逻辑

垃圾分类识别在算法层面就是一个标准的图像分类问题。输入是 RGB 图像,输出是四类概率分布。有人会想用颜色直方图或者边缘检测做规则匹配,比如"绿色的是厨余、蓝色的是可回收"——这条路在实验室里能跑出 60% 左右的准确率,但一换光照、一换拍摄角度就崩。原因很简单:垃圾的视觉特征不是颜色决定的,塑料瓶可以是透明的,纸盒可以是彩色的,规则匹配无法覆盖这种类内差异。

所以主流做法是卷积神经网络。选型上有三个梯队:ResNet50 精度稳但参数量 25M,MobileNetV3 参数量只有 5M 左右、推理快,EfficientNet-B0 在精度和速度之间平衡得不错。毕业设计场景我一般推荐 MobileNetV3-Small 或 ResNet18,理由是:训练在单张消费级显卡甚至 CPU 上能跑完,推理延迟在 50ms 以内,答辩演示不会卡。如果你追求更高的精度且实验室有卡,可以上 ResNet50 做对比实验,这本身就是论文里的一节。

数据集方面,公开可用的有 TrashNet(2527 张,6 类)和华为云垃圾分类数据集(约 1.5 万张,40 类)。TrashNet 类别少、图片干净,适合快速跑通;华为云那个类别细、噪声大,适合做数据增强和难例挖掘的章节。我的建议是先用 TrashNet 跑通全流程,再换大数据集做提升,这样论文有对比、有故事。

2.2 从零搭一个可训练的项目骨架

项目目录结构决定了你后期改代码会不会痛苦。我见过太多毕设把所有代码塞在一个main.py里,改一个参数要翻三百行。下面是我常用的骨架:

garbage-classify/ ├── configs/ │ └── default.yaml # 超参数、路径配置 ├── data/ │ ├── raw/ # 原始图片,按类别分文件夹 │ └── split/ # 划分后的 train/val/test ├── src/ │ ├── dataset.py # Dataset 与 DataLoader │ ├── model.py # 模型定义 │ ├── train.py # 训练循环 │ ├── evaluate.py # 评估与混淆矩阵 │ └── predict.py # 单图推理 ├── app/ │ └── server.py # Flask 接口 ├── requirements.txt └── README.md

配置文件用 YAML 而不是硬编码,是为了让答辩老师一眼看到你的超参数是可管理的。configs/default.yaml内容大致如下:

data: root: data/split num_classes: 4 image_size: 224 batch_size: 32 num_workers: 4 model: name: mobilenet_v3_small pretrained: true train: epochs: 30 lr: 0.001 weight_decay: 0.0001 device: cuda

参数说明:image_size设 224 是因为 ImageNet 预训练权重默认输入尺寸,改了要同步改模型第一层;batch_size32 是 8G 显存下的安全值,显存小就降到 16;num_workers在 Windows 上设 0 更稳,Linux 上设 4 能加速数据加载。pretrained: true是关键——从 ImageNet 权重微调,比从零训练收敛快得多,小数据集上精度能差 15 个点以上。

2.3 数据划分与增强:别让测试集泄漏进训练集

数据划分是最容易翻车的地方。很多人直接把所有图片按 8:2 随机分,结果同一张图的不同角度版本同时出现在训练集和测试集里,测试准确率虚高到 98%,一上真实场景就掉到 60%。正确做法是按"原始来源"分组划分,同一来源的图片只能进一个集合。

import os import random import shutil from pathlib import Path def split_dataset(raw_dir, out_dir, ratios=(0.7, 0.15, 0.15), seed=42): random.seed(seed) raw = Path(raw_dir) out = Path(out_dir) for cls_dir in raw.iterdir(): if not cls_dir.is_dir(): continue imgs = list(cls_dir.glob("*.jpg")) + list(cls_dir.glob("*.png")) random.shuffle(imgs) n = len(imgs) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { "train": imgs[:n_train], "val": imgs[n_train:n_train + n_val], "test": imgs[n_train + n_val:], } for split_name, files in splits.items(): target = out / split_name / cls_dir.name target.mkdir(parents=True, exist_ok=True) for f in files: shutil.copy(f, target / f.name) print(f"{cls_dir.name}: train={len(splits['train'])}, " f"val={len(splits['val'])}, test={len(splits['test'])}") if __name__ == "__main__": split_dataset("data/raw", "data/split")

这段脚本按类别分别划分,保证每个类别的训练/验证/测试比例一致,避免某类样本全进了训练集导致验证时该类无法评估。seed=42固定随机种子,保证你论文里的数据可复现——答辩老师如果让你重跑,结果要能对上。

数据增强用 torchvision 的 transforms 组合:随机裁剪、水平翻转、颜色抖动。注意垃圾分类场景下不要用垂直翻转,因为垃圾不会倒着放,垂直翻转会引入不真实的样本。颜色抖动幅度也别太大,否则透明塑料瓶和玻璃瓶的区分特征会被破坏。

3. 训练、评估与推理:把模型跑出能写进论文的数字

3.1 训练循环的关键参数与早停策略

训练脚本的核心是损失函数、优化器和学习率调度。垃圾分类四分类用交叉熵损失就够,类别不均衡时加weight参数。优化器选 AdamW,学习率 1e-3 配合余弦退火,比固定学习率收敛更平滑。

import torch import torch.nn as nn from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR from torchvision import models def build_model(num_classes=4, pretrained=True): weights = models.MobileNet_V3_Small_Weights.DEFAULT if pretrained else None model = models.mobilenet_v3_small(weights=weights) in_features = model.classifier[3].in_features model.classifier[3] = nn.Linear(in_features, num_classes) return model def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total = 0.0, 0, 0 for imgs, labels in loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * imgs.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += imgs.size(0) return total_loss / total, correct / total # 早停:验证集 loss 连续 5 轮不降就停 best_val_loss = float("inf") patience, wait = 5, 0 for epoch in range(cfg["train"]["epochs"]): train_loss, train_acc = train_one_epoch(model, train_loader, optimizer, criterion, device) val_loss, val_acc = evaluate(model, val_loader, criterion, device) scheduler.step() if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), "best.pth") wait = 0 else: wait += 1 if wait >= patience: print(f"Early stop at epoch {epoch}") break print(f"Epoch {epoch}: train_loss={train_loss:.4f} train_acc={train_acc:.4f} " f"val_loss={val_loss:.4f} val_acc={val_acc:.4f}")

逻辑说明:每个 epoch 先训练再验证,验证 loss 创新低就保存权重,连续 5 轮不降就提前停。这样既防止过拟合,又省训练时间。torch.save只存state_dict而不是整个模型,文件小、加载灵活。注意model.train()和model.eval()必须成对出现,忘了切 eval 模式,BatchNorm 和 Dropout 会在验证时继续更新,验证指标会飘。

3.2 混淆矩阵:找出模型到底在哪两类之间犯迷糊

准确率是个笼统指标,答辩时老师更想看混淆矩阵。四分类的混淆矩阵能直接告诉你:厨余和其他垃圾是不是经常混,可回收和有害垃圾有没有分不清。

import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, classification_report def plot_confusion(model, loader, device, class_names): model.eval() preds, trues = [], [] with torch.no_grad(): for imgs, labels in loader: imgs = imgs.to(device) out = model(imgs).argmax(1).cpu().numpy() preds.extend(out) trues.extend(labels.numpy()) cm = confusion_matrix(trues, preds) print(classification_report(trues, preds, target_names=class_names)) fig, ax = plt.subplots(figsize=(6, 5)) im = ax.imshow(cm, cmap="Blues") ax.set_xticks(range(len(class_names))) ax.set_yticks(range(len(class_names))) ax.set_xticklabels(class_names, rotation=45) ax.set_yticklabels(class_names) for i in range(len(class_names)): for j in range(len(class_names)): ax.text(j, i, cm[i, j], ha="center", va="center") plt.colorbar(im) plt.tight_layout() plt.savefig("confusion_matrix.png", dpi=150)

classification_report会输出每个类别的 precision、recall、f1-score。如果某一类 recall 特别低,说明该类样本被大量误判,要么加样本,要么调类别权重。我做过的一个项目里,"有害垃圾"的 recall 只有 0.62,查下来是电池和药品包装的图片太少,补了 300 张后涨到 0.81。这个排查过程写进论文,比单纯报一个准确率有说服力得多。

3.3 Flask 推理接口:把模型变成能演示的服务

模型训练完,得有个界面能演示。Flask 是最轻的选择,一个上传接口加一个预测接口,前端用最简单的 HTML 表单就行。

from flask import Flask, request, jsonify, render_template from PIL import Image import torch import io from torchvision import transforms app = Flask(__name__) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = build_model(num_classes=4, pretrained=False) model.load_state_dict(torch.load("best.pth", map_location=device)) model.to(device).eval() CLASS_NAMES = ["可回收物", "厨余垃圾", "有害垃圾", "其他垃圾"] preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) @app.route("/") def index(): return render_template("index.html") @app.route("/predict", methods=["POST"]) def predict(): file = request.files.get("image") if not file: return jsonify({"error": "no image"}), 400 img = Image.open(io.BytesIO(file.read())).convert("RGB") tensor = preprocess(img).unsqueeze(0).to(device) with torch.no_grad(): probs = torch.softmax(model(tensor), dim=1)[0] idx = probs.argmax().item() return jsonify({ "class": CLASS_NAMES[idx], "confidence": round(probs[idx].item(), 4), "all": {CLASS_NAMES[i]: round(probs[i].item(), 4) for i in range(4)} }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)

参数说明:Normalize的均值和标准差必须和训练时一致,这是新手最常犯的错——训练用了 ImageNet 统计量,推理忘了加,准确率直接掉一截。unsqueeze(0)是给单张图补上 batch 维度。host="0.0.0.0"让局域网内其他设备也能访问,答辩时用手机拍一张就能演示。返回all字段是为了前端能画概率条,比只显示一个类别更有说服力。

4. 部署上线:从本地 Flask 到能长期跑的服务

4.1 环境依赖与 requirements 的坑

部署第一步是把依赖锁死。pip freeze > requirements.txt会把当前环境所有包都导出,包括无关的,建议手写核心依赖:

torch==2.1.0 torchvision==0.16.0 flask==3.0.0 pillow==10.1.0 numpy==1.26.0 scikit-learn==1.3.2 pyyaml==6.0.1 gunicorn==21.2.0

版本号要写死,否则换台机器pip install可能装到不兼容的新版本。torch 和 torchvision 版本必须匹配,2.1.0 配 0.16.0,装错了会报undefined symbol之类的玄学错误。如果目标机器没有 GPU,装 CPU 版 torch 用pip install torch --index-url https://download.pytorch.org/whl/cpu,体积小很多。

4.2 用 Gunicorn 替代 Flask 自带服务器

app.run()是开发服务器,单线程、性能差、不能上生产。部署用 Gunicorn:

gunicorn -w 4 -b 0.0.0.0:5000 app.server:app --timeout 120

-w 4是 4 个 worker 进程,一般设为 CPU 核数乘 2 加 1。--timeout 120是请求超时,模型首次加载慢,设小了会 502。注意 Gunicorn 在 Windows 上不支持,Windows 部署要么用 Waitress,要么直接上 Linux。我一般建议毕设部署用一台便宜的 Linux 云主机,把模型文件和代码传上去,用 systemd 托管 Gunicorn,开机自启。

4.3 模型加载慢与内存占用的优化

Flask 每个 worker 会各自加载一份模型,4 个 worker 就是 4 份内存。MobileNetV3 小模型还好,ResNet50 一份就 100M 左右,4 份 400M,小内存机器扛不住。解决办法有两个:一是减少 worker 数到 2,二是用torch.jit或 ONNX 导出模型,加载更快、内存更省。

# 导出 ONNX,推理时用 onnxruntime dummy = torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy, "model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, opset_version=11, )

导出后推理用onnxruntime.InferenceSession,不依赖 torch,内存占用能降一半以上,CPU 推理速度也更快。这一步在毕设里算加分项,能体现你考虑过部署成本。

5. 避坑与排查:那些答辩前夜才暴露的问题

5.1 训练准确率 99% 但演示时全错

现象:训练日志里验证准确率 0.99,答辩现场拍一张矿泉水瓶,模型判成"厨余垃圾"。 原因:训练集和测试集来自同一批网图,风格高度一致,模型学到的是背景和拍摄风格,不是物体本身。真实手机拍摄的光照、角度、背景完全不同,分布偏移导致崩溃。 解决:训练时加入强增强(随机旋转、透视变换、背景替换),并且留一部分自己手机拍的图作为"真实测试集",不参与训练。论文里报告这个真实测试集的准确率,比报告网图测试集更有价值。

5.2 推理接口第一次请求超时

现象:服务启动后第一个请求要等十几秒才返回,后面就快了。 原因:模型权重是懒加载的,第一次请求才真正把权重读进内存并初始化计算图。 解决:在 Flask 启动时预热一次,用一张全零图跑一遍model(tensor),把初始化开销提前到服务启动阶段。Gunicorn 的--preload参数也能让 master 进程先加载再 fork,避免每个 worker 重复加载。

5.3 中文类别名在接口里变成乱码

现象:返回的 JSON 里"可回收物"显示成\u53ef\u56de\u6536\u7269。 原因:Flask 的jsonify默认ensure_ascii=True,把非 ASCII 字符转义了。 解决:app.config["JSON_AS_ASCII"] = False,或者用json.dumps(..., ensure_ascii=False)手动构造响应。前端fetch拿到后正常解析即可,不影响功能,但演示时看着别扭。

5.4 换机器后 torch 报 CUDA 版本不匹配

现象:本地训练好的模型拷到服务器,torch.load报RuntimeError: CUDA error。 原因:本地 torch 是 CUDA 11.8 编译的,服务器驱动只支持到 11.6,或者服务器根本没 GPU。 解决:保存权重时用torch.save(model.state_dict())只存参数,加载时map_location="cpu"先加载再决定放哪。部署前用nvidia-smi确认驱动版本,按官方对照表装对应 CUDA 版本的 torch。没 GPU 就装 CPU 版,代码里device自动判断。

5.5 图片上传后方向不对

现象:手机拍的照片上传后模型识别错,下载下来看是横着的。 原因:手机照片带 EXIF 方向信息,PIL 默认不读取,导致图像被旋转。 解决:用PIL.ImageOps.exif_transpose(img)自动纠正方向,再送进预处理。这一行不加,竖拍的照片全变横的,模型自然认不出。

6. 把毕设做成能讲二十分钟的项目:几个提分技巧

答辩时最怕的不是模型精度不够,而是讲不出技术细节。我见过一个学生,准确率只有 82%,但他把数据清洗、增强策略、混淆矩阵分析、部署优化讲得清清楚楚,最后拿了优秀。另一个学生准确率 95%,问他"为什么选 MobileNet 不选 ResNet",答"网上说 MobileNet 快",直接被打断。

第一个技巧是准备一张"技术决策表",把每个选型的原因写清楚。比如:

决策点选择备选理由
模型MobileNetV3-SmallResNet50参数量 5M vs 25M,CPU 推理 30ms vs 120ms,精度差 2 个点可接受
优化器AdamWSGD小数据集上收敛更快,无需精细调 lr
部署Gunicorn + ONNXFlask 原生并发能力提升,内存占用降一半
增强随机裁剪+翻转仅翻转提升泛化,真实测试集准确率 +6 个点

这张表往 PPT 一放,老师的问题基本都能接住。

第二个技巧是留一个"失败案例"页。挑几张模型判错的图,分析为什么错——是光照太暗、物体被遮挡,还是类别本身边界模糊(比如用过的纸巾算厨余还是其他)。这体现你有反思能力,比只报喜不报忧强得多。

第三个技巧是把部署过程录成一段两分钟的视频。从git clone到pip install到启动服务到上传图片出结果,全程录屏。答辩现场网络不稳、环境可能出问题,视频是后悔药。而且这段视频本身就是你"部署指南"的实证,比文字描述可信。

最后一个习惯:所有实验数据当天记录,别攒到最后补。我一般让学生建一个experiments.md,每次跑完训练就记一行——日期、改动、验证准确率、备注。到写论文时,这些记录直接就是实验章节的素材,不用回忆"当时那个 0.87 是哪次跑出来的"。这个习惯我保持了六年,帮我在无数次答辩和评审里省下了翻日志的时间。希望帮到你。

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

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

C语言sizeof深度解析:从运算符本质到内存对齐实战

C语言里的 sizeof&#xff0c;你真正弄懂了吗&#xff1f;我在不少技术交流群里见过这样的场景&#xff1a;有人问sizeof(int)和sizeof(int*)是不是一回事&#xff0c;底下能吵好几页。还有人写代码时用sizeof(指针)去算数组长度&#xff0c;结果跑出个固定 8&#xff0c;找半天…

作者头像 李华
网站建设 2026/10/1 4:24:17

第一次作业不用慌:一套从拆解到交付的完整执行框架

“第一次作业”这四个字&#xff0c;恐怕是学生时代到职场生涯里&#xff0c;出现频率最高、也最容易让人心里发慌的场景了。我到现在还记得自己交第一份课程论文时的状态&#xff1a;材料下载了十几个&#xff0c;文档打开了一上午&#xff0c;光标在空白页上闪了一整天&#…

作者头像 李华
网站建设 2026/10/1 4:23:49

12dB全向铜丝天线DIY:9段螺旋+3mm铜管的高增益物理实现

简介&#xff1a;本资源是一份面向无线通信DIY爱好者与入门级射频实践者的高增益全向天线制作教程&#xff0c;聚焦解决低成本、易上手实现12dB增益全向辐射的现实难题。针对开槽天线成本高、电缆天线精度难控等痛点&#xff0c;方案采用铜丝铜管PVC管等常见材料&#xff0c;完…

作者头像 李华
网站建设 2026/10/1 4:22:13

端到端数字图像水印CNN毕设源码复现:从模型训练到避坑指南

简介&#xff1a;这份资源是围绕卷积神经网络实现端到端数字图像处理的代码复现项目&#xff0c;面向计算机相关专业正在做毕业设计、期末大作业或课程设计的学生&#xff0c;以及需要项目实战练习的学习者。项目经导师指导并认可&#xff0c;评审分达98分&#xff0c;可作为高…

作者头像 李华
网站建设 2026/10/1 4:21:51

AI Skills赋能数竞教研:学案制作一体化实战指南

学案的革命&#xff08;数竞版&#xff09;&#xff1a;竞赛教研与学案制作一体化 skills接触数学竞赛教研的老师应该都有同感&#xff1a;每周最耗时的事情&#xff0c;不是上课&#xff0c;而是做学案。找题、对难度、配解析、调格式、作图、排版&#xff0c;一套二试几何专题…

作者头像 李华
网站建设 2026/10/1 4:21:47

小程序开发全流程解析:合肥企业数字化转型的实用指南

1. 先聊清楚&#xff1a;数字化转型为什么要从小程序切入1.1 很多合肥老板问的第一个问题在合肥做本地化服务这行&#xff0c;这几年我见过太多老板拿着手机问我&#xff1a;我们公司到底要不要做小程序&#xff1f;做了能干嘛&#xff1f;说实话&#xff0c;这个问题背后藏着的…

作者头像 李华