☰
YOLOv5垃圾分类识别实战:从数据处理到部署避坑指南
2026/10/13 14:58:50 网站建设 项目流程

简介:这是一份基于YOLOv5的垃圾分类识别项目包,针对可回收垃圾、有害垃圾、厨余垃圾、其他垃圾四类生活垃圾,面向人工智能与计算机视觉方向的开发者和在校学生,解决物品自动检测与分类问题。项目使用Python语言搭建完整训练与推理流程,已在云端环境完成模型训练,下载后可用PyCharm直接打开运行。压缩包共1001个文件,大小约71.98MB。其中431个txt文件为标注数据,231个jpg与209个jpeg为训练及测试图片,39个yaml和7个yml为配置参数,31个py为训练、推理等脚本,另有2个pt格式的预训练权重文件,类型覆盖从数据准备到模型部署的关键环节,目录结构清晰,便于按需查阅。项目已有5561人学习下载。包含可直接运行的推理脚本、测试图片、配置文件和权重,将图片放入指定文件夹后运行脚本即可生成识别结果,当前可识别瓶子、报纸、电池、剩饭、碎瓷片等典型物品。由于训练数据规模有限,模型针对上述样本效果尚可,同时保留扩展训练接口,有兴趣的读者可自行补充数据集在云端继续训练,用于进一步提升模型泛化能力,也适合作为YOLOv5目标检测的入门实践参考。

1. 为什么要拿 YOLOv5 做垃圾分类识别:先认清你面对的是什么问题

垃圾分类识别是典型的「类别粒度比目标检测默认设定更细」的场景。你说这是瓶矿泉水瓶,检测网络只需要把它从背景里框出来;但你要的是「可回收物-塑料瓶」和「可回收物-玻璃瓶」分开,甚至同一张图里出现好几个瓶瓶罐罐,还要按可回收、有害、厨余、其他四类去归类。这不是模型选型问题,而是数据、类别设计、部署形态三者相互拉扯的问题。

YOLOv5 到今天依然是这个任务最稳的起点:生态成熟、单阶段结构快、模型权重小、社区资料多,拿来做垃圾分类的基线几乎是零门槛。但正因为门槛低,很多人三个月前跑通了 demo,三个月后还卡在同一个地方——类别分不准、定制场景识别率掉得厉害、拿到新瓶型就漏检。这篇文章不打算给你复述 YOLOv5 的原理,而是把我做垃圾分类识别时验证过的一整套方案讲清楚:从类别怎么定、数据集怎么做,到训练参数怎么设、推理阶段怎么调,最后是四个最容易翻车的地方和对应的排查路径。

2. 把「垃圾分类」翻译成检测任务:类别体系、数据来源与标注方案的选型理由

2.1 别急着标数据,先定类别体系:四分类、细分品类还是两阶段方案

垃圾分类识别和通用物体检测最大的区别在于:类别定义不是模型给的,是政策和管理规则给的。国内多数场景要求的是四分类(可回收、有害、厨余、其他),但四分类直接作为检测类别会出现一个大问题——类别内差异极大。比如「可回收」这个类里既有矿泉水瓶(细长)又有纸箱(方形)还有易拉罐(矮圆柱),模型既要学它们的共性,又要区分它们和「其他」里的陶瓷杯、卫生纸。特征维度被拉得太宽,模型容量不够,准确率会卡在 80% 上下很难再涨。

我一般的做法是:把任务拆成两个层次,但模型还是单阶段。类别体系上做「细分品类」,比如塑料瓶、玻璃瓶、易拉罐、纸箱、纸杯、果皮、剩饭、电池、灯泡、织物,这些是 YOLOv5 能稳定学出来的物理类别;分类逻辑在推理阶段用映射表做二次归并,把细分品类映射到四分类。这样做有三个好处:第一,模型学的是具体物体的视觉特征而不是抽象管理概念,学得动;第二,推理端可以做置信度过滤,识别不了的就落到「其他」,不会硬着头皮给错答案;第三,以后管理部门改规则,你只需要改映射表,不用重训。

