基于YOLO和PyTorch的垃圾分类目标检测系统实战
2026/9/23 17:26:39 网站建设 项目流程

简介:一套基于深度学习的垃圾分类目标检测系统源码,主要面向毕业设计、期末大作业和课程设计,适合已有Python基础、希望快速搭建同类项目的学生使用。代码注释完善,项目结构清晰,下载部署后即可运行;压缩包内附Jupyter Notebook教程,可逐步跟进数据处理、模型训练与检测推理的完整流程。包内共117个文件,大小仅7.33MB,以Python脚本、YAML配置、JSON数据和前端页面文件为主,同时包含Dockerfile与Shell脚本,便于快速完成环境配置;还包含微信小程序相关文件(WXML/WXSS),可作为移动端展示或实验拓展参考。源码来自一个高分项目,作者自述获得98分评价,整体逻辑完整,从项目组织到代码风格都值得借鉴,也可作为系统设计、文档撰写与答辩展示的参考样板。已有368人学习下载,适合需要参考完整项目结构或快速启动垃圾分类检测开发的学习者。

1. 垃圾分类目标检测:这可能是你毕业设计里性价比最高的一道题

如果你在找“Python基于深度学习的垃圾分类目标检测系统源码”,大概率是毕设、课设或者找工作用的作品集。这个课题流行了好几年,但每年都还有人往坑里跳:要么下载了一份跑不起来的残缺代码,要么模型训练出来精确率只有五成,要么答辩时被老师问一句“你怎么证明你的模型真的学到了特征”就卡住。先说结论:垃圾分类目标检测,核心难点从来不是模型本身,而是数据怎么处理、训练参数怎么调、以及系统怎么包装成能演示的作品。

只要是做目标检测的大作业,“检测系统”四个字基本就锁定了技术路线:YOLO 系列作为检测器、PyTorch 作为训练框架、PyQt5 或 Web 页面做交互界面。这个组合能够覆盖“深度学习算法”和“软件工程”两块课程要求,这也是为什么它成为 Python 高分大作业的常青树。这篇文章不谈虚的,直接按数据准备、模型训练、界面集成、答辩验收的顺序,把每一步的做法和坑都拆给你看,连训练时最容易翻车的参数设置一起讲。适合的人群是:有 Python 基础、看过一点深度学习理论、但第一次独立做完整目标检测项目的同学。

2. 从数据集到 YOLO 格式:这一步做好了,训练就成功了一半

2.1 公开数据集的类别映射,比你想象中更麻烦

做垃圾分类检测,通常选有图像分类版本的公开数据集,因为这类数据集的图像通常是一张图一个类别,做检测时拿过来当“单目标图像”用,比自己去网上 crawl 图片省很多时间。常见做法是下载含有几十个垃圾类别的图像数据集,这些数据集中图像的表示形式多为“类别名/图片.jpg”,但没有框的标注文件。所以第一步不是急着写训练代码,而是统一类别体系。

# 定义你最终要检测的类别清单,这里以常见的 6 类为例 WASTE_CLASSES = { 'cardboard': 'hard_paper', # 纸板 -> 硬纸类 'paper': 'hard_paper', 'glass': 'glass', 'metal': 'metal', 'plastic': 'plastic', 'trash': 'other_waste', # 数据集里的混杂垃圾归为其他 }

逻辑说明:这段代码解决的是类别名不统一的问题。公开数据集里的标签可能是英文常见名,而中国高校题目里常见的是“可回收物/有害垃圾/厨余垃圾/其他垃圾”这种四分类口径。建议在训练前先做一次类别映射,把源数据集的类别合并到你最终要展示的类别上,而不是直接拿源类别去训练。参数说明:映射表的键是源数据集的类名,值是你自定义的类名。如果你做的是“可回收 vs 厨余 vs 有害 vs 其他”四分类,就按这个维度写映射。

这里有一个很多人忽略的细节:类别数越少,训练难度越低,mAP 越高。比如 6 类与 40 类的训练难度差别不是 6 倍,而是远高于这个倍数,因为相似类别(比如不同材质的纸)会让模型频繁产生混淆预测。你的题目叫“垃圾分类目标检测”,而不是“垃圾细粒度识别”,所以类别控制在 4 到 8 类是稳妥的范围。

