基于Python的垃圾分类系统毕设实战:从CNN训练到Flask部署

📅 2026/8/27 5:08:21
基于Python的垃圾分类系统毕设实战:从CNN训练到Flask部署
简介图像识别是人工智能领域应用最广泛的技术之一在智能环保、智慧城市等场景中扮演着关键角色。卷积神经网络CNN作为图像识别的核心模型通过自动提取图像深层特征能够有效区分不同物体。在工程实践中迁移学习解决了小数据集训练的难题而Python配合PyTorch框架则大幅降低了模型开发门槛。本文从垃圾分类这一典型应用场景出发系统梳理了基于Python的垃圾分类系统的完整实现链路从数据集准备、数据增强、MobileNetV2迁移学习训练到Flask后端集成、前端交互与SQLite存储再到部署演示与答辩准备。文章既涵盖CNN原理与训练调参细节也提供工程化部署方案帮助读者快速掌握图像识别项目的完整开发流程为毕业设计或实际项目落地提供可复用的参考。 很多学弟学妹从网盘或者代码仓库里下载了一个“基于python的垃圾分类系统源码毕业设计.zip”解压完之后对着一个 models 文件夹、几个.py 文件、一串 requirements.txt 愣住不知道哪些文件是核心、从哪里开始改、怎么在答辩的时候把自己的工作量讲清楚。今天我就用实际做过的同类项目做一次完整复盘把数据准备、模型训练、Web 端整合、部署上线、答辩准备这些环节一次说透。这个项目本身很适合计算机、软件工程、数据科学方向的学生拿来做毕设因为它的链路很完整——既包含图像识别里的卷积神经网络与迁移学习又包含 Flask 后端、前端页面、数据库存储这些工程化内容工作量能讲清楚、技术点也够新而且用到的公开资源足够多不会出现“做完之后连自己也解释不清楚”的尴尬局面。1. 项目到底在做什么把垃圾识别拆成几个具体问题1.1 从需求到功能清单毕设题目里写“垃圾分类系统”听起来像个宏大工程但如果拆开看核心需求其实很收敛用户上传一张垃圾图片系统判断它是可回收垃圾、厨余垃圾、有害垃圾还是其他垃圾然后把结果和置信度展示出来。很多拿到源码的同学上来就急着跑代码先把依赖装上、把 app.py 一运行发现页面上传图片能出结果就觉得完事了。这个状态在开题阶段可以但离真正能交差的毕设还差得远。更合理的做法是先把系统拆成几个独立模块。第一个模块是数据处理模块负责把图片从文件系统读进来做尺寸归一化、数据增强划分训练集和测试集第二个模块是模型模块负责定义网络结构、加载预训练权重、训练和保存模型第三个模块是预测模块负责加载模型、处理单张输入图片、输出分类结果第四个模块是 Web 服务模块负责接收用户上传的图片、调用预测模块、把结果渲染到页面上并把历史记录存储下来。这样拆开之后整个项目的代码结构会清晰很多你在答辩时也能按模块讲清楚“我做了什么”。我带学生做这类项目的时候还会额外加一个统计页面的需求用户每次识别都会写入一条数据库记录页面上用图表统计这个月各类垃圾的识别次数。为什么要加这个因为毕设评分一般不只看识别效果更看系统完整性。一个能“留下痕迹”的系统比一个只返回结果的 demo 有说服力得多。实现这个功能不复杂SQLite 存记录、前端用几个小时就能做过图表但花了很少的力气就能让整个系统的完成度高一个档次。1.2 为什么是 Python 和深度学习路线有的同学会问垃圾分类为什么不用传统的图像处理方法比如颜色直方图加 SVM、HOG 特征加随机森林因为传统方法的特征需要人手工设计垃圾种类千差万别——一个塑料瓶和一张纸的颜色接近一个果皮和一个布料的纹理接近手工特征很难把所有情况覆盖。深度学习卷积神经网络CNN的好处是它自动从大量样本里学特征底层学到边缘、纹理高层学到物体部件最后一层学到类别相关的语义特征。你不需要告诉模型“塑料瓶一般是透明的、有反光”只要数据量够它自己就能把这些特征提取出来。所以在技术路线选择上CNN 几乎是这个题目的默认方案。至于为什么很多毕设源码里选择用 PyTorch 而不是 TensorFlow我会在后面模型部分详细讲这里先给一个结论PyTorch 对初学者调试友好、生态里开源模型多、代码写起来也直观拿来做毕设的主力框架搭 Web 服务、写训练脚本都比较顺手。1.3 整体系统架构再理一下整个系统是怎么流转的。用户在前端页面上传图片Flask 后端接收到图片后先做预处理——把图片缩放成模型要求的尺寸比如 224x224转成张量再按训练时的标准化参数做归一化。预处理后的数据输入模型模型前向传播得到四个类别的概率分布后端取概率最高的类别返回给前端同时把这条记录写入 SQLite 数据库。前端拿到结果后展示分类名称、置信度并根据类别显示不同的图标和颜色。这套流程听起来简单但它实际上覆盖了“数据 → 模型 → 服务 → 前端 → 存储”整条链路。我在给项目写文档的时候有一个习惯先画一张系统架构图把模块边界、数据流向、接口调用关系标明再开始写代码。对着架构图写代码比自己想到哪写到哪高效得多也避免了很多“这个函数该放哪个文件”的纠结。就算你拿到的源码里面没有架构文档我也建议你自己整理一张这对后面改代码和答辩都有很大帮助。2. 数据集准备不要拿到源码就开始焦虑模型精度2.1 数据从哪来怎么划分很多毕设源码发布方喜欢把数据集也压缩到包里但你下载来的数据集很可能本身就是从某个公开项目搬运的并不一定适合你训练。常见的开源数据来源包括 GitHub 上搜索垃圾数据集、高校共享的垃圾分类图像库以及 Kaggle 上的多类垃圾图片集。你需要注意的关键点是这些数据的类别定义是否和你目标一致有的数据集是“纸板、玻璃、金属、塑料”四分类有的则是“可回收、有害、厨余、其他”四分类这直接影响模型的输出层设计。拿到数据之后我建议把它整理成这样的目录结构dataset/ ├── train/ │ ├── recyclable/ │ ├── kitchen/ │ ├── harmful/ │ └── other/ ├── val/ │ ├── recyclable/ │ ├── kitchen/ │ ├── harmful/ │ └── other/ └── test/ ├── recyclable/ ├── kitchen/ ├── harmful/ └── other/为什么不用一个目录然后靠程序随机划分因为同一个来源的一张图片如果同时出现在训练集和验证集里会造成数据泄露——模型“见过”这张图片验证集结果虚高看起来效果很好但实际换个新场景就崩。按目录手动划分可以尽量降低同源图片同时出现在两个集合里的概率这一点很简单但非常重要。划分比例我用的是 8:1:1训练集八成、验证集一成、测试集一成。如果你的类别数据量差异很大比如可回收垃圾有 5000 张、有害垃圾只有 800 张那就要处理类别不平衡问题我在 2.3 节里说。2.2 数据增强让模型多看几个“角度”的垃圾很多拿到源码的新手会发现一个现象模型在训练集上准确率很高在验证集上却一直上不去这就是过拟合。垃圾图片往往是在不同光线、不同背景下拍的如果训练数据不够多样模型就会记下背景特征而不是垃圾本身。数据增强是解决这个问题最简单的手段。我在项目中使用了这几种增强组合随机水平翻转让模型学会不管物体朝左还是朝右都能识别随机旋转 15 度模拟拍摄角度偏差随机亮度、对比度扰动模拟光线变化随机裁剪缩放模拟距离远近。具体的 PyTorch 写法是from torchvision import transforms train_transforms transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])这里面的 Normalize 用到的 mean 和 std 是 ImageNet 预训练模型的标准化参数不要随便改成 (0.5, 0.5, 0.5)因为后面要加载的 ImageNet 预训练权重是在这套标准化规则下训练出来的输入分布不一致迁移效果会大打折扣。验证集和测试集的变换则只做 Resize、ToTensor 和 Normalize不做随机增强这样才能真实反映模型的识别能力。这点我在带学生时反复提醒因为确实有同学把 RandomHorizontalFlip 也用在验证集上自己都没发现。2.3 中文目录名和编码问题这个是刚接触 PyTorch 的人一定会踩的坑。很多数据集发布方直接用中文做目录名比如“可回收垃圾”PyTorch 的 ImageFolder 在读取这些目录名时可能出现编码问题轻则标签乱码重则直接报错。我建议在目录命名时统一用英文或拼音然后在代码里做一个字典映射label_map { recyclable: 可回收垃圾, kitchen: 厨余垃圾, harmful: 有害垃圾, other: 其他垃圾, }这样做的好处不只是避免编码问题还让模型的输出层标签变得稳定不管数据集被拷贝到哪个系统上都不会因为文件系统的中文编码差异而出错。这也是一个能讲给答辩老师听的工程细节你考虑到了跨平台兼容性问题而不是只会用魔法数字。3. 模型选型和训练调参从过拟合到性能达标3.1 为什么用迁移学习 MobileNetV2网络上有大量现成的分类模型源码最常见的选择包括 ResNet50、VGG16、EfficientNet、MobileNetV2 等。选型的核心考量是你的硬件条件和应用场景不是哪个结构越深就选哪个。我的实际建议是个人笔记本用 MobileNetV2实验室有 GPU 可以用 ResNet50但都不建议从头训练一个随机初始化的 CNN。迁移学习的原理很直接ImageNet 上预训练好的模型已经学会了通用视觉特征比如边缘、纹理、形状这些特征对垃圾图片一样有效。我们只需要把模型最后的全连接分类层替换成自己的四分类输出层然后只微调后几层就能用很小的数据量拿到还不错的准确率。这个思路在毕设里尤其重要因为毕设数据集通常只有几千张从头训练 CNN 几乎必然过拟合。MobileNetV2 选得比较多是因为它轻量、推理快CPU 上也能跑毕设答辩现场演示的时候如果电脑没有独立显卡ResNet 系列可能一次推理要几秒MobileNetV2 基本一眨眼的工夫就出结果。演示流畅度对答辩体验影响很大没必要在模型推理速度上给自己增加风险。下面是模型替换分类层的示意代码import torch.nn as nn from torchvision import models model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) num_ftrs model.classifier[1].in_features model.classifier nn.Sequential( nn.Dropout(0.2), nn.Linear(num_ftrs, 4), )3.2 训练参数学习率、Batch Size、Epochs训练参数是另一个能看出有没有真实做过项目的地方。我见过很多源码里直接把 batch size 设成 64、epochs 设成 100跑起来极其随意。实际项目里参数之间是互相牵制的需要结合起来调。我用的是 batch size 32、初始学习率 0.001、优化器 Adam、损失函数 CrossEntropyLoss。这里解释一下为什么batch size 32 在大多数个人电脑上不会爆显存梯度更新频率也比较稳学习率 0.001 是 Adam 的经典起始值太大容易震荡太小收敛太慢。如果训练了十几个 epoch 发现准确率没有上升再把学习率调整到 0.0003 冻住前面层、只训练分类层这是一个比较稳定的节奏。还有一个很实用的操作是学习率衰减。我通常在训练到第 20 个 epoch 时把学习率降到 0.0001让模型在后期能更精细地收敛。如果你不想手动调也可以直接用 PyTorch 的 CosineAnnealingLR但毕设里手动调一次并解释清楚反而更能体现你的掌控力。训练代码的大致骨架from torch.optim import Adam from torch.nn import CrossEntropyLoss optimizer Adam(model.parameters(), lr0.001) criterion CrossEntropyLoss() for epoch in range(30): model.train() for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() if epoch 20: optimizer.param_groups[0][lr] 0.0001 # 每个 epoch 后跑一次验证输出准确率3.3 评估模型别只盯着总准确率有的训练日记里只看总的准确率比如“最终准确率 92%”但实际分类报告一看厨余垃圾准确率 98%有害垃圾准确率只有 70%总数字被拉高了。这种情况答辩老师只要问“每一类的识别效果怎么样”就会露馅而且从实际应用角度看把一袋过期药品识别成其他垃圾比把纸张识别成其他垃圾问题严重得多。所以评估的时候我会同时看准确率、精确率、召回率、F1-score 和混淆矩阵。用 sklearn 就能很方便地算出来from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_true, y_pred, target_nameslabel_map.values())) print(confusion_matrix(y_true, y_pred))如果发现某类指标偏低通常的处理思路是加数据量、调整类别权重、降低该类的判断阈值。我实际用下来最简单有效的是给 loss 传入类别权重让模型在训练时更重视样本少的类别class_weights torch.tensor([1.0, 2.0, 1.5, 1.0]) criterion CrossEntropyLoss(weightclass_weights)权重的具体值不需要很精细按各类别样本比例的倒数归一化一下就能用。这种细节写在论文里是能加分的因为它是针对问题做的实例分析而不是把别人的训练脚本抄一遍。4. Web 端与系统集成让项目从一个脚本变成一个产品4.1 Flask 后端与项目结构模型训练好之后最核心的问题就是怎么把模型能力开放给用户使用。毕设里最常用的是 Flask因为它在保持简单的同时已经足够支撑单个用户量级的演示。选用 Flask 而不是 Django 的理由也很实际Django 自带 Admin、ORM、Auth但对一个单体演示项目来说这些功能大多数用不上反而会让目录结构复杂化Flask 可以让你用不到 100 行代码把请求处理和模板渲染跑通。我的项目文件结构大致如下garbage_classification/ ├── app.py # Flask 入口 ├── model.py # 模型加载与推理函数 ├── utils.py # 图片预处理辅助函数 ├── static/ │ ├── css/ │ ├── js/ │ └── uploads/ # 用户上传的临时图片 ├── templates/ │ ├── index.html # 首页上传与展示 │ └── history.html # 历史记录页 ├── data.db # SQLite 数据库 ├── models/ │ └── garbage_mobilenet.pth ├── requirements.txt └── train.py # 训练脚本这个结构的核心原则是入口文件只管路由模型相关代码单独拆到 model.py图片处理逻辑放到 utils.py训练代码单独放。我在实际评审毕设源码时最怕看到的是一个 app.py 里堆了 1000 行既包含模型训练又包含前端路由看半天找不到重点。模块拆清楚代码质量分就能高不少。4.2 模型推理接口与前端交互模型加载有一个细节不要在每次请求时都重新加载模型那会非常慢。正确做法是在 app.py 启动的时候把模型加载到内存之后的预测请求直接调用这个全局模型对象。我用的是 PyTorch 的torch.load加model.eval()模式并在推理时用torch.no_grad()关闭梯度计算节省显存和计算时间。前端页面的交互逻辑是用户上传图片页面通过 fetch 把图片数据作为 FormData 发送到/predict接口后端做推理之后返回 JSON前端用 JavaScript 把结果渲染到页面上。这个流程用 jQuery 或原生 fetch 都能实现不需要引入 Vue/React 这类框架。我这里给一个最简但完整的 Flask 接口from flask import Flask, request, jsonify, render_template from model import load_model, predict_image import os app Flask(__name__) model load_model(./models/garbage_mobilenet.pth) app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): file request.files.get(image) if file is None: return jsonify({error: no image uploaded}), 400 save_path os.path.join(./static/uploads, file.filename) file.save(save_path) label, confidence predict_image(model, save_path) return jsonify({label: label, confidence: confidence})这里我故意没有写复杂的校验逻辑是想先让你跑通主干。真正落地时还需要在 predict_image 里做Image.open()、resize(224, 224)、ToTensor()、Normalize()然后做一次model(image.unsqueeze(0))取 softmax 最大值的索引作为预测结果。4.3 历史记录与统计展示加了 SQLite 存储之后系统才真正形成闭环。每次识别成功就把图片路径、预测类别、置信度和识别时间写入数据表。表结构大概这样CREATE TABLE records ( id INTEGER PRIMARY KEY AUTOINCREMENT, image_path TEXT, label TEXT, confidence REAL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );历史页面可以展示最近 20 条记录并统计各类别占比。统计占比最简单的实现是 SQL 里用 GROUP BYSELECT label, COUNT(*) FROM records GROUP BY label;前端报表可以用 Chart.js 画一个饼图这个库对毕设来说足够简单且效果不错。加上这些功能之后你在答辩时就能说“系统不仅实现了单次识别还支持识别记录的管理和统计分析”这比单纯一个图像识别 demo 的完整度高很多。有一点要提醒Flask 自带的开发服务器在app.run()默认端口 5000这个端口在部分机器上容易被占用如果启动时报Address already in use最简单的办法是换端口或在路由前面加前缀。如果局域网内演示把app.run(host0.0.0.0)打开手机浏览器也能访问演示时有惊喜效果。5. 常见问题与排查技巧一份老程序员视角的故障清单5.1 环境配置阶段的问题从压缩包解压到模型能跑起来最常见的坑就是环境不一致。有人用 Python 2、有的人没有 GPU 却在跑 CUDA 版 PyTorch、有的人 torch 和 torchvision 版本不匹配导致models.mobilenet_v2报错。我的建议是严格按照 requirements.txt 里的版本装不要盲目装最新版。比如 PyTorch 1.x 时代和 2.x 时代的 API 有差异你下载的源码如果是 1.x 写的装 2.x 可能会出现个别方法不兼容。如果没有 GPU直接在 CPU 上跑完全没问题。CPU 训练模型会慢很多但模型推理速度对 MobileNetV2 来说是可以接受的。如果训练时出现显存不足把 batch size 从 32 降到 16 或者 8问题一般就解决了。5.2 模型预测结果全部是同一个类别这是图像分类项目里出现频率最高的“灵异事件”。模型训练时准确率不低但部署到 Web 端后无论上传什么图片都输出“可回收垃圾”置信度还特别高。遇到这个情况第一反应不是怀疑模型坏了而是检查推理时的预处理是否和训练时一致。常见的罪魁祸首有三个一是推理时没有做 Normalize或者 Normalize 参数和训练不一致二是图片读入时通道顺序变成了 BGR 而不是 RGBPIL 打开是 RGBcv2.imread读入是 BGR如果你在 utils.py 里用了 OpenCV 读取就要反向转换cv2.cvtColor(img, cv2.COLOR_BGR2RGB)三是模型的class_to_idx映射顺序是乱的你训练时的第 0 类可能不是“可回收垃圾”但前端显示结果时又没有查映射表直接把索引对应的中文字符串对齐了。排查思路就是打印出模型的输出向量看它对哪一类输出最大再去和训练脚本里的标签顺序对照。5.3 上传大图卡顿和临时文件清理另一个容易被忽视的问题是用户上传的图片尺寸可能非常大比如手机拍出的 4000x3000 像素图片直接缩放和推理会拖慢响应速度。我的做法是在后端起一个缩略图逻辑先用 PIL 把图片缩放到 800 像素以内再送到模型前处理的 Resize 步骤。这样既加快了推理也减小了保存到 uploads 目录的文件体积。临时文件清理也建议在代码里带上否则演示几次后 uploads 文件夹会积累一堆图片。最简单的办法是在保存临时文件时用时间戳或 uuid 重命名然后在写入数据库或返回结果后用os.remove清理。如果担心删除失败也可以写一个定时任务或启动时清理旧文件毕设阶段不需要太复杂手动清理加“上传时清理一次旧文件”就够了。5.4 模型文件过大和跨机器部署训练完的 PyTorch 模型保存为.pth文件MobileNetV2 大约 14MBResNet50 大约 100MB。如果你换了一台电脑演示记得把models/文件夹整个带上。如果嫌 PyTorch 环境部署麻烦可以考虑把模型转换成 ONNX 格式onnxruntime 推理时不需要装完整的 PyTorch包体积小很多。对于毕设来说能跑起来才是第一位的如果你时间紧不建议在模型部署格式上花太多时间等到延展部分再提也可以。6. 毕业设计答辩与后续扩展建议6.1 答辩老师常问的问题与回答思路很多同学做完项目技术实现没问题但一到答辩就被问住。垃圾分类这个题目的高频问题就那么几个提前准备就不会慌。第一个问题是“为什么选择 MobileNetV2 而不是更深的网络”。答因为系统需要部署在 Web 端进行实时识别MobileNetV2 采用深度可分离卷积参数量小、推理速度快同时识别精度能满足需求更适合资源受限的环境。不要只答“网上都说这个好”要说明你关注了场景需求。第二个问题是“数据是怎么来的如何保证数据质量”。答说明数据来源、划分比例、去重原则以及数据增强缓解过拟合的方式。如果数据里有噪音也坦诚说明并补充如何通过交叉验证发现和修正。第三个问题是“这个系统如果换一批真实场景的数据效果能不能保持”。这个问题本质上是考泛化能力你要承认局限同时提出优化方向增加数据采集渠道、使用更强的数据增强、在模型中加入 dropout、采用更多样的迁移学习策略。第四个问题是“你在这个项目里最有挑战的部分是什么”。这类问题不要泛泛而谈“训练模型很难”而是选一个具体细节比如中文路径编码问题、类别不平衡导致有害垃圾识别率偏低、Web 接口响应慢等讲清楚问题现象、排查过程和最终解决办法。有细节的答案才有说服力。6.2 从四分类到实时识别与更多功能如果还有时间这个项目可以往两个方向扩展。一个是实时摄像头识别视频流中的每一帧都抽出来做推理然后标注结果。这个方向可以换成 YOLO 目标检测模型不仅能识别垃圾类别还能定位位置不过工作量会显著增加。另一个方向是做成微信小程序或带语音识别的查询助手。用户说出“过期药品属于什么垃圾”系统通过语音识别转文字再根据规则库或模型返回分类结果。这类扩展适合做功能创新也适合非图像专业背景的同学展示更多工程能力。此外可以让系统变成“先图像识别识别置信度低时再走人工确认”并由用户反馈修正结果把这些反馈收集起来作为后续训练数据形成一个持续优化的闭环。这种设计在答辩时很加分因为它展示了你对系统生命周期和用户体验的思考而不仅仅是跑通了一个模型。6.3 源码学习和二次开发的正确姿势最后说一个很多“下载源码型”选手关心的问题拿到一份源码之后正确的学习顺序是什么。第一遍不要试图读懂每一个文件先找到入口文件通常是 app.py 或 main.py运行起来走通整个上传 → 识别 → 返回结果的流程第二遍画出系统的请求链路标出每个环节调用了哪个函数、哪个文件第三遍再深入看模型部分重点看数据加载、模型定义、训练循环这三块第四遍才去做改动比如改类别、加记录、调参数。如果你一上来就对着全部代码逐行研究大概率两天就放弃了。我每次带毕设都会给一个原则读源码不是背源码而是理解作者的决策。为什么用 Flask 不用 Django为什么选这个数据集为什么在损失函数里加类别权重这些问题比代码本身更有价值。你能复现并解释这些决策才算是真正“吃透”了这份源码也才能在答辩时把工作量讲得有底气。本文还有配套的精品资源点击获取