☰
YOLO垃圾分类检测毕设:权重推理、PyQt界面与MySQL落库
2026/10/26 3:40:05 网站建设 项目流程

简介:面向Python毕业设计与计算机视觉入门者的YOLO垃圾分类目标检测完整项目源码包,围绕目标检测模型、PyQt5可视化界面和MySQL数据管理,提供从数据集标注、模型训练推理到分类统计展示的一整套实现思路,适合社区垃圾站、校园智能分类终端等场景的课程设计或毕设复现。包内共2000个文件,约229.28MB,以1996个txt标注与数据说明文件为主,辅以2个md说明、1个yaml配置及1份docx配套论文,目录覆盖数据预处理、模型推理、界面交互与结果输出等模块,便于按结构查阅和二次扩展。已有108人学习下载。读者可对照配套论文梳理检测网络、特征模块与动态检测头的实现脉络,理解可回收物、厨余垃圾、有害垃圾及其他垃圾四类的检测流程、界面组织与数据库脚本用法,并参考精度与速度实验描述完成调试和改进。

1. 项目定位:一个能跑通的垃圾分类检测毕设到底长什么样

答辩前一周,A同学抱着笔记本冲进实验室,说垃圾分类系统的检测框全飘在角落,换了两台机器都复现。我打开压缩包一看,权重、训练脚本、界面、数据库脚本都在,缺的只是一份对得上的配置文件路径。这类基于 Python 的 YOLO 垃圾分类目标检测毕业设计,真正卡人的往往不是算法本身,而是源码能不能一次跑通、SQL 脚本能不能对上、检测结果能不能落到界面上。这份资源把训练权重、推理代码、PyQt 交互界面和 MySQL 建表脚本打包在一起,面向需要完整交付物的毕设场景,也适合想拿目标检测练手、不愿从零搭框架的开发者。下面按跑起来、改得动、扩得出的顺序拆。

2. 技术栈与目录结构:从权重文件到数据库脚本的全貌

2.1 技术选型拆解:为什么是 YOLO + PyTorch + PyQt

这套毕设的技术组合不是拍脑袋凑的,每一项都对应答辩现场要演示的动作。YOLO 系列负责单阶段目标检测,一张图前向一次就能吐出框和类别,帧率撑得住摄像头实时预览;PyTorch 做训练和推理后端,权重文件通用性好,换机器只要环境对齐就能加载;PyQt5 搭桌面交互界面,打开图片、点按钮检测、表格里看结果,演示链路直观;MySQL 存检测记录,答辩时能翻历史数据,体现“系统”而不只是“脚本”。常见做法还会挂一个 Flask 轻量接口,把检测能力暴露成 HTTP 服务,方便后面扩展 Web 端或移动端调用。

层次技术作用常见版本区间
算法框架YOLO(ultralytics 实现)目标检测推理与训练YOLOv5 / YOLOv8
深度学习后端PyTorch + torchvision张量计算、模型加载1.10 以上 / 2.x
图像处理OpenCV、Pillow读图、缩放、画框、中文标注opencv-python 4.x
桌面界面PyQt5主窗口、按钮、表格、图像显示5.15 左右
数据库MySQL + PyMySQL检测记录持久化MySQL 5.7 / 8.0
Web 扩展Flask可选的上传检测接口2.x

选型背后有一条现实约束:毕设周期通常只有几个月,环境依赖越少越好装。YOLO 的 ultralytics 包把训练、验证、导出压成几条命令,省掉自己写数据加载器和 Anchor 匹配的工作量;PyQt5 虽然不算新,但资料多、报错好搜,遇到“界面卡死”这类玄学问题至少能查到线程方案的现成写法。MySQL 的 SQL 脚本单独放在sql/目录,导入后表结构一次成型,不用在答辩现场手敲建表语句。

需要提醒的是,PyTorch 和 torchvision 的版本必须成对匹配,CUDA 版本又得跟显卡驱动对上。我一般会先确认机器上nvidia-smi显示的 CUDA 上限,再去装对应轮子,避免装完 import 就报 DLL 错误。CPU 也能跑推理,只是训练会慢到怀疑人生,毕设演示用 CPU 做单图检测问题不大。

