四类垃圾3000张标注数据集实战:从数据清洗到模型训练全流程

发布时间:2026/10/25 1:44:06
四类垃圾3000张标注数据集实战:从数据清洗到模型训练全流程

简介:这份数据集面向参加生活垃圾智能分类竞赛的选手及计算机视觉学习者,提供已完成标注的四类垃圾图像样本,可直接用于目标检测模型的训练与验证。压缩包共约2000个文件,以1998个XML标注文件为主,另含少量txt划分列表,整体约411.59MB,XML对应每张图像的类别与边界框信息,txt则给出训练集与验证集的划分清单,便于快速接入YOLO系列流程。数据覆盖有害垃圾(电池、过期药品)、可回收垃圾(易拉罐、小号矿泉水瓶)、厨余垃圾(小土豆、白萝卜、胡萝卜)及其他垃圾(瓷片、鹅卵石、砖块)等赛题指定类别,尺寸与形态贴近实际投放场景。已有1626人学习下载,适合需要现成标注数据开展分类识别实验、复现赛题方案或扩充训练样本的读者参考使用。

1. 四类垃圾 3000 张已标注数据集:拿到手先想清楚它能干什么

上周有个做嵌入式视觉的朋友找我,说他接了个社区智能垃圾桶的活,硬件板子都打样了,卡在数据上——自己拍了两周照片,标了不到 400 张就崩溃了。我让他先别急着继续标,把手上这份四类垃圾约 3000 张的已标注数据集跑一遍基线,看看模型到底能不能收敛、类别间到底混不混。结果两小时出第一版 mAP,问题立刻暴露:可回收物和厨余的混淆率远超预期,根本不是数据量不够,是标注粒度和拍摄场景的问题。这就是一份现成标注数据集最直接的价值:它让你在投入标注人力之前,先把「模型能不能学、学到什么程度、卡在哪」这件事验证掉。

这份资源是四类生活垃圾的图像数据集,总量 3000 张左右,已完成标注,格式上通常对应目标检测或图像分类两种用法。它适合三类人:一是做课程设计或毕业设计、需要快速跑通完整训练流程的学生;二是做边缘端垃圾分类 Demo、想先验证算法可行性再决定是否自采数据的工程师;三是想拿一个类别边界清晰、规模适中的多分类任务练手数据清洗和增强策略的从业者。3000 张这个量级不算大,但四类均分下来每类七百多张,做迁移学习够用,从零训练不够——这个判断会贯穿后面所有章节。

2. 先验数据再动手:3000 张的分布、标注格式与清洗口径

拿到任何标注数据集,我的习惯是先不写一行训练代码,而是把数据「看一遍」。3000 张听起来不多,但真出问题时,八成不是模型结构,而是某几类样本严重偏斜、某批图片分辨率异常、或者标注框大面积越界。这一章就是把验收动作做扎实。

2.1 目录结构与标注格式的快速盘点

常见做法是数据集按类别分文件夹(分类任务),或者图片与标注文件分离(检测任务,标注多为 XML、JSON 或 TXT)。先跑一段脚本把结构、数量、格式摸清楚,比肉眼翻文件夹靠谱得多。

import os from collections import Counter from pathlib import Path root = Path("./garbage_dataset") # 换成你的实际路径 # 统计每个子目录下的图片数量,快速看类别分布 img_ext = {".jpg", ".jpeg", ".png", ".bmp"} class_counter = Counter() for sub in root.iterdir(): if sub.is_dir(): n = sum(1 for f in sub.rglob("*") if f.suffix.lower() in img_ext) class_counter[sub.name] = n total = sum(class_counter.values()) print(f"总图片数: {total}") for name, n in class_counter.most_common(): print(f"{name}: {n} 张, 占比 {n/total:.1%}") # 检查是否存在标注文件与图片数量不匹配的情况 for sub in root.iterdir(): if not sub.is_dir(): continue imgs = {f.stem for f in sub.rglob("*") if f.suffix.lower() in img_ext} labels = {f.stem for f in sub.rglob("*") if f.suffix.lower() in {".xml", ".json", ".txt"}} if labels: missing = imgs - labels if missing: print(f"[警告] {sub.name} 有 {len(missing)} 张图缺标注,示例: {list(missing)[:3]}")

这段脚本干三件事:统计总量、按类别算占比、检查图片与标注文件是否一一对应。参数上唯一要改的是root路径和img_ext集合——如果你的数据集是 PNG 为主,记得把.png留在集合里。输出里如果某一类占比低于 15%,后面训练就得考虑类别加权;如果出现「缺标注」警告,先别训练,把缺的补齐或剔除,否则检测任务里这些图会被当成纯背景,直接污染负样本。

2.2 分辨率、损坏文件与重复样本的排查