这里有个取舍要讲清楚。如果你想做的是「扫码式」的逐物品识别,类别粒度越细越好,甚至可以定到 30 个类以上;如果你做的是社区垃圾桶站的整体画面识别,需要把一批垃圾一次性归类,那就必须用细分品类加映射的方案,否则画面里的厨余垃圾和可回收物混在一起时,单个类别检测框的置信度全都会下降。实际落地的团队大部分走的是后者,我也建议新手先从这个方案入手。

2.2 数据集从哪来:公开数据集起步,自采数据解决「自己的场景」

垃圾分类领域有几套公开数据集,包含了常见生活垃圾的图片和 VOC 格式标注,类别覆盖从塑料、玻璃、纸张到电池、灯管。用它做预训练是划算的,但我需要提醒你:公开数据集的标注质量参差不齐,有些图的边界框明显框大了,有些类别标签标错了,直接拿来做测试会是灾难。我的习惯是公开数据只做预训练和置信度基线测试,部署前必须用自采数据微调。

自采数据有几个原则。第一,背景要杂:垃圾桶站、小区地面、厨房台面、办公室桌面,每个场景都要采;第二,光照要杂:白天强光、黄昏逆光、晚上灯光下都要采,否则夜间的漏检率会高到让你怀疑模型白训了;第三,同类物体要收集形态差异:塑料瓶包括矿泉水瓶、饮料瓶、洗发水瓶,形态差异比你想的大得多,只采一种瓶型上去就翻车。采集量上,每个细分品类最少 300 张真实图像,边界框总数不少于 1500 个,这个量级足够微调出一个能看的版本。

标注工具方面,能支持目标检测标注的常见开源工具都可以,导出成 YOLO 格式的 txt 文件最省事,也正好是 YOLOv5 原生读入的格式。如果你只有 VOC 格式的数据集,也可以用 YOLOv5 仓库自带的格式转换脚本,但那个脚本对某些字段的处理比较粗,转完我会抽查 50 个文件确认归一化坐标没有越界。

2.3 把图片和标注整理成 YOLOv5 目录结构:一份可直接跑的准备工作

YOLOv5 对数据目录的约定非常死板:images 和 labels 必须分开放,且训练集、验证集、测试集要独立。我把整理工作写成一个脚本,作用是生成标准目录结构,并按比例划分数据集。

import os import random import shutil from pathlib import Path root = Path("garbage_dataset") images_dir = root / "images" labels_dir = root / "labels" # 先扫描所有图片和同名 txt 标注 image_paths = list((root / "raw_images").glob("*.jpg")) all_items = [] for img_path in image_paths: txt_path = root / "raw_labels" / (img_path.stem + ".txt") if txt_path.exists(): all_items.append((img_path, txt_path)) random.seed(42) random.shuffle(all_items) train_ratio, val_ratio = 0.8, 0.15 train_count = int(len(all_items) * train_ratio) val_count = int(len(all_items) * val_ratio) for split, items in [ ("train", all_items[:train_count]), ("val", all_items[train_count:train_count + val_count]), ("test", all_items[train_count + val_count:]), ]: split_images = images_dir / split split_labels = labels_dir / split split_images.mkdir(parents=True, exist_ok=True) split_labels.mkdir(parents=True, exist_ok=True) for img_path, txt_path in items: shutil.copy(img_path, split_images / img_path.name) shutil.copy(txt_path, split_labels / txt_path.name)

这段脚本的含义是:把 raw_images 和 raw_labels 两个目录下的图片与同名标注配对,随机打乱后按 8:1:1 划分成训练、验证、测试三份。参数方面,train_ratio 和 val_ratio 是划分比例,我推荐 0.8 和 0.15,留 0.1 做最终测试;random.seed(42) 固定随机种子,确保每次复现划分结果一致,排查问题时不会因为数据分布变化而分不清是模型问题还是数据问题。

