简介面向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 端或移动端调用。层次技术作用常见版本区间算法框架YOLOultralytics 实现目标检测推理与训练YOLOv5 / YOLOv8深度学习后端PyTorch torchvision张量计算、模型加载1.10 以上 / 2.x图像处理OpenCV、Pillow读图、缩放、画框、中文标注opencv-python 4.x桌面界面PyQt5主窗口、按钮、表格、图像显示5.15 左右数据库MySQL PyMySQL检测记录持久化MySQL 5.7 / 8.0Web 扩展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建议锁大版本比如torch1.10、ultralytics8.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, encodingutf-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_pathweights/best.pt, conf0.25, iou0.45): # 加载权重首次加载会稍慢之后常驻内存 self.model YOLO(weight_path) self.conf conf # 置信度阈值越低框越多误检也越多 self.iou iou # NMS 的 IoU 阈值控制重叠框合并力度 def detect(self, image_path, save_pathNone): img cv2.imread(image_path) if img is None: raise FileNotFoundError(f图片读取失败: {image_path}) # 推理imgsz 控制输入分辨率需与训练时一致或接近 results self.model(img, confself.conf, iouself.iou, imgsz640, verboseFalse) 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, detectionsconf和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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;bbox用字符串存四个坐标是图省事查询时不需要按坐标过滤如果要按区域统计拆成四个INT字段更合适。utf8mb4别省中文标签和路径里的中文都可能踩编码坑。写入用 PyMySQL连接参数从配置读别把密码写死在代码里import pymysql def save_records(records, image_name, cfg): conn pymysql.connect( hostcfg[host], portcfg[port], usercfg[user], passwordcfg[password], databasecfg[database], charsetutf8mb4, autocommitTrue ) 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()autocommitTrue省掉手动提交批量插入数据量大时改成事务提交性能更好。前端回显用 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 \ datadata/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/train \ namegarbage_v1model选预训练权重做迁移学习小数据集从yolov8n或yolov5s起步参数量小、训得快毕设场景够用。epochs给 100 到 200配合patience20早停验证指标连续 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 也扛得住。批量检测则是把目录遍历一遍结果汇总成 CSVimage_name,label,score,bbox四列答辩时拿来做类别分布统计很方便。Web 接口版本在core/detector.py外面包一层 Flask 路由上传图片存临时目录调检测、把结果转 JSON 返回前端用简单页面展示。注意上传大小限制和临时文件清理别让演示机器磁盘被塞满。改造方向改动点注意视频流检测输入源换成 VideoCapture逐帧推理隔帧检测避免界面卡顿批量目录检测遍历图片目录结果写 CSV文件名去重异常图跳过并记录Web 上传接口Flask 路由 临时文件限制大小、及时删临时文件统计报表按 label 聚合 detect_record加时间范围过滤别全表扫我自己踩得最深的一次是给视频流版本加多线程读帧图省事把cap.read()放在 UI 线程里结果一开摄像头界面就假死还以为是模型推理太慢查了半天才发现是读帧阻塞。从那以后我每次改输入源都先把读帧和推理拆到两个线程用队列传帧宁可多写二十行也不让主线程碰阻塞调用。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?