简介:这份深度学习图像分类数据集面向从事计算机视觉入门与垃圾分类识别实践的开发者、学生及算法爱好者,围绕塑料瓶、玻璃瓶、金属瓶等可回收物类别构建,可用于训练与评估瓶类垃圾自动分拣模型。资源按目录组织,同类样本归入同一文件夹,并附json类别文件便于直接读取标签。包内共约2000个文件,以1998张jpg图像为主体,另含1个py脚本与1个json类别文件,压缩包约39.65MB,体量轻便,适合快速加载与本地实验。数据已预先划分为训练集约2200张、验证集约200张、测试集约100张,可直接用于模型训练、调参与性能对比,省去自行切分与整理的时间。目前已有268人学习下载,适合希望快速搭建垃圾分类分类基线、验证网络结构或完成课程设计的读者参考使用。
1. 垃圾分类图像分类数据集:从「能跑通」到「能落地」差在哪
垃圾分类、瓶子垃圾图像分类这类题目,很多人第一反应是「不就是个二分类/多分类吗,ResNet 拉起来跑一遍就完事」。真上手才发现,翻车点几乎全在数据上:同一类瓶子在阳台光、厨房灯、闪光灯下颜色完全不一样;透明塑料瓶和玻璃瓶在低对比度背景里几乎融为一体;标注时「可回收」和「其他垃圾」的边界靠人拍脑袋。模型结构再新,喂进去的是脏数据,出来的就是玄学准确率。
这篇笔记锁定的就是「垃圾分类、瓶子垃圾图像分类」这个深度学习数据集方向:它讲的是怎么把一批真实场景下的垃圾/瓶子图片,整理成能训练、能评估、能复现的图像分类数据集,并跑通一条从数据清洗到模型验证的完整链路。适合两类人:一是手里已经有一批手机拍的垃圾图片、想做成可用数据集的从业者;二是想拿垃圾分类当图像分类入门实战、但不想停在玩具数据集上的新手。下面按「数据集怎么建 → 怎么训 → 坑在哪 → 怎么验证」推下去。
2. 垃圾分类图像分类数据集怎么建:采集、清洗与标签体系
2.1 先定标签体系,再谈采集
垃圾分类图像分类最常见的返工,不是模型调不出来,而是标签体系中途改。今天按「可回收/厨余/有害/其他」四分,明天发现瓶子想单独拎出来做细分类,整个目录结构、标注文件、训练脚本全要重来。我的习惯是先把标签体系写成一张表,锁定层级再动手。
| 层级 | 标签示例 | 用途 | 注意点 |
|---|---|---|---|
| 粗分类 | 可回收、厨余、有害、其他 | 四分类基线 | 类别互斥,别留「其他」当垃圾桶 |
| 细分类 | PET瓶、玻璃瓶、易拉罐、纸盒 | 瓶子专项 | 只对可回收子集展开 |
| 属性 | 透明/不透明、干净/脏污 | 难例分析 | 不参与主训练,用于切片评估 |
标签体系定完后,目录就按类别名/图片组织,这是后续所有框架都认的最小结构。这里有个血泪经验:别用中文目录名。很多训练脚本、缓存、可视化工具对中文路径处理不一致,轻则报编码错,重则静默丢样本,你还在那纳闷为什么类别数对不上。
采集阶段要刻意覆盖三类变量:光照(自然光、室内灯、闪光灯)、背景(纯色、杂乱桌面、地面)、拍摄距离(近景特写、中景带环境)。垃圾分类场景里,背景杂乱是最大的干扰源,模型很容易学到「桌布颜色」而不是「瓶子材质」。如果条件允许,同一物体换 3 个背景各拍一张,比同一背景拍 30 张有用得多。
2.2 用脚本做数据清洗与去重
采集回来的原始图,第一件事不是标注,是清洗。手机连拍会产生大量近似帧,直接进训练集会造成数据泄漏——验证集里出现和训练集几乎一样的图,准确率虚高,上线就崩。下面这段脚本做两件事:剔除损坏文件、用感知哈希找近似重复。
import os from PIL import Image import imagehash def clean_and_dedup(root_dir, hash_threshold=5): seen = {} # hash -> 保留的文件路径 removed = [] for cls in os.listdir(root_dir): cls_dir = os.path.join(root_dir, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath = os.path.join(cls_dir, fname) try: img = Image.open(fpath).convert("RGB") except Exception: os.remove(fpath) # 打不开的直接删 removed.append(fpath) continue h = imagehash.phash(img) # 感知哈希,抗轻微缩放/压缩 dup = False for kept_hash, kept_path in seen.items(): if h - kept_hash <= hash_threshold: dup = True os.remove(fpath) # 近似重复,删掉后来的 removed.append(fpath) break if not dup: seen[h] = fpath return removed if __name__ == "__main__": gone = clean_and_dedup("./raw_dataset") print(f"清理 {len(gone)} 张")逻辑说明:phash把图片压成一个 64 位指纹,两张图指纹的汉明距离越小越像。hash_threshold=5是经验值,越小越严格。阈值调到 3 以下会漏掉一些真实重复,调到 8 以上会把不同角度的同一瓶子误删,建议先在小批量上试。注意这段脚本是「保留先出现的、删后出现的」,所以跑之前最好把质量高的图排在前面,或者改成按文件大小/清晰度择优保留。
清洗完还要做一次类别均衡检查。垃圾分类数据集天然不均衡:其他垃圾的图片往往远多于有害垃圾。别急着上重采样,先统计每类数量,差距在 3 倍以内的,靠数据增强就能压住;超过 5 倍的,再考虑对少数类做定向采集或加权采样。
2.3 划分训练/验证/测试集的两个硬约束
划分不是随机切一刀就完事。垃圾分类图像分类有两个硬约束:同一物体的多张图不能跨集,同一背景的图尽量不跨集。否则模型学到的是物体身份或背景,不是类别特征。
实操上,如果采集时记录了「物体ID」或「拍摄批次」,就按 ID 分组划分;没有记录的话,用图片的 EXIF 拍摄时间做近似分组,同一分钟拍的归到同一集。划分比例常用 7:1.5:1.5,小数据集可以 8:1:1。测试集一旦划出就冻结,调参阶段只看验证集,别手贱去翻测试集结果,那是你最后唯一的后悔药。
3. 用迁移学习跑通瓶子垃圾图像分类基线
3.1 为什么垃圾分类场景优先选迁移学习
垃圾分类、瓶子垃圾图像分类的数据集规模,个人和小团队通常在一千到一万张量级。这个量级从零训练 CNN,基本等于让模型背题:训练集准确率能冲到 99%,验证集原地踏步。迁移学习把在 ImageNet 上学到的边缘、纹理、材质特征直接搬过来,你只需要微调最后的分类头,几百张图就能出可用结果。
选型上,最新的图像分类模型层出不穷,但对这个任务,我一般从两个方向起步:要精度、算力够,用 ConvNeXt 或 EfficientNetV2 这类现代骨干;要速度、要部署到边缘设备,用 MobileNetV3 或轻量 ResNet。别一上来就追最花哨的结构,垃圾分类的瓶颈在数据质量,不在骨干网络那零点几个点的差距。
3.2 最小可复现训练脚本
下面用 PyTorch 写一个能直接跑的分类基线,骨干换成任意 torchvision 模型都能用。关键是数据增强要针对垃圾场景设计:颜色抖动模拟不同光照,随机裁剪模拟不同拍摄距离,水平翻转安全(瓶子左右翻转不改变类别)。
import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models # 训练集增强:针对光照和距离变化 train_tf = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), # 模拟远近 transforms.ColorJitter(0.3, 0.3, 0.3, 0.1), # 模拟光照色温 transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) val_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) train_ds = datasets.ImageFolder("./dataset/train", train_tf) val_ds = datasets.ImageFolder("./dataset/val", val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) # 迁移学习:换掉分类头 model = models.efficientnet_v2_s(weights="DEFAULT") num_classes = len(train_ds.classes) model.classifier[1] = nn.Linear(model.classifier[1].in_features, num_classes) model = model.cuda() criterion = nn.CrossEntropyLoss(label_smoothing=0.1) # 缓解过拟合 optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20) for epoch in range(20): model.train() for x, y in train_loader: x, y = x.cuda(), y.cuda() optimizer.zero_grad() loss = criterion(model(x), y) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct = total = 0 with torch.no_grad(): for x, y in val_loader: x, y = x.cuda(), y.cuda() pred = model(x).argmax(1) correct += (pred == y).sum().item() total += y.size(0) print(f"epoch {epoch} val_acc {correct/total:.4f}")逻辑说明:RandomResizedCrop的scale=(0.7,1.0)控制裁剪范围,太小会把瓶子裁没,太大起不到模拟距离的作用。ColorJitter四个参数分别是亮度、对比度、饱和度、色调,垃圾分类里色调别调太大,否则会把绿色瓶子染成蓝色,制造错误标签。label_smoothing=0.1是防过拟合的常用手段,标签越脏越有用。
参数说明:学习率3e-4是微调预训练模型的稳妥起点,从零训练才用 1e-3 量级。weight_decay=1e-4配合 AdamW。T_max=20要和总 epoch 数一致,余弦退火才能完整走完一个周期。num_workers在 Windows 上设 0 更稳,Linux 上设 4 到 8。
3.3 冻结策略与微调层数的取舍
迁移学习不是无脑全量微调。数据量小于两千张时,先冻结骨干只训分类头 5 个 epoch,再解冻最后两个 stage 微调,效果通常比一上来全解冻好。原因是随机初始化的分类头一开始梯度很大,会把预训练好的骨干特征带偏。
判断该解冻多少层,看验证集准确率曲线:如果冻结阶段验证准确率就卡住不动,说明特征不匹配,需要解冻更多层;如果解冻后训练准确率飙升但验证掉,说明过拟合,退回去多冻几层并加大增强。这套「先冻后放」的节奏,比任何自动调参都靠谱。
4. 垃圾分类图像分类的避坑与排查清单
4.1 验证准确率虚高,上线就崩
现象:验证集准确率 95% 以上,实际拍新图测试惨不忍睹。原因:训练集和验证集存在近似重复图,或者同一物体跨集,模型在「背题」。解决:回到 2.2 的去重脚本,把哈希阈值调严到 3,并检查划分是否按物体ID分组。另外确认验证集的增强是否误用了训练增强,验证集只能做 Resize 和 Normalize。
4.2 透明瓶子被大量误判
现象:PET 透明瓶和玻璃瓶互相误判,混淆矩阵里这两类互相污染。原因:透明物体在杂乱背景下边缘特征弱,模型抓不到材质差异。解决:采集时给透明瓶补纯色背景样本,训练时加一点边缘增强(如随机灰度化),评估时单独把透明子集切出来看指标,别被整体准确率掩盖。
4.3 类别不均衡导致少数类全灭
现象:有害垃圾类召回率接近 0,模型把所有样本都预测成多数类。原因:交叉熵在极端不均衡下偏向多数类。解决:先用WeightedRandomSampler给少数类加权,权重设为类别频率的倒数;再配合label_smoothing。如果少数类样本少于 50 张,优先补采,别指望算法变魔术。
4.4 训练损失正常但准确率不动
现象:loss 缓慢下降,准确率卡在随机水平。原因:多半是标签和图片没对上,ImageFolder按目录名排序生成类别索引,如果你中途改过目录名,索引就错位了。解决:打印train_ds.classes和train_ds.class_to_idx,抽几张图连同标签可视化确认。这个黑匣子不打开,调参调到天亮也没用。
4.5 显存溢出与批次大小的取舍
现象:换了大骨干就 OOM。原因:EfficientNetV2 这类模型输入分辨率高、激活值大。解决:优先降 batch size 而不是降分辨率,垃圾分类的材质细节对分辨率敏感;配合梯度累积模拟大 batch。batch_size=16加累积 2 步,等效 32,显存省一半。
5. 进阶:用混淆矩阵和切片评估验证数据集质量
模型训完,别只看一个总准确率就收工。垃圾分类图像分类的真正价值,在于用评估结果反推数据集哪里有问题。混淆矩阵是最直接的体检报告:对角线之外的高值,直接告诉你哪两类在数据层面就分不清。
import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, classification_report all_preds, all_labels = [], [] model.eval() with torch.no_grad(): for x, y in val_loader: x = x.cuda() pred = model(x).argmax(1).cpu().numpy() all_preds.extend(pred) all_labels.extend(y.numpy()) cm = confusion_matrix(all_labels, all_preds) print(classification_report(all_labels, all_preds, target_names=val_ds.classes, digits=3)) # 归一化后画图,看每类的误判去向 cm_norm = cm / cm.sum(axis=1, keepdims=True) plt.imshow(cm_norm, cmap="Blues") plt.xticks(range(len(val_ds.classes)), val_ds.classes, rotation=45) plt.yticks(range(len(val_ds.classes)), val_ds.classes) plt.colorbar() plt.tight_layout() plt.savefig("confusion_matrix.png", dpi=150)逻辑说明:classification_report给出每类的精确率、召回率、F1,比总准确率信息量大得多。归一化混淆矩阵按行归一,每一行是「真实为某类的样本被预测成各类的比例」,能直接看出误判流向。比如玻璃瓶那一行如果大量落到 PET 瓶列,说明这两类在数据上就缺乏区分度,该去补采而不是调模型。
切片评估是更进一步的做法:把验证集按「光照条件」「背景类型」「是否透明」打上属性标签,分组算准确率。整体 90% 但「暗光+杂乱背景」切片只有 60%,那这个数据集在真实场景的短板就暴露了。这一步做完,你才知道下一批该采什么图,而不是盲目堆量。
我自己的习惯是:每训完一版模型,先看混淆矩阵,再看切片指标,最后才决定是补数据还是改模型。十次里有七次,问题出在数据而不是网络。垃圾分类这个方向尤其如此,瓶子垃圾图像分类的难点从来不是模型不够新,而是你的数据集有没有覆盖真实世界的脏乱差。把评估当数据集的质检工具用,比把它当模型排行榜用,回报高得多。希望帮到你。
本文还有配套的精品资源,点击获取