3000 张来自不同渠道的图,分辨率不统一是常态,偶尔混进几张打不开的损坏文件也不稀奇。更隐蔽的是重复样本——同一张图换个文件名出现两次,训练集和验证集各一份,验证指标会虚高,这是血泪经验里最常见的一种「假高分」。

from PIL import Image import hashlib bad_files, sizes, hashes = [], Counter(), {} for f in root.rglob("*"): if f.suffix.lower() not in img_ext: continue try: with Image.open(f) as im: sizes[im.size] += 1 im.verify() # 校验文件完整性 except Exception as e: bad_files.append((str(f), str(e))) continue # 用文件内容哈希找完全重复的图 h = hashlib.md5(f.read_bytes()).hexdigest() hashes.setdefault(h, []).append(str(f)) print("损坏文件:", bad_files[:5], "共", len(bad_files)) print("分辨率分布 Top5:", sizes.most_common(5)) dups = {h: v for h, v in hashes.items() if len(v) > 1} print("重复组数:", len(dups)) for h, v in list(dups.items())[:3]: print("重复:", v)

im.verify()只校验文件头,速度快但不够彻底,真要严格可以再im.load()一次。哈希去重用的是文件字节级 MD5,能抓完全相同的副本,抓不到「裁剪过、压缩过」的近重复——近重复得靠感知哈希,3000 张的规模用不上,先做字节级就够。分辨率分布如果出现大量小于 200×200 的图,检测任务里小目标会很难学,建议单独拎出来看是否要剔除或放大。

2.3 划分训练/验证集时的分层抽样

四类数据如果直接random.shuffle再切 8:2,小类别可能被切得更少。正确做法是按类别分层抽样,保证每类在验证集里的比例和全集一致。

import random from sklearn.model_selection import train_test_split samples = [] # [(路径, 类别), ...] for sub in root.iterdir(): if sub.is_dir(): for f in sub.rglob("*"): if f.suffix.lower() in img_ext: samples.append((str(f), sub.name)) paths = [s[0] for s in samples] labels = [s[1] for s in samples] train_p, val_p, train_y, val_y = train_test_split( paths, labels, test_size=0.2, stratify=labels, random_state=42 ) print("训练集:", Counter(train_y)) print("验证集:", Counter(val_y))

stratify=labels是关键参数,它保证切分后每类比例一致;random_state=42固定随机种子,方便复现。切完打印两边的类别计数,如果验证集里某类只剩个位数,说明该类总量本来就少,得回头考虑数据增强或调整test_size。

3. 从标注到可训练张量:分类与检测两条流水线怎么搭

数据验收干净了,接下来是把它喂进模型。这份数据集最常见的两种用法是图像分类(整图归一类)和目标检测(框出每个垃圾实例)。两条流水线的预处理逻辑差别不小,选错方向后面全是返工。

3.1 分类流水线:Dataset 与增强策略

分类任务最简单,按文件夹读图、统一尺寸、归一化即可。增强策略上,垃圾分类场景有个特点:颜色和纹理是强特征(比如塑料瓶的透明感、果皮的褐色),所以颜色抖动要克制,别把关键线索抖没了。

import torch from torch.utils.data import Dataset, DataLoader from torchvision import transforms from PIL import Image class GarbageCls(Dataset): def __init__(self, items, transform=None): self.items = items # [(路径, 类别名), ...] self.transform = transform self.class_to_idx = {c: i for i, c in enumerate(sorted({x[1] for x in items}))} def __len__(self): return len(self.items) def __getitem__(self, idx): path, cls = self.items[idx] img = Image.open(path).convert("RGB") if self.transform: img = self.transform(img) return img, self.class_to_idx[cls] train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), 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]), ])

ColorJitter里saturation=0.1是刻意压低的,因为垃圾分类里颜色区分度很高,抖太狠等于人为制造混淆。Normalize用的是 ImageNet 统计量,配合预训练权重时必须这么设,否则特征分布对不上。class_to_idx用sorted保证类别索引稳定,避免不同机器上跑出不同的标签映射。

3.2 检测流水线:标注解析与坐标归一化

如果标注是 XML(VOC 格式)或 JSON(COCO 格式),检测任务要先把框解析出来,再转成模型要的归一化中心点格式。这一步最容易翻车的是坐标越界和宽高为 0。

import xml.etree.ElementTree as ET def parse_voc(xml_path, img_w, img_h): tree = ET.parse(xml_path) boxes = [] for obj in tree.findall("object"): name = obj.find("name").text.strip() bnd = obj.find("bndbox") x1 = float(bnd.find("xmin").text) y1 = float(bnd.find("ymin").text) x2 = float(bnd.find("xmax").text) y2 = float(bnd.find("ymax").text) # 裁剪到图像边界内,防止越界框 x1, x2 = max(0, x1), min(img_w, x2) y1, y2 = max(0, y1), min(img_h, y2) if x2 - x1 < 2 or y2 - y1 < 2: # 过滤极小框 continue cx = (x1 + x2) / 2 / img_w cy = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h boxes.append((name, cx, cy, w, h)) return boxes