2.2 批量生成标注文件并划分数据集

图片分类数据没有框,常见做法是用脚本为每张图生成一个覆盖整图的标注框。如果你想验证模型在真实场景下的泛化能力,可以留出 20% 的图片,手动用开源标注工具画几个物品堆叠场景的框。下面这个脚本负责把分类数据集转成 YOLO 需要的 txt 标注。

import os import random from pathlib import Path IMAGE_DIR = "datasets/raw_images" LABEL_DIR = "datasets/labels" # 保存 YOLO 格式 txt 标注 IMAGE_OUT = "datasets/images" # 保存整理后的图片 os.makedirs(LABEL_DIR, exist_ok=True) os.makedirs(IMAGE_OUT, exist_ok=True) for class_name, images in class_to_images.items(): cls_id = list(WASTE_CLASSES.values()).index(class_name) for img_path in images: # 整图作为目标框:x_center=0.5, y_center=0.5, w=1, h=1 txt_path = os.path.join(LABEL_DIR, Path(img_path).stem + ".txt") with open(txt_path, "w") as f: f.write(f"{cls_id} 0.5 0.5 1 1\n") # 复制或移动图片到统一目录 shutil.copy(img_path, os.path.join(IMAGE_OUT, Path(img_path).name))

逻辑说明:YOLO 的 txt 标注行格式是“类别id x_center y_center width height”,所有坐标值都是相对于图片宽高的比例值,范围 0 到 1。上面生成的标注等价于“整张图里有一个目标”,这在目标检测里叫弱标注,能让模型学到“这个类别的东西长什么样”,但学不好“定位”。参数说明:0.5 0.5 1 1代表框中心在图片正中心、宽高为整图尺寸。如果你的数据集里有部分图是多个物体堆叠,建议还是手动框一框,借用开源标注工具画框后导出 YOLO 格式即可。

数据划分这一步,别用 random.shuffle 裸分,先用脚本检验各类别图片数量。常见坑是测试集里某个类别只有个位数样本,训练时这类样本掉点非常厉害。按类别分层抽样是更稳的方式。

def split_dataset(image_paths, labels, train_ratio=0.8): from sklearn.model_selection import train_test_split # 按标签分层划分,保证每个类别在训练/验证集中都有足够样本 train_imgs, val_imgs, train_labels, val_labels = train_test_split( image_paths, labels, test_size=1 - train_ratio, stratify=labels, random_state=42 ) return train_imgs, val_imgs

逻辑说明:stratify=labels的意思就是让划分后的训练集和验证集里,每个类别的占比和原始数据集保持一致。很多同学在这里直接用random.shuffle,最后验证集里塑料类 3 张图片、玻璃类 40 张,训练时 loss 表现很好,validation 指标却忽高忽低。参数说明:random_state=42固定随机种子,保证每次跑出的划分一致,方便复现调试。

2.3 数据增强参数:别把真实场景增没了

垃圾分类的数据增强,和做车牌识别、人脸检测不太一样,因为它面向的是“垃圾放在桌子上、地上、垃圾桶旁”这类真实场景,纯几何变换太多会让模型学到错误的位置相关性。我一般会把增强分成两类:一类是色彩抖动(亮度、对比度、饱和度),模拟不同光照环境;另一类是 Mosaic 和随机仿射变换,用来提升模型对尺度和遮挡的鲁棒性。

在 YOLOv5 或 YOLOv8 的训练配置里,超参数文件中有几个关键项:

  • hsv_h: 0.015:色调变化范围,不用太大,垃圾的颜色是重要特征
  • hsv_s: 0.7:饱和度变化范围,往大调一点可以让模型适应不同颜色的垃圾桶和塑料袋
  • hsv_v: 0.4:亮度变化范围,主要是模拟白天、傍晚不同光照
  • degrees: 0.0:旋转角度,这里建议设 0,因为“瓶子倒了”和“瓶子立着”在语义上同类,但旋转过多会干扰模型理解