划分完之后,还要确认一件事:图片和 txt 文件同名,txt 里每行是「类别序号 x_center y_center width height」,全部是归一化坐标。偶尔会遇到标注工具导出的坐标是绝对值,这种情况要在训练前做一次归一化,否则模型训练时 loss 直接变成 NaN,属于最典型的低级翻车。

3. 训练垃圾分类模型的关键参数:从过拟合到欠拟合之间找平衡点

3.1 选择预训练权重与输入分辨率:不要一上来就跑大模型

YOLOv5 提供了从 n 到 x 的多个模型尺寸。垃圾分类识别这个任务,检测目标通常不是极小目标,但类间差异细(比如纸杯和塑料杯),我的经验是:s 和 m 两个尺寸最适合做基线,n 太小容易欠拟合,l 和 x 在真实部署时推理速度明显下降,NVIDIA Jetson 这类边缘设备上根本跑不动。

预训练权重建议用 COCO 的 yolov5s.pt。虽然 COCO 没有垃圾分类类别,但它学到了通用物体的轮廓和纹理特征,迁移到垃圾上比从随机权重开始训练收敛快很多,泛化能力也好不少。有人觉得 COCO 预训练和垃圾分类数据分布差异大,干脆不用,实际做对比之后会发现,从随机初始化出发训练同样的 epoch,mAP 低了大概 8 个百分点,速度还慢了一倍。预训练权重从来没有「必须完全匹配任务」的要求,只要底层特征复用得上就值得用。

输入分辨率这块,我推荐 640。理由是:垃圾场景里常有多个物体堆叠,边界框尺寸分布广,640 既能保证小物体特征不丢失,又能在常见的消费级显卡上以 batch size 16 顺利训练。如果你只关注小区垃圾桶站这类近距离场景,1280 会带来明显的精度提升,但训练时间和显存占用几乎翻倍,需要在资源和效果之间做取舍。

3.2 训练命令与每个参数的实际含义

假设你的数据文件是 garbage.yaml,内容指定了训练集和验证集路径、类别数量和类别名清单,训练命令长这样:

python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data garbage.yaml \ --weights yolov5s.pt \ --hyp data/hyps/hyp.scratch-low.yaml \ --name garbage_s_640

逐项说明参数:--img 640 是训练时的输入尺寸,训练时 YOLOv5 会在这个尺寸基础上做随机缩放增强,范围是 0.5 到 1.5 倍;--batch 16 是一次迭代喂给模型的图片数,这个值受显存限制,如果训练时报 CUDA out of memory,优先把 batch 降到 8,而不是换小模型;--epochs 150 是训练轮数,垃圾分类这种中等规模数据集,150 轮足够,如果 100 轮时验证集 mAP 还在明显上升,可以接着训到 200 轮;--hyp 指向超参数文件,YOLOv5 自带的 hyp.scratch-low.yaml 适合小模型微调,它把学习率、数据增强幅度都调得比较保守,不容易跑飞。

训练过程中要盯两个指标:训练 loss 和验证 mAP。训练 loss 持续下降但验证 mAP 从某个 epoch 开始不再涨,说明模型开始过拟合,垃圾分类场景里过拟合的典型表现是「训练集上分得很准,换一批新图就漏检」,本质上是模型记住了训练图的背景而不是垃圾本体。解决方法是提前终止训练,或者把 hyp 文件里的 hsv_h、hsv_s、flipud 等增强参数调大,让模型见过更多变化。另一个指标是各类别的单独 AP,YOLOv5 训练日志会输出每个类的 AP 值,你要找出 AP 最低的那几个类,它们就是之后需要补充数据的方向。

3.3 类别不平衡与难例挖掘:为什么你的易拉罐总是被认成塑料瓶

