简介:生活中真实废弃物图像分类数据集是一份面向计算机视觉入门学习与垃圾分类应用开发的标注数据资源,包含约4800张真实场景图片,涵盖纸板、食品有机物、玻璃、金属、杂项垃圾、纸张、塑料、纺织品垃圾和植被等9个常见类别,适合用于训练图像分类网络或验证模型在不同垃圾材质上的泛化能力。数据已经过预处理,并划分好训练集与测试集,类别目录清晰;图片来自真实生活场景,背景与光照较为自然,能更真实地反映实际垃圾分类任务中的输入分布。压缩包内含2000个文件,其中1998张JPG图像可直接送入分类网络训练或测试,1个Python脚本用于快速可视化数据集并检查标注质量,1个JSON文件记录类别配置,整体大小约156MB。已有65人学习下载,上手门槛较低,研究者和开发者可基于此快速开展垃圾分类相关实验,省去数据采集与标注环节,也可作为算法效果对比的基准数据。
1. 这个标题真正解决的问题:真实现场照片和“摆拍数据集”之间的落差
做垃圾分类识别或废弃物检测的朋友,应该都经历过这种翻车:网上公开的垃圾数据集照片拍得整整齐齐,瓶子规规矩矩立在纯色背景上,模型训练完准确率能上 95%,一放到小区回收箱或者保洁阿姨手里那台旧手机上,直接开始乱报。这背后的差距不在模型,而在数据本身。生活中真实废弃物图像分类数据集【已标注,约4,800张数据】这样的项目,要解决的正是“真实拍摄条件下废弃物图像分类”的落地难题:视角混乱、背景复杂、光照不稳定、瓶罐互相遮挡。约 4,800 张的规模不大不小,适合做迁移学习微调,也适合验证从标注到训练再到部署的完整小闭环。这篇文章会把这类数据集从拆解、清洗到训练和踩坑讲透,给准备动手的读者一条能直接走通的路。
2. 拆开数据集看门道:类目口径、标注格式与验证集策略
2.1 先把“类目表”和标注口径对清楚
拿到任何一份“已标注”的废弃物图像数据,我做的第一件事不是急着训练,而是先看类别表。如果是自己采集的,常见做法是按回收价值和使用频率分 6 到 12 类:塑料瓶、易拉罐、纸箱纸板、玻璃瓶、织物衣物、厨余垃圾、塑料袋膜、有害垃圾(电池、灯管)等。几十个细分类别对 4,800 张来说太散了,分分钟出现某个类只有二三十张的情况,后面训练会很难受。
我看到真实废弃物数据时,最关心的是“标注口径”。同样是“纸”,是只框纸箱,还是包括报纸、纸杯这类带塑料覆膜的纸制品?同样是“瓶子”,装着半瓶水的塑料瓶怎么算?“已标注”这三个字只说明有人动过标签,不说明标签定义和你业务一致。我一般会先打印一张类目表,对着实际图片抽查每个类至少 30 张,确认边角料和歧义样本的标注方式。这一步省不掉,后面所有指标都建立在这个口径之上。
如果类目定义不清,尽早做合并。比如我有一次把“玻璃瓶”和“碎玻璃”分成两类,结果碎玻璃样本只有十几张,训练出来完全不可用,后来干脆统一成“玻璃制品”,指标立刻稳定。对小型数据集来说,类目数量控制在 8 类以内比追求细粒度分类可靠得多。
2.2 4,800 张的常见组织方式与验证集保留策略
这种量级的数据集,文件组织方式大多是按类别分文件夹的 ImageFolder 结构,极少数会带 CSV 或 JSON 标注文件。如果是按文件夹组织,训练代码写起来最简单,PyTorch 的torchvision.datasets.ImageFolder直接就能读。组织方式如下,这是约定俗成的结构,拿到数据后建议先整理成这个样子:
waste_dataset/ ├── train/ │ ├── plastic_bottle/ │ ├── aluminum_can/ │ ├── paper/ │ ├── glass/ │ └── fabric/ └── val/ ├── plastic_bottle/ ├── aluminum_can/ ├── paper/ ├── glass/ └── fabric/划分比例上,我常用 8:1:1,也就是约 3,840 张训练、480 张验证、480 张测试。这里有个关键教训:如果原始数据是从视频里按帧抽出来的,同一个瓶子在连续几帧里会出现很多次,直接随机切分会让训练集和验证集里出现“同一物体”,验证指标会虚高。正确做法是先按拍摄场景或视频片段分组,同一个场景的帧必须全部划到同一个集合里,不能跨集合。
我还会顺手做一次哈希去重。很多公开的真实场景数据收集时会重复采样,通过 dhash 或 md5 去重能砍掉不少冗余图片。实测 4,800 张里去重后可能只剩 4,500 张左右有效数据,这种损耗是正常的。验证集数量不足时,宁可从训练集里再挤一点过去,也不要让验证集低于每类 30 张。
2.3 拿到压缩包后我干的第一件事:跑一遍资产体检
“已标注”不代表没毛病。真实废弃物照片里,模糊、过曝、文件损坏、完全空白的垃圾图都很常见。我用一段脚本做资产体检,检查三件事:每类数量分布、图像尺寸分布、损坏文件数量。
import os from PIL import Image from collections import Counter train_dir = "path/to/waste_dataset/train" stats = Counter() bad_files = [] sizes = [] for class_name in os.listdir(train_dir): class_path = os.path.join(train_dir, class_name) if not os.path.isdir(class_path): continue stats[class_name] = len(os.listdir(class_path)) for fname in os.listdir(class_path): fpath = os.path.join(class_path, fname) try: with Image.open(fpath) as im: sizes.append(im.size) except Exception: bad_files.append(fpath) print("每类数量:", dict(stats)) print("损坏文件数量:", len(bad_files)) if sizes: min_side = min(min(w, h) for w, h in sizes) print("最小边小于224像素的图片数量:", sum(1 for w, h in sizes if min(w, h) < 224))这段脚本的逻辑是按类别目录遍历所有图片,统计每类数量并尝试用 PIL 打开图片。打不开的就是损坏文件,应直接从数据集中剔除或替换。尺寸统计则用于确认是否有大量过小的图片,如果很多图的最小边都小于 224 像素,训练时 Resize 到 224 会产生严重形变,需要放弃这些图或提前做超分预处理,不能直接丢进模型。
我做这个体检时还会顺手输出 20 张每类的随机样例拼成一张网格图,肉眼快速过一遍。这种“先体检后训练”的习惯,能省下后面排错的时间,比直接跑 baseline 靠谱得多。
3. 用 PyTorch 在真实废弃物数据上跑通分类流程:从数据增强到微调
3.1 数据增强:针对真实场景的关键参数设置
真实废弃物照片和公开数据集最大的区别在于拍摄条件不可控。垃圾桶里的瓶子可能是歪的,隔着塑料袋拍的纸箱是模糊的,夜间的易拉罐颜色偏掉。所以数据增强不能只用 Resize 和归一化,要针对场景做三个针对性配置:随机缩放裁剪模拟拍摄距离变化、旋转模拟角度变化、颜色抖动模拟光照差异。
import torch from torchvision import datasets, transforms, models from torch.utils.data import DataLoader train_tf = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.5, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=20), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_tf = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_set = datasets.ImageFolder("path/to/waste_dataset/train", train_tf) val_set = datasets.ImageFolder("path/to/waste_dataset/val", val_tf) train_loader = DataLoader(train_set, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_set, batch_size=32, shuffle=False, num_workers=4)这里的scale=(0.5, 1.0)很关键。公开分类数据集常用的 scale 下限是 0.08,但对废弃物场景来说,裁剪比例太小会让模型学到“某个局部纹理”而不是“物体整体”,所以我把下限收紧到 0.5,模拟的是相机靠近和远离物体的距离变化范围。RandomRotation是模拟废弃物随手丢在地上的任意角度。ColorJitter的亮度扰动调到 0.2 是为了覆盖室内外光照差异,但饱和度扰动只给 0.1,避免把塑料瓶和易拉罐的颜色信息破坏掉。
3.2 迁移学习选型:为什么从 ResNet 起步
4,800 张训练数据对深度学习模型来说只能算“小数据”。从头训练一个 ResNet 或者 Vision Transformer 都会严重过拟合,正确路线是加载 ImageNet 预训练权重,只替换最后的全连接分类层。基础选型上,我建议直接选 ResNet18 或 ResNet34,而不是一上来就上大模型。原因很简单:废弃物分类的判别特征大多是颜色、纹理、轮廓,ResNet18 已经足够;模型太大在后期部署到手机或边缘设备时反而被嫌弃。
import torch.nn as nn device = torch.device("cuda" if torch.cuda.is_available() else "cpu") num_classes = len(train_set.classes) model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) model.fc = nn.Linear(model.fc.in_features, num_classes) model.to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=1e-3, momentum=0.9, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30)替换model.fc这一行是迁移学习的核心操作。in_features从预训练模型里自动取出倒数第二层的维度,num_classes由数据集自动推断。全连接分类层是随机初始化的,前面的卷积层保留的是 ImageNet 上学到的通用纹理和形状特征,它们对废弃物识别同样有价值。优化器我习惯先用 SGD 加动量而不是 Adam,因为在小数据集迁移学习场景下,SGD 配合余弦退火的泛化能力通常更好。weight_decay=1e-4起正则作用,初始化学习率 1e-3 是一个比较稳的起点。
3.3 训练循环与评估指标:为什么准确率不够用
训练循环本身不复杂,但有两个细节值得特别注意。第一是每轮训练后必须在验证集上算“宏平均 F1”,而不是只看整体准确率。真实废弃物数据普遍存在类别不平衡,塑料瓶可能占了三分之一样本,如果只看准确率,模型只要把所有图都预测成塑料瓶就能拿 60% 以上的准确率,看起来“还行”,实际毫无用处。第二是学习率调度,我用CosineAnnealingLR而不是 StepLR,让学习率在训练后期平滑下降,对提升小数据集上的收敛稳定性有帮助。
import numpy as np from sklearn.metrics import f1_score, confusion_matrix for epoch in range(30): model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss += loss.item() model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for images, labels in val_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) f1 = f1_score(all_labels, all_preds, average="macro") acc = np.mean(np.array(all_preds) == np.array(all_labels)) print(f"Epoch {epoch+1}/30 | Loss: {running_loss/len(train_loader):.4f} | Acc: {acc:.4f} | Macro F1: {f1:.4f}")这段代码在每轮 epoch 结束后把验证集所有预测收集起来,同时计算整体准确率和宏平均 F1。宏平均 F1 对每个类一视同仁,少数类的表现会直接拖低分数,所以它是判断这类不平衡数据分类效果比准确率更靠谱的指标。我一般还会在训练结束后打印一份混淆矩阵,专门看哪些类互相打架,这个信息后面的避坑章节会详细展开。
4. 真实废弃物数据的五个典型坑位:从标注噪声到类别不平衡的排错笔记
4.1 训练集是“干净背景”,应用时是垃圾桶,模型直接翻车
现象:训练时验证集准确率 94%,把模型部署到真实场景后,面对同一种塑料瓶,预测结果在几个类别之间反复横跳。原因:训练集里相当一部分图片是回收站或桌子上的单物体照片,背景是纯色墙面或桌面,模型学到了“背景纹理”而不是“瓶子形状”。解决:在训练增强里加入RandomErasing随机擦除,模拟物体被遮挡的情况,同时刻意增加套着垃圾袋、混有其他杂物这类“脏场景”图片。框架自带实现,加在ColorJitter之后即可:
from torchvision.transforms import RandomErasing transforms.RandomErasing(p=0.25, scale=(0.02, 0.15), ratio=(0.3, 3.3))参数p=0.25表示四分之一概率对图片做擦除,scale控制擦除面积占整张图的比例。真实废弃物场景中物体经常被部分遮挡,特别是瓶子被塑料袋半盖住的情况很常见。加上擦除增强后,模型被迫关注剩余可见区域,比靠完整轮廓识别更接近真实场景。
4.2 某类样本太少,模型“直接放弃治疗”
现象:训练结束,发现“织物”这一类在混淆矩阵里几乎全被预测成“塑料膜”。原因:两类别在纹理上接近,而织物样本只有总量的 2%,模型发现把它全判成塑料膜能把整体损失降得更低,就主动放弃了少数类。解决:用WeightedRandomSampler替代默认随机采样,让每个 batch 里各类别出现概率均衡。
from torch.utils.data.sampler import WeightedRandomSampler class_counts = [train_set.targets.count(i) for i in range(num_classes)] weights = [1.0 / class_counts[t] for t in train_set.targets] sampler = WeightedRandomSampler(weights, num_samples=len(train_set), replacement=True) train_loader = DataLoader(train_set, batch_size=32, sampler=sampler, num_workers=4)这里的weights数组长度等于训练集总样本数,每个样本的权重是它所属类别的样本数的倒数。样本量越少的类,单个样本被抽中的概率越大。同时要顺手做数据增强翻倍,对样本少的类用更激进的增强方式生成变体,相当于变相扩充数据量。用加权采样之后,宏平均 F1 会有明显提升,但要注意训练 epoch 数也要相应增加,否则少数类还没学透就结束了。
4.3 “已标注”数据里的噪声,比想象中严重
现象:从数据集里随机抽了 50 张图人工复核,发现标签错误或“名不副实”的情况有 6 张,比如易拉罐被标注成塑料瓶。原因:真实废弃物图里经常有多个物体重叠,标注时只看到最显眼的一个,另一个被漏掉或者标错。解决:先用训练好的模型跑一遍全部数据,生成置信度分数,把置信度低于 0.5 的样本列出来,交给人工复核。这一步建议用 CVAT 或 labelimg 这类标注工具打开,配合便捷操作逐张确认,比直接改文件名要高效得多。
这类数据清洗工作量不大,却直接影响模型上限。我有一次做完噪声过滤后,单纯依靠修正数据,没改任何模型结构,验证集 F1 涨了约 4 个百分点。真实废弃物数据集的标注质量参差不齐,“已标注”只能当作起点,不能当作免检证明。
4.4 玻璃瓶和透明塑料瓶,模型根本“看不见”材质
现象:玻璃瓶被大量预测为透明塑料瓶,且这两类的混淆程度在训练过程中始终无法改善。原因:这两类物体在颜色、透明度、形状上高度相似,常规 RGB 图像里单靠像素信息很难分辨材质。解决:先从数据侧拉差距,把瓶盖、标签这些局部区域作为辅助信息;透明塑料瓶通常带贴纸压印花纹,玻璃瓶多为光滑表面,增强时对这两类不做强烈模糊处理。如果业务场景固定,常见做法是补充近红外或多光谱数据来区分玻璃和塑料,但如果只有 4,800 张普通可见光图片,建议直接合并成“透明瓶”一个类,在工程上更实惠。
4.5 数据量只有 4,800 张,深模型容易“死记硬背”
现象:训练集准确率一路冲到 99%,验证集准确率却停滞在 88% 附近,且两者差距随训练轮数持续拉大。原因:模型容量太大,训练数据不足时直接背诵了训练集。解决:三个手段同时上,第一是提前停止训练,只保留验证集最优的 checkpoint;第二是加大weight_decay到 1e-3;第三是检查增强策略,确认验证集上的精度没有因为增强过猛而失真。这类问题的典型特征是训练集和验证集的差距超过 8 个百分点,看到就说明过拟合已经发生,需要立刻调整而不是继续延长训练。
5. 让模型在真实场景真正站稳:Grad-CAM 可视化与部署前自检
5.1 用 Grad-CAM 检查模型到底看到了什么
指标好看只说明统计上不错,部署前我习惯用 Grad-CAM 可视化抽查几十张图,看模型分类时到底盯着哪里。如果一张易拉罐图片的高响应区域跑到旁边的背景纸箱上,说明模型大概率在偷懒,靠背景线索做判断。
import torch from torchvision import models, transforms from PIL import Image model.eval() img = Image.open("sample_can.jpg").convert("RGB") t = val_tf(img).unsqueeze(0).to(device) # 获取最后一层卷积特征和预测输出 features = [] def hook_fn(module, input, output): features.append(output) handle = model.layer4[-1].register_forward_hook(hook_fn) out = model(t) handle.remove() _, pred = torch.max(out, 1) feature_map = features[0][0] grad = torch.autograd.grad(out[0, pred], feature_map)[0] weights = grad.mean(dim=(1, 2), keepdim=True) cam = torch.relu((weights * feature_map).sum(dim=0)).cpu().data.numpy()这段代码把最后一层卷积的梯度均值作为通道权重,加权求和得到响应热力图。热力图高亮位置应该集中在废弃物主体上,而不是背景或桌面。我跑这个过程时,通常会发现有一部分图的响应点在瓶盖、标签这类文本区域,这其实是好信号,说明模型在利用有判别力的细节特征。如果响应点散落到背景,则需要回到数据侧继续补充“更脏”的样本,或者加强遮挡类增强。
5.2 用模型筛选得到更多困难样本,形成迭代闭环
4,800 张只是起点。实际业务中的数据每天都在新增,常见做法是用当前最优模型跑一遍新采集图片,按置信度倒序排列,只挑置信度最低的 20% 交给标注工具处理。标注完成后增量微调模型,下一轮再加入新样本。这个闭环跑上个两三轮,模型的真实场景表现会比单纯堆数据快得多,也节省大量标注成本。
我在部署前还会做一次“真实冒烟测试”:拿一部旧手机或普通摄像头,在实际投放点位拍摄 10 分钟视频,每隔 5 帧抽一张图,当作独立的最终测试集。这套测试集绝不参与训练,只用来裁决模型能不能上线。最终测试集通过之后,再考虑用 TensorRT、ONNX 或量化手段压缩模型、调整推理速度,这时候选调batch_size、num_workers才有意义,不然纯属本末倒置。
我现在的习惯是,任何数据集项目都先跑通一版完整 pipeline,再回头精细化调参。更新的数据、更复杂的模型结构都要在同一个评估基准上对比,避免指标虚高。这套方法在真实废弃物图像分类这一类小规模数据集上,跟过不少项目都管用,希望帮到你。
本文还有配套的精品资源,点击获取