这段不是代码,是 YOLO 训练时超参数配置的语义,在你的训练 yaml 中修改即可。数据增强的调试建议是:先用默认参数训练一轮看 mAP,如果发现验证集里“光线偏暗”的图片检测效果差,再逐步增大hsv_v;如果发现小目标(远处的小纸团)漏检率高,可以把 Mosaic 概率调高到 1.0。反过来,如果增强太猛导致训练 loss 降不下去,优先调回色彩类增强参数而不是关掉几何增强。

3. 模型选型与训练:用 YOLO 系列跑通最小闭环

3.1 为什么选 YOLOv5 / v8 而不是更“前沿”的模型

你要交的是一个能演示、能答辩的系统,不是刷榜的竞赛方案。目标检测领域每半年就有新模型出现,但作为个人大作业,模型的可解释性、生态成熟度、显卡资源占用才是第一位的。YOLOv5 的优点是资料全、调参经验多、任何一个报错都能搜到解决方案;YOLOv8 的优点是代码结构更干净、训练接口更统一,而且自带实例分割和姿态估计的扩展。选 v5 还是 v8,取决于你的复现能力——如果你之前完全没有跑通 v5 的经验,直接上 v8 是更快的一条路。

环境配置这一步,建议别跟着网上所谓的“保姆级教程”一步一步复制粘贴,因为 CUDA 版本和 PyTorch 版本之间的匹配关系才是关键。用你本机nvidia-smi看到的 CUDA 版本号去官网挑对应版本的 PyTorch,这条准则能避开九成环境问题。环境配不好就放弃 GPU 训练、用 CPU 跑通代码流程,也比卡在环境里强,因为代码跑通了之后 GPU 是顺势的事。

3.2 训练命令与参数调整:只看 Loss 是新手习惯

YOLOv8 使用命令行工具训练,常见命令如下:

yolo detect train data=waste.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 workers=4 device=0

参数说明:model=yolov8n.pt表示加载 YOLOv8 nano 预训练权重,nano 是参数量最小的版本,显存占用约 2GB,适合学生显卡;imgsz=640是把输入图片统一缩放到 640×640,这也是速度与精度之间的折中值;batch=16是每批图片数量,如果你的显卡只有 6GB 显存,把 batch 降到 8 或 4;workers=4是数据加载线程数,Windows 下设 0 可以避免一些奇怪的报错。epochs=100是训练轮数,但对于垃圾检测这类中低难度任务,50 到 80 轮基本已经收敛,100 轮是为了保险。

训练完成后,别只看results.png里那张 loss 下降曲线。loss 降了不代表检测效果好,要重点看验证集上的mAP50mAP50-95两个指标。mAP50 是 IoU 阈值为 0.5 时的平均精确率,通俗解释就是“预测框和真实框重叠一半以上就算检测成功”;mAP50-95 则是在 0.5 到 0.95 一系列阈值下的平均值,它对框的定位精度要求更严格。作为大作业,mAP50达到 0.85 以上就算很有说服力,mAP50-95能到 0.6 就已经说明你的模型不是背答案。

3.3 训练结果里哪张图最值得放进答辩 PPT

很多人答辩时只会展示 loss 曲线和 mAP 数值,但老师更想看的是模型“错在哪里”。训练完后 YOLO 会生成val_batch0_pred.jpg这类图片,里面是模型在验证集上的预测结果,有框有类别有置信度。这里要特别留意两类图:

  • 正确预测且置信度高(0.8 以上)的图,放进答辩 PPT 说明“模型学到特征了”
  • 错误预测(比如把玻璃瓶认成塑料瓶)的图,放到“分析与改进”页,讲清楚为什么出错——这比只说“准确率 90%”更有技术深度

再用下面这段代码,从测试集中跑一遍推理并输出分类混淆矩阵:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") metrics = model.val(data="waste.yaml", split="test") print(metrics.box.maps) # 每个类别的 mAP print(metrics.box.matrix) # 混淆矩阵,形状为 [cls_num, cls_num]

逻辑说明:混淆矩阵对角线上的数值代表正确分类的比例,非对角线元素代表两个类别互相混淆的程度。比如“塑料瓶”和“玻璃瓶”两类间的数值大,说明模型主要靠颜色而不是形状来区分垃圾,这个结论写进论文分析段就是加分项。如果发现某个类别的 mAP 显著偏低,先回到数据集检查这个类别的图片数量、标注质量和场景多样性,而不是急着调模型结构。

