简介这份资源是面向计算机、通信、人工智能、自动化等相关专业学生与教师的Python毕业设计垃圾分类数据系统完整源码包适合作为毕业设计、期末课程设计或课程大作业的参考方案也可供初学者学习进阶。压缩包共20个文件约35.12MB包含7个py源码文件、6个zip数据集压缩包、3个ui界面文件以及cpp、jpg、md、png等辅助文件涵盖界面设计、分类逻辑与数据样本等模块。目前已有296人学习下载。项目代码经过调试测试可正常运行答辩评审分达98分整体结构清晰、借鉴价值较高。读者可从中获取完整的系统实现思路、界面与逻辑分离的代码组织方式、垃圾分类数据集及说明文档基础较好的同学还能在此基础上修改调整实现不同功能快速完成自己的毕设或课程设计任务。1. 垃圾分类数据系统到底在做什么从一次答辩被问到哑口无言的经历说起很多计算机毕业设计选题里「基于 Python 的垃圾分类数据系统」看起来是最没技术含量的一类——不就是增删改查加个图表吗我带过几届学生的毕设真正翻车的往往不是代码写不出来而是答辩时老师问一句「你这个分类准确率怎么算的、数据集从哪来、为什么用这个模型不用那个」当场卡壳。这个系统的核心其实有两层一层是业务数据管理垃圾类别、投放记录、区域统计、用户积分另一层是图像识别拍一张垃圾照片返回它属于可回收物、厨余、有害还是其他。前者是 Web 后端的基本功后者才是拉开差距的地方。这篇文章面向三类人正在做计算机毕业设计、需要一套能跑通又能讲清楚原理的 Python 项目想用 Python 入门图像分类但不知道从哪下手的新手以及需要一套垃圾分类数据管理后台做二次开发的从业者。我会把数据从哪来、模型怎么选、后端怎么搭、参数怎么调、哪里最容易翻车按我自己做过的方案讲一遍。你照着走能拿到一个可演示、可答辩、可继续扩展的系统而不是一个只能跑 demo 的空壳。2. 数据从哪来、怎么洗垃圾分类数据集与预处理流水线2.1 公开数据集的选择与类别对齐垃圾分类识别能不能做出来七成取决于数据。常见做法是找公开的垃圾分类图像数据集这类数据集一般按「可回收物 / 厨余垃圾 / 有害垃圾 / 其他垃圾」四大类组织每类下面再细分小类比如可回收物下有塑料瓶、纸箱、易拉罐。我一般会先确认三件事类别数量、每类样本量、图片分辨率是否统一。如果某类样本只有几十张训练出来必然偏科这时候要么做数据增强要么直接砍掉这个类别别硬撑。选数据集时有个容易被忽略的点类别命名要和你的业务数据库对齐。数据库里垃圾类别表用的是中文名加编码数据集文件夹用的是英文名中间必须有一张映射表否则后面统计报表和识别结果对不上答辩时老师一查就露馅。环节常见做法参数/注意点数据集来源公开垃圾分类图像数据集确认类别数与样本均衡度类别映射建一张 category_map 表中文名、英文名、编码三列对齐划分比例训练/验证/测试 7:2:1按类别分层抽样别随机切图片尺寸统一缩放到 224×224与所选模型输入一致2.2 用 Python 做数据清洗与增强拿到原始图片后第一步不是直接喂模型而是清洗删掉损坏文件、去掉重复图、统一格式。下面这段脚本做的是遍历目录、校验图片可读性、按类别统计数量并把不合格的文件挪到隔离目录避免污染训练集。import os import shutil from PIL import Image from collections import defaultdict RAW_DIR dataset/raw # 原始数据根目录下面按类别分子目录 CLEAN_DIR dataset/clean # 清洗后输出目录 BAD_DIR dataset/bad # 损坏或异常文件隔离目录 stats defaultdict(int) for cls in os.listdir(RAW_DIR): cls_path os.path.join(RAW_DIR, cls) if not os.path.isdir(cls_path): continue out_path os.path.join(CLEAN_DIR, cls) os.makedirs(out_path, exist_okTrue) for fname in os.listdir(cls_path): fpath os.path.join(cls_path, fname) try: with Image.open(fpath) as img: img.verify() # 校验文件完整性 with Image.open(fpath) as img: img img.convert(RGB) # 统一转 RGB去掉 alpha 通道 img img.resize((224, 224)) # 统一尺寸 img.save(os.path.join(out_path, fname)) stats[cls] 1 except Exception: os.makedirs(os.path.join(BAD_DIR, cls), exist_okTrue) shutil.move(fpath, os.path.join(BAD_DIR, cls, fname)) for k, v in stats.items(): print(f{k}: {v} 张可用)这段代码的关键在img.verify()和二次Image.open。verify()只检查文件头检查完文件对象就失效了所以必须重新打开才能做转换。convert(RGB)是为了处理 PNG 带透明通道的情况很多模型不接受四通道输入。resize放在清洗阶段做比在训练时每轮都做要省时间。跑完之后看打印的统计如果某类数量明显低于其他类就要回到 2.1 说的增强或砍类。数据增强我一般不在清洗阶段做而是放在训练时的Dataset里用随机翻转、随机裁剪、颜色抖动。原因是增强后的图如果落盘会占大量空间而且每轮都变能提升泛化。常见做法是用torchvision.transforms组合训练集用带随机的变换验证和测试集只用 resize 加归一化。提示归一化的均值方差要用你自己数据集的统计值别直接抄 ImageNet 的。抄了也能跑但收敛会慢答辩被问到「为什么用这组参数」也答不上来。3. 模型选型与训练从迁移学习到可解释的分类结果3.1 为什么毕业设计优先选迁移学习而不是从零训练从零训练一个卷积网络在几万张图上没有 GPU 基本跑不动而且效果大概率不如迁移学习。迁移学习的思路是拿一个在大规模数据集上预训练好的骨干网络常见的有 ResNet、MobileNet、EfficientNet把最后的分类层换成你的类别数然后微调。对垃圾分类这种类别不多、样本量中等的任务迁移学习通常几轮就能到不错的准确率。选哪个骨干如果答辩机器没有独立显卡优先 MobileNet 系列参数量小、推理快如果有 GPU 且追求准确率ResNet50 是稳妥选择。EfficientNet 准确率高但输入分辨率敏感调参麻烦新手容易翻车。我一般会先用 MobileNetV3 跑通全流程再视情况换大的。骨干网络参数量级适合场景注意点MobileNetV3小CPU 推理、演示准确率略低够用ResNet50中有 GPU、追求精度训练显存占用较高EfficientNet-B0中精度与速度平衡输入尺寸敏感3.2 训练脚本与关键参数下面是一个基于 PyTorch 的训练骨架包含数据加载、模型替换分类头、训练循环和验证。参数我按经验给了默认值后面会解释每个怎么调。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models DEVICE torch.device(cuda if torch.cuda.is_available() else cpu) BATCH_SIZE 32 # 显存不够就降到 16 或 8 EPOCHS 15 # 迁移学习通常 10~20 轮足够 LR 1e-3 # 分类头学习率微调时可对骨干用更小值 NUM_CLASSES 4 # 与你的类别数一致 train_tf transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) train_ds datasets.ImageFolder(dataset/clean/train, transformtrain_tf) val_ds datasets.ImageFolder(dataset/clean/val, transformval_tf) train_loader DataLoader(train_ds, batch_sizeBATCH_SIZE, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_sizeBATCH_SIZE, shuffleFalse, num_workers4) model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.DEFAULT) model.classifier[-1] nn.Linear(model.classifier[-1].in_features, NUM_CLASSES) model model.to(DEVICE) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lrLR, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxEPOCHS) for epoch in range(EPOCHS): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(DEVICE), labels.to(DEVICE) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(DEVICE), labels.to(DEVICE) pred model(imgs).argmax(dim1) correct (pred labels).sum().item() total labels.size(0) print(fepoch {epoch1}, val_acc {correct/total:.4f}) torch.save(model.state_dict(), garbage_cls.pth)逻辑上分四块数据变换、数据集加载、模型改造、训练循环。参数说明几个关键的。BATCH_SIZE直接决定显存占用CPU 训练建议 8 或 16GPU 可以 32 起步。LR用 1e-3 是因为只训练了新的分类头如果要对整个网络微调骨干部分的学习率要降到 1e-4 甚至更低否则预训练权重会被破坏。CosineAnnealingLR让学习率按余弦曲线下降比固定学习率收敛更稳。weightsDEFAULT表示加载预训练权重这是迁移学习的前提别写成None。训练完保存的是state_dict部署时要用同样的模型结构加载。验证准确率如果卡在某个值上不去先看是不是某类样本太少再看学习率是不是太大导致震荡。3.3 把识别结果接进业务系统模型跑通只是第一步毕业设计要的是「系统」。识别接口一般做成一个独立的服务后端收到图片后调用模型返回类别和置信度再写进投放记录表。下面是一个最小推理函数配合 Flask 或 FastAPI 暴露成接口。from PIL import Image import torch from torchvision import transforms CLASS_NAMES [可回收物, 厨余垃圾, 有害垃圾, 其他垃圾] infer_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def predict(image_path, model, device): img Image.open(image_path).convert(RGB) tensor infer_tf(img).unsqueeze(0).to(device) # 增加 batch 维度 model.eval() with torch.no_grad(): logits model(tensor) prob torch.softmax(logits, dim1) conf, idx prob.max(dim1) return CLASS_NAMES[idx.item()], conf.item()unsqueeze(0)是因为模型期望输入是[batch, channel, height, width]单张图没有 batch 维度。softmax把 logits 转成概率max同时拿到置信度和类别索引。置信度低于某个阈值比如 0.6时业务上可以返回「无法确定请人工选择」这比硬给一个错类别体验好得多。这个阈值怎么定看你的验证集上错误样本的置信度分布一般取能让错误率明显下降的值。4. 后端与数据库把识别、记录、统计串成一个系统4.1 表结构设计与字段取舍垃圾分类数据系统的数据库不复杂但字段设计要经得起答辩追问。核心表一般四张用户表、垃圾类别表、投放记录表、区域表。投放记录表是核心它关联用户、类别、区域还存识别置信度和时间戳。表名关键字段说明userid, username, pointspoints 存积分投放正确可加分categoryid, name, code, en_nameen_name 用于和模型类别映射recordid, user_id, category_id, area_id, confidence, created_at每次投放一条areaid, name, parent_id支持多级区域统计confidence字段别省它既是识别质量的记录也是后面做统计和排查的依据。created_at建索引按时间做报表查询会快很多。category表里的en_name就是 2.1 说的映射表落地模型输出英文类别写库时转成 category_id。4.2 用 Flask 串起识别与记录写入后端我一般用 Flask轻量、上手快、毕业设计够用。下面是一个投放接口的骨架接收图片和用户信息调用模型写记录返回结果。from flask import Flask, request, jsonify import sqlite3, datetime, os app Flask(__name__) UPLOAD_DIR uploads os.makedirs(UPLOAD_DIR, exist_okTrue) def get_db(): conn sqlite3.connect(garbage.db) conn.row_factory sqlite3.Row return conn app.route(/api/drop, methods[POST]) def drop(): user_id request.form.get(user_id) area_id request.form.get(area_id) file request.files.get(image) if not file: return jsonify({code: 400, msg: 缺少图片}), 400 save_path os.path.join(UPLOAD_DIR, f{datetime.datetime.now().timestamp()}.jpg) file.save(save_path) cls_name, conf predict(save_path, model, DEVICE) conn get_db() cat conn.execute(SELECT id FROM category WHERE name ?, (cls_name,)).fetchone() conn.execute( INSERT INTO record (user_id, category_id, area_id, confidence, created_at) VALUES (?,?,?,?,?), (user_id, cat[id], area_id, conf, datetime.datetime.now().isoformat()) ) conn.commit() conn.close() return jsonify({code: 0, category: cls_name, confidence: round(conf, 4)})request.files拿上传文件file.save落盘后再推理避免内存里反复处理。写库前先查 category_id这一步不能省否则外键对不上。返回里带confidence前端可以据此提示用户「识别置信度较低请确认」。sqlite3适合毕设演示如果要做并发或数据量大换成 MySQL 或 PostgreSQLSQL 基本不用改改连接方式即可。4.3 统计报表的 SQL 与前端展示答辩时老师最爱看的就是统计图。按类别统计投放次数、按区域统计、按时间趋势三条 SQL 就能撑起一个仪表盘。-- 各类别投放次数占比 SELECT c.name, COUNT(*) AS cnt FROM record r JOIN category c ON r.category_id c.id GROUP BY c.name ORDER BY cnt DESC; -- 近 7 天每日投放量 SELECT DATE(created_at) AS day, COUNT(*) AS cnt FROM record WHERE created_at DATE(now, -7 days) GROUP BY day ORDER BY day; -- 各区域投放量 SELECT a.name, COUNT(*) AS cnt FROM record r JOIN area a ON r.area_id a.id GROUP BY a.name ORDER BY cnt DESC;第一条给饼图第二条给折线图第三条给柱状图。前端用 ECharts 或 Chart.js 接 JSON 就行。注意DATE(created_at)在 SQLite 里对 ISO 格式字符串有效如果你存的是时间戳整数要先转换。这类细节答辩时容易被问到提前统一时间存储格式。5. 避坑与排查那些让系统跑不起来的真实问题5.1 类别映射错位导致统计全乱现象识别结果看着对但统计报表里「有害垃圾」数量异常高。原因模型输出的类别顺序和CLASS_NAMES列表顺序不一致或者ImageFolder按文件夹名排序得到的索引和你的列表对不上。解决打印train_ds.classes确认实际顺序用它来生成CLASS_NAMES别手写。数据库里的 category 表也要按同一套映射初始化。5.2 训练准确率高但演示时识别全错现象验证集准确率 90% 以上现场拍照识别却乱给。原因训练数据是摆拍图背景干净现场照片背景杂乱、光照不同分布不一致。解决训练时加强数据增强随机裁剪、颜色抖动、随机擦除演示前用手机拍几十张真实场景图做一次测试必要时把这些图加入训练集再微调几轮。5.3 上传大图导致接口超时或内存爆掉现象用户上传几 MB 的高清图接口响应慢甚至进程被杀。原因图片没压缩就直接送进模型或者落盘前在内存里做了多次转换。解决在保存前用 PIL 限制最长边比如 1024推理时再 resize 到 224。Flask 配置MAX_CONTENT_LENGTH限制上传大小超限直接返回错误。5.4 数据库并发写入报锁现象多人同时投放时SQLite 报database is locked。原因SQLite 默认写锁粒度粗并发写会冲突。解决毕设演示场景可以加写重试或者直接换 MySQL。如果坚持用 SQLite开启 WAL 模式PRAGMA journal_modeWAL;能缓解但别指望它扛高并发。5.5 模型文件路径写死导致换机器跑不了现象在自己电脑上跑得好好的换到答辩教室的电脑就报找不到模型。原因代码里用了绝对路径。解决所有路径用相对路径或基于os.path.dirname(__file__)拼接模型文件、数据库文件、上传目录都跟着项目走。打包时把模型文件一起带上别只拷代码。6. 让系统更耐问置信度阈值调优与一个可复现的验证习惯答辩和实际使用中最容易被追问的是「你这个识别到底准不准错了怎么办」。与其含糊其辞不如主动做一件事用验证集画一张置信度分布图找出一个阈值让低于阈值的样本里错误占比明显偏高然后业务上对这些样本走人工确认。具体做法是跑一遍验证集收集每个样本的预测置信度和是否正确按置信度分桶统计错误率。import numpy as np confs, corrects [], [] model.eval() with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(DEVICE), labels.to(DEVICE) prob torch.softmax(model(imgs), dim1) conf, pred prob.max(dim1) confs.extend(conf.cpu().numpy()) corrects.extend((pred labels).cpu().numpy()) confs np.array(confs) corrects np.array(corrects) for lo in [0.5, 0.6, 0.7, 0.8, 0.9]: mask confs lo if mask.sum() 0: print(f阈值 {lo}: 覆盖 {mask.mean():.2%}, 其中准确率 {corrects[mask].mean():.4f})跑完你会看到阈值越高覆盖的样本越少但准确率越高。选一个覆盖率和准确率的平衡点比如阈值 0.7 时覆盖 85% 样本、准确率 95%那剩下 15% 就走人工确认。这个数字写进论文或答辩 PPT比空说「准确率 90%」有说服力得多。我自己的习惯是每换一次模型或数据集都重新跑一遍这个脚本把阈值记在配置里而不是写死在代码里。这样换模型时不用改业务逻辑只改一个配置项。另外验证集一定要留出没参与过任何训练的样本别拿训练集当验证集自欺欺人这个坑我见过太多人踩。把阈值、覆盖率、准确率三个数一起记录时间久了你会对模型的脾气有直觉知道什么时候该信它、什么时候该拦一道。希望帮到你。本文还有配套的精品资源点击获取