max(0, x1)和min(img_w, x2)这两行是后悔药,标注工具里手抖拖出边界的框很常见,不裁剪的话归一化后会出现负坐标或大于 1 的值,训练时 loss 直接炸。x2 - x1 < 2过滤掉宽高不足 2 像素的框,这类框在缩放后基本消失,留着只会干扰回归。解析完记得统计每类框的数量,如果某类框数远少于图片数,说明该类很多图是「无目标」的,检测任务里要当负样本处理。

3.3 类别不平衡的加权与采样取舍

四类均分是理想情况,实际常有偏差。两种处理方式:损失函数加权,或者重采样。加权改动小、不丢数据,我一般优先用它。

from collections import Counter import torch counts = Counter(train_y) total = sum(counts.values()) # 类别权重与频次成反比,频次越低权重越高 weights = {c: total / (len(counts) * n) for c, n in counts.items()} class_weights = torch.tensor([weights[c] for c in sorted(weights)], dtype=torch.float32) criterion = torch.nn.CrossEntropyLoss(weight=class_weights)

total / (len(counts) * n)是标准的逆频次加权,len(counts)是类别数。权重算出来如果某类超过 3,说明该类样本实在太少,光靠加权可能过拟合,得配合过采样或额外采集。重采样(WeightedRandomSampler)适合类别差距极大的场景,但会改变每个 epoch 的样本分布,调试时不容易判断是模型问题还是采样问题,新手建议先用加权。

4. 训练与评估:3000 张规模下的参数设定和指标解读

数据流水线搭好,训练本身反而不复杂。3000 张的规模决定了几个关键选择:必须用预训练权重、epoch 不用太多、学习率要小。这一章把参数怎么定、指标怎么看讲透。

4.1 迁移学习下的学习率与 epoch 设定

从零训练 3000 张四分类,大概率欠拟合或过拟合二选一。用 ImageNet 预训练的 ResNet18 或 MobileNetV3,冻结主干先训分类头,再解冻微调,是这类规模的标准打法。

import torchvision.models as models import torch.nn as nn import torch.optim as optim model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) for p in model.parameters(): p.requires_grad = False # 先冻结主干 model.fc = nn.Linear(model.fc.in_features, 4) # 四类 optimizer = optim.Adam(model.fc.parameters(), lr=1e-3) # 只训分类头,学习率可大些 # 第二阶段解冻后,整体学习率降到 1e-4 量级

第一阶段只训fc层,lr=1e-3没问题,因为参数量小。跑 5 个 epoch 左右验证集准确率就会趋稳。第二阶段解冻全部参数,学习率必须降到1e-4甚至1e-5,否则预训练特征会被破坏。epoch 总数控制在 20 到 30,配合早停(验证 loss 连续 5 轮不降就停),3000 张跑下来单卡十几分钟的事。

4.2 混淆矩阵比准确率更能说明问题

四分类任务里,总体准确率 85% 可能掩盖了某一类几乎全错。评估必须看混淆矩阵,尤其关注「可回收物 vs 其他垃圾」「厨余 vs 其他」这两组容易混的。