4. 从模型到“系统”:把源码变成能演示的 Python 大作业

4.1 界面选型:PyQt5 还是 Flask Web 页面

大作业系统通常有两种展示形态:桌面端和 Web 端。选择依据是你的项目定位与演示环境。如果你是在机房答辩、老师用投影仪看演示,Web 页面的适应性更好,因为不依赖本机安装 Python 环境;如果要求交的是“软件系统”且需要支持本地相机实时检测,则 PyQt5 桌面端更合适。两者在工程量上限上没有本质差别,差别在于排查问题时的难易程度——PyQt5 的界面线程问题会比 Flask 更隐蔽。

对多数人来说,PyQt5 会更稳妥一些,因为“上传图片→显示结果→保存结果”这三步逻辑非常直接,代码量也小。以下是选择界面框架的横向对照:

对比维度PyQt5 桌面端Flask Web 端
环境依赖需要安装 PyQt5 库和正确显示驱动需要浏览器,环境更通用
相机实时检测线程设计相对复杂需要 WebRTC 或 MJPEG 推流
演示可靠性现场翻车概率低端口占用、浏览器兼容都有风险
代码量界面代码约 100 行前后端合计约 200 行以上

4.2 PyQt5 界面骨架:按钮、预览、结果展示

下面是基于 PyQt5 的最小检测界面实现,注意去掉无关装饰,只保留核心逻辑:

import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QVBoxLayout, QWidget from PyQt5.QtGui import QPixmap, QImage import cv2 from ultralytics import YOLO class WasteDetectorApp(QMainWindow): def __init__(self): super().__init__() self.model = YOLO("best.pt") # 加载训练好的权重 self.setWindowTitle("垃圾分类检测系统") self.image_label = QLabel("请选择图片") self.btn = QPushButton("选择图片并检测") self.btn.clicked.connect(self.detect_image) layout = QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.btn) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def detect_image(self): file_path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.jpg *.png)") if not file_path: return results = self.model(file_path) # 推理 annotated = results[0].plot() # 画出检测框和标签 h, w, c = annotated.shape qimg = QImage(annotated.data, w, h, 3 * w, QImage.Format_RGB888).rgbSwapped() self.image_label.setPixmap(QPixmap.fromImage(qimg)) if __name__ == "__main__": app = QApplication(sys.argv) win = WasteDetectorApp() win.show() sys.exit(app.exec_())

逻辑说明:results[0].plot()是 YOLO 库自带的绘制方法,会把检测框、类别名和置信度画到图片上返回 numpy 数组。这里把 OpenCV 的 BGR 格式通过rgbSwapped()转成 Qt 能显示的 RGB 格式,这是 PyQt 显示图片最容易忘记的一步。整个界面只有两个核心元素:图片显示控件和触发检测的按钮。参数说明:QLabel既用来提示文字,也用来承载图片,省去额外的显示控件;QFileDialog.getOpenFileName返回文件路径,若用户点击取消则返回空字符串,所以这里需要做一次空值判断。

界面写到这里已经是一个“系统”了,但离高分还差一步:视频流检测。常见的做法是在界面里加一个“打开摄像头”按钮,用QThread子线程不断读取摄像头帧并送入模型。线程这块是很多学生项目里最容易写崩的地方——直接在 UI 线程里跑模型会导致界面卡死。更稳的写法是每读取一帧就送入模型,但把model()调用放在子线程中,等结果出来后用信号传给主线程更新画面。

4.3 我见过翻车最多的地方:路径全部写死

测试界面时一切正常,拷贝到另一台电脑演示就崩——大概率是代码里用了绝对路径。你的代码目录里应该只出现相对路径,模型权重文件放在项目根目录的weights/子目录中,用os.path.join("weights", "best.pt")引用。同理,界面里如果用了自定义图标、字体,也走相对路径。这个细节虽然简单,但在答辩现场价值千金,因为它决定了你的项目能不能在老师电脑上直接跑起来。

5. 训练与部署避坑:5 个高频问题与排查顺序

结合这几年见到学生项目里反复出现的问题,下面按“现象 → 原因 → 解决”写几条最容易踩的坑。