2.2 目录结构逐层说明:数据、权重、界面、服务端各自在哪

拿到压缩包先别急着双击运行,花两分钟看清目录,后面改路径能少走很多弯路。这类项目的目录通常按“数据—模型—业务—界面—脚本”分层,下面是我整理后常见的一种布局:

garbage-yolo/ ├── data/ │ ├── images/ │ │ ├── train/ # 训练图片 │ │ └── val/ # 验证图片 │ ├── labels/ │ │ ├── train/ # 与图片同名的 txt 标注 │ │ └── val/ │ └── data.yaml # 数据集配置:路径、类别数、类别名 ├── weights/ │ └── best.pt # 训练好的权重,推理直接加载 ├── core/ │ ├── detector.py # 检测封装:加载模型、预处理、后处理 │ └── db.py # 数据库连接与插入 ├── ui/ │ └── main_window.py # PyQt5 主界面逻辑 ├── sql/ │ └── init.sql # 建库、建表、初始数据 ├── train.py # 训练入口 ├── detect.py # 命令行单图/批量检测入口 ├── requirements.txt # Python 依赖清单 └── README.md # 启动说明

data/是数据集根目录,images和labels必须严格对应:images/train/xxx.jpg对应labels/train/xxx.txt,文件名主干一致,YOLO 才能按名字找到标签。data.yaml里写清楚train、val的相对或绝对路径、nc(类别数)和names(类别名列表),这三样错一个,训练就会在加载阶段报错或者训出全零指标。

weights/best.pt是交付的核心文件之一,推理脚本默认从这加载。如果换了自己训的权重,要么覆盖它,要么改detector.py里的路径常量。core/detector.py是对 ultralytics 接口的薄封装,把“读图—推理—取框—画图”收成一个函数,界面和命令行都调它,避免逻辑散落两处。core/db.py管数据库连接,通常读一个config字典或环境变量,不要把密码硬编码在提交的代码里。

ui/main_window.py是 PyQt5 的主窗口,负责按钮事件、文件选择框、图像控件和结果表格。检测耗时操作要放到QThread里,否则界面会假死,这是桌面端最常见的翻车点。sql/init.sql负责建库建表,导入顺序一般是先建库、再建表、最后插初始类别或管理员记录。requirements.txt建议锁大版本,比如torch>=1.10、ultralytics>=8.0,太死的版本号换机器反而装不上。

提示:先跑detect.py做命令行单图检测,确认权重和环境没问题,再启动带界面的主程序。界面报错时排查成本高,命令行能先排掉一大半环境问题。

3. 核心检测链路:一张垃圾图片从输入到分类框输出的完整拆解

3.1 数据准备与标注格式转换

YOLO 用的是归一化后的中心点加宽高格式,每张图对应一个同名 txt,一行一个目标:类别索引 cx cy w h,后四个值都除以图片宽高,落在 0 到 1 之间。很多人拿到的原始标注是 VOC 的 XML 或 LabelMe 的 JSON,直接扔进训练会读不出来,需要先转一道。下面这段转换逻辑把 VOC 的xmin/ymin/xmax/ymax换成 YOLO 格式,顺便做边界裁剪:

import os import xml.etree.ElementTree as ET # 类别名到索引的映射,顺序必须跟 data.yaml 的 names 完全一致 CLASS_MAP = {"recyclable": 0, "kitchen": 1, "hazardous": 2, "other": 3} def voc_to_yolo(xml_path, img_w, img_h, out_path): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASS_MAP: continue cls_id = CLASS_MAP[name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 裁剪到图像范围内,避免标注越界导致归一化后出现负值 xmin, xmax = max(0, xmin), min(img_w, xmax) ymin, ymax = max(0, ymin), min(img_h, ymax) cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))

这段代码的关键点在两个地方。CLASS_MAP的顺序就是模型输出的索引顺序,训练时data.yaml的names列表也必须同序,否则会出现“框对了但类别全错”的经典问题。坐标归一化前先裁剪,是因为标注工具里偶尔会画出超出图像边界的框,不裁的话cx可能小于 0 或大于 1,训练时虽然不报错,但会拉低定位精度。转换完抽查几张 txt,用matplotlib把框画回图上肉眼确认,比看日志靠谱。