from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for imgs, labels in val_loader: preds = model(imgs).argmax(dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm = confusion_matrix(all_labels, all_preds) print(cm) print(classification_report(all_labels, all_preds, target_names=sorted(class_to_idx)))

classification_report里的f1-score是重点,某类 recall 低说明漏检多,precision 低说明误判多。混淆矩阵对角线外的数值,直接告诉你哪两类在互相污染。如果「可回收物」大量被判成「其他垃圾」,八成是训练集里可回收物的拍摄角度太单一,模型没学到泛化特征,这时候补数据比调参有用。

4.3 验证集指标虚高的三种典型来源

第一种是前面说的重复样本跨集泄漏,训练集和验证集有同一张图,指标自然好看。第二种是验证集太小,3000 张切 20% 只有 600 张,四类均分每类 150 张,波动大,单次结果不可信,建议做 5 折交叉验证取均值。第三种是增强用在了验证集上——验证集的 transform 只能有 Resize、ToTensor、Normalize,绝不能带随机翻转和颜色抖动,否则每次评估结果都不一样,等于在噪声上做判断。这三种来源我都踩过,尤其是第三种,早期图省事复用了训练 transform,调了两天才发现是评估流程的锅。

5. 避坑清单:标注、划分、训练里最容易翻车的五件事

这一章全是踩坑记录,每条按「现象 → 原因 → 解决」写,照着对号入座能省不少时间。

现象一:训练 loss 正常下降,验证准确率卡在 25% 不动。原因:四分类随机猜就是 25%,说明模型根本没学到东西,最常见的是标签映射错位——class_to_idx在训练和验证时用了不同的排序,或者 DataLoader 的shuffle把标签和图片对错了。解决:固定一份class_to_idx存成 JSON,训练、验证、推理全部读同一份;切分后抽 10 张图人工核对图片和标签是否对应。

现象二:检测任务里大量框的宽高归一化后接近 0。原因:原始标注用的是绝对坐标,但解析时误把已经归一化的坐标又除了一次图像尺寸,或者图像尺寸读的是缩略图尺寸而非原图尺寸。解决:解析前先Image.open(path).size拿真实宽高,打印几个框的原始坐标和归一化结果对照,宽高小于 0.01 的直接过滤并记录数量。

现象三:某一类在混淆矩阵里几乎全被判成另一类。原因:这两类在视觉上确实接近(比如某些塑料制品既像可回收又像其他垃圾),或者该类样本的拍摄背景高度一致,模型学的是背景不是物体。解决:先看该类样本的图片,如果背景单一,做背景替换或裁剪增强;如果确实视觉接近,考虑合并类别或引入更细的特征(比如材质标注)。

现象四:训练时 GPU 显存溢出,但 batch size 已经设得很小。原因:检测任务里图片分辨率没统一,某批图特别大,或者num_workers开太多导致每个 worker 都缓存了一份数据。解决:在 Dataset 里强制Resize到固定尺寸,num_workers从 2 开始试,pin_memory=True配合 GPU 训练能提速但会占额外内存,显存紧张时关掉。

现象五:换一台机器复现,结果差很多。原因:随机种子没固定全——random、numpy、torch三家的种子都要设,DataLoader的worker_init_fn也要设,否则多 worker 下的增强随机性不可复现。解决:写一个set_seed(42)函数,在训练脚本开头调用,覆盖三家库;DataLoader传generator=torch.Generator().manual_seed(42)。

提示:这五条里,标签映射错位和验证集增强泄漏是最隐蔽的,因为它们不会报错,只会让指标「看起来还行」或「莫名其妙」。每次新数据集上手,我都会先跑一个 10 张图的小批量过拟合测试——如果模型连 10 张图都记不住,问题一定在数据管道,不在模型。

6. 把 3000 张用出 3 万张的效果:增强、半监督与边界验证

3000 张的天花板是明确的,但通过合理的增强和训练策略,实际效果能往上顶一截。这一章讲三个具体技巧,都是我在小数据集上反复验证过的。

第一个技巧是「针对性增强」而非「通用增强」。垃圾分类的强特征是形状和材质纹理,所以随机裁剪(RandomResizedCrop,scale 设 0.7 到 1.0)比随机旋转更有用,因为旋转会破坏「瓶子立着」这类先验。翻转可以用,但垂直翻转要慎用——垃圾桶里的东西上下颠倒不符合真实拍摄分布。我一般会做一组消融:只翻转、只裁剪、两者都上,看验证集 f1 哪个高,别凭感觉堆增强。

第二个技巧是用这份标注数据做「种子」,配合半监督扩量。具体做法是先用 3000 张训一个还不错的教师模型,然后对未标注的现场图片跑推理,挑置信度高于 0.9 的伪标签加入训练集,迭代两到三轮。这里的关键是置信度阈值不能太低,0.7 以下引入的噪声会把模型带偏。伪标签数据要单独统计来源,评估时验证集必须始终是纯人工标注的那部分,否则指标不可信。

第三个技巧是边界验证——专门构造一批「难例」来测模型边界。比如把可回收物放在厨余垃圾堆里拍、把标签被遮挡的瓶子拍进去、用不同光照条件拍同一类物体。这批图不用多,每类二三十张,但能暴露模型在真实场景下的短板。我习惯在训练完成后固定跑一遍这批难例,记录每类的通过率,作为下一轮数据采集的方向。

策略数据量变化预期收益主要风险
针对性增强不变验证 f1 提升 3~8 个点增强过强导致欠拟合
半监督伪标签扩 2~3 倍泛化能力提升明显阈值过低引入噪声
难例边界验证不变暴露真实短板难例构造偏差

最后说个习惯。从那以后我每次拿到标注数据集,不管多急,都强制走一遍「统计分布 → 查重查损 → 小批量过拟合 → 混淆矩阵」这四步,哪怕只花半小时。这四步能挡掉八成后期调参时根本想不到的玄学问题。这份四类 3000 张的数据集规模适中、类别清晰,正好适合把这套流程完整跑一遍,跑通了再往自己的真实场景上迁移,比一上来就闷头训大模型靠谱得多。希望帮到你。

本文还有配套的精品资源,点击获取