基于深度学习与YOLO的垃圾分类目标检测系统毕设实战指南
简介面向计算机相关专业毕业设计、课程设计与期末大作业场景这套基于深度学习的垃圾分类目标检测系统源码包聚焦目标检测在生活垃圾识别中的应用评审99分、代码完整可运行零基础也能上手。压缩包共126个文件、66.07MB20个Python脚本负责推理13个Jupyter Notebook记录训练调参12个Vue组件构建前端另附onnx模型、答辩PPT与参考报告等覆盖从训练到部署的完整链路。该项目已有200人学习下载属于可借鉴的高分范本内容上不仅提供可运行源码还包含答辩展示PPT、参考报告与课程实践模板便于快速理清项目结构并二次开发。借助Dockerfile可快速搭建环境配合前端页面直观展示识别结果对准备答辩、项目实战或完成毕设的同学均具实用价值。1. 垃圾分类目标检测系统毕设一张图说清它和图像分类的三个区别Python毕设热门题里基于深度学习的垃圾分类目标检测系统几乎自带高分气质——它比图像分类多一个目标定位输出比纯目标检测多一个可落地的环保应用场景。它要解决的从来不只是“这张图里有没有纸壳”而是“纸壳在哪个位置、属于哪一类别”。一个系统做完深度学习里的卷积神经网络、损失函数、数据增强、评估指标全都能在答辩PPT里讲出实例这是很多纯理论题目给不了的。我平时带毕设学生最愿意推荐这类题目的原因是它“雷达图很均衡”既有深度学习理论深度又有目标检测工程落脚点还带一个贴近生活痛点的演示效果。你不需要自己发明算法只需要把公开的检测框架训练好、集成好、讲清楚。这篇文章就按我自己搭这类系统的完整路径来写从数据集准备到训练、验证、避坑、答辩演示全部走一遍新手能照着做熟手也能看到我在参数选择和边界问题上的取舍。2. 垃圾分类数据集准备从公开数据集到 YOLO 格式转换脚本2.1 选数据集公开数据集与自建数据集的取舍做垃圾分类目标检测第一步不是装环境也不是急着找模型而是先把训练原料搞定。现在能直接用的公开数据集不少常见的是 TrashNet 这类按材质分类的数据集以及国内一些算法竞赛放出的垃圾图片集。TrashNet 的类别一般围绕纸板、玻璃、金属、纸张、塑料、其他垃圾数量不大但胜在干净适合快速跑通流程。国内竞赛数据集类别更细里面有旧衣服、电池、瓜果皮这些更贴近小区分类的场景但标注质量参差不齐下载后经常要花时间清洗。我的建议是如果你的毕设重点在“系统”和“演示”用公开数据集起步就好如果想在论文里写一句“构建了真实场景数据集”那就把自己实验室或者校园垃圾桶周围拍的照片补进去再配合数据增强做一个公开集自采小样本的混合训练。千万别一上来就自己标几千张图标注工具再省力也是纯体力活工作量会挤占调参时间最后模型效果反而不好。选择数据集时还要想清楚一个问题你的题目叫“垃圾分类目标检测”不是“图像分类”。图像分类只需要知道整张图属于哪一类目标检测必须给出每个目标的边界框和类别。这意味着你不能直接用分类数据集跑检测模型必须有标注框。公开检测数据集如果原本是 COCO 格式或 VOC 格式都要先转成你想用的框架需要的格式。2.2 把 VOC 标注转成 YOLO 格式转换脚本与四个边界坑我自己常用的训练框架是 Ultralytics YOLO它需要的标签是每张图片对应一个同名 txt 文件每行格式是“类别序号 中心点x 中心点y 宽 高”所有坐标都归一化到 0 到 1。网上很多毕设数据集给的是 VOC 的 XML 格式所以第一步通常是写一个转换脚本。下面这个就是我最常让学生复用的版本。import os import xml.etree.ElementTree as ET from glob import glob def convert_voc_to_yolo(xml_file, out_dir, class_names): tree ET.parse(xml_file) root tree.getroot() img_name root.findtext(filename) img_w int(root.find(size).findtext(width)) img_h int(root.find(size).findtext(height)) lines [] for obj in root.iter(object): cls obj.findtext(name) if cls not in class_names: continue cls_id class_names.index(cls) box obj.find(bndbox) x1 float(box.findtext(xmin)) y1 float(box.findtext(ymin)) x2 float(box.findtext(xmax)) y2 float(box.findtext(ymax)) x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: out_path os.path.join(out_dir, os.path.splitext(img_name)[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines)) # 用法示例 class_names [cardboard, glass, metal, paper, plastic, trash] os.makedirs(labels, exist_okTrue) for xml_path in glob(annotations/*.xml): convert_voc_to_yolo(xml_path, labels, class_names)这段脚本的逻辑很直接先读图片真实宽高再解析每个目标的 bndbox 坐标把左上右下两个点换算成中心点和宽高最后除以图片尺寸做归一化。等号右边的 x_center 和 y_center 一定是小数不能出现大于 1 的值否则训练时目标检测框架会直接报错或者忽略这个标签。实际跑转换脚本时有四个边界坑最容易让人翻车。第一个坑是类别列表和 data.yaml 里的 names 顺序必须完全一致一旦转换脚本里是“plastic 在前”训练配置文件里是“glass 在前”所有标签都会错位模型学出来的东西就全乱了。我通常会明确写出 class_names 数组然后在 data.yaml 里手动复制一遍避免“记性好”带来的错误。第二个坑是部分 XML 的 size 字段缺失或写成 0除零后出现 inf训练 loss 直接变成 nan。转换前要先检查图片文件真实尺寸必要时用 PIL 读一遍图补齐。第三个坑是标注框本身是 0 宽或 0 高这类数据进入 YOLO 后会报 “degenerate bbox” 警告。过滤办法是如果 w 或 h 小于 1e-6就跳过这个目标。第四个坑是图片没有对应标注这类图会被当成背景样本参与训练如果数量太多模型就会倾向于不输出检测框表现为大量漏检。我在转换后都会统计一下 images 和 labels 的文件名数量两边对不上就去查。2.3 数据划分与类别平衡别让训练集拖后腿转换完成之后数据划分也有讲究。很多同学直接 random.shuffle 然后按比例切遇到多类别均衡的数据还好一旦数据集里玻璃瓶多、电池少随机划分很可能让验证集里完全没有电池。我的习惯是做一个简单的分层划分让每个类别在训练集和验证集中的样本比例都接近整体比例。import os import random from collections import defaultdict def split_by_first_class(label_dir, train_ratio0.8, seed42): random.seed(seed) class_to_images defaultdict(list) for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue with open(os.path.join(label_dir, txt)) as f: first_line f.readline() if not first_line: continue cls_id first_line.split()[0] class_to_images[cls_id].append(txt) train_files, val_files [], [] for cls_id, files in class_to_images.items(): random.shuffle(files) cut int(len(files) * train_ratio) train_files.extend(files[:cut]) val_files.extend(files[cut:]) return set(train_files), set(val_files) train_set, val_set split_by_first_class(labels) print(ftrain images: {len(train_set)}, val images: {len(val_set)})这个脚本有一个前提假设一张图里只有一类目标。如果你自建的数据集里一张图同时有矿泉水瓶和废纸团这个脚本会让同一张图被分到多个类别集合里最后可能同时出现在训练集和验证集造成数据泄漏。我自己的处理办法是遇到多标注图片时按“主导类别”或者“占比最大的类别”做分层同时在划分完成后做一次交集检查确保 train_set 和 val_set 没有重复。划分完成后目录结构要整理成 YOLO 默认能识别的布局。常见做法是dataset/ images/ train/ val/ labels/ train/ val/images 和 labels 下的文件名一一对应图片后缀可以是 jpg、png但标签后缀必须是 txt。我还会在 data.yaml 的 path 字段写绝对路径因为相对路径容易随着你换机器、换终端的当前目录而变化训练到一半报找不到图片真的很泄气。3. 用 Ultralytics YOLOv8 跑通垃圾分类目标检测环境配置与最小训练命令3.1 环境安装适合 0 基础纯小白的版本锁定思路现在做目标检测最省心的开源方案就是 Ultralytics YOLO 系列。网上很多 CSDN 博客写的环境配置教程都很详细但信息多了容易乱。我通常建议按一套固定流程来先建虚拟环境再装 PyTorch最后装 ultralytics 库。虚拟环境是后悔药哪怕把 torch 装坏了也不会影响系统里的 Python。python -m venv .venv source .venv/bin/activate # Windows 下执行 .venv\Scripts\activate pip install --upgrade pip pip install -i https://pypi.tuna.tsinghua.edu.cn/simple torch torchvision pip install -i https://pypi.tuna.tsinghua.edu.cn/simple ultralytics这里有两个细节。第一装 PyTorch 时不要用默认的海外源国内直接换清华源会快很多也能避免很多莫名其妙的超时问题。第二ultralytics 这个库会自带当前 PyTorch 的兼容版本要求如果你机器上已经有别的深度学习环境最好先检查一下 torch 和 torchvision 版本对不对。0 基础同学最容易踩的坑是 CUDA 版本不匹配pip 装的是 CPU 版 torch训练时慢到怀疑人生。import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.version.cuda)运行完这几行如果第二行输出是 False说明 PyTorch 根本没有识别到 GPU。常见原因是你安装的是 CPU 版或者是显卡驱动版本和 CUDA 不匹配。我没有能力在这里帮你远程诊断但你可以先在系统命令行执行nvidia-smi看右上角显示的 CUDA 版本再去 PyTorch 官网选对应的安装命令这是最快的方式。不要一上来就责怪显卡坏十次里九次是版本没对齐。3.2 训练参数从 data.yaml 到关键超参环境通了接下来要写训练配置文件。这一步值得放慢因为整个训练过程的所有路径和类别名都在这里错一个训练白跑几个小时。# waste.yaml path: /absolute/path/to/dataset train: images/train val: images/val names: 0: cardboard 1: glass 2: metal 3: paper 4: plastic 5: trashpath 建议写绝对路径train 和 val 是相对 path 的目录。names 的顺序必须和上一章转换脚本里的 class_names 完全一致。我自己习惯写完后先跑一个 2 行的冒烟测试用 batch 很小的方式验证数据能否被正确加载比如yolo train datawaste.yaml modelyolov8n.pt batch2 epochs1如果这一步能跑过再进完整训练。训练命令我一般分两种方式。一种是用命令行yolo taskdetect modetrain modelyolov8s.pt datawaste.yaml epoch100 imgsz640 batch16 device0 projectruns/waste nameexp1另一种是写在 Python 脚本里方便答辩时把实验记录统一管理from ultralytics import YOLO model YOLO(yolov8s.pt) results model.train( datawaste.yaml, epochs100, imgsz640, batch16, device0, lr00.01, patience15, projectruns/waste, nameexp1 )这段代码的意思是用预训练的 YOLOv8s 作为起点在垃圾分类数据上做迁移学习。yolov8s 是 small 版本显存和速度都比较居中如果显存不够就换 yolov8n如果追求精度就换 yolov8m。imgsz 是训练分辨率分类垃圾里很多小件物品我起步就用 640不太建议只用 320不然易拉罐和瓶盖会糊成一团。batch 大小受显存限制2060 级别大约 16 到 32 之间可以接受。lr0 对迁移学习来说 0.01 是比较稳的起点如果 loss 出现大幅度震荡我会降到 0.005。patience 是早停参数连续 15 个 epoch 验证集没有提升就自动停能帮你节约半天时间。3.3 训练过程中的监控日志、曲线与权重选择训练一旦启动不要只在终端看进度条。Ultralytics 会在 project/name 目录下生成 results.png里面包含训练 loss、验证 loss、精确率、召回率、mAP50 等曲线的汇总图。我每隔二十个 epoch 会打开看一眼重点观察两点一是训练 loss 和验证 loss 是否同步下降二是验证集 mAP50 是否还有上升趋势。训练结束后目录下会出现 best.pt 和 last.pt。best.pt 是根据验证集 mAP 选取的最优权重last.pt 是最后一个 epoch 的权重。提交代码和答辩演示都用 best.pt不要自己凭感觉挑 last.pt。如果你在训练过程中觉得 loss 还在下降可以把 epochs 调大然后继续用 last.pt 作为预训练权重接着训这也是一种常见做法。但要注意训练集的 loss 下降不一定是好消息验证集 mAP 长期不动才是过拟合的信号这时要做的不是继续训而是加数据增强或者减少模型复杂度。4. 模型验证与导出用 mAP、PR 曲线和 ONNX 导出确认模型真的能用4.1 用测试集评估mAP、PR 曲线和混淆矩阵怎么看很多同学训练完只看一眼损失函数图觉得 loss 低了就是好了然后直接拿去做界面。这个认知在毕设答辩里很容易露馅。目标检测的评估要看验证集上的 mAP50 和 mAP50-95以及每个类别的精确率、召回率和可视化结果。mAP50 是 IoU 阈值取 0.5 时的平均精确度mAP50-95 更严格对不同 IoU 阈值取平均。垃圾分类场景里mAP50 能达到 90 以上是比较理想的mAP50-95 有个 70 到 80 已经能说明模型学到了类别特征。from ultralytics import YOLO model YOLO(runs/waste/exp1/weights/best.pt) metrics model.val(datawaste.yaml, imgsz640, batch16) print(metrics.box.map50) print(metrics.box.map)验证完成之后project/name 目录下会多出 confusion_matrix.png、PR_curve.png 等文件。混淆矩阵要重点关注对角线如果系统类别之间的误检很高比如玻璃瓶经常被识别成金属罐那不是模型“笨”而是这两类在形状、颜色上确实像。解决方向不是盲目调参而是增加这些类别的样本数量或者加入更多场景数据让模型学区分特征。PR 曲线能看到不同置信度下精确率和召回率的权衡如果曲线整体偏左下说明模型要嘛不出框要嘛出的框全是误检这时要回到数据处理和训练参数上排查。4.2 导出为 ONNX为答辩演示做准备训练好的 PyTorch 权重可以直接用于 Python 推理但答辩时现场环境不一定和你训练环境完全一致而且演示机器可能没有 GPU。这时候提前把模型导出成 ONNX 格式用 CPU 也能跑至少不会在关键时刻翻车。导出命令非常简单from ultralytics import YOLO model YOLO(runs/waste/exp1/weights/best.pt) model.export(formatonnx, imgsz640, halfTrue)导出后会生成 best.onnx 文件。你可以把它理解为一种跨平台的模型交换格式很多部署框架都认它。答辩时可以顺手讲一句“我已经把模型导出到 ONNX为后续手机端或者嵌入式端部署留好了接口”这句话在老师和评审眼里是加分项因为说明你不只会在 Python 环境里玩。导出 ONNX 后要验证一下输出是否和 PyTorch 一致最稳妥的办法是拿同一张验证集图片分别用两种方式推理对比检测框坐标和置信度。不要想当然觉得导出成功就等于结果一样尤其是 halfTrue 半精度模式下置信度会有细微差别。如果发现 ONNX 推理结果漏框可以关掉 half 再重新导出用精度换稳定。4.3 用测试集图片做快速推理conf 阈值怎么调验证完指标还要看具体图片效果。这里最容易犯的错是拿着训练集图片展示训练时模型见过这些图当然会“表现好”但这不能说明泛化能力。正确做法是从验证集或单独留出的测试集里挑图做推理。推理代码很简洁from ultralytics import YOLO model YOLO(runs/waste/exp1/weights/best.pt) results model.predict( sourcetest_images/, conf0.35, imgsz640, saveTrue, save_txtTrue )修改 conf 能过滤掉置信度低于阈值的预测框。conf 调高到 0.6误检少了但漏检会明显增加调低到 0.2几乎不过滤框会很多很杂。垃圾分类场景里不需要把所有可疑目标都框出来漏检一个瓶子比误框一堆背景更让人不舒服所以我日常演示用 0.4 到 0.5。如果你的模型 mAP 已经挺高可以适当上调 conf让演示画面更干净。5. 垃圾分类目标检测系统常见坑与排查五个最容易翻车的位置5.1 现象训练时 loss 变成 nan 或者直接中断训练刚开始两三个 epoch终端输出里 loss 突然变成 nan或者直接进程崩掉。第一次遇到我怀疑是显存问题后来才发现是标签数据有毛病。最常见的原因是标签里有坐标为 0 或大于 1 的情况尤其是目标框刚好在图片边缘时归一化后宽或高变成 0YOLO 就算不出有效损失。还有可能是 data.yaml 里的类别数量比标签里的最大类别编号小模型输出向量对不上。解决办法训练前对 labels 目录做一次全量检查用脚本读取每个 txt 的每一行确认前一位是合法类别编号、后四位都在 0 到 1 之间并且宽高值大于 0.0001。一旦发现异常文件回到转换脚本里加过滤条件重新生成。不要直接进训练损失 nan 是标签问题的报警器报警了就要先把数据治好。5.2 现象易拉罐、瓶盖这类小件垃圾大量漏检模型对大件物体比如纸箱、快递盒检测很好但小尺寸目标经常漏。这个现象在移动端演示时特别明显因为摄像头距离远小目标换算到网络输入尺寸后可能只有十几个像素。原因有两层一是训练时 imgsz 不够大模型下采样后小目标的特征被压没了二是数据里小目标框的占比太低训练时模型没见够。解决方向训练分辨率从 640 提到 768 通常会对小目标有直接帮助代价是显存压力和训练时间增加。另一个有效操作是开启更强的马赛克增强让模型在同一张图上频繁接触大小不一的目标。如果你的题目本身是“移动小目标检测”那最好在数据准备阶段就把小目标单独提出来统计一下数量不要等到模型训练完再补救因为数据集层面的问题靠训练参数很难治本。5.3 现象Flask 界面一推理就卡顿演示变成幻灯片我见过很多毕设系统用 Flask 做网页端上传照片还可以接受一旦改成摄像头视频流每帧都在主进程里执行模型推理界面就会卡成幻灯片。原因很简单模型推理耗时超过每帧间隔而且 Flask 开发服务器是单线程的一次推理还没结束下一次请求又堵上来了。一个稳定做法是用队列做异步推理摄像头线程只负责采集帧把帧丢进队列另一个工作线程不断取帧做推理把结果放回结果队列Flask 接口只读取最新结果。代码逻辑不复杂但对答辩演示的流畅度提升非常明显。还有一个性价比更高的选择演示时直接用第 6 章里的 OpenCV 窗口做本地实时检测不必非走 Flask网页端留一个上传图片的入口就够支撑系统完整度了。5.4 现象PPT 里的演示效果和现场设备完全不一样答辩前一天在实验室笔记本上效果很好答辩现场换到投影仪或者演示机画面不仅变暗检测框还老是闪。造成这种落差的原因基本有三个现场摄像头分辨率低拍到的人脸距离远小目标占比大投影仪色彩偏色原本对比度就低的塑料瓶和背景融为一体现场机器没有装正常显卡驱动PyTorch 直接跑在 CPU 上帧率感人。应对措施分两步。第一步答辩前在同样的低分辨率下用模型实测一遍如果实测效果差就在演示机上把 imgsz 降到 480提高推理速度同时调高 conf 到 0.5 减少闪烁。第二步PPT 里不要只放训练集效果截图放验证集图片并且把 CPU 推理这条后路也测通确保即便现场没有 GPU 也能把系统跑起来。演示视频可以提前录制一段作为备胎万一现场环境实在拉胯播放视频也比卡死强。6. 答辩演示加分实时检测稳定化参数与训练日志图表化6.1 用摄像头实时检测做现场演示的稳定化参数现场演示最加分的动作是拿个水瓶放到摄像头前模型实时把它框出来并标上 plastic。这里我用 OpenCV 加 YOLO 做一个最小可用的实时检测脚本核心是把推理输入尺寸和置信度阈值固定下来避免画面抖动。import cv2 from ultralytics import YOLO model YOLO(runs/waste/exp1/weights/best.pt) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while cap.isOpened(): ret, frame cap.read() if not ret: break results model.predict(frame, imgsz640, conf0.4, device0, verboseFalse) annotated results[0].plot() cv2.imshow(garbage detection demo, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()演示时把摄像头支稳不要让画面大幅度晃动检测框就不会来回跳。每次调用 predict 都带一次模型前向计算如果帧率不够优先把 imgsz 从 640 改成 480而不是降低 conf因为降低置信度只会让画面更乱。用 GPU 推理没问题但如果是 CPU 环境可以把预训练权重先转成 ONNX 再用 onnxruntime 跑速度会比原生 PyTorch 快不少。这个脚本里的每一行都是可以调的视频分辨率、推理尺寸、置信度阈值答辩前全部试一遍心里就有底了。6.2 把训练日志和混淆矩阵做成答辩图表训练完的 results.png、confusion_matrix.png 和 PR_curve.png 是好素材但不能直接截一张整图丢进 PPT。我习惯按三类图整理第一类是 loss 曲线用来讲模型收敛过程第二类是 PR 曲线用来讲置信度阈值选择的依据第三类是每个类别的 AP 柱状图用来讲哪类垃圾容易误检。这样评委一眼能看到你的工作量和分析深度。做柱状图时可以用 pandas 读取验证结果文件里的每类 AP 值再画成条形图。逻辑很简单先找最好的几个类别和最差的几个类别然后结合两三张真实推理图解释为什么差。比如金属类被塑料类干扰就可以说这两类在光照反射下有相似纹理后续可以增加对抗样本。这种基于自己实验数据的分析比背一段深度学习的定义更有说服力。我做这套系统的习惯是所有实验记录都放在同一个 runs 目录下每跑一个版本就用一个名字区分开最后答辩时能说出“我试过 imgsz 640 和 768640 的 mAP50 是 0.93768 是 0.91考虑到实时性我选了 640”这样的话。这种细节比任何 PPT 模板都值钱。答辩前记得导出一次 ONNX再准备一段齐的演示视频作为后备。我见过太多人栽在数据准备和部署兼容性上自己先把自己的系统跑透现场就不慌。希望这篇笔记能帮你在毕设这条路上少走几步弯路。本文还有配套的精品资源点击获取