问题 1:训练时 loss 出现 nan,训练中断

现象:前几个 epoch 的 loss 还能下降,到某一轮突然变成 nan,之后一直报错。

原因:最常见的是学习率设置过高导致梯度爆炸,其次是数据里有异常标注(比如坐标值超过 1 的框),另外还有可能是 batch size 太大导致显存溢出后某一步数据损坏。

解决:优先检查标注文件里是否有widthheight大于 1 的值;然后把学习率调低一半重训;如果仍然出现 nan,把 batch size 减半。

问题 2:训练时 mAP 很高,但测试集上识别效果很差

现象:validation 上的 mAP 超过 0.9,换一张没见过的图片检测效果却一塌糊涂。

原因:这是典型的过拟合,或者你的验证集分布与测试集不一致。很多同学把同一个数据集既做训练又做验证,没有留出独立的测试集,导致模型“背”下了训练图片,而不是学到了泛化特征。

解决:把数据集分成训练集(70%)、验证集(15%)、测试集(15%),训练过程中只允许用训练集和验证集,最后用从未参与训练的测试集做最终评估。

问题 3:检测框偏移、框比物体大很多

现象:置信度很高,但框的位置明显不对——框的中心在物体旁边,或者框大到跨越整图。

原因:整图标注(0.5 0.5 1 1)占比太高。当训练数据里“整图一个框”的比例超过 80% 时,模型会倾向于输出大框。

解决:把大部分分类数据转成“目标居中、框占整图 50%~80% 面积”的标注——在生成标注脚本里按 0.6–0.8 的比例缩小宽高并保持中心在图片中心附近。

问题 4:界面点击“选择图片”后卡死无响应

现象:点击按钮后界面白屏,过几秒恢复,检测期间无法进行任何其他操作。

原因:检测放在了 UI 主线程中,模型推理是耗时操作,阻塞了界面的事件循环。

解决:将检测过程放进QThread,完成后通过信号把结果传回主线程。代码层面就是新建一个继承QThread的类,在run()方法里执行model()results[0].plot()

问题 5:换一台电脑后 import 报错或模型加载失败

现象:在自己的电脑上运行正常,拷贝到其他机器上运行报错,比如No module named 'torch'或模型文件路径找不到。

原因:没有做环境导出,也没有把权重文件放进项目目录的固定路径中。Windows 下如果把权重放在桌面并通过绝对路径引用,换机器必崩。

解决:在项目根目录建requirements.txt,用pip freeze > requirements.txt导出依赖清单并在文档中写明安装命令;模型权重文件放在项目weights/目录下,代码中统一使用相对路径引用。

6. 答辩验收前的自查方法:用数据证明你的系统有效

如果你已完成上述步骤,最后要做的事不是再调一轮模型,而是系统地做一次验证与数据准备。准备一个包含 50 张新图片的测试集(可以是网上找的、自己拍的),运行一次完整推理并把结果保存到统一目录:

yolo predict model=weights/best.pt source=test_images/ save=True conf=0.25

然后统计这一批测试图片中成功检测出目标的比例、错误率以及平均置信度。这些数字写进论文的“结果分析”章节,比任何描述都更有说服力。答辩时如果老师问“准确率为什么不是 99%”,可以说明置信度阈值的影响:conf=0.25是允许低置信度预测出现,因此框会更多但不一定更准;如果把阈值提高到 0.5,误检会下降但漏检可能上升,“阈值选择需要根据场景来权衡”本身就是一句很好的答辩回答。

还有一个容易被忽视的加分项:运行一次视频推理并录屏。在代码里传入一段垃圾桶附近的视频,让模型逐帧检测并输出标注后的视频文件,这能直观展示你的系统不是“只在静态图上有效”。如果视频文件太大,可以只截取 20 秒用来演示。

我的一个习惯是:在最终提交前,把整台电脑断网后再跑一遍系统。因为依赖项是否完全打包好、模型文件是否被外部引用,断网环境下都会暴露出来;答辩现场的电脑条件通常不会比你的开发机更好,提前在“最差环境”下验证,是避免现场翻车的最后一道保险。这个习惯帮我避免过很多次尴尬,希望能帮到你。

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

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

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

立即咨询