ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

真实生活废弃物图像分类数据集:从训练到部署全流程解析

真实生活废弃物图像分类数据集:从训练到部署全流程解析 简介面向垃圾分类与图像分类任务的生活废弃物数据集共约4,800张已标注的真实场景照片覆盖纸板、食品有机物、玻璃、金属、杂项垃圾、纸张、塑料、纺织品垃圾与植被等9个类别。图片来自实际环境呈现不同程度的光照和背景变化已统一预处理并划分训练集、测试集可直接作为分类网络输入也适合用于图像分类、分割等模型的对比实验与改进。资源包约156MB共2000个文件以1998张jpg图片为主体另附1个py格式的show可视化脚本和1个json类别说明文件可快速查看样本及类别对应关系。目前已有67人学习下载适合机器学习初学者快速搭建垃圾分类基线也适合研究者在真实噪声环境下验证算法鲁棒性尤其适合需要带标注真实废弃物数据做迁移学习或模型微调的场景。1. 真实生活废弃物图像分类数据集到底解决什么问题在智能垃圾厢房、无人回收柜、小区分类督导这类项目里“认垃圾”往往用图像分类就能顶住但真正能让识别模型在生产环境站住脚的不是纸面准确率而是训练数据是不是真实生活照片。「生活中真实废弃物图像分类数据集【已标注约4,800张数据】」就是把这一步补上来的资源约4800张真实拍摄、已标好的图片规模刚好卡在“能动手训练”和“不至于没法维护”之间。我见过不少团队拿公开数据集调出来的模型演示时很漂亮一到垃圾桶前面就翻车原因几乎都是背景太干净、物体太完整。这类数据集的价值不在“量大”而在“真实”。真实生活废弃物意味着光线忽明忽暗、垃圾袋反光、瓶罐相互遮挡、纸箱被压扁到变形这些才是投放点摄像头的常态。适合的人群也很明确正在做垃圾分类识别原型验证的算法工程师、给回收设备做视觉方案的集成商、以及想拿一份带标注数据快速走通“数据—训练—部署”全流程的入门者。接下来我会按我自己的落地习惯把这4800张图从拆解、训练到部署完整讲一遍。2. 先看懂这份真实生活废弃物数据集真实场景为什么比公开数据集难2.1 “生活中”三个字意味着什么光线、遮挡和变形的叠加先别急着把数据扔进训练脚本先想清楚“真实生活”这四个字给数据带来了什么。公开数据集里的物品照片大多数是单一主体、居中、光照均匀、背景干净的而真实垃圾场景里瓶子和易拉罐是混杂在塑料袋、落叶、外卖盒之间的物体边缘互相遮挡甚至主体本身就不完整。瓶身标签被磨损、易拉罐被踩扁、纸箱沾满污渍这些形态变化比类别本身更难学。另一个被低估的因素是光线。室内的垃圾分类投放点通常有顶灯但傍晚的室外回收柜可能只有一侧照明导致物体一半过曝一半在阴影里。再加上手机或监控摄像头的自动白平衡会随场景漂移同一只塑料瓶在不同时段拍出来颜色特征可以差很远。这个数据集里的图像如果拍摄自多种时间段和场景那它天然就比单一光照条件下采集的数据更接近推理环境。所以我的建议是拿到数据后第一件事不是找模型而是先做数据层面的“摸底”。你最好确认这些图像是否包含夜间/弱光样本是否有多物体堆叠样本是否有接近物体特写的样本。这三类样本的比例直接决定训练时该把增强策略往哪个方向调。比如夜间样本多就要在训练时加重亮度抖动和对比度扰动如果特写样本多裁剪尺寸就不宜设得太小。2.2 标签体系怎么理解单标签、多标签和类别口径已标注数据集的标签格式五花八门常见的有三种按类别目录组织图片、CSV/JSON文件记录图片名到标签的映射、或者用PASCAL VOC/COCO格式的XML/JSON标注文件。这个数据集是图像分类任务大概率是前两种。拿到手先确认一件事每张图是只有一个标签单标签分类还是同一张图里多个物体各带一个标签多标签分类。这两种任务的目标函数完全不同前者用Softmax交叉熵后者要用Sigmoid Binary Cross Entropy。类别口径也是容易出问题的地方。同样是“纸”有的标注方分为“纸箱”和“纸张”有的统一标为“纸类”同样是饮料瓶有的按材质分“塑料瓶”“易拉罐”有的按回收价值分为“可回收物”“其他垃圾”。如果你的落地场景是回收积分柜那按材质分类更有用如果是社区垃圾分类督导按“可回收/厨余/有害/其他”四分类更贴需求。先搞清楚这份约4800张数据的类别是怎么划分的再决定是直接用还是要做类别合并。我通常会拿到数据后先做一次“标签口径核对”尤其关注那些容易混淆的类别塑料瓶和玻璃瓶、纸箱和纸杯、金属罐和铝箔。如果标注人员在两个类别之间的定义不一致模型的决策边界也会被带歪。遇到这种情况宁可把易混淆类别合并成一个“容器类”或“包装类”也别强求模型去分它分不清的细类。2.3 用脚本复核标注类别分布和文件完整性别盲信不管数据来源写得多规范我建议先用一段脚本把标注文件完整过一遍。4800张图不大全量扫一遍也就几秒钟但能省掉后面训练时的很多莫名其妙的坑。下面这段代码按最常见的CSV标注格式来检查统计每类数量、检查图片文件是否存在、找出重复标注的图片名。import csv import os from collections import Counter csv_path labels.csv # 标注文件常见格式filename,label image_root images # 图片所在目录 with open(csv_path, r, encodingutf-8) as f: rows list(csv.DictReader(f)) # 1. 类别分布 label_counter Counter(r[label] for r in rows) print(类别数量:, len(label_counter)) for label, cnt in label_counter.most_common(): print(f {label}: {cnt}) # 2. 文件缺失检查 missing [r[filename] for r in rows if not os.path.exists(os.path.join(image_root, r[filename]))] print(缺失图片数:, len(missing)) # 3. 检查同名图是否被重复标注 dup [fn for fn, c in Counter(r[filename] for r in rows).items() if c 1] print(重复标注图片数:, len(dup)) # 4. 检查图片尺寸是否有异常过小或零字节 import PIL.Image bad_size [] for fn in os.listdir(image_root): p os.path.join(image_root, fn) if os.path.getsize(p) 1024: # 小于1KB大概率是损坏或空文件 bad_size.append(fn) print(疑似损坏图片数:, len(bad_size))这段代码的逻辑很简单但每一步都有明确目的。类别分布能告诉你哪些类是少数派后面要不要做类别加权文件缺失检查能避免训练中途报错重复标注检查针对的是某些数据集在生成时分了train/test两份图片名重复但标签可能不一致如果不清理就会被污染尺寸检查是为了防止极小或损坏的图片在归一化时产生异常值。参数方面重点看两个 1024这个阈值针对JPG/PNG垃圾图像足够敏感正常照片即使缩小到224x224也远大于1KB但如果你的数据里有黑白图标类可以放宽到512字节encodingutf-8是必须的标注文件如果是GBK保存读取会直接抛错遇到中文标签尤其常见可以先手动打开文件确认编码。2.4 按拍摄场景拆分数据集随机拆分会让你后面哭很多人拿到数据后习惯train_test_split(..., stratifyy)直接随机切分但真实生活图像数据集最忌讳的就是完全随机划分。垃圾图像通常是成组采集的同一个投放点、同一条流水线、同一次拍摄批次里的图片背景、光照、相机参数高度一致。如果同一批拍摄的照片一部分进了训练集一部分进了验证集那验证集指标会虚高模型实际上是在“认场景”而不是“认垃圾”。我一般按“场景ID”或“拍摄批次”分组先组后拆。假设文件名里带场景前缀比如scene01_frame0123.jpg我需要保证同一个前缀的所有图片只出现在训练集或验证集之一。这里给出一个按组拆分的示例import os import random from collections import defaultdict # 假设文件名规则{scene_id}_{frame_id}.jpg all_files [f for f in os.listdir(image_root) if f.lower().endswith((.jpg, .jpeg, .png))] groups defaultdict(list) for fn in all_files: scene_id fn.split(_)[0] # 按第一个下划线前的部分分组 groups[scene_id].append(fn) group_ids list(groups.keys()) random.shuffle(group_ids) train_ratio, val_ratio 0.7, 0.1 # 剩下0.2给测试集 n_train int(len(group_ids) * train_ratio) n_val int(len(group_ids) * val_ratio) train_groups set(group_ids[:n_train]) val_groups set(group_ids[n_train:n_train n_val]) test_groups set(group_ids[n_train n_val:]) train_files [f for g in train_groups for f in groups[g]] val_files [f for g in val_groups for f in groups[g]] test_files [f for g in test_groups for f in groups[g]] print(ftrain{len(train_files)}, val{len(val_files)}, test{len(test_files)})这里核心是train_groups/val_groups/test_groups这三个集合它们存的是“场景组”而不是单张图片这样能保证同一个场景的照片全部落在同一份数据里。如果你手上的文件名没有清晰的分组前缀就退而求其次按拍摄时间戳的日期分组或者按文件夹名分组。哪怕只有一个模糊的批次信息也比完全随机好得多。分组时的比例也值得说70%训练、10%验证、20%测试对4800张图来说验证集只有480张如果类别数超过20类那平均每类验证样本不到24张指标波动会很大。这时候可以适当减小测试集比例把验证集提到15%20%。3. 用这份数据集训练图像分类模型从目录到可用模型3.1 把标注格式转成 PyTorch 能直接吃的目录结构当标注是CSV/JSON格式时直接喂给训练代码会比较麻烦好在4800张图规模不大用文件复制或硬链接转成ImageFolder目录结构是最稳的方案。torchvision.datasets.ImageFolder期望的目录结构是root/class_name/xxx.jpg类名就是文件夹名。转换脚本如下import csv import pathlib import shutil source_csv labels.csv # filename,label image_dir pathlib.Path(images) target_dir pathlib.Path(data_sorted) # 输出根目录 # 也可以用 os.link 做硬链接省空间跨盘符复制时只能 shutil.copy2 with open(source_csv, encodingutf-8) as f: for row in csv.DictReader(f): label row[label].strip() src image_dir / row[filename] dst target_dir / label / row[filename] dst.parent.mkdir(parentsTrue, exist_okTrue) if not dst.exists(): shutil.copy2(src, dst) # 保留原始文件元信息 print(转换完成输出到:, target_dir)这段代码用shutil.copy2而不是shutil.copy是为了保留文件的时间戳和权限信息后面如果做增量更新可以用修改时间判断哪些文件变了。如果你图省空间且源目录和目标目录在同一个文件系统上可以把shutil.copy2(src, dst)换成os.link(src, dst)硬链接不占额外空间但要注意硬链接不能跨分区也不能对Windows系统上的某些网络盘使用。转换完目录之后再用前面第2章的统计脚本对data_sorted下的文件夹做一遍验证确认每个类别的图片数量和标注文件一致。这一步虽然重复但能拦截掉“CSV写的是A类文件实际在B类文件夹里”这类人工复制错误。3.2 模型选型4800张图该用多大的模型对于4800张图的图像分类任务从头训练一个ResNet-50或EfficientNet是浪费这类模型在ImageNet-1K上已经学到过大量纹理、边缘和形状特征垃圾图像里的塑料纹理、纸箱折痕、金属反光和ImageNet里的物体是有共性的。迁移学习在这类小数据集上是绝对的主流加载预训练权重替换最后的全连接分类头然后整体微调或只微调后半段。具体选哪个骨干取决于你的部署环境。如果最终跑在边缘盒子或摄像头上建议MobileNetV3-Large或EfficientNet-B0推理速度快模型小如果跑在服务器或带GPU的工控机上ResNet-50或EfficientNet-B2能在精度和速度之间取得更好的平衡。最新的图像分类模型这几年迭代很快但要注意像ViT这类Transformer架构在小数据集上微调未必比卷积网络更好而且需要更多训练技巧不建议在4800张图这种体量上冒险。我个人的选择逻辑很简单先拿EfficientNet-B0跑通流程如果验证集准确率卡在可接受线下方再换ResNet-50或EfficientNet-B2。模型不是越大越好垃圾图像分类的难点在数据形态多样性不在模型容量很多项目在MobileNetV3上就够用了。3.3 训练脚本增强、优化器和学习率参数一次调到位下面是一份可以直接跑的PyTorch训练脚本核心部分。我用的是torchvision.models.efficientnet_b0加载ImageNet预训练权重替换分类头为垃圾数据集的类别数。训练参数基于“约4800张图 迁移学习”这个前提来设定。import torch import torch.nn as nn import torchvision.models as models import torchvision.transforms as T import torchvision.datasets as datasets from torch.utils.data import DataLoader device cuda if torch.cuda.is_available() else cpu batch_size 32 # 显存不够就降到16 epochs 30 lr 1e-3 # AdamW的初始学习率不宜太大 weight_decay 1e-4 label_smoothing 0.1 # 垃圾类别边界模糊平滑系数可以略高 # 训练增强真实垃圾图像最关键的就是裁剪和颜色抖动 transform_train T.Compose([ T.RandomResizedCrop(224, scale(0.6, 1.0)), # 模拟堆叠遮挡和远近变化 T.RandomHorizontalFlip(p0.5), T.ColorJitter(brightness0.4, contrast0.3, saturation0.2), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) transform_val T.Compose([ T.Resize(256), T.CenterCrop(224), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_set datasets.ImageFolder(data_sorted/train, transformtransform_train) val_set datasets.ImageFolder(data_sorted/val, transformtransform_val) train_loader DataLoader(train_set, batch_sizebatch_size, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_set, batch_sizebatch_size, shuffleFalse, num_workers4, pin_memoryTrue) # 用预训练权重替换分类头 model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1) in_features model.classifier[1].in_features model.classifier[1] nn.Linear(in_features, len(train_set.classes)) criterion nn.CrossEntropyLoss(label_smoothinglabel_smoothing) optimizer torch.optim.AdamW(model.parameters(), lrlr, weight_decayweight_decay) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) model model.to(device) best_acc 0.0 for epoch in range(epochs): model.train() total_loss, correct, total 0.0, 0, 0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) preds model(imgs) loss criterion(preds, labels) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) # 防止个别噪声样本梯度爆掉 optimizer.step() total_loss loss.item() * imgs.size(0) correct (preds.argmax(dim1) labels).sum().item() total imgs.size(0) print(fEpoch {epoch1}/{epochs} train_loss{total_loss/total:.3f} train_acc{correct/total:.3f}) # 简单验证 model.eval() val_correct, val_total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) preds model(imgs) val_correct (preds.argmax(dim1) labels).sum().item() val_total imgs.size(0) val_acc val_correct / val_total print(f val_acc{val_acc:.3f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_garbage_model.pt) scheduler.step()这里有几个参数值得单独说。RandomResizedCrop的scale(0.6, 1.0)是我根据真实垃圾图特点调的垃圾图像里经常出现“主体只占画面一角”的情况所以尺度下限我放在0.6比ImageNet常用的0.08要保守得多避免把太多背景区当主体。ColorJitter的亮度抖动0.4和对比度0.3是为了应对投放点一天之内的光照变化但饱和度只给0.2防止塑料瓶的鲜艳颜色被过度扰动。label_smoothing0.1对这个任务尤其有用。垃圾类别之间本来就是连续过渡的一个沾了奶茶的纸杯算“纸类”还是“厨余垃圾”标注人员也头疼。标签平滑会让模型预测不那么“自信”相当于告诉它类别边界不必过分尖锐这对真实数据集的泛化有实际帮助。clip_grad_norm我只设置了5.0正常训练几乎不会触发但它能在少数标注错误样本制造超大梯度时保住整个训练不崩。3.4 验证和测试别只盯着Top-1准确率上一步的代码已经会保存验证集准确率最高的模型但训练完成不等于交付。我习惯再做两件事一是对测试集重新计算准确率二是逐类别看混淆矩阵。4800张图的数据集里类别数如果超过10类整体准确率很容易被样本量大的几个类主导。比如“塑料瓶”占了40%样本模型把所有图都判成塑料瓶整体准确率也能有40%实际毫无使用价值。建议把测试集预测结果保存下来用sklearn.metrics.confusion_matrix生成混淆矩阵重点看对角线之外哪些单元格有大数字。如果“易拉罐”经常被错判为“金属碎片”那不是标签问题就是这两类的视觉特征确实相似需要考虑合并类别。这一步做完你才算真正知道这个模型哪里能用、哪里不能用。4. 垃圾分类图像实战避坑这4800张图里藏着哪些雷4.1 坑一标注边界模糊一张图同时出现多个垃圾现象训练集损失下降正常但验证集准确率在某个类别上死活上不去比如“纸盒”和“外卖袋”互相误判。翻看预测错误的图片时发现很多“错误”其实标注也没错因为画面里同时有纸盒和外卖袋标注规则只标了“最主要”的那个模型从中学习了不同的东西。原因真实垃圾图像很少是单物体场景。一张俯拍照片里常常同时存在三四种垃圾标注人员只能选一个“主角”打标签。同一张图如果在人工复核时被不同人标注标签倾向就完全随机了。解决两个方向。第一是修改训练目标把单标签改成多标签分类即允许一张图同时打多个类别标签Loss函数从CrossEntropy切成BCEWithLogitsLoss。缺点是标注成本变高因为原来一条记录要变成多条。第二是保持单标签但在数据清洗时删掉那些“多物体且目标物占比过小”的图片或者用目标检测先裁剪出主体再送分类器。后者其实是真实项目里更常见的做法先用检测框定位垃圾再对框内图像分类。4.2 坑二训练集和验证集同源指标虚高到不真实现象训练时验证集准确率一路飙到0.95模型看起来已经能毕业但把这套权重接到实际摄像头画面上一测准确率掉到0.7以下。所有人在训练阶段都以为模型很能打。原因就是第2章说的分组拆分没做。同一段连续拍摄的视频里第10帧和第110帧几乎一模一样如果帧被随机切到训练集和验证集两边模型在训练时其实已经“见过”验证集图片验证指标失去意义。解决严格按场景组或拍摄批次拆分确保同一场景下的所有帧只出现在训练集、验证集、测试集的某一侧。如果原始数据没有场景ID至少按下发文件的时间戳划分比如说用前3天拍摄的做训练第4天做验证第5天做测试。跨时间段验证才能真实反映模型的场景适应能力。4.3 坑三类别严重不平衡少数类直接学成“摆设”现象整体准确率0.88看起来很体面但检查单个类别的召回率发现占比不足1%的“有害垃圾”比如电池、药品召回率几乎是0。模型把所有图都倾向分到样本多的“塑料瓶”“纸盒”等类。原因真实垃圾类别天然长尾分布日常生活中塑料瓶、纸盒就是比废电池、过期药品多得多。如果训练时不做干预Softmax交叉熵会由多数类主导少数类特征根本没机会被充分学习。解决我先用sklearn.utils.class_weight.compute_class_weight算出每个类别的权重传给CrossEntropyLoss(weight...)同时用WeightedRandomSampler对DataLoader采样让每轮迭代里少数类图片出现的概率明显提高。但注意采样权重不要拉得太极端有些类只有十几张图采样次数再多也只能反复看到那几张很容易过拟合。这类过少的类别建议合并到上级类别或者干脆针对它们单独扩充数据。4.4 坑四背景泄漏模型在“抄答案”而不是认垃圾现象训练准确率正常但部署后发现模型对“背景是厨房台面”的图片几乎全部判为“厨余垃圾”对“白色背景”的图片几乎全部判为“可回收物”。有人开玩笑说是模型学会了认桌布而不是认垃圾。原因数据采集时某些类别的样本集中来自特定环境。比如“厨余垃圾”的照片大多在厨房垃圾桶旁拍的“可回收物”的照片大多在小区回收点拍的背景差异和类别标签形成了虚假关联。图像分类模型学习的其实是“特征环境的联合分布”当环境特征足够强时它会忽略物体本身。解决根本办法是增加背景多样性也就是在模型训练时做“环境增强”。我的常用做法是在RandomResizedCrop之后叠加RandomErasing随机抹掉一小块区域强迫模型不能只依赖单一背景线索更彻底的办法是把分类模型接到目标检测框后面只对检测框内的物体做分类框外的背景完全丢掉。这个数据集的标签如果只有分类标签没法直接训练检测器但可以从分类模型的错误案例里挑出“背景泄漏”典型图用它们作为补充数据的采集方向去别的场景补拍。4.5 坑五不同来源数据亮度和色温差异大模型被颜色分布骗了现象测试集整体准确率还行但按来源分组看来自设备A的照片准确率0.92设备B只有0.71。模型对设备B的图“发虚”错误集中在深色和反光明显的物体上。原因设备A和B的摄像头白平衡策略、动态范围都不一样导致同一种垃圾的颜色直方图分布不同。模型在训练时大量见到设备A的色彩风格对B的色彩风格缺乏泛化能力。这类问题在真实生活图像里很常见因为垃圾投放点摄像头品牌杂、安装角度杂。解决最直接的办法是把不同来源的图片做全局颜色归一化。常见做法是训练时对每张图额外做一次随机亮度/对比度扰动扩大颜色分布的覆盖范围或者在预处理阶段用灰度图/边缘图的副本来训练一个辅助分支。从部署角度看建议推理前也做一次AutoContrast之类的简单校正。这个坑很难彻底消掉但可以通过在测试集里按来源分组评估至少知道哪些相机需要单独适配而不是假装模型已经很稳。5. 让垃圾分类模型真正用起来增量更新和ONNX导出同一份约4800张的数据集训练出90%准确率的模型只是起点。真实投放点的垃圾形态每天都在变比如换季时的落叶、节假日的礼盒、突发的快递包装这些新样本不在原始标注里模型不会自己认识。所以最后一个技巧是建立“数据回流”的闭环把推理时置信度在0.4到0.8之间的低置信度样本存下来定期人工复核后回填到训练集。这种样本比主动补拍更真实因为它就是模型最吃力、最容易翻车的那部分数据。回填时注意控制旧类和新类的比例我一般控制在3:1以内避免新数据把模型学偏。然后是部署。边缘摄像头设备通常用ONNX Runtime或TensorRT第一步要把PyTorch模型转成ONNX。这里有一个关键细节导出的输入尺寸必须和训练时的224×224完全一致动一发则牵全身。以下是一个稳定的导出流程import torch model.load_state_dict(torch.load(best_garbage_model.pt, map_locationcpu)) model.eval() dummy_input torch.randn(1, 3, 224, 224) # 固定batch1避免动态shape踩坑 torch.onnx.export( model, dummy_input, garbage_cls.onnx, input_names[input], output_names[logits], opset_version13, dynamic_axesNone, # 固定输入尺寸边缘端最稳 ) print(导出完成模型大小约, __import__(os).path.getsize(garbage_cls.onnx) // 1024, KB)参数opset_version13是比较保守的选择能同时兼容ONNX Runtime 1.10以上的版本dynamic_axesNone表示固定batch1如果必须支持动态尺寸至少要把height和width固定下来。导出后用onnxruntime跑一遍同样的输入数据对比PyTorch输出的Top-1类别是否一致差异往往出现在BatchNorm的求值模式和输入尺寸不一致这两个地方。这套“4800张图起步—迁移学习—增量回流—ONNX落地”的路径我在两个垃圾分类项目上完整走通过最大的教训不是模型选型而是数据处理阶段不能偷懒——分组要按场景拆、标注要逐条核、类别口径要提前定义好。数据准备做得细后面训练和部署会顺很多数据准备得糙模型再“最新”也救不回来。希望帮到你。本文还有配套的精品资源点击获取
返回列表