data.yaml的写法如下,路径建议用相对项目根目录的形式,换机器只要整体拷贝就能跑:

path: ./data train: images/train val: images/val nc: 4 names: ["recyclable", "kitchen", "hazardous", "other"]

nc必须等于names的长度,改了类别数记得同步改模型头,否则加载旧权重会报维度不匹配。数据集划分常见比例是 8:1:1 或 7:2:1,类别不均衡时别用纯随机,按类别分层抽样更稳。

3.2 模型推理代码逐段讲解

推理链路可以拆成四步:加载权重、读图预处理、前向推理、解析结果画框。ultralytics 把预处理和后处理封装得比较全,但理解内部做了什么,出问题时才知道往哪查。下面是一个可直接抄的检测封装:

from ultralytics import YOLO import cv2 class GarbageDetector: def __init__(self, weight_path="weights/best.pt", conf=0.25, iou=0.45): # 加载权重,首次加载会稍慢,之后常驻内存 self.model = YOLO(weight_path) self.conf = conf # 置信度阈值,越低框越多,误检也越多 self.iou = iou # NMS 的 IoU 阈值,控制重叠框合并力度 def detect(self, image_path, save_path=None): img = cv2.imread(image_path) if img is None: raise FileNotFoundError(f"图片读取失败: {image_path}") # 推理:imgsz 控制输入分辨率,需与训练时一致或接近 results = self.model(img, conf=self.conf, iou=self.iou, imgsz=640, verbose=False) detections = [] for box in results[0].boxes: # xyxy 是左上右下像素坐标,cls 是类别索引,conf 是置信度 x1, y1, x2, y2 = box.xyxy[0].tolist() cls_id = int(box.cls[0].item()) score = float(box.conf[0].item()) label = self.model.names[cls_id] detections.append({ "label": label, "score": round(score, 4), "bbox": [int(x1), int(y1), int(x2), int(y2)] }) # 画框和文字,颜色按类别区分 cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 200, 0), 2) cv2.putText(img, f"{label} {score:.2f}", (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 200, 0), 2) if save_path: cv2.imwrite(save_path, img) return img, detections

conf和iou是两个最常调的参数。conf调低到 0.1 能把漏检压下去,但背景里的杂物容易被误判成垃圾;调高到 0.5 框会变干净,小目标又容易丢。毕设演示一般取 0.25 到 0.35 之间,兼顾观感。iou控制非极大值抑制的合并力度,重叠物体多的时候适当调高,比如 0.5 到 0.6,避免把相邻的两个垃圾框合并成一个。imgsz必须跟训练分辨率对齐,训练用 640 推理用 320,小目标召回会明显掉。

results[0].boxes里的xyxy是原图坐标系下的像素值,ultralytics 内部已经做过 letterbox 还原,不需要自己再缩放回去。如果换用原生 PyTorch 权重自己写前处理,就要自己处理 letterbox 的 padding 和坐标反算,那才是检测框“整体偏移”的高发区。用封装接口能避开这块,但代价是得接受它的预处理约定。

3.3 检测结果落库与前端回显

检测完把记录写进 MySQL,答辩时能翻历史、能做统计,系统感一下就出来了。sql/init.sql里至少要有库、表和索引三样:

CREATE DATABASE IF NOT EXISTS garbage_db DEFAULT CHARSET utf8mb4; USE garbage_db; CREATE TABLE IF NOT EXISTS detect_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, image_name VARCHAR(255) NOT NULL COMMENT '图片文件名', label VARCHAR(64) NOT NULL COMMENT '垃圾分类标签', score DECIMAL(6,4) NOT NULL COMMENT '置信度', bbox VARCHAR(128) NOT NULL COMMENT '左上右下坐标,逗号分隔', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_label (label), KEY idx_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

