简介:基于YOLOv5的智能生活垃圾分类系统源码,面向计算机视觉、深度学习方向的高校学生,适合作为毕业设计、期末大作业或课程设计的完整参考项目。资源围绕生活垃圾分类检测场景,运用YOLOv5目标检测框架,覆盖数据配置、模型训练与推理检测等关键环节。
资源包共76个文件,以Python脚本、YAML配置和Markdown说明文档为主。py文件约40个,包含训练、验证、检测等核心处理流程;yaml文件约22个,用于定义模型结构与数据参数;另有Markdown文档可辅助理解整体思路。压缩包约178KB,结构清晰。
目前已有165人学习下载。项目代码含注释、界面简洁,功能覆盖垃圾识别与目标定位,经过严格调试可直接部署运行。作为导师认可度较高的高分毕设项目,对需要快速搭建视觉识别系统、完成课程设计或毕设演示的同学,具备实在的参考与复用价值。
1. 为什么垃圾分类毕设选YOLOv5:从选题价值到可跑通性
每年毕业设计选“智能垃圾分类”这个题目的学生不少,但真正能交出一套可运行、可演示、可答辩系统的并不多。大多数人的项目卡在同一个位置:模型训练跑通了,检测结果却一塌糊涂;或者环境装了一星期,最后连 demo 都没跑起来。这套基于 YOLOv5 的智能生活垃圾分类系统,解决的核心问题就是把“目标检测”这件事落到真实的垃圾分类场景:输入一张垃圾照片,输出带有类别标签的检测框,对应到可回收物、有害垃圾、厨余垃圾和其他垃圾。适合两类人:一类是做毕设、需要完整源码和文档支撑的学生;另一类是刚接触深度学习目标检测、想找一个完整项目练手的新手。下面按我实际拆这个项目的顺序来写。
2. 环境与源码结构:两天跑通基线的配置路径
2.1 环境版本:Python、CUDA、PyTorch怎么匹配
YOLOv5 对环境的要求不算苛刻,但版本错配是最容易翻车的点。装环境之前先确认三件事:显卡型号和显存大小、显卡驱动版本、以及你打算用的 PyTorch 版本。很多人在这步栽跟头,是因为没搞清“驱动支持的 CUDA 版本”和“PyTorch 运行时实际调用的 CUDA 版本”是两回事。nvidia-smi 里显示的高版本 CUDA,只代表驱动上限,不代表你 pip 安装的 PyTorch 就能用它。
我一般建议的配置组合如下表所示,注意根据自己的显卡选择,而不是照抄教程。
| 组合 | Python | CUDA | PyTorch | 适用场景 |
|---|---|---|---|---|
| 稳妥组合 | 3.8 | 11.7 | 1.13.1 | 老显卡、多次装环境失败的情况 |
| 新卡组合 | 3.10 | 12.1 | 2.1.0 | RTX 30/40 系显卡 |
创建虚拟环境时用 conda,因为 conda 创建的 Python 环境和系统 Python 隔离得干净,后面装错版本想回退也容易。激活环境后先装核心依赖,不要一开始就把 requirements.txt 整个装上,那样出了问题不好定位。
conda create -n yolov5 python=3.10 -y conda activate yolov5 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121这里指定了 cu121 的 wheel 源,PyTorch 会捆绑对应的 CUDA 运行时。--index-url参数把安装源指向 PyTorch 官方 wheel 仓库,避免从 PyPI 默认源装到 CPU 版本。装完之后验证一下 GPU 是否可用:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"打印结果为True和显卡名称,说明 CUDA 环境是通的。如果这里输出False,基本可以判定是 PyTorch 装成了 CPU 版,重新用指定 CUDA 版本的命令覆盖安装即可。
2.2 源码目录:每个文件是干什么的
源码包解压之后,你会发现目录结构和官方 YOLOv5 仓库基本一致,这是好事,因为查阅资料时可以照着官方文档和社区问答来排查问题。花半小时把目录过一遍比直接开跑要靠谱得多,至少你要知道 train.py 是训练入口、detect.py 是推理入口,别把两者混了。
| 文件/目录 | 作用 | 日常使用频率 |
|---|---|---|
| train.py | 训练主入口,加载数据配置和模型结构 | 高 |
| detect.py | 推理入口,支持图片、视频、摄像头 | 高 |
| models/ | 网络结构定义,yolov5s.yaml 等 | 中 |
| data/ | 数据集配置和超参数配置 | 高 |
| utils/ | 数据增强、损失计算、指标评估等工具 | 低 |
| runs/ | 训练和推理输出目录,自动生成 | 中 |
项目里垃圾分类相关的代码和数据配置一般集中在 data/ 和 utils/ 中,你要改的其实就是 data/garbage.yaml 和 models/yolov5s.yaml。前者定义数据集路径和类别数,后者定义网络结构大小。训练时输出的图纸、权重、日志都在 runs/train/exp 里,这个目录是你后期调参时盯得最多的地方。
2.3 首次跑通:先用官方权重验证环境
拿到源码先别急着训练自己的数据集。正确的顺序是用官方预训练权重跑一次推理,确认整条链路是通的,再做后续替换。
python detect.py --source data/images/bus.jpg --weights yolov5s.pt --conf-thres 0.25--source指定输入图片,--weights指定预训练权重,--conf-thres是置信度阈值,低于这个值的检测框会被过滤掉。跑完在 runs/detect/exp 里能看到标注了类别和得分的输出图。
这一步的意义在于验证环境完整性。如果这一步都报错,问题一定出在环境配置或依赖版本上,而不是你的数据或代码逻辑。等到能稳定输出结果,再开始准备自己的数据集,这样能把“环境问题”和“数据问题”分开排查,避免到时候训练失败不知道锅该甩给谁。
3. 数据集构建与标注:从原始图片到YOLO格式的完整链路
3.1 类别设计:按物品分类还是按垃圾四分法
垃圾分类数据集的类别设计直接决定模型的可用性和训练难度。现在公开的垃圾分类数据集有按物品细分的,比如纸张、塑料、玻璃、金属、布料、电池、灯管、药品、果皮、剩饭,也有直接按“可回收物、有害垃圾、厨余垃圾、其他垃圾”四分类的。两种方案各有取舍。
按物品细分类别,模型学的是“这个物体是什么”,比如一个饮料瓶,无论它是否被压扁、是否带有标签,模型都要认得出来。这个方案的好处是后续可以按映射表把物品映射到四分类,比如塑料瓶映射到可回收物,坏处是标注工作量大、类别间外观差异有时很小。
按四分类直接做,标注工作量小,但模型要学“什么东西属于可回收物”这种抽象概念,同一类别的物体外观差异极大,对数据量和训练难度都不友好。作为毕设项目,我建议折中:选择 10 类左右的常见物品细分类别,像纸板、塑料瓶、玻璃瓶、易拉罐、果皮、电池、灯管、布料、香烟头、一次性餐盒,训练完成后在推理脚本里维护一张映射表,把物品类别映射到四分类桶,再输出投放建议。这样既控制了标注成本,又能讲清楚“为什么这么做”的设计逻辑。
3.2 标注规范:用labelImg时要注意的几件事
标注工具推荐 labelImg,因为它生成的 VOC 格式 XML 文件转换路径最清晰,社区资料也多。标注时有一条硬性要求:类别名称统一用英文字母或数字,不要用中文。YOLO 读取类别时按 classes.txt 的行号索引,中文字符编码问题会导致类别错位甚至读不出文件。
另外标注框要贴合目标轮廓。垃圾物品往往形状不规则,比如被压扁的纸箱、揉成团的塑料袋,别把整张桌子框进去,也别只框一半。标注框边缘只需留 1 到 2 个像素的余量,这样训练出来的框回归更精准。还有一条容易被忽略的:背景不标注。很多人把垃圾所在的桌面、地面、垃圾桶也标进去了,这会把大量背景像素教成物体,推理时满屏误检。
3.3 VOC转YOLO:转换脚本与坐标校验
labelImg 默认保存为 VOC 格式的 XML 文件,每个 XML 里包含图片尺寸和所有标注框的左上角、右下角坐标。YOLO 训练需要的是 txt 文件,每行一个框,格式为class x_center y_center width height,四个坐标都已归一化到 0 到 1 之间。转换脚本是必经之路,下面这个脚本是清理过的最核心逻辑。
import xml.etree.ElementTree as ET import os def convert(size, box): dw = 1.0 / size[0] dh = 1.0 / size[1] x_center = (box[0] + box[1]) / 2.0 y_center = (box[2] + box[3]) / 2.0 w = box[1] - box[0] h = box[3] - box[2] return x_center * dw, y_center * dh, w * dw, h * dh def xml_to_txt(xml_path, out_path, classes): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) with open(out_path, "w") as f: for obj in root.iter("object"): cls = obj.find("name").text if cls not in classes: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) cx, cy, w, h = convert((img_w, img_h), (xmin, xmax, ymin, ymax)) f.write(f"{classes.index(cls)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n")convert函数把左上角和右下角坐标换算成中心点加宽高的形式,再除以图片宽高做归一化。注意w和h在推导时用的是box[1] - box[0],也就是 x 方向差和 y 方向差,别把顺序搞混。classes是一个列表,顺序必须和后面data/garbage.yaml里的names完全一致,否则会错位。
转换完必须校验。打开生成的 txt 文件,看坐标是否都在 0 到 1 之间,有没有出现负数或大于 1 的异常值。常见问题是用 PIL 读图片尺寸时,size是(width, height),而用 OpenCV 读是(height, width),搞反了会导致宽高互换,标注框全部错位。
3.4 数据划分:训练集、验证集的目录得长这样
YOLOv5 读取数据时按目录自动匹配图片和标签。标准结构是images/train、images/val和labels/train、labels/val两套目录一一对应。图片叫001.jpg,标签就必须叫001.txt,后缀可以不同,文件名主体必须一致。
datasets/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── garbage.yaml划分比例我按 8:1:1 来做,训练集占八成,验证集和测试集各占一成。划分时不要用 random 函数瞎洗,要按目录分层采样,比如按拍照场景分,同一场景的图片不要同时落进训练集和验证集,否则会引入数据泄漏,验证集指标虚高,实际部署后立刻现原形。
4. 训练与调参:从默认超参到稳定收敛
4.1 先写对数据配置文件:yaml里的每个字段都有用
训练前的第一步是配置data/garbage.yaml,这个文件描述数据集位置和类别信息。YOLOv5 新版把train和val的路径放在 yaml 顶层,注意路径是相对运行目录的,写绝对路径也没问题,但绝对路径在换机器后要全部改一遍,所以我会用相对路径加项目根目录的方式。
path: ./datasets train: images/train val: images/val test: images/test nc: 10 names: ["cardboard", "plastic_bottle", "glass_bottle", "can", "fruit_peel", "battery", "lamp", "cloth", "cigarette_butt", "lunch_box"]path是数据集根目录,train和val是相对于path的子目录。nc是类别数,必须和names列表长度一致,这是最常见的低级错误。names的顺序和检测模型输出的类别索引一一对应,推理时系统靠这个列表把 0、1、2 之类的数字翻译成“塑料瓶”“玻璃瓶”。
一个容易被忽略的坑是:训练集、验证集图片和标签文件必须一一配对。如果某张图片没有对应的 txt 标签,YOLOv5 训练时会跳过它并输出警告;但如果某个 label 存在而图片缺失,会直接报错。我在准备数据后会用脚本检查两边文件数量是否一致,确保没有孤儿文件。
4.2 训练命令:核心参数怎么传
数据配置写好后就开始训练。第一次跑建议用官方预训练权重做迁移学习,也就是--weights yolov5s.pt,以 COCO 预训练模型作为初始化,再微调到垃圾分类数据集上。从零训练不是不行,但需要的数据量和训练轮次都要成倍增加,毕设项目没必要。
python train.py --data data/garbage.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt --batch-size 16 --epochs 100 --imgsz 640 --workers 4--batch-size受显存限制,8G 显存跑 16 已经到极限。--epochs先设 100,后期看曲线再决定是否提前终止。--imgsz是输入分辨率,640 是精度和速度的平衡点;如果数据里的目标比较小,可以试 960,显存不足降到 512 也行。--workers是数据加载线程数,Windows 下建议设 0 或 2,设太高容易报 DataLoader worker 相关错误。
训练过程中终端会滚动输出 GPU 利用率、当前 epoch、各类 loss、mAP 等指标。我一般盯两个关键指标:一个是box_loss和cls_loss是否稳定下降,另一个是验证集上 mAP@0.5 是否在持续上升。如果训练到一半 loss 不再下降甚至回升,就要考虑调学习率或者回退到上一个检查点。
4.3 超参数:决定收敛质量的几个关键项
YOLOv5 的超参数集中在data/hyps/hyp.scratch.yaml里。毕设场景不需要动太多,专注调整几个影响最明显的项目即可。
| 超参数 | 默认值 | 影响说明 |
|---|---|---|
| lr0 | 0.01 | 初始学习率,数据量小时调小到 0.001 更稳 |
| lrf | 0.01 | 最终学习率因子,控制学习率衰减终点 |
| mosaic | 1.0 | 是否用 mosaic 增强,显存不足时设 0 |
| fl_gamma | 0.0 | 焦点损失参数,类别不平衡时设为 0.5-1.0 |
垃圾分类数据集的常见问题是类别分布不均衡,比如“其他垃圾”里的物品形态杂、样本多,但某些类别样本极少。对于样本很少的类别,靠增加图片数量比调超参数更有效,因为过采样策略比损失函数调整更直观。如果实在收集不到图片,则可以裁剪已有图片做数据扩充——注意是裁剪后作为新样本加入训练集,而不是简单的翻转和旋转。
另一个常被忽视的参数是--cache,加在 train.py 参数里可以把图片预加载到内存,训练提速明显,但显存小的机器慎用。磁盘速度慢、图片数量多的情况下,这个参数带来的收益很可观。
4.4 训练产物:weights和曲线图怎么读
训练完成后,runs/train/exp目录下有几个必须搞清楚的文件。weights/best.pt是验证集上 mAP 最高的权重,部署时选它;weights/last.pt是最后一个 epoch 的权重,断点续训时用它。results.png汇总了所有训练曲线,包括损失函数曲线、精确率、召回率和 mAP 曲线。
看 results.png 时有个技巧是看验证集 loss 曲线是否出现拐点。训练集 loss 持续下降、验证集 loss 却掉头向上的那个位置,就是过拟合开始的信号,最佳权重往往就在拐点之前。best.pt 选择的是 mAP 最高的 epoch,这个权重不一定和损失拐点完全一致,但通常处于同一区间。
另一个常见困惑是训练结束后有多份 exp 目录。跑多次训练会生成 exp、exp2、exp3 这样的递增目录,注意确认 weights 属于哪一次实验,别在部署时拿错权重。我一般会在训练结束后直接复制一份 best.pt 到项目根目录并改名,避免后面被 runs 目录下的多个 exp 搞混。
5. 避坑:垃圾分类训练中六个真实翻车现场
5.1 CUDA和PyTorch版本不匹配:一跑就报CUDA error
现象:执行torch.cuda.is_available()输出 True,但一跑 train.py 就报CUDA error: no kernel image is available for execution on the device。
原因:这个报错说明 PyTorch 编译时的 CUDA 版本和当前显卡驱动支持的版本不一致,通常是显卡驱动版本太老,或者 PyTorch 的 CUDA 版本高于驱动能支持的版本。比如装了 cu121 的 PyTorch,驱动却是老版本。
解决:先查nvidia-smi确认驱动支持的 CUDA 版本,然后到 PyTorch 官网安装和驱动匹配的版本。驱动老就直接pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117降级。从这个坑之后我养成了习惯:每次装完环境先把训练跑起来再去做别的。
5.2 归一化坐标全为0或超出范围:VOC转YOLO时数据全废
现象:转换脚本跑完没有报错,但打开 txt 文件发现坐标要么都是 0,要么出现 1.5 这样的越界值。训练时 loss 直接飞起,模型根本学不进去。
原因:大部分情况是读 XML 时图片尺寸取错。YOLOv5 的图片读取和 labelImg 的尺寸记录方式不同,导致归一化时除以错误的宽高,坐标全部错位。
解决:转换后在脚本里加入校验逻辑,凡是坐标小于 0 或大于 1 的直接抛异常,不要默默写进 txt。同时抽样对比原图和标注框,写一个简单的可视化脚本,把检测框画到图上人工过一遍。这个过程虽然琐碎,但能提前拦下大部分数据问题。
5.3 mosaic增强导致显存溢出:连640分辨率也救不了
现象:batch-size 设 8 还是 OOM,报RuntimeError: CUDA out of memory。
原因:YOLOv5 默认开启了 mosaic 增强,这个增强会把 4 张图拼成 1 张参与训练,相当于单样本实际显存消耗是肉眼看到的 4 倍。小显存显卡不开 mosaic 反而跑得动。
解决:在 train.py 参数里加--mosaic 0关闭 mosaic,或者把 batch-size 降到 4。更折中的方案是保留 mosaic 但调小--imgsz到 512。垃圾分类的检测目标一般不大,imgsz 降到 512 对精度的影响在可接受范围内。
5.4 训练loss不降:学习率太大或类别不平衡
现象:跑了 20 个 epoch,box_loss 始终在 0.08 左右抖动,cls_loss 纹丝不动,mAP 曲线基本贴着 0。
原因:分类 loss 不下降的最常见原因是主线清楚、垃圾类别间相似度太高,比如瓶装可乐和易拉罐外形接近,模型始终分不清;另一个常见原因是正负样本比例失衡,某些类别样本太少,模型干脆把所有样本都预测为多数类。
解决:如果确认数据没问题,先把lr0从 0.01 改成 0.001 重新训练。如果依然不降,集中检查少数类别的标注框——样本少的类别逐一肉眼复核,别轻易依赖自动标注工具。
5.5 mAP很高但实测效果差:数据分布和真实场景脱节
现象:验证集 mAP@0.5 到 0.9 以上,结果用摄像头一拍,满屏漏检,连塑料瓶都找不到。
原因:这是最典型的过拟合变体。训练集中大量图片是网上下载的干净背景,目标大、光线好、角度正,而真实场景是桌面、地面、垃圾桶里的垃圾,光线变化大、目标互相遮挡,分布完全不在一个维度。
解决:去真实场景补拍一批图片并加入训练集,或者把一张大图裁切成多张小图来扩充场景多样性。这个坑提醒我明白了一点:验证集指标只是参考,模型最终是为了应对真实环境,而不是刷分。
5.6 训练时best.pt一直没有更新:mAP曲线异常
现象:训练跑完了,但 best.pt 一直停留在前几个 epoch,后续 mAP 不再刷新。
原因:学习率衰减过快,模型在后期基本停止更新;或者验证集数据太少,mAP 波动巨大,偶尔一个高值被锁定后再也没机会刷新。
解决:检查lr0和lrf的设置,数据量足够时把lrf调小让后半程还在微调;验证集图片太少时,增加验证集规模。从这之后我每次训练结束后都会看一眼 best.pt 的时间戳,确认保存顺序没有异常。
6. 部署与验证:把模型接到摄像头做实时检测
6.1 ONNX导出与OpenCV DNN推理
PyTorch 训练出的模型文件不能直接部署到嵌入式设备或移动端,导出为 ONNX 格式是为了让推理摆脱 PyTorch 环境依赖。ONNX 是一个通用的模型交换格式,之后用 OpenCV DNN 或 ONNXRuntime 都能加载。
python export.py --weights best.pt --include onnx --imgsz 640在best.pt的同级目录会生成best.onnx。这一步的作用是把模型的结构和权重序列化到 ONNX 格式,导出时--imgsz必须和训练时的分辨率一致,否则会报维度错误或导致精度下降。
如果不想换推理后端,直接用 PyTorch 的torch.hub加载本地的 best.pt 也可以,但每次启动都要花时间加载权重,且依赖 torch 环境。ONNX 模型的优势是体积更小、在无 GPU 的环境也能跑 CPU 推理。垃圾分类场景不追求高帧率,CPU 推理一两百毫秒的延迟完全可接受。
6.2 摄像头实时检测脚本
把 ONNX 接到摄像头前,先跑一下单张图片确认导出模型行为正常,别直接上摄像头,要不问题定位困难。一个简单的摄像头推理循环长这样:
import cv2 import onnxruntime as ort sess = ort.InferenceSession("best.onnx") input_name = sess.get_inputs()[0].name cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() img = cv2.resize(frame, (640, 640)) img = img[:, :, ::-1].transpose(2, 0, 1) / 255.0 img = img[None].astype("float32") outputs = sess.run(None, {input_name: img}) # 解析outputs中的检测结果并绘制框 cv2.imshow("garbage detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break代码里把 BGR 转 RGB、归一化到 0-1、加 batch 维度的三步预处理,每一步做错结果都会异常。ONNX 输出的是原始检测头数据,需要按 YOLOv5 的格式解码候选框,再叠加 NMS。建议直接用官方 detect.py 里对应的 utils 函数,别自己硬写,很多细节只有踩过坑才知道。
从那以后我每次做检测类项目,都会强制走一遍这个流程:先单张图片验证、再视频验证、最后部署。少一步,都可能让你在摄像头前对着满屏的误检框发呆。希望帮到你。
本文还有配套的精品资源,点击获取