垃圾分类数据集与代码实战:从图像分类到YOLOv8训练全指南

发布时间:2026/9/26 10:31:04
垃圾分类数据集与代码实战:从图像分类到YOLOv8训练全指南 简介面向垃圾分类算法入门与实践的完整资料包适合正在学习图像识别、神经网络及 OpenCV 的学生或开发者。数据集涵盖硬纸板、纸张、塑料瓶、玻璃瓶、铜制品与不可回收垃圾六大类代码基于 TensorFlow、Keras、NumPy 和 OpenCV 构建并提供了完整的训练与预测流程训练脚本负责搭建模型并迭代权重预测脚本可读取任意图片路径直接输出所属类别同时还包含图片预处理和结果可视化等辅助脚本便于理解从数据准备到模型评估的每个环节。资源共 7 个文件以 5 个 Python 脚本为主另含 1 个预训练 H5 权重文件和 1 个数据集压缩包整体大小约 161 MB目录结构简洁解压后即可按脚本分工快速跑通。目前已有 30634 人学习下载可作为垃圾分类课程设计、图像分类竞赛或毕业设计的入门参考也适合通过替换数据集迁移到其他多类别识别任务。1. 垃圾分类数据集及代码先想清楚“分什么类”再动手垃圾分类数据集及代码乍看是两样东西一堆图片和一段训练脚本。真正把这两样东西拼成可用模型的人会发现卡住他们的从来不是代码本身而是数据没统一、类别没定准、跑了几天验证集分数挺高、落地一测就翻车。这套任务本质是一个图像分类或目标检测工程和“识猫识狗”的区别在于类别之间长得像场景又脏又乱而且很多公开数据集是从竞赛或园区项目里拆出来的格式和分类口径都不一样。这篇文章适合三类人想快速搭一个分类演示或课程设计的人想用 YOLOv8 训练自己数据集的工程师以及已经在跑但效果不如预期、想系统排查问题的人。核心思路是先定分类体系再统一数据格式然后选分类还是检测的路线最后把训练和部署之间的坑逐个填平。下面按这个顺序展开每一步都能直接抄。2. 数据集怎么选、怎么整理分类体系、公开来源与格式统一2.1 四分类还是细分类先定坐标再定数据集垃圾分类没有一个通用标准不同城市、不同小区、不同竞赛定义都不一样。常见的公开数据集大体分两套坐标一套是四分类也就是可回收物、厨余垃圾、有害垃圾、其他垃圾另一套是细分类从十几个类别到几十个类别不等比如塑料瓶、易拉罐、玻璃、电池、果皮、一次性餐具。选哪套决定了后面所有代码都要改所以第一步不是下载数据而是问自己这个模型到底用在哪里。如果只是做小区垃圾桶旁的识别提示四分类就够了模型简单数据也好找。如果是做分拣机器人或者智能回收箱需要告诉机械臂“这是矿泉水瓶、不是饮料瓶”那必须走细分类。细分类的坑在于公开数据集质量参差不齐一个“塑料瓶”类别里可能混着洗发水瓶、药瓶甚至一个空瓶和一个压扁的瓶在语义上就不同。我的建议是从小处着手先用四分类跑通流程再挑其中易混的类别做二级模型或加样本。公开来源方面常见做法是去百度飞桨 AI Studio、Kaggle 这类平台找现成的垃圾分类图集也有高校论文附带的数据集。下载后不要急着训练先看目录结构。有的包是 ImageFolder 风格一个类别一个文件夹有的是 CSV 文件记录图片路径和标签还有一些是检测格式带 XML 或 JSON 标注文件。这三种格式对应完全不同代码路径。很多人上来就写ImageFolder加载结果发现数据里混着标注文件训练脚本直接跳过所有图片这是最常见的翻车点。2.2 把原始图片整理成可训练目录一个校验脚本无论下载的数据是什么格式我都建议先统一成“train/类别名/图片文件”和“val/类别名/图片文件”的目录结构。这样做有两个好处第一PyTorch 的ImageFolder和 YOLO 的分类模式都能直接消费第二后面做类别增删、合并时只需要改文件夹不用改代码。下面这个脚本做三件事扫描原始目录下的所有图片、校验图片是否完整、按指定数量比例复制到目标目录。直接保存为prepare_clean.py运行。import os import shutil import random from pathlib import Path from PIL import Image source_root Path(raw_dataset) # 下载数据解压后的根目录 target_root Path(trash_dataset) # 整理后的目标目录 category_names [recyclable, kitchen, hazardous, other] train_ratio 0.85 seed 42 min_side 32 # 过滤掉长宽任意一边小于 32px 的图 random.seed(seed) def verify_image(path): try: with Image.open(path) as im: im.load() w, h im.size except Exception: return False return min(w, h) min_side for cat in category_names: src_cat_dir source_root / cat if not src_cat_dir.exists(): print(f[warn] 缺类别目录: {src_cat_dir}) continue good_paths [] for p in src_cat_dir.iterdir(): if p.suffix.lower() not in {.jpg, .jpeg, .png, .bmp}: continue if verify_image(p): good_paths.append(p) else: print(f[bad] {p} 跳过损坏或分辨率过低) if len(good_paths) 0: print(f[warn] {cat} 没有可用图片) continue random.shuffle(good_paths) n_train int(len(good_paths) * train_ratio) for split, paths in [(train, good_paths[:n_train]), (val, good_paths[n_train:])]: out_dir target_root / split / cat out_dir.mkdir(parentsTrue, exist_okTrue) for p in paths: shutil.copy2(p, out_dir / p.name) print(f{cat} {split}: {len(paths)} 张) print(整理完成)这段脚本里verify_image是防止训练中途卡死的关键。Image.open本身是惰性的只读文件头不真正解码所以必须调用im.load()强制读完像素数据截断的 JPEG 和伪装成图片的文本文件在这里都会被揪出来。min_side过滤小图是为了避免模型在高分辨率原图上适配不了后面统一缩放时又放大模糊图。训练集和验证集的比例我一般用 85:15 而不是 8:2因为垃圾图片通常总量不多验证集留 15% 足够看出模型真实水平留太多反而让训练集更少。注意seed 42固定随机顺序保证两次整理结果一致否则你后面复现实验结果时怎么都解释不了分数波动。2.3 类别不平衡与数据清洗决定模型上限的两件事分类模型的上限不是网络结构决定的而是数据分布决定的。垃圾分类公开数据集的通病是厨余垃圾图片远多于有害垃圾一个极端情况下“其他垃圾”占了训练集的 60%模型学到的其实是“猜大多数类”而不是“识别垃圾”。这种问题在验证集上还不太明显因为验证集同样不平衡准确率虚高。落地之后有害垃圾这个类别几乎全废。应对办法有两个层面。第一层是数据层面对样本少的类别做翻倍增强旋转、翻转、随机裁剪、色彩抖动都可以对样本多到冗余的类别做随机下采样。第二层是训练层面在损失函数里按类别样本数的倒数加权或者用WeightedRandomSampler让每个 batch 里各类别出现概率接近。我通常两个都做但以数据层面为主因为增强还能顺带提升模型的抗干扰能力。清洗同样关键。公开数据集的标签错误率往往在 5% 以上常见的是把“一次性餐盒”标成“塑料瓶”、“骨头”标成“其他”。这类噪声在训练集里模型还能硬扛一部分但在验证集里会直接影响你对模型真实精度的判断。建议整理完目录后每个类别随机抽 20 张图人工过一遍发现明显错标就把文件移出目录。二十张图花不了十分钟却能省掉后面几天的无用调参。3. 用 PyTorch 跑通分类基线模型选型与最小训练代码3.1 分类还是检测垃圾桶边有两个典型场景标题里有“代码”很多人期望一段代码跑完出结果但代码是跟着场景走的。垃圾分类有两种典型落地场景一种是摄像头拍一张垃圾桶照片告诉用户“这个垃圾属于哪类”只需要给整张图一个类别标签这是图像分类另一种是智能回收箱的识别仓需要定位垃圾在画面里的位置判断里面有没有多个物品这是目标检测。先分清场景再选模型能省不少力气。如果目标是判断传送带上的单个垃圾分类模型最简单也最稳如果目标是模拟真实垃圾桶里堆叠的场景分类模型会明显不够用因为袋子、瓶子叠在一起模型看到的特征是一片混乱。多数公开数据集是按分类任务组织的也就是一张图一个标签所以先用分类模型跑通基线是对的但这不代表最后部署也是它。3.2 最小可训练的分类代码ResNet18 从数据加载到反向传播下面这段代码是我常用的最小训练骨架用 ResNet18 做主干适配四分类任务。结构上保持最简去掉分布式、TensorBoard 这些对新手不友好的东西目的是先让训练转起来再谈优化。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models device cuda if torch.cuda.is_available() else cpu train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_data datasets.ImageFolder(trash_dataset/train, transformtrain_transform) val_data datasets.ImageFolder(trash_dataset/val, transformval_transform) train_loader DataLoader(train_data, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_data, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue) model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, len(train_data.classes)) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay1e-4) for epoch in range(20): model.train() total_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) preds outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fepoch {epoch1:02d} | loss {total_loss/len(train_data):.4f} f| val acc {val_acc:.4f})这段代码有几个地方要说明。shuffleTrue只对训练集开验证集保持固定顺序保证每个 epoch 的评估结果可比。num_workers4让数据加载并行如果机器内存紧张就改 2 或 0。model.fc nn.Linear(...)是把 ResNet18 最后的 1000 类全连接层换成自己的类别数这里len(train_data.classes)会自动读取四个子文件夹的名称。训练循环里我每轮都做一次验证而不是全部训完再看结果因为垃圾分类的模型在 5 到 8 个 epoch 后验证集准确率就会进入平台期早停能省时间。CrossEntropyLoss自带 softmax所以模型输出不需要额外套一层softmax只在推理时用argmax(dim1)取类别序号。注意optimizer.zero_grad()必须在每个 batch 开始前调用否则梯度会累加这是新手最容易踩的坑。3.3 参数怎么调学习率、batch size 与验证间隔给一张参数表照着调基本不会跑偏。这里我按从“能跑”到“跑得好”的顺序讲。参数常见取值影响调参建议输入尺寸224x224越大细节越多显存占用越高先用 224遇明显看不清的类别再上 320学习率0.001过大损失爆炸过小收敛慢用预训练模型时保持 0.001不微调可试 0.0001batch size16~64太大容易过拟合并占显存显存 6G 以下用 16以上用 32epochs15~30分类任务通常 20 轮内饱和观察 val acc 连续 5 轮不升就停weight_decay1e-4正则化抗过拟合数据少时必开别设 0num_workers2~8数据加载速度内存小就设 2设太高会卡死验证间隔上我不建议每个 epoch 都存模型权重磁盘会很快塞满。正确做法是每个 epoch 结束后比较当前 val acc 和历史上最好的一次只有超过才覆盖保存best_model.pt。这样训练完你手里只有一个文件不会在几十个权重里纠结哪个是好的。有个经验如果 loss 一直在降、val acc 纹丝不动先怀疑数据不要怀疑网络。常见原因包括验证集太简单、训练集和验证集之间信息泄漏或者是某个类别样本太少导致模型只盯住了大类别。这时去打印每个类别的精确率和召回率比换模型更管用。4. 处理数据集用于 YOLOv8 训练从标注格式到训练命令4.1 为什么要用 YOLOv8 做垃圾分类检测任务的两个理由把垃圾分类做成目标检测最常见的理由是垃圾桶里不只有一个垃圾。分类模型擅长回答“这张图里最主要的物体是什么”但当画面里同时出现塑料袋、纸盒、易拉罐和半杯奶茶时分类模型只能输出一个标签检测模型却能给出多个“框类别”的组合。另一个理由是回收箱的识别仓需要定位坐标机械臂或翻板要根据坐标做后续动作这已经超出了分类的范畴。所以当你的场景是“拍一张照片输出画面里每个垃圾的类别和位置”就走 YOLO。YOLOv8 是目前比较好上手的框架安装简单训练命令短自带的标注数据和预训练权重生态也比较完整。下面我会按“数据准备 → 标注转换 → 训练 → 推理”把这条路走通。4.2 把标注转成 YOLO 需要的 txt坐标换算脚本YOLO 训练不认文件夹分类它需要每个图片对应一个 txt里面写类别序号 x_center y_center width height四个坐标全部归一化到 0~1。公开数据集里最常见的标注格式是 VOC 的 XML两个格式之间就差一个坐标换算脚本。我这里给一个从 VOC XML 转 YOLO txt 的通用脚本。import xml.etree.ElementTree as ET from pathlib import Path class_names [recyclable, kitchen, hazardous, other] xml_dir Path(voc_annotations) out_dir Path(yolo_labels) out_dir.mkdir(exist_okTrue) for xml_path in xml_dir.glob(*.xml): 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) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} f{box_w:.6f} {box_h:.6f}) out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) if not lines: print(f[warn] {xml_path.name} 没有任何合法标注)这段脚本里最容易算错的是坐标归一化。注意分母必须是图片的真实宽高img_w和img_h不是标注框的宽高。很多人在这一步直接把 xmin、xmax 除以 224 或 640得到的结果全是大于 1 的非法坐标训练时要么随机崩要么 loss 变成 NaN。另一个坑是 XML 里可能引用了图片里根本没出现的类别名脚本里用if name not in class_names: continue直接跳过避免类别索引越界。转换完成后还要把图片和 txt 按 YOLO 要求的目录结构摆好txt 和对应图片的主文件名要完全一致。我见过不少人把标注文件导出了却忘记把图片一起复制到新目录训练时大量图片“缺标签”被静默跳过一个 epoch 跑完 loss 居高不下。4.3 训练与推理yaml 配置、命令与参数YOLOv8 用data.yaml告诉训练脚本数据在哪里、类别叫什么。在trash_dataset根目录下新建一个trash.yamlpath: C:/Users/you/trash_dataset # 改成你的绝对路径 train: images/train val: images/val names: 0: recyclable 1: kitchen 2: hazardous 3: other注意path不建议写相对路径因为这样会和训练命令的当前工作目录耦合换个目录跑就报错。train和val是相对path的子目录目录下直接放图片不需要按类别分子文件夹。然后用一行命令启动训练yolo detect train \ datatrash.yaml \ modelyolov8n.pt \ epochs50 \ imgsz640 \ batch16 \ device0参数说明yolov8n.pt是 nano 版预训练权重显存占用小适合先验证数据是否有问题确认可行后再换yolov8s.pt或yolov8m.pt提升精度。imgsz640是训练输入尺寸不要盲目改大尺寸翻倍显存占用约翻四倍。batch16在 8G 显存上比较稳如果显存溢出优先降到 8而不是换小模型。device0指定第一块 GPUCPU 环境改成devicecpu但训练时间会非常可观。训练结束后模型保存在runs/detect/train/weights/best.pt。用下面的命令对单张图或整个文件夹做推理yolo detect predict modelruns/detect/train/weights/best.pt \ sourcetest_images/ \ save_txtTrue \ conf0.25save_txtTrue会保存检测结果的坐标文件方便你拿去算 mAP 或者接后续程序。conf0.25是置信度阈值落地场景建议提到 0.4 以上因为垃圾分类的误报比漏报更让用户反感——把矿泉水瓶识别成有害垃圾比识别不出来更破坏信任。5. 垃圾分类训练避坑清单五个高频问题与排查方法5.1 数据阶段的翻车坏图片、错标签与不成比例的类别坑一训练到一半报PIL.UnidentifiedImageError。现象是前几个 epoch 正常突然中断报错指向某个图片打不开。原因是有图片文件虽然后缀是.jpg但实际内容损坏或者是一张 0 字节的空文件。解决方法是回看 2.2 节的校验脚本在训练前把所有图片im.load()一遍过滤掉。不要觉得这个概率低公开数据集里图片数量上万时坏图几乎是必现的。坑二验证集准确率很高真实场景一测就废。现象是训练时 val acc 到 0.95拿到园区实拍照片上只有 60%。原因是验证集和训练集来自同一个数据源甚至同一批拍摄环境模型学到的是背景特征而不是垃圾本身瓶子的纹理没记住记住了照片里的白色桌面。解决方法是按拍摄地点或时间段划分验证集而不是随机划分更稳妥的做法是单独预留一部分实际场景的照片训练过程完全不碰最后只用来做最终评估。坑三有害垃圾类别样本太少模型完全学不会。现象是训练 loss 在下降但看每个类别的召回率有害垃圾几乎是 0。原因是数据不平衡模型把所有样本都猜成占多数的类别就能拿到很低的 loss。解决方法是先统计各类别数量再对少数类做增强或复制同时给CrossEntropyLoss传入weight向量让少数类的错误惩罚更重。加了这两步之后再看每个类别的召回率曲线而不是整体准确率。5.2 训练与推理阶段的踩坑显存溢出、误检与文件夹结构陷阱坑四显存溢出 OOM训练一启动就死。现象是跑 YOLO 或 ResNet 时报CUDA out of memory。原因无非两个batch size 太大或者输入尺寸太大。解决方法是先把 batch 降到 8、imgsz降到 640确认能起步再往上加。另外检查是否有其他进程占着 GPU用nvidia-smi看显存占用训练机的 GPU 上挂着桌面窗口或推理服务都会挤占显存。还有一个很容易忽略的点num_workers太高时数据加载进程会复制一部分显存4G 小显存卡把num_workers设成 0 或 2 也能缓解 OOM。坑五推理时把破碎塑料瓶框出来了但品牌文字干扰严重。现象是模型检测结果飘忽不定同一个瓶子的置信度在 0.3 和 0.85 之间跳。原因是训练数据里正面、干净、完整瓶子太多而真实场景是压扁的、反光的、带标签的、半遮挡的。解决方法不是盲目的改成更大的模型而是做针对性增强随机旋转、随机裁剪、模拟遮挡以及把训练集里过“干净”的样本减少一些。检测模型对形状和边缘更敏感适当引入运动模糊和亮度变化往往比换 backbone 更见效。这类问题排查时我的习惯是先把验证集结果画出来看预测框和真实框的偏差集中在哪。偏差都在目标中心附近但漏检多可能是训练数据太少偏差完全随机且置信度极低多半是数据格式没对齐训练根本没看到真实标注。这两个方向找错后面调参都白搭。6. 从训练到落地模型导出、易混淆类处理与验证习惯训练出best.pt只是开始落地部署阶段还有两件事值得提前做导出成 ONNX 格式以及处理易混淆类。yolo export modelruns/detect/train/weights/best.pt formatonnx opset12这行命令可以把模型导出为标准 ONNX之后用 ONNX Runtime 或 TensorRT 都能加载不再依赖 Ultralytics 的 Python 环境。导出的模型在推理机上不需要装 PyTorch依赖少、启动快也更适合嵌进摄像头程序或边缘设备里。如果只用 Python 做演示这一步可以跳过但凡是上真机的项目我都不建议直接拿.pt文件去部署环境差异会把简单的事拖成半天。易混淆类是这个领域最考验细节的地方。厨余垃圾里的大骨头、玉米棒和椰子壳在四分类模型里经常互相误判因为它们全是高含水、纤维多的形态。我的处理习惯不是硬调模型而是在四分类模型之上再加一个“厨余细分类”小模型先判断大类再在厨余类内部细分。这种做法比直接训练一个二十分类的单模型要稳因为每个模型的任务更简单训练数据也能各自优化。另一个折中做法是干脆把易混淆类合并成一个“难分厨余”类宁可让用户听到一个模糊分类也不要给一个高置信度的错误答案。验证习惯上我不太信任单一准确率数字。每次迭代完我会先看每个类别的混淆矩阵再抽查错误样本的实际图片。模型在验证集上 0.95 的准确率并不能告诉我它是不是把一个矿泉水瓶当成了玻璃瓶所以我现在养成了一个习惯每次训练完把预测错误的前 20 张图自动保存到一个文件夹里逐个看是标注问题、遮挡问题还是模型问题。这个文件夹比任何训练曲线都更能说明下一步该怎么调。希望帮到你。写作说明章节数选择 6内容覆盖“数据集 → 代码 → 训练 → 避坑 → 部署”有一定深度。第 2、3、4 章是重心代码块与参数说明结合给出可直接复用的脚本。第 5 章专列高频踩坑现象、原因、解决三要素齐备。第 6 章收在部署细节与验证习惯符合落笔一人称收尾的要求。本文还有配套的精品资源点击获取