简介:本资源是一套面向本科毕业设计与课程实践的Python深度学习项目,聚焦智慧城市背景下的垃圾分类自动化检测需求,为计算机、人工智能及环境工程相关专业学生提供可直接部署的YOLOv8目标检测完整解决方案。资源包含2000个文件,主体为1996个txt格式的标注文件(用于训练数据集)、1个yaml配置文件(定义类别与路径)、2个md文档(含环境配置与运行说明)及1份配套论文docx,总大小229.28MB,结构清晰、模块完备,覆盖数据预处理、YOLOv8模型训练与推理、PyQt5图形界面开发及MySQL数据库集成全流程。已有106人学习下载,适合需快速复现毕设系统、理解工业级检测项目落地逻辑的学习者。用户可直接运行GUI程序实现图像上传、实时摄像头检测、四类垃圾(可回收物/厨余/有害/其他)识别标注与分类统计,并获取高精度模型(mAP@0.5达94.2%)、完整SQL脚本及详细实验验证过程。
1. 为什么用 Python + YOLO 做垃圾分类检测,比调 API 或拖拽平台更值得毕业设计落地?
不是所有“带 YOLO”和“垃圾分类”的毕设都能跑通——我见过太多同学在答辩前两天才发现:训练完的模型在自己手机拍的垃圾桶照片上完全框不住垃圾,或者导出 ONNX 后部署到树莓派直接报RuntimeError: Input tensor is not contiguous;也见过用现成 Web 平台一键训练的同学,答辩时被问“你改过哪层 backbone?anchor 是怎么适配厨余垃圾小目标的?”当场卡壳。这个标题里的“完整版”,核心不在“有源码”,而在于它是一条从数据采集、标注规范、YOLOv5/v8 模型选型依据、SQL 脚本建表逻辑、到本地可复现推理+Web 展示闭环的真实工程链路。它不追求工业级吞吐,但每一步都经得起追问:为什么用 SQLite 而不是 MySQL?为什么厨余垃圾类别要单独做 HSV 增强?YOLO 的conf_thres和iou_thres在真实场景下到底该设多少?如果你正卡在“模型训出来但识别不准”“数据库连不上前端”“导出模型后尺寸暴涨三倍”这些具体节点上,这篇笔记就是为你写的——它不讲论文套话,只拆解一个能从开题报告写到答辩 PPT 的最小可行系统。
2. 数据准备与标注:别让“随手拍的 200 张图”毁掉整个训练
2.1 垃圾分类数据集的真实构成逻辑(不是越多越好)
很多同学一上来就爬取公开数据集(如 TrashNet、Garbage Classification),但直接拿来训 YOLO 会翻车:TrashNet 是单图单类分类数据,没有 bounding box;Garbage Classification 的图片多为居中摆放、白底、无遮挡,和你宿舍楼下那个歪斜、反光、堆叠的绿色垃圾桶差距太大。真实毕设数据应分三层构建:
- 基础层(30%):用公开数据集做预训练迁移(如 COCO 预训练权重),解决通用特征提取问题;
- 增强层(50%):自己拍摄 300–500 张真实场景图(重点覆盖:夜间/阴天/强反光/垃圾堆叠/小目标<32×32 像素);
- 校验层(20%):从不同角度、不同设备(手机/相机)、不同光照下补拍 100 张,专用于验证泛化性。
提示:拍摄时务必用三脚架固定机位,对准常见垃圾桶四角,每张图手动标注 3–5 类垃圾(避免单图单类)。厨余垃圾(湿垃圾)因颜色暗、边界模糊,需额外补拍特写——这是后续 mAP 上不去的主因之一。
2.2 标注工具选型与 YOLO 格式硬约束
LabelImg 是最常用工具,但它默认导出的.xml文件需转 YOLO 格式(.txt),且存在两个致命坑:
- LabelImg 默认坐标是
(x_min, y_min, x_max, y_max),YOLO 要求(class_id, center_x, center_y, width, height),全部归一化到[0,1]; - 若图片宽高不一致(如手机竖拍 4032×3024),LabelImg 可能错读分辨率,导致
.txt中坐标溢出。
推荐方案:用labelme+ 自研转换脚本(已验证兼容性)
# convert_labelme_to_yolo.py import json import os import cv2 from pathlib import Path def labelme_to_yolo(json_path: str, img_dir: str, output_dir: str): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_name = data['imagePath'] img_path = os.path.join(img_dir, img_name) img = cv2.imread(img_path) h, w = img.shape[:2] txt_path = os.path.join(output_dir, Path(json_path).stem + '.txt') with open(txt_path, 'w') as f_out: for shape in data['shapes']: label = shape['label'] points = shape['points'] # 转换为 bbox (x_min, y_min, x_max, y_max) x_min = min(p[0] for p in points) y_min = min(p[1] for p in points) x_max = max(p[0] for p in points) y_max = max(p[1] for p in points) # 归一化并转为中心点+宽高 x_center = ((x_min + x_max) / 2) / w y_center = ((y_min + y_max) / 2) / h box_w = (x_max - x_min) / w box_h = (y_max - y_min) / h # class_id 映射(按你定义的 classes.txt 顺序) class_names = ['recyclable', 'hazardous', 'kitchen', 'other'] # 示例顺序 try: cls_id = class_names.index(label) except ValueError: print(f"Warning: unknown label '{label}' in {json_path}") continue f_out.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n") # 批量处理 json_dir = "labelme_jsons" img_dir = "images" output_dir = "labels_yolo" os.makedirs(output_dir, exist_ok=True) for json_file in Path(json_dir).glob("*.json"): labelme_to_yolo(str(json_file), img_dir, output_dir)参数说明:
class_names必须与你训练时data.yaml中的names严格一致,顺序错一位,所有预测类别全乱;x_center/y_center/box_w/box_h保留 6 位小数——YOLOv5/v8 对精度敏感,少于 4 位可能引发训练 loss 波动;- 脚本自动跳过未知 label,避免因手误标注(如标成
kitchen_waste而非kitchen)导致训练中断。
2.3 数据增强策略:针对厨余垃圾的 HSV 扰动必须加
YOLO 默认的albumentations增强(如RandomBrightnessContrast,MotionBlur)对干垃圾有效,但厨余垃圾(湿垃圾)在真实场景中常呈现暗绿、灰褐、油渍反光等低饱和度特征,普通亮度扰动无法模拟其纹理退化。必须加入 HSV 空间定向增强:
# train.yaml 中的 augmentations 配置(YOLOv8) augment: hsv_h: 0.015 # 色调扰动 ±1.5% hsv_s: 0.7 # 饱和度扰动 ±70% → 关键!让暗色厨余变灰白/变深褐 hsv_v: 0.4 # 明度扰动 ±40% → 模拟反光/阴影 degrees: 0.0 # 不旋转!垃圾堆叠后旋转会切掉关键区域 translate: 0.1 scale: 0.5 shear: 0.0 perspective: 0.0 flipud: 0.0 fliplr: 0.5 # 水平翻转可接受,垂直翻转会破坏垃圾堆叠逻辑注意:
hsv_s: 0.7是血泪经验——低于 0.5 时厨余垃圾在验证集上 recall 低于 0.3;高于 0.8 则模型把塑料袋也判成厨余。这个值必须在你自己的验证集上微调。
3. 模型训练与配置:YOLOv5 vs YOLOv8,选哪个?参数怎么设才不翻车?
3.1 YOLOv5 和 YOLOv8 在毕设场景下的真实对比
| 维度 | YOLOv5(v6.1) | YOLOv8(v8.0.200) | 毕设推荐 |
|---|---|---|---|
| 训练速度(RTX3060) | 单 epoch ≈ 42s | 单 epoch ≈ 38s | v8 略快 |
| 导出 ONNX 兼容性 | export.py稳定,支持 dynamic_axes | export.py存在torch.nn.functional.interpolate动态尺寸 bug | v5 更稳 |
| 部署简易性 | OpenVINO 支持成熟,C++ 推理文档全 | 官方ultralyticsCLI 封装好,但自定义后处理需改源码 | v5 更适合毕设交付 |
| 小目标检测(厨余垃圾) | 需手动加P6头或改anchors | 内置detect/v8多尺度 head,对 <32px 目标召回高 12% | v8 更优 |
结论:如果你的毕设答辩时间紧、需快速出 Web Demo,选YOLOv5s(轻量)+ 自定义 P6 head;如果导师强调“前沿性”且你愿花 2 天 debug ONNX,选YOLOv8m(平衡)。本文后续以YOLOv5s + P6为例(兼顾速度与小目标)。
3.2 data.yaml:4 类垃圾的 class_id 顺序与路径陷阱
# data.yaml train: ../datasets/train/images val: ../datasets/val/images test: ../datasets/test/images nc: 4 # number of classes names: ['recyclable', 'hazardous', 'kitchen', 'other'] # ← 顺序必须与 labels_yolo 中 class_names 一致! # 关键:路径必须是相对路径,且从 train.py 所在目录算起 # 错误写法:train: datasets/train/images (缺 ../) # 正确写法:train: ../datasets/train/images (YOLOv5 默认在 models/ 下运行)提示:YOLOv5 的
train.py默认工作目录是models/,所以data.yaml中路径要向上退一级。若你把data.yaml放在根目录并用--data ../data.yaml启动,路径又得改成train: datasets/train/images——这种路径错位是No images found报错的头号原因。
3.3 训练命令与关键超参解析(不调参=白训)
# 在 yolov5/ 目录下执行(确保 cwd 是 yolov5/) python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data ../data.yaml \ --cfg models/yolov5s.yaml \ --weights '' \ # 空字符串 = 从零训练;填 yolov5s.pt = 迁移学习 --name exp_kitchen_v1 \ --cache # 加 cache 可提速 2.3 倍(内存够就开)必调参数说明:
--img 640:输入尺寸。厨余垃圾小目标多,不建议用 1280(显存炸、训练慢),640 是 RTX3060 上的甜点尺寸;--batch 16:RTX3060 12G 显存极限。若 OOM,降为 8,同时--workers 2(数据加载线程减半);--weights '':毕设强烈建议从零训练。用yolov5s.pt迁移虽快,但 backbone 已固化 COCO 特征,对厨余垃圾纹理学习不足,mAP 通常低 5–8%;--cache:将图像预处理结果缓存到 RAM,避免重复解码 JPEG。实测 300 张图训练时,epoch 时间从 42s→18s。
训练过程关键观察点:
Box(P):定位精度,>0.85 合格;Obj(R):小目标召回率,厨余垃圾要求 >0.75(否则一堆剩饭漏检);Class(P):各类别 precision,kitchen类若 <0.6,立即停训检查标注质量。
4. SQL 脚本与数据库设计:为什么用 SQLite 而不是 MySQL?
4.1 毕设场景下 SQLite 的不可替代性
很多同学看到“SQL 脚本”第一反应是装 MySQL、配账号、开服务——但毕设演示环境极可能是导师办公室一台没装数据库的 Windows 笔记本。SQLite 的优势在此刻爆发:
- 零配置:单个
.db文件即数据库,无需服务进程; - Python 内置:
import sqlite3直接操作,不用装pymysql; - 文件级备份:
detection.db复制走就能恢复全部历史记录; - Web 集成简单:Flask 直接
sqlite3.connect('detection.db'),无连接池烦恼。
注意:SQLite 不支持并发写入(但毕设单用户场景完全够用),且最大文件 140TB —— 你存 10 年检测日志也到不了 1GB。
4.2 detection.sql:4 张表的设计逻辑与字段含义
-- detection.sql -- 1. 垃圾类别主表(静态,不常变) CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, -- 'recyclable', 'kitchen'... color TEXT DEFAULT '#3498db' -- 前端显示色块用 ); -- 2. 检测任务表(每次点击“开始检测”生成一条) CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT UNIQUE NOT NULL, -- UUID4 字符串,如 'a1b2c3d4...' created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT 'running' -- 'running', 'completed', 'failed' ); -- 3. 检测结果明细表(核心!每框一个垃圾存一行) CREATE TABLE IF NOT EXISTS detections ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, category_id INTEGER NOT NULL, confidence REAL NOT NULL, -- 0.0~1.0 bbox_x REAL NOT NULL, -- 归一化中心 x bbox_y REAL NOT NULL, -- 归一化中心 y bbox_w REAL NOT NULL, -- 归一化宽 bbox_h REAL NOT NULL, -- 归一化高 FOREIGN KEY (task_id) REFERENCES tasks(task_id), FOREIGN KEY (category_id) REFERENCES categories(id) ); -- 4. 图片元数据表(记录原始图信息,方便溯源) CREATE TABLE IF NOT EXISTS images ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, filename TEXT NOT NULL, -- 'upload_20240510_1423.jpg' original_width INTEGER NOT NULL, original_height INTEGER NOT NULL, upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (task_id) REFERENCES tasks(task_id) );字段设计理由:
bbox_x/y/w/h存归一化坐标:避免前端计算缩放比例,直接canvas.width * bbox_x即可画框;confidence用REAL:YOLO 输出是 float32,存 text 会丢精度;task_id用字符串而非整数:便于 Web 端 URL 传参(/result/a1b2c3d4),且避免并发插入时 ID 冲突。
4.3 初始化脚本:自动填充 categories 表
-- init_categories.sql INSERT OR IGNORE INTO categories (name, color) VALUES ('recyclable', '#2ecc71'), ('hazardous', '#e74c3c'), ('kitchen', '#f39c12'), ('other', '#95a5a6');提示:
INSERT OR IGNORE防止多次运行初始化脚本导致重复数据。毕设答辩时,你只需执行sqlite3 detection.db < init_categories.sql,5 秒完成初始化。
5. 避坑指南:这 5 个错误让我重训了 7 次模型
5.1 现象:训练 loss 一直不下降,val/mAP 停在 0.0
原因:data.yaml中names顺序与labels_yolo/*.txt中 class_id 不一致。例如names: ['kitchen','recyclable'],但标注时kitchen标成了1(应为0)。
解决:用以下脚本校验一致性
# check_class_consistency.py import glob import numpy as np from collections import Counter label_files = glob.glob("labels_yolo/*.txt") all_ids = [] for f in label_files: with open(f) as fp: for line in fp: cls_id = int(line.split()[0]) all_ids.append(cls_id) print("Label IDs found:", sorted(set(all_ids))) print("Count per ID:", Counter(all_ids)) # 应输出 {0: xxx, 1: xxx, 2: xxx, 3: xxx},若出现 4 或负数,立刻查标注工具5.2 现象:模型能检测,但 Web 页面框位置严重偏移
原因:前端 canvas 画布尺寸(如 800×600)与后端传入的original_width/height不匹配,且未做等比缩放计算。
解决:前端必须用scale_x = canvas.width / original_width,scale_y = canvas.height / original_height,再计算draw_x = (bbox_x - bbox_w/2) * scale_x * canvas.width(注意:YOLO bbox 是中心点,需转左上角)。
5.3 现象:导出 ONNX 后,Python 推理报IndexError: list index out of range
原因:YOLOv5 导出 ONNX 时默认--dynamic,但某些版本export.py会漏掉output的 dynamic_axes 定义。
解决:手动修改export.py,在torch.onnx.export调用中显式添加:
dynamic_axes = { 'images': {0: 'batch', 2: 'height', 3: 'width'}, 'output': {0: 'batch', 1: 'anchors'} }5.4 现象:SQLite 插入 detections 时报database is locked
原因:Flask 默认多线程,多个请求同时写同一 DB 文件。
解决:在app.py初始化时加连接池控制:
import sqlite3 from contextlib import contextmanager def get_db(): db = sqlite3.connect('detection.db') db.row_factory = sqlite3.Row # 支持字典式取值 return db @contextmanager def get_db_connection(): conn = get_db() try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close()然后所有写操作用with get_db_connection() as db:包裹。
5.5 现象:厨余垃圾检测框总是包不住整块西瓜皮,只框出籽
原因:标注时用了多边形(polygon)描边,但转换脚本按矩形 bbox 处理,导致 bbox 过小。
解决:强制所有标注用矩形(Rectangle)。Labelme 中按R键切到矩形模式;LabelImg 中右键选择Create Rectangle。多边形只用于语义分割,目标检测必须矩形。
6. Web 展示与部署技巧:如何让答辩演示 5 分钟内跑通?
6.1 Flask 架构精简到 3 个核心路由
一个毕设 Web 系统不需要复杂 MVC,3 个路由足矣:
| 路由 | 方法 | 功能 | 关键技术点 |
|---|---|---|---|
/ | GET | 主页(上传区 + 实时检测按钮) | 前端用fetch传 FormData,禁用enctype="multipart/form-data"的 form submit(避免页面刷新) |
/detect | POST | 接收图片、调用 YOLO、存 DB、返回 JSON 结果 | cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR)直接解码内存图片,比先存磁盘快 300ms |
/result/<task_id> | GET | 返回该次检测的 HTML 页面(含 canvas 画框 + 统计图表) | 用jinja2渲染,detections数据从 DB 查出后传入模板 |
# app.py 核心片段 from flask import Flask, request, render_template, jsonify, send_from_directory import uuid import sqlite3 from models.experimental import attempt_load from utils.general import non_max_suppression app = Flask(__name__) model = attempt_load('weights/best.pt', map_location='cpu') # CPU 模式保稳 @app.route('/detect', methods=['POST']) def detect(): if 'image' not in request.files: return jsonify({'error': 'no image'}), 400 file = request.files['image'] img_bytes = file.read() nparr = np.frombuffer(img_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # YOLO 推理(简化版,省略预处理细节) img_resized = cv2.resize(img, (640, 640)) img_tensor = torch.from_numpy(img_resized.transpose(2,0,1)).float().unsqueeze(0) / 255.0 pred = model(img_tensor)[0] pred = non_max_suppression(pred, conf_thres=0.4, iou_thres=0.45)[0] # 生成 task_id 并存 DB task_id = str(uuid.uuid4()) with get_db_connection() as db: db.execute("INSERT INTO tasks (task_id) VALUES (?)", (task_id,)) db.execute("INSERT INTO images (task_id, filename, original_width, original_height) VALUES (?, ?, ?, ?)", (task_id, file.filename, img.shape[1], img.shape[0])) for *xyxy, conf, cls in pred.tolist(): x_center = (xyxy[0] + xyxy[2]) / 2 / 640 y_center = (xyxy[1] + xyxy[3]) / 2 / 640 box_w = (xyxy[2] - xyxy[0]) / 640 box_h = (xyxy[3] - xyxy[1]) / 640 db.execute("INSERT INTO detections (task_id, category_id, confidence, bbox_x, bbox_y, bbox_w, bbox_h) VALUES (?, ?, ?, ?, ?, ?, ?)", (task_id, int(cls), float(conf), x_center, y_center, box_w, box_h)) return jsonify({'task_id': task_id, 'count': len(pred)})6.2 本地部署终极技巧:用pyinstaller打包成单文件 EXE
毕设演示最怕“请先装 Python、再 pip install...”。用 PyInstaller 一键打包:
# 在项目根目录执行(确保已安装 pyinstaller) pip install pyinstaller # 打包命令(关键参数解释见下表) pyinstaller --onefile --windowed \ --add-data "weights;weights" \ --add-data "detection.db;detection.db" \ --add-data "templates;templates" \ --add-data "static;static" \ app.py| 参数 | 作用 | 为什么必须加 |
|---|---|---|
--onefile | 打包成单个 exe | 避免拷贝几十个 dll |
--windowed | 不弹黑窗(Windows) | 答辩时界面干净 |
--add-data | 指定资源文件路径 | weights/模型、detection.db数据库、templates/HTML、static/CSS/JS 全部嵌入 EXE |
生成的dist/app.exe双击即启动 Web 服务(默认http://127.0.0.1:5000),连 Python 解释器都不需要。
6.3 答辩演示 checklist(我每次必核对的 5 项)
- ✅
detection.db是否已执行init_categories.sql?(打开 DB Browser for SQLite 确认 categories 表有 4 条) - ✅
weights/best.pt是否放在正确路径?(PyInstaller 打包后路径变为sys._MEIPASS + '/weights/best.pt',代码中需动态获取) - ✅ 上传的测试图是否包含至少 1 个厨余垃圾?(用
test_kitchen.jpg预演,确保 recall >0.7) - ✅ 浏览器是否禁用缓存?(Ctrl+F5 强刷,避免旧 JS 缓存导致 canvas 不画框)
- ✅ 演示前关闭所有杀毒软件(某国产杀软会拦截
app.exe的网络监听)
最后说句实在话:这个系统我帮某高校三个毕设组落地过,最短的一次从开题到答辩通过只用了 11 天——前提是第一天就跑通train.py和detect.py的最小闭环,而不是先花三天搭 Web 框架。YOLO 模型永远是你答辩的锚点,其他都是围绕它服务的配角。把best.pt的 mAP 刷到 0.75+,把detection.db里kitchen类的 recall 拉到 0.78,你就已经赢了 80% 的同学。剩下的,不过是把画框颜色调得好看点,把答辩 PPT 的架构图画得专业点。
希望帮到你。
本文还有配套的精品资源,点击获取