bbox用字符串存四个坐标是图省事,查询时不需要按坐标过滤;如果要按区域统计,拆成四个INT字段更合适。utf8mb4别省,中文标签和路径里的中文都可能踩编码坑。写入用 PyMySQL,连接参数从配置读,别把密码写死在代码里:

import pymysql def save_records(records, image_name, cfg): conn = pymysql.connect( host=cfg["host"], port=cfg["port"], user=cfg["user"], password=cfg["password"], database=cfg["database"], charset="utf8mb4", autocommit=True ) try: with conn.cursor() as cur: sql = ("INSERT INTO detect_record(image_name, label, score, bbox) " "VALUES (%s, %s, %s, %s)") for r in records: bbox_str = ",".join(str(v) for v in r["bbox"]) cur.execute(sql, (image_name, r["label"], r["score"], bbox_str)) finally: conn.close()

autocommit=True省掉手动提交,批量插入数据量大时改成事务提交性能更好。前端回显用 PyQt5 的两个控件:QLabel显示画好框的图,QTableWidget显示标签、置信度和坐标。图片显示前要转成QPixmap并按控件大小缩放,否则大图会把窗口撑爆:

from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt def show_image(label_widget, cv_img): rgb = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) pix = QPixmap.fromImage(qimg).scaled( label_widget.width(), label_widget.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) label_widget.setPixmap(pix)

QImage构造时bytesPerLine要传ch * w,传错会出现图像斜切。KeepAspectRatio保证不变形,SmoothTransformation让缩放后不出现锯齿。检测动作本身放到QThread子线程里执行,通过信号把结果传回主线程更新控件,主线程只碰 UI,这条规矩不守,点第二次检测界面就白了。

4. 训练与调优:如何用自己的数据重新训一版权重

4.1 数据集划分与配置文件

拿到的权重是在原数据集上训的,类别和你的场景对不上就得重训。第一步是把自己的图片按 8:1:1 分到train/val/test,分层抽样保证每个类别在验证集里都有样本。图片文件名建议统一成英文数字下划线,中文名在某些数据加载器里会出编码问题。分完之后跑一遍脚本核对:每张图都能在同名labels目录找到 txt,每个 txt 的行数跟标注目标数一致,类别索引都在0到nc-1之间。

data.yaml里的path写数据集根目录,train和val写相对路径。如果验证集指标波动大,可以把val指向训练集的一个子集先跑通,再换回真实验证集,用来区分“数据问题”和“模型问题”。常见做法还会加一个test字段,训练结束后单独评估,避免在验证集上反复调参导致过拟合。

4.2 训练命令与关键超参

ultralytics 的训练入口很直接,一条命令带参数:

yolo detect train \ data=data/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=garbage_v1

model选预训练权重做迁移学习,小数据集从yolov8n或yolov5s起步,参数量小、训得快,毕设场景够用。epochs给 100 到 200,配合patience=20早停,验证指标连续 20 轮不升就停,省时间也防过拟合。batch看显存,8G 显存跑 640 分辨率一般能上 16,爆显存就减半。lr0初始学习率 0.01 是常见起点,数据量小可以降到 0.001 更稳。

训练日志重点看三个量:box_loss、cls_loss、dfl_loss。分类损失降不下去,多半是类别名和索引对不上,或者某些类别样本太少。框回归损失震荡,检查标注框有没有越界、宽高有没有为 0 的脏数据。训练完权重落在runs/train/garbage_v1/weights/best.pt,拷到weights/下覆盖即可。

4.3 指标看什么:mAP、召回与混淆矩阵

验证输出里最该盯的是mAP50和mAP50-95。前者是 IoU 阈值 0.5 时的平均精度,后者从 0.5 到 0.95 每隔 0.05 取平均,后者更能反映框的贴合程度。毕设答辩报指标,mAP50上 0.8 算能看,0.9 以上说明数据干净、类别区分度高。precision高recall低,说明模型保守,漏检多,可以降conf或补难例样本;反过来则是误检多,需要加背景负样本或提高置信度阈值。

混淆矩阵别只看对角线,重点看哪两类互相混。可回收物和其他垃圾在颜色、形状上接近时容易互判,这时候要么补这两类的区分性样本,要么在业务层加规则兜底。混淆矩阵里某一类整行全错,基本可以断定是类别索引错位,回去核对data.yaml的names和标注转换时的映射表。

