真实废弃物图像分类:4800张标注数据实战与避坑指南

📅 发布时间:2026/9/28 2:14:11
真实废弃物图像分类:4800张标注数据实战与避坑指南
简介这份生活中真实废弃物图像分类数据集面向计算机视觉初学者与图像分类、分割方向的算法实践者用于解决垃圾分类场景下真实样本获取难、标注成本高的问题。数据已完成预处理可直接作为分类网络输入覆盖纸板、食品有机物、玻璃、金属、杂项垃圾、纸张、塑料、纺织品垃圾和植被共9个类别并已划分训练集与测试集同类图片集中存放便于直接构建训练流程。资源包共2000个文件以1998张jpg图像为主体另含1个py可视化脚本与1个json类别说明文件压缩包约155.99MB运行show脚本即可快速浏览样本分布与图像质量。目前已有65人学习下载适合用于分类模型训练、数据增强实验及分割网络改进的对比验证也可作为课程设计或毕业项目的现成数据基础。1. 真实废弃物图像分类4800 张已标注数据能跑出什么结果你手头如果有一批生活垃圾、可回收物、厨余、有害垃圾的现场照片想做一个能落地的图像分类模型第一道坎往往不是模型选型而是数据。网上公开的图像分类数据集要么是 CIFAR-10 这种 32×32 的玩具级样本要么是 ImageNet 那种一千类、和废弃物场景完全不搭的通用数据。真实场景里的废弃物图像有背景杂乱、光照不均、遮挡严重、类间差异小比如塑料袋和保鲜膜这些特点拿通用数据集训出来的模型直接迁移准确率经常掉到 60% 以下。这个标题讲的是一份约 4,800 张、已完成标注的真实废弃物图像分类数据集。它的价值不在于数量大而在于「真实」和「已标注」这两点——图像来自实际拍摄而非合成标注已经做完省去了最耗时的环节。适合谁用做环保类视觉产品的工程师、想验证图像分类算法在细粒度场景下表现的研究者、以及需要快速搭一个垃圾分类 demo 的开发者。4,800 张的规模不算大但足够跑通从数据清洗到模型部署的完整链路也足够暴露真实数据里那些公开数据集不会教你的坑。下面按「数据怎么用 → 模型怎么选 → 训练怎么调 → 坑在哪」的顺序拆开讲。2. 废弃物数据集的标注格式与预处理从原始标注到可训练张量2.1 分类标注和检测标注的区别以及这份数据大概率是哪种废弃物图像数据集的标注方式直接决定你能跑什么任务。分类标注是「一张图一个标签」输出是类别索引检测标注是「一张图多个框加类别」输出是边界框坐标加类别。标题说的是「图像分类数据集」所以标注格式大概率是每张图对应一个类别标签常见组织方式有两种按文件夹分目录train/塑料瓶/xxx.jpg或者一个 CSV 记录filename,label。但真实废弃物图像有个麻烦一张图里可能同时出现塑料瓶和纸盒。如果标注时只给了一个主类别模型学到的就是「这张图里最主要的物体」遇到多物体场景会犹豫。我一般会先抽查 50 到 100 张图确认标注粒度和图像内容是否一致。如果发现大量多物体图只标了一类要么接受这个噪声要么用检测标注重做——但重做成本很高4,800 张不是小数目。常见做法是先按文件夹结构组织再用脚本转成统一的 CSV 或 DataFrame方便后续切分和增强。下面这段代码把文件夹结构的数据集转成带标签的清单并做基本的完整性检查。import os import pandas as pd from PIL import Image # 数据集根目录假设结构为 root/类别名/图片文件 root waste_dataset records [] corrupt [] for label in sorted(os.listdir(root)): class_dir os.path.join(root, label) if not os.path.isdir(class_dir): continue for fname in os.listdir(class_dir): fpath os.path.join(class_dir, fname) try: # 只读文件头验证图片是否可解码避免坏图混入 with Image.open(fpath) as im: im.verify() records.append({path: fpath, label: label}) except Exception as e: corrupt.append((fpath, str(e))) df pd.DataFrame(records) print(有效样本:, len(df)) print(损坏样本:, len(corrupt)) print(类别分布:\n, df[label].value_counts()) df.to_csv(waste_labels.csv, indexFalse)这段代码的逻辑是遍历每个类别文件夹用 PIL 的verify()做轻量校验——它只检查文件头不加载全部像素速度快适合几千张图的批量筛查。corrupt列表记录打不开的文件这些图如果直接进训练会在 DataLoader 里抛异常提前清掉能省很多调试时间。value_counts()输出类别分布这一步很关键如果某个类别只有几十张而其他类别上千张后面训练必然偏斜需要提前决定是过采样、欠采样还是加类别权重。参数上root换成你自己的路径即可。如果数据集是 CSV 格式而非文件夹跳过遍历部分直接读 CSV 后做同样的verify校验。注意verify()之后如果要再次打开图片做尺寸统计需要重新Image.open因为verify会关闭文件对象。2.2 图像尺寸、通道和增强策略的确定真实拍摄的废弃物图像尺寸五花八门手机拍的可能是 4032×3024网络图可能是 800×600。分类模型通常要求固定输入尺寸常见选择是 224×224ResNet 系列标准或 299×299Inception 系列。直接缩放到 224 会丢失小物体细节比如一个远处的烟头但废弃物分类主要靠整体纹理和颜色224 一般够用。增强策略要针对废弃物场景设计。水平翻转、随机裁剪是安全的颜色抖动要谨慎因为颜色是分类的重要线索比如绿色厨余 vs 透明塑料抖动幅度过大会破坏这个信号。我一般用轻度颜色抖动亮度 ±0.2、对比度 ±0.2不做色相偏移。旋转方面废弃物图像没有固定朝向±15 度的小角度旋转可以加90 度旋转要慎重——倒置的瓶子在现实中少见加了反而引入噪声。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize(256), # 先缩到短边 256 transforms.RandomResizedCrop(224), # 随机裁剪到 224带尺度扰动 transforms.RandomHorizontalFlip(), # 水平翻转废弃物无方向性 transforms.ColorJitter(brightness0.2, contrast0.2), # 轻度颜色扰动 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) # ImageNet 统计量 ]) 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]) ])RandomResizedCrop(224)的默认尺度范围是 0.08 到 1.0意味着可能裁出只含物体局部的图这对细粒度分类有帮助。Normalize用的 ImageNet 统计量如果你从零训练可以用自己数据算的均值和方差但用预训练权重时必须和预训练时一致。验证集只用CenterCrop保证评估可复现。注意如果类别分布严重不均RandomResizedCrop可能让小类别更吃亏因为裁剪后物体占比变化大。这种情况可以把尺度下限调到 0.3减少极端裁剪。3. 图像分类模型选型从 ResNet 到 Transformer 在废弃物场景的取舍3.1 预训练骨干网络的选择依据4,800 张图属于小样本范畴从零训练 CNN 几乎不可能收敛到可用精度。标准做法是用 ImageNet 预训练权重做迁移学习。骨干网络的选择要看三个维度参数量、推理速度、对细粒度纹理的敏感度。ResNet-50 是稳妥的基线25M 参数224 输入下单张推理在主流 GPU 上约 5msImageNet 预训练权重随处可得。EfficientNet-B0 参数量只有 5.3M精度接近 ResNet-50适合部署到边缘设备。如果追求更高精度且不在乎推理成本ConvNeXt-Tiny 或 Swin-Tiny 这类 Transformer 架构在细粒度分类上通常比 CNN 高 2 到 4 个百分点但需要更多数据增强来防止过拟合。废弃物分类的难点在于类间相似度高——「塑料瓶」和「玻璃瓶」在缩略图里几乎一样靠的是材质反光和透明度这些细节。CNN 的局部感受野对这种纹理敏感Transformer 的全局注意力能捕捉物体和背景的关系。我的经验是如果数据量低于 5,000 张ResNet-50 加强增强往往比 Swin 更稳数据量上万后 Transformer 的优势才明显。骨干网络参数量224 推理延迟适合场景ResNet-5025M~5ms通用基线部署方便EfficientNet-B05.3M~3ms边缘设备精度略低ConvNeXt-Tiny28M~7ms追求精度GPU 充足Swin-Tiny28M~9ms细粒度需强增强3.2 用 timm 加载预训练模型并替换分类头timm库统一了各种骨干网络的加载接口改分类头只要一行。下面以 ResNet-50 为例展示从加载到替换分类头的完整流程。import timm import torch.nn as nn num_classes 6 # 根据你的数据集实际类别数修改 # 加载 ImageNet 预训练权重不包含原始分类头 model timm.create_model(resnet50, pretrainedTrue, num_classes0) # 查看特征维度ResNet-50 输出 2048 feat_dim model.num_features print(特征维度:, feat_dim) # 替换为适配废弃物类别的分类头 model.fc nn.Sequential( nn.Dropout(0.3), # 小数据集加 dropout 防过拟合 nn.Linear(feat_dim, num_classes) ) # 只训练分类头时冻结骨干 for name, param in model.named_parameters(): if fc not in name: param.requires_grad False total sum(p.numel() for p in model.parameters()) trainable sum(p.numel() for p in model.parameters() if p.requires_grad) print(f总参数: {total/1e6:.1f}M, 可训练: {trainable/1e6:.2f}M)num_classes0让 timm 不创建分类头只返回骨干特征提取器。model.num_features给出骨干输出维度ResNet-50 是 2048。替换的fc里加了Dropout(0.3)因为 4,800 张图对 2048 维特征来说样本偏少不加 dropout 训练集准确率能到 99% 但验证集卡在 70% 左右。冻结骨干后只训练分类头可训练参数从 25M 降到约 12K2048×6 6。这个阶段学习率可以设大一点1e-3 起步。训练 5 到 10 个 epoch 后如果验证集准确率不再上升解冻最后两个 stage 做微调学习率降到 1e-4。这种「先冻后解」的策略比一上来就全量微调更稳血泪经验是直接全量微调在小数据集上很容易把预训练权重带偏。提示timm.create_model的pretrainedTrue会从网络下载权重首次运行需要联网。如果环境离线提前把权重文件放到~/.cache/torch/hub/checkpoints/下。4. 训练配置与调参4,800 张图上的 batch size、学习率和早停4.1 数据切分与类别不平衡处理4,800 张图按 7:1.5:1.5 切成训练、验证、测试训练集约 3,360 张。切分时必须按类别分层采样否则某个类别可能全被分到测试集。用sklearn的train_test_split加stratify参数即可。类别不平衡在废弃物数据里几乎必然出现——塑料瓶和纸箱可能各上千张有害垃圾电池、药品可能只有一两百张。处理方式有三种过采样小类别、给损失函数加类别权重、或者用 focal loss。我一般先用类别权重因为它不改动数据分布实现简单。import numpy as np from sklearn.model_selection import train_test_split from torch.utils.data import WeightedRandomSampler df pd.read_csv(waste_labels.csv) train_df, temp_df train_test_split( df, test_size0.3, stratifydf[label], random_state42 ) val_df, test_df train_test_split( temp_df, test_size0.5, stratifytemp_df[label], random_state42 ) # 计算类别权重频率越低权重越高 class_counts train_df[label].value_counts().sort_index() weights 1.0 / class_counts.values class_weights weights / weights.sum() * len(class_counts) print(类别权重:, dict(zip(class_counts.index, class_weights.round(3)))) # 或者用 WeightedRandomSampler 做重采样 sample_weights train_df[label].map( lambda x: 1.0 / class_counts[x] ).values sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue )stratify保证切分后各类别比例一致。class_weights传给CrossEntropyLoss的weight参数让模型对少数类错误惩罚更大。WeightedRandomSampler是另一种思路每个 epoch 按权重采样小类别被抽到的概率更高。两者选一个即可同时用会导致双重补偿反而让少数类过拟合。4.2 学习率调度和早停的实操参数迁移学习的标准配置是分类头阶段用Adam或SGD学习率 1e-3余弦退火到 1e-5微调阶段换SGD学习率 1e-4加ReduceLROnPlateau在验证损失不降时减半。早停耐心值设 7 到 10 个 epoch因为废弃物数据噪声大验证准确率波动 2 到 3 个百分点很正常耐心太小会过早停止。import torch from torch.optim import Adam, lr_scheduler device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion torch.nn.CrossEntropyLoss( weighttorch.tensor(class_weights, dtypetorch.float32).to(device) ) optimizer Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3) scheduler lr_scheduler.CosineAnnealingLR(optimizer, T_max20, eta_min1e-5) best_acc 0.0 patience 8 wait 0 for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) pred model(imgs).argmax(dim1) correct (pred labels).sum().item() total labels.size(0) acc correct / total print(fEpoch {epoch}: val_acc{acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_waste_model.pth) wait 0 else: wait 1 if wait patience: print(f早停于 epoch {epoch}最佳准确率 {best_acc:.4f}) breakCosineAnnealingLR的T_max设成总 epoch 数学习率按余弦曲线从 1e-3 降到 1e-5。eta_min是下限防止学习率降到零。早停逻辑里wait计数在验证准确率不升时累加超过patience就停。保存的是验证集上最好的权重不是最后一个 epoch 的——这点很重要最后一个 epoch 往往已经过拟合。注意class_weights要转成 float32 张量再传给损失函数直接传 numpy 数组在部分 PyTorch 版本会报类型错误。如果用了WeightedRandomSampler损失函数里的weight参数应该去掉避免双重加权。5. 废弃物分类落地避坑从标注噪声到部署精度掉的排查5.1 标注噪声导致验证准确率虚高现象训练集准确率 98%验证集 85%但测试集只有 72%。原因标注时把一些模糊图片草率归类训练集和验证集共享了这些噪声模型记住了噪声模式测试集换了新噪声就崩。解决用cleanlab或简单的人工复查找出模型高置信度预测和标注不一致的样本重新核对标签。4,800 张里通常有 3% 到 5% 的错标清理后测试集准确率能回升 5 到 8 个百分点。5.2 图像重复导致数据泄漏现象验证集准确率异常高接近 100%但实际部署时效果差。原因同一张图或高度相似的连拍图同时出现在训练集和验证集。废弃物数据采集时容易连拍或者从视频抽帧相邻帧几乎一样。解决切分前用感知哈希pHash去重汉明距离小于 5 的视为重复只保留一张。这个步骤在 4,800 张规模下花不了十分钟但能避免评估结果完全失真。5.3 颜色抖动过强破坏材质线索现象模型把透明玻璃瓶和透明塑料瓶混淆两类准确率都低于 60%。原因增强时用了ColorJitter(hue0.1)色相偏移让透明物体的反光颜色改变模型学不到材质差异。解决去掉色相抖动只保留亮度和对比度的小幅扰动。如果材质区分是核心难点考虑加一个灰度分支或边缘检测分支让模型同时看颜色和纹理。5.4 输入尺寸和部署端不一致现象训练时验证准确率 88%导出 ONNX 部署后掉到 75%。原因训练用RandomResizedCrop加CenterCrop部署时直接Resize到 224×224长宽比变了物体形变。解决部署预处理必须和验证集一致——先Resize(256)保持长宽比再CenterCrop(224)。这个细节在 PyTorch 训练脚本里容易写对转到 C 或移动端时经常被简化掉。5.5 类别权重过大导致多数类欠拟合现象加了类别权重后小类别召回率上去了但整体准确率降了 10 个百分点。原因权重按频率倒数计算如果多数类和小类样本比是 10:1权重比也是 10:1损失函数被小类主导多数类学不好。解决权重开平方或设上限比如weights 1 / np.sqrt(class_counts)把权重比压到 3:1 以内。或者改用 focal loss让模型自己调节难易样本的权重。6. 用混淆矩阵和置信度分布验证模型是否真的可用训练完拿到 88% 的验证准确率不代表模型能上线。废弃物分类的评估要看三个东西混淆矩阵、各类别 F1、以及预测置信度分布。混淆矩阵能告诉你哪两类在互相混淆——如果「塑料瓶」和「玻璃瓶」的交叉项很大说明模型没学到材质特征加多少数据都难提升得换思路。各类别 F1 比整体准确率更能反映小类表现有害垃圾 F1 低于 0.6 的话实际使用中漏检风险很高。置信度分布是容易被忽略的一环。一个好的模型正确预测的置信度应该集中在 0.8 以上错误预测的置信度分散在 0.3 到 0.6。如果错误预测的置信度也高达 0.9说明模型过自信这时候需要温度缩放temperature scaling做校准。下面这段代码画出混淆矩阵和置信度直方图。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix, f1_score import numpy as np model.eval() all_preds, all_labels, all_probs [], [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.to(device) logits model(imgs) probs torch.softmax(logits, dim1) preds logits.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) all_probs.extend(probs.max(dim1).values.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) f1 f1_score(all_labels, all_preds, averageNone) print(各类 F1:, dict(zip(class_names, f1.round(3)))) # 混淆矩阵热力图 plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(预测) plt.ylabel(真实) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150) # 置信度分布正确 vs 错误 all_probs np.array(all_probs) all_preds np.array(all_preds) all_labels np.array(all_labels) correct_mask all_preds all_labels plt.figure(figsize(8, 4)) plt.hist(all_probs[correct_mask], bins30, alpha0.6, label正确) plt.hist(all_probs[~correct_mask], bins30, alpha0.6, label错误) plt.xlabel(预测置信度) plt.ylabel(样本数) plt.legend() plt.tight_layout() plt.savefig(confidence_dist.png, dpi150)confusion_matrix的行是真实标签列是预测标签对角线是正确预测。看非对角线的大值定位混淆对。f1_score(averageNone)返回每个类别的 F1比准确率更能暴露小类问题。置信度直方图里如果错误样本的置信度分布和正确样本重叠严重说明模型无法区分「确定」和「不确定」部署时需要加一个置信度阈值低于阈值的转人工复核。温度缩放校准很简单在验证集上优化一个温度参数 T让softmax(logits / T)的负对数似然最小。T 大于 1 会软化概率分布降低过自信。代码不超过十行但对实际部署的可靠性提升明显。我自己的习惯是每次训完模型先不看准确率先看混淆矩阵里有没有「反常识」的混淆对——比如「纸箱」和「玻璃瓶」混在一起那一定是标注或数据出了问题不是模型能力不够。这个习惯帮我省了很多次盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取