生活垃圾数据天然不平衡:果皮、塑料瓶、纸箱数量大,电池、灯管数量少。如果不做处理,模型会把所有注意力放在高频类上,低频类的 AP 会惨不忍睹。YOLOv5 本身没有内置类别频率加权机制,所以要在数据处理层面处理。

我会先统计每个类别的边界框数量,找出边界框数小于 500 的类别,单独做数据增强,具体是三件事:对这类图片做随机旋转 90 度和水平翻转,增加样本量;用小批量过采样,让每个 batch 里至少出现一张包含低频类别的图;如果低频类还是不够,就去真实场景补拍。补拍的时候不要只在理想角度拍,要在垃圾混放、物体半遮挡、阴影遮挡的条件下拍,这些难例比那些摆拍的数据对模型提升大得多。

难例挖掘是另一个容易被忽视的点。第一轮训练完,把验证集里所有被漏检、误检的图片收集起来,人工确认后单独放在 hard_examples 目录里,混入训练集再训一轮。这个操作一般能把低频类的 AP 提升 5 到 10 个点。不要指望数据增强能完全替代真实难例,增强只是让模型在已有数据上做插值,真实难例才能教会模型处理未见的组合方式。

4. 用训练好的模型做识别推理:置信度阈值、NMS 与类别映射的落地逻辑

4.1 推理脚本的几个关键输出与踩坑点

训练完成后,YOLOv5 的 detect.py 可以直接用来跑推理,但直接它的默认参数不一定适配垃圾分类场景。

python detect.py \ --weights runs/train/garbage_s_640/weights/best.pt \ --source ./test_images \ --img 640 \ --conf 0.35 \ --iou 0.45 \ --save-txt \ --save-conf

--conf 0.35 是置信度阈值,低于这个值的检测框会被丢弃。垃圾分类场景里,这个值我一般设成 0.35 到 0.4,太高会漏掉被遮挡的物体,太低会产生大量误检。--iou 0.45 是 NMS 的 IoU 阈值,多个重叠框如果 IoU 超过这个值,就只保留分数最高的那个。--save-txt 会把检测结果存成 txt,--save-conf 会在 txt 里附上每个框的置信度,分析 badcase 时这个文件非常有用。

检测结果里,每个物体的类别序号是训练时 data.yaml 里定义的下标。整合成垃圾分类四分类时,需要一个映射表。比如训练时定了 0 塑料瓶、1 玻璃瓶、2 果皮,推理时就要把 0 和 1 归到「可回收」,2 归到「厨余」。这个映射逻辑单独写一个函数处理干净,不要散落在不同脚本里。