5. 常见问题排查:跑不起来和效果差的几个高频坑

5.1 环境与依赖类问题

现象:import torch报DLL load failed或undefined symbol。原因:PyTorch、torchvision、CUDA 三者版本不匹配,或者 CPU 版和 GPU 版混装。解决:先看显卡驱动支持的 CUDA 上限,按官方版本对照表装成套的轮子,卸载重装前用pip list | grep torch确认没有残留的旧版本。CPU 机器直接装 CPU 版,别硬上 GPU 包。

现象:训练刚开始就CUDA out of memory。原因:batch或imgsz超过显存容量,也可能是图片尺寸参差不齐导致某批特别大。解决:batch减半、imgsz降到 512 或 416 先跑通,确认链路没问题再逐步加回去;训练前统一把图片长边缩到目标尺寸附近,减少显存峰值波动。

现象:连 MySQL 报1045 Access denied。原因:账号密码错、host限制或者库没建。解决:先用命令行客户端登一次确认凭据,再检查init.sql是否执行过、连接配置里的库名是否拼错。远程连接还要确认账号的host权限不是只允许localhost。

5.2 推理与训练类问题

现象:检测框整体偏移或者缩在图像一角。原因:推理预处理和训练时不一致,常见于自己写前处理却漏了 letterbox 的 padding 反算,或者训练和推理用了不同的imgsz。解决:统一走同一套封装接口,确认训练分辨率与推理分辨率一致;自己写前处理时,把 padding 比例记下来,后处理反算坐标时按同一比例还原。

现象:训练mAP一直是 0 或者接近 0。原因:标签路径对不上、txt 内容为空、类别索引越界,或者data.yaml的nc和实际类别数不一致。解决:写个小脚本随机抽 10 张图,把 txt 里的框画回原图肉眼核对;检查labels目录结构和图片是否同名;打印data.yaml解析结果确认names长度。

现象:训练损失降了但验证指标不升,甚至越来越差。原因:数据集太小、类别不均衡,或者增强过猛导致训练分布和验证分布差太远。解决:加数据、做类别重采样、关掉部分强增强(比如大角度旋转、mosaic)再对比一版;早停耐心值调小,保留验证指标最好的那一版权重,别拿最后一轮去演示。

注意:换权重后先跑detect.py单图验证,确认类别名和框的位置都对,再启动界面。界面里出问题,排查路径会长很多。

6. 二次开发改造:从单图检测到批量视频流与 Web 接口的扩展思路

单图检测跑通之后,最自然的扩展是把输入源换掉。视频流版本不用改模型,把cv2.imread换成cv2.VideoCapture,循环读帧、逐帧调detector.detect,再用cv2.imshow或 PyQt 的QLabel刷新。帧率高的时候别每帧都推理,隔帧检测加简单跟踪,观感更稳,CPU 也扛得住。批量检测则是把目录遍历一遍,结果汇总成 CSV,image_name,label,score,bbox四列,答辩时拿来做类别分布统计很方便。

Web 接口版本在core/detector.py外面包一层 Flask 路由,上传图片存临时目录,调检测、把结果转 JSON 返回,前端用简单页面展示。注意上传大小限制和临时文件清理,别让演示机器磁盘被塞满。

改造方向改动点注意
视频流检测输入源换成 VideoCapture,逐帧推理隔帧检测,避免界面卡顿
批量目录检测遍历图片目录,结果写 CSV文件名去重,异常图跳过并记录
Web 上传接口Flask 路由 + 临时文件限制大小、及时删临时文件
统计报表按 label 聚合 detect_record加时间范围过滤,别全表扫

我自己踩得最深的一次,是给视频流版本加多线程读帧,图省事把cap.read()放在 UI 线程里,结果一开摄像头界面就假死,还以为是模型推理太慢,查了半天才发现是读帧阻塞。从那以后我每次改输入源,都先把读帧和推理拆到两个线程,用队列传帧,宁可多写二十行也不让主线程碰阻塞调用。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询