简介这是一份面向目标检测初学者与算法工程师的垃圾分类检测数据集围绕垃圾材质识别任务构建可用于训练和验证YOLO、Faster R-CNN等主流检测模型适合课程设计、毕业项目或工业分拣场景的原型开发。压缩包共约2000个文件以xml标注文件和1个说明txt为主整体约128MB其中JPEGImages存放8341张清晰原图Annotations提供对应VOC格式xml标注labels则给出YOLO格式txt标签三种目录一一对应方便直接接入不同训练框架。数据集共设Cardboard、Glass、Metal、Paper、Plastic五类总标注框数达8815个各类分布相对均衡其中Plastic框数最多为1911个Glass最少为1601个标注均为矩形框未做数据增强便于自行设计增广策略。目前已有224人学习下载可作为垃圾分类检测任务的可靠起点帮助读者省去从零采集与标注的成本快速进入模型调参与效果对比阶段。1. 垃圾分类检测数据集8341 张 yolovoc 双格式到底解决了什么手上有个做智能回收箱的团队模型在实验室垃圾桶上跑得挺欢一到小区投放点就翻车——塑料袋半透明、纸盒被压扁、饮料瓶带包装纸模型把可回收物认成其他垃圾。问题不在网络结构在数据。他们当时只有自己拍的 600 多张图类别还不均衡。后来换成一份 8341 张、同时带 yolo 和 voc 两种标注格式的垃圾分类检测数据集mAP 直接涨了一截。这件事说明一个朴素道理目标检测这行数据集的质量和格式兼容性往往比换 backbone 更管用。这份数据集的核心价值就三点。第一8341 张的体量足够撑起一次从零训练或微调不会像几百张那样过拟合。第二yolo 和 voc 双格式意味着你既能直接喂给 YOLO 系列也能转成 COCO 或用于 SSD、Faster R-CNN 这类框架省掉自己写转换脚本的功夫。第三垃圾分类这个场景本身类别边界模糊是检验模型细粒度识别能力的好靶子。适合谁做智慧环卫、回收箱、边缘部署检测的工程师以及想拿一个真实场景练手 YOLO 训练全流程的人。下面从格式差异讲到训练落地再讲我踩过的坑。2. yolo 与 voc 标注格式的差异和转换逻辑2.1 两种格式到底差在哪VOC 格式的标注是一个 XML 文件对应一张图里面用bndbox记录xmin、ymin、xmax、ymax四个绝对像素坐标类别名写在name里。YOLO 格式则是一个 txt 文件对应一张图每行是class_id x_center y_center width height后四个值全部是相对图像宽高的归一化值范围 0 到 1。这个差异决定了你拿到数据集后第一件事是确认目录结构和标签是否配对。维度VOCYOLO单图标签文件Annotations/xxx.xmllabels/xxx.txt坐标类型绝对像素归一化 0-1类别表示字符串 name整数 class_id一图多目标多个 object 节点多行文本常用框架Faster R-CNN、SSDYOLOv5/v8/v11很多人拿到双格式数据集后犯的错是以为两种格式的图片是分开的两套。实际上绝大多数情况下图片是同一套只是标签目录不同。所以第一步永远是核对图片数量和标签数量是否一致。2.2 用脚本核对图片与标签配对拿到数据集先别急着训练先跑一遍配对检查。下面这段脚本同时检查 yolo 的 txt 和 voc 的 xml 是否与图片一一对应缺哪个一目了然。import os img_dir images # 图片目录 yolo_dir labels_yolo # yolo 标签目录 voc_dir annotations_voc # voc 标签目录 imgs {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .png, .jpeg))} yolo {os.path.splitext(f)[0] for f in os.listdir(yolo_dir) if f.endswith(.txt)} voc {os.path.splitext(f)[0] for f in os.listdir(voc_dir) if f.endswith(.xml)} print(图片数:, len(imgs)) print(yolo 标签数:, len(yolo), 缺失:, len(imgs - yolo)) print(voc 标签数:, len(voc), 缺失:, len(imgs - voc)) # 打印前 10 个缺失样本方便定位 print(yolo 缺失样例:, list(imgs - yolo)[:10])逻辑说明用集合差集找出「有图无标签」的样本这是训练时最常见的报错来源。参数上img_dir换成你实际的图片路径注意有些数据集图片放在JPEGImages下。如果缺失数量超过 5%基本可以判断数据集打包时漏了文件需要重新获取或手动剔除这些图而不是硬训练。2.3 从 VOC 转 YOLO 的完整脚本如果数据集只给了 VOC或者你想统一成 YOLO 格式训练转换脚本必须自己掌握。核心是把绝对坐标归一化并把类别名映射成固定顺序的 id。import os import xml.etree.ElementTree as ET from PIL import Image classes [recyclable, kitchen, hazardous, other] # 按你的类别顺序改 cls2id {c: i for i, c in enumerate(classes)} voc_dir, img_dir, out_dir annotations_voc, images, labels_yolo os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() # 用图片真实尺寸做归一化别用 xml 里的 size有时不准 img_name root.find(filename).text w, h Image.open(os.path.join(img_dir, img_name)).size lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in cls2id: # 跳过未定义类别避免 id 错乱 continue b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) # 坐标裁剪到图像范围内防止越界 xmin, ymin max(0, xmin), max(0, ymin) xmax, ymax min(w, xmax), min(h, ymax) xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls2id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))逻辑说明归一化必须用图片真实宽高而不是 XML 里size节点因为有些标注工具写进去的尺寸和实际图片对不上这是血泪经验。cls2id的顺序一旦定下就不能改训练配置里的names必须和它完全一致否则类别全错位。坐标裁剪那两行是后悔药防止个别标注框超出图像边界导致归一化后出现大于 1 的值YOLO 训练时这类样本会被静默丢弃或报错。3. 用这份数据集训练 YOLO 的完整流程3.1 目录组织与 data.yaml 配置YOLO 训练对目录结构有约定最稳妥的是按下面这样组织图片和标签分开放train 和 val 各自独立。dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 对应 yolo txt │ └── val/ └── data.yamldata.yaml是训练的入口配置写错一个字段模型就找不到数据。path: /home/user/dataset # 数据集根目录绝对路径最稳 train: images/train val: images/val nc: 4 # 类别数必须和 names 长度一致 names: [recyclable, kitchen, hazardous, other]参数说明path用绝对路径能避免相对路径在不同工作目录下解析失败的玄学问题。nc和names必须严格对应顺序和转换脚本里的cls2id一致。如果验证集也想用训练集把val指向images/train即可但这样评估指标会虚高不建议。3.2 划分训练集与验证集8341 张如果全丢进训练没有验证集就无法判断过拟合。常见做法是按 8:2 或 9:1 划分且要保证类别分布均衡。下面脚本按比例随机划分并复制文件。import os, random, shutil random.seed(42) # 固定种子保证可复现 src_img, src_lbl images_all, labels_all ratio 0.2 # 验证集比例 names [os.path.splitext(f)[0] for f in os.listdir(src_img) if f.lower().endswith((.jpg, .png))] random.shuffle(names) val_n int(len(names) * ratio) val_set, train_set set(names[:val_n]), set(names[val_n:]) for split, s in [(train, train_set), (val, val_set)]: os.makedirs(fdataset/images/{split}, exist_okTrue) os.makedirs(fdataset/labels/{split}, exist_okTrue) for n in s: for ext in (.jpg, .png): p os.path.join(src_img, n ext) if os.path.exists(p): shutil.copy(p, fdataset/images/{split}/) break shutil.copy(os.path.join(src_lbl, n .txt), fdataset/labels/{split}/)逻辑说明random.seed(42)是为了让划分结果可复现团队协作时别人能跑出同样的验证集。参数ratio控制验证集大小数据量 8000 级别时 0.2 足够太小会导致评估波动大。注意这里假设图片和标签同名如果你的数据集命名不一致需要先做一次重命名对齐。3.3 启动训练与关键参数环境配好后一条命令就能开跑。以 YOLOv8 为例常见做法是加载预训练权重做微调而不是从零训练。yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0参数说明model选yolov8n.pt这类预训练权重小数据集上微调收敛快得多。imgsz640是通用起点如果垃圾目标普遍偏小可以提到 800 或 1024但显存占用会明显上升。batch根据显存调16 是 8G 显存的安全值。patience20表示 20 轮没提升就早停省时间。lr0初始学习率微调时 0.01 偏大可以降到 0.001从零训练才用 0.01。3.4 训练过程该盯哪些指标训练日志里重点看三个box_loss、cls_loss、mAP50。box_loss下降说明框的位置在收敛cls_loss下降说明分类在学。如果box_loss一直震荡不降多半是学习率太大或标注框有问题。mAP50是验证集上的主指标垃圾分类这种细粒度任务能到 0.85 以上算不错。如果训练集 mAP 高但验证集低就是过拟合需要加数据增强或减模型容量。4. 垃圾分类检测的避坑与排查清单4.1 类别 id 错位导致全盘皆错现象训练 loss 正常下降但推理时所有类别都识别成同一个或者类别名和实际物体对不上。原因data.yaml里的names顺序和标签文件里的class_id不一致比如转换时按字母序排配置里按业务序排。解决把转换脚本里的classes列表和data.yaml的names逐字比对顺序必须完全一致改完重新生成标签。4.2 图片与标签不同名导致静默丢样本现象训练时提示找到的图片数比预期少或者某些类别样本量异常低。原因YOLO 按文件名匹配图片和标签图片叫IMG_001.jpg而标签叫001.txt就配不上这些样本被静默跳过。解决跑一遍第 2.2 节的配对检查脚本把不一致的文件重命名对齐别指望框架报错提醒你。4.3 小目标被下采样吃掉现象大件垃圾识别准塑料袋、烟头这类小目标几乎检不出。原因YOLO 的 P3 特征图 stride 是 8输入 640 时小于 8 像素的目标基本丢失。解决把imgsz提到 1024或改用带 P2 层的模型结构也可以在数据增强里关闭 mosaic 的随机缩放避免小目标被进一步缩小。4.4 验证集泄漏进训练集现象验证集 mAP 高得离谱上线后效果断崖式下跌。原因划分时同一张图的不同增强版本或同一场景的连拍图被分到了训练和验证两边造成信息泄漏。解决划分前按场景或拍摄批次分组同一组只进一个集合别用纯随机划分处理连拍数据。4.5 标注框越界引发归一化异常现象训练报错提示坐标超出范围或某些框在可视化时跑到图像外。原因VOC 转 YOLO 时没做坐标裁剪个别标注框的xmax大于图片宽度。解决转换脚本里加坐标裁剪把xmin/ymin限制在 0 以上xmax/ymax限制在宽高以内再归一化。5. 把 8341 张用到极致数据增强与格式互转的进阶技巧数据集到手只是起点怎么把它榨干才是分水岭。我一般会做两件事一是针对垃圾分类场景定制增强二是保留双格式的互转能力方便换框架时不用重新标注。先说增强。垃圾分类的难点是遮挡和形变通用增强不够用。我习惯在 YOLO 的hyp配置里调几个参数mosaic1.0保留但把close_mosaic设成最后 10 轮关闭让模型后期在真实分布上收敛degrees10做小角度旋转模拟垃圾被随手扔的姿态hsv_v0.4加大亮度扰动应对室内外光照差异flipud0.5上下翻转因为垃圾没有固定朝向。这些参数不是拍脑袋是几次翻车后调出来的——之前degrees开到 30模型把横躺的瓶子学成了另一个类别。再说格式互转。这份数据集同时给了 yolo 和 voc意味着你可以随时切框架。如果要从 YOLO 转回 VOC核心是反归一化把相对坐标乘回图片宽高。下面这段脚本做反向转换方便你把训练好的 YOLO 数据拿去跑 Faster R-CNN 做对比实验。import os from PIL import Image import xml.etree.ElementTree as ET classes [recyclable, kitchen, hazardous, other] img_dir, lbl_dir, out_dir images, labels_yolo, annotations_voc os.makedirs(out_dir, exist_okTrue) for txt in os.listdir(lbl_dir): if not txt.endswith(.txt): continue stem os.path.splitext(txt)[0] img_path None for ext in (.jpg, .png): if os.path.exists(os.path.join(img_dir, stem ext)): img_path os.path.join(img_dir, stem ext) break if img_path is None: continue w, h Image.open(img_path).size root ET.Element(annotation) ET.SubElement(root, filename).text os.path.basename(img_path) size ET.SubElement(root, size) ET.SubElement(size, width).text str(w) ET.SubElement(size, height).text str(h) with open(os.path.join(lbl_dir, txt)) as f: for line in f: cid, xc, yc, bw, bh line.split() xc, yc, bw, bh map(float, (xc, yc, bw, bh)) xmin int((xc - bw / 2) * w) ymin int((yc - bh / 2) * h) xmax int((xc bw / 2) * w) ymax int((yc bh / 2) * h) obj ET.SubElement(root, object) ET.SubElement(obj, name).text classes[int(cid)] b ET.SubElement(obj, bndbox) for k, v in zip((xmin, ymin, xmax, ymax), (xmin, ymin, xmax, ymax)): ET.SubElement(b, k).text str(v) ET.ElementTree(root).write(os.path.join(out_dir, stem .xml))逻辑说明反归一化时用int取整VOC 习惯用整数像素坐标。classes列表必须和训练时一致否则类别名会错。这段脚本的价值在于当你发现 YOLO 在某个子类上表现不好想换两阶段检测器验证是不是结构问题不用重新标注直接转格式就能跑。最后给一个验证训练是否真的学到东西的小技巧从验证集里挑 20 张用训练好的权重推理把预测框和真实框画在同一张图上对比。如果预测框普遍偏大或偏小说明回归分支没学好回去检查标注框是否贴合物体如果框对了但类别错说明分类分支欠拟合考虑加类别权重或补该类样本。这个习惯帮我省下过好几次盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取