CLASS_TO_FOUR = { 0: "可回收", # 塑料瓶 1: "可回收", # 玻璃瓶 2: "可回收", # 易拉罐 3: "可回收", # 纸箱 4: "厨余", # 果皮 5: "厨余", # 剩饭 6: "有害", # 电池 7: "有害", # 灯管 8: "其他", # 织物 } def map_to_four(result_detections): four_class_votes = {"可回收": 0, "有害": 0, "厨余": 0, "其他": 0} for det in result_detections: class_id = int(det["class_id"]) conf = det["confidence"] if conf >= 0.35: four_class_votes[CLASS_TO_FOUR[class_id]] += 1 return max(four_class_votes, key=four_class_votes.get)

这段代码看起来简单,但里面有两个容易被忽略的设计。第一,它在做「投票」而不是「取最高置信度」——因为垃圾分类识别往往面对一堆垃圾,不是只有单个物体,投票比单框置信度更能反映整体类别;第二,阈值 0.35 在映射函数里又做了一次过滤,避免低置信度检测框对投票结果产生干扰。如果你只取置信度最高的那个框作为最终分类结果,遇到画面里同时有果皮和矿泉水瓶时,结果通常是被果皮主导,但按四分类的桶站管理语义,这袋垃圾到底是厨余还是可回收,需要重新考虑。

4.2 部署时把模型导出为 ONNX:分类速度与精度之间的可调空间

训练好的 PyTorch 模型直接部署不是不行,但推理效率上差一截。导出成 ONNX 再做前向推理,速度提升明显,尤其对 CPU 部署或边缘设备。YOLOv5 仓库自带导出脚本:

python export.py \ --weights runs/train/garbage_s_640/weights/best.pt \ --img 640 \ --include onnx \ --simplify

--simplify 会调用 onnx-simplifier 对计算图做简化,去掉一些冗余算子,我建议默认带上。导出完之后,可以用 onnxruntime 做一次推理验证,确保输出形状和 PyTorch 原模型一致。这里有一个常见的坑:导出的 ONNX 模型输出格式和 YOLOv5 原版的 detect.py 解析方式不完全一致,需要按输出矩阵的前 4 列是框坐标、第 5 列是物体置信度、后面是各类别置信度的方式解析。如果你直接用 onnxruntime 的输出做后处理,大概率会得到一堆乱码坐标。

部署阶段最关键的一个参数是置信度阈值。训练阶段你用的是 0.35,部署时我一般会根据实际运行环境的误检率调高到 0.45 或 0.5。因为部署场景里背景更杂,垃圾桶站的水泥纹理、反光地面、路人身上的图案都会触发误检,调高阈值是性价比最高的误检抑制手段。但阈值不是越高越好,调太高会把真实垃圾漏掉,具体调多少,要在目标场景的实拍样本上做验证,记录准确率和召回率的平衡点。

5. 垃圾分类识别避坑指南:四个真实踩过的坑与排查路径

5.1 一类物体分裂成多个边界框:同一个瓶子被识别成三个框

现象:模型把同一个矿泉水瓶同时识别出塑料瓶、玻璃瓶、易拉罐三个不同类别的框,置信度都不低。

原因:这种情况下最有可能的是 NMS 的 IoU 阈值过高,导致两个类别框因为 IoU 低于阈值而没有被合并,而不是模型真的把同一个物体识别成了多个类别。另一种情况是你的训练数据里同一个物体在不同图上被标注成了不同类别,标注噪声被模型学进去了。

解决:先把 --iou 从 0.45 调低到 0.3,看多框现象是否减少。如果减少了,说明是 NMS 合并问题。如果没减少,去检查标注数据:把所有训练图里同一个物体的标注类别抽出来看,是否存在不同标注员标了不同类别的情况。标注一致性检查在垃圾分类这种细粒度任务里非常关键,建议标注前定一份带参考图的标注规范,标注后随机抽查 5% 的文件。

5.2 夜晚灯光下识别率骤降:同样的模型白天 90 分,晚上 60 分

现象:模型在白天光照良好的图片上 mAP 接近 0.9,但换上夜间灯光场景后,漏检率显著增加,尤其是深色物体完全消失。

原因:这是光照域差问题。训练集里白天的图片占比过高,模型学到的颜色分布整体偏亮;夜间灯光下色温偏黄且局部过曝,模型的特征分布发生偏移。这类问题不是调参能解决的。

解决:数据采集阶段就要把夜间场景补进去,这是最省事的手段。如果已经训练完了,补救方案是:采集夜间图片做标注,用这些图片对模型做增量训练,学习率设成正常训练的 0.1 倍,epochs 设成 30 到 50。注意增量训练只需要输入夜间数据,不需要把白天数据再喂一遍,否则白天效果会被稀释。

5.3 瓶盖、标签等小物体整个漏检:小目标问题在垃圾场景的具体表现

现象:大件的纸箱、瓶子都能检测出来,但瓶盖、烟头、电池这类小物体基本漏检或置信度极低。

原因:YOLOv5 默认下采样到 80x80、40x40 的特征图,小物体在低分辨率特征图上信息基本丢失。训练时的输入尺寸如果偏小,小物体特征就更加留不住。

解决:有两个手段。第一是把训练尺寸从 640 升到 1280,直接增加小物体的像素数量,代价是训练时间和显存增加;第二是调整 anchor 尺寸,在模型配置文件里针对小物体增加小 anchor。实际操作中,我遇到最多的情况是两者都要做:1280 输入加小 anchor,小物体的 AP 能从 0.3 提升到 0.65 左右。如果你受限于硬件资源不想改训练尺寸,那可以尝试把图片裁剪成几个不重叠的区域分别做检测,再把结果合并,这是不重训模型的替代方案。

5.4 训练 loss 正常但验证 mAP 震荡剧烈:学习率与 batch size 的配合问题

现象:训练 loss 曲线平滑下降,但每次验证的 mAP 波动非常大,上一轮 0.8,下一轮就掉到 0.6,再下一轮又涨回 0.78。

原因:最常见的是学习率设置偏大,加上验证集图片数量少,导致 mAP 对模型权重微小变化非常敏感。另一个原因是 batch size 太小,BN 层的统计量不稳定,波动被放大了。

解决:把 hyp 文件里的 lr0 从默认值调低,比如从 0.01 降到 0.005,观察 mAP 曲线是否变平滑;同时把验证集扩充到 500 张以上,减少评估时的随机性。还有一个不太直观但很有效的操作:在训练最后 20 个 epoch 启用余弦退火学习率衰减,这会让模型在收敛末期更稳定,mAP 曲线的震荡幅度会明显变小。

6. 让垃圾分类识别模型「越用越准」:混淆矩阵与增量训练的实战习惯

模型跑通只是第一步,真正要投入使用,需要建立起「评估、排查、迭代」的闭环。我每次做完一个版本的模型,会在测试集上统计出混淆矩阵,重点看那些成对出现的错误模式。比如易拉罐和塑料瓶互相混淆,说明两者在颜色和形状特征上太接近,需要补充更多带包装、带标签的样本;纸杯和纸箱混淆,大概率是模型学习了纹理而没有学习结构,需要补充角度不同的样本。混淆矩阵就是判断模型缺陷在哪里的地图,不要只盯整体 mAP。

拿到混淆矩阵后,我把错误样本分成三类:漏检、误检、类别分错。漏检是框没出来,看置信度阈值的设置和该类别样本量;误检是背景被当成垃圾,看阈值和背景数据;类别分错是框出来了但标签错,看类别间的特征差异和标注一致性。每类错误都有对应的解法,不是无脑加数据。

数据补完不是重新从头训练,而是做增量训练。增量训练的操作是把旧权重作为预训练权重,只喂新采集的数据,学习率设成正常训练的十分之一,训练 30 到 50 个 epoch。这个做法能保留旧模型已经学到的所有能力,同时把新样本的信息融合进去,不会出现「旧数据全忘」的灾难。我有一次图省事,在新旧数据合并后从头训练了 200 轮,结果新版模型对旧场景的识别效果反而下滑了,后来分析是采样分布变化导致模型过拟合了新场景。从那以后我一直用增量训练的模式,稳很多。

关于置信度阈值,我会给每个类别设独立的置信度阈值而不是全局统一值。查混淆矩阵时经常发现,塑料瓶置信度普遍 0.8 以上,电池普遍只有 0.5 左右,如果统一用 0.5 做阈值,误检主要来自塑料瓶,漏检主要来自电池。分开设阈值之后,两类问题同时缓解。这个操作实现成本很低,在推理脚本里加一个 per-class 的阈值字典就行,建议你一上来就按这个思路设计。

最后再提醒一句:垃圾分类识别的难点不在模型结构,在你对场景和数据的管理。把类别体系设计好、数据分布看清、迭代闭环跑起来,YOLOv5 这个方案能撑住绝大多数真实场景。希望这些踩坑经验能帮你少走一段弯路。

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

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

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

立即咨询