
简介YOLO算法垃圾检测数据集标注文件包专为计算机视觉目标检测场景设计面向需要训练垃圾分类模型的开发者、竞赛选手或科研人员。压缩包内共有2000个XML格式标注文件对应纸箱、纸张、塑料、铝、玻璃、硬纸板、瓶子、塑料瓶等常见生活垃圾类别每个文件完整记录目标物体的边界框坐标、类别名称与图像尺寸信息可直接用于YOLO系列模型的标签解析、格式转换与训练数据搭建。包体积约348.31MB文件类型统一为XML便于脚本批量处理也方便按类别筛选与检查标注质量。目前已有346人学习浏览。解压后即可获得结构清晰的标注目录省去手工标注的不菲成本尤其适合需要快速验证YOLO算法、构建自定义垃圾检测数据集的中级及以上视觉开发者使用。1. 这就是那个 13707 张图的 YOLO 垃圾检测数据集先认清它解决什么问题YOLO 算法做垃圾检测听着像“拿个数据集、训个权重就完事”但真正落到回收箱、分拣线和巡检无人机上数据集质量比模型结构更决定上限。标题里这份 13707 张带标签图像覆盖纸箱、纸张、塑料、铝、玻璃、硬纸板、瓶子、塑料瓶八类常见生活垃圾适合智能环卫、回收设备和做目标检测落地的工程师。拿来就能训练是它的价值但标注是否统一、类别是否重叠、训练验证切分是否干净才决定你三天能出模型还是三周都在调数据。我按拿到这类 zip 数据集后的实际流程往下走。2. 解压与标注体检13707 张图不是解出来就能直接训的2.1 解压与顶层目录体检先看清数据是怎么组织的拿到 zip 之后第一步不是立刻打开图片看标注效果而是先把它从“压缩包资源”变成“可训练目录”。我习惯在专门的 datasets 目录下解压而不是直接扔进项目仓库里避免和代码混在一起mkdir -p ~/datasets/garbage cd ~/datasets/garbage mv ~/Downloads/yolo算法-垃圾检测数据集-13707张图像带标签-*.zip ./garbage.zip unzip -q garbage.zip -d garbage_data先重命名再解压主要为了绕开中文文件名在部分 unzip 版本里的编码处理差异省得后面路径解析时冒出一堆%E7%BA%B8%E7%AE%B1之类的东西。解压完成后第一步看目录结构cd garbage_data find . -maxdepth 2 -type d | sort | head -60 du -sh * 2/dev/null | sort -h第一条命令列出两层以内的子目录第二条按大小排序列出顶层内容。一个 13707 张图的数据集正常体量应该有几百 MB 到几个 GB取决于图像原始分辨率。如果只有几十 MB要么图像被压得很小要么里面其实是缩略图训练前要慎重。这里要分两种常见布局一种是已经分好images/train、images/val、labels/train、labels/val另一种是只有一个images和一个labels平铺目录甚至每个类一个子目录。第一种相对省事但千万别立刻信任它的划分后面 4.1 会讲为什么第二种你必须自己写脚本切分。还需要确认图像和标签的文件名是否一一对应。YOLO 训练时要求同一张图的图像文件和标签文件同名同路径前缀只是扩展名不同。常见命名是img_0001.jpg和img_0001.txt。如果标签目录里多出一些没有对应图像的文件或者反过来训练时 ultralytics 会报 “Label not found”或直接静默跳过排查起来非常费时间。我会顺手统计一下图片格式分布find images -type f | sed s/.*\.// | sort | uniq -c结果里如果出现 bmp、webp、tiff就要考虑统一转成 jpg 或 png。YOLO 训练可以吃这些格式但混合格式会降低数据加载效率而且有的老版本 opencv 对 webp 支持不完整读取时可能返回空图。统一转换我用一个很短的 Python 脚本批量做避免手动一张张处理。2.2 标注体检脚本类别分布、空标注与极小框一次查清目录结构确认之后真正决定数据集能不能用的是标签文件里的内容。我写过一个极简体检脚本每次拿到新数据集都会先跑一遍不依赖训练框架只用标准库import os from collections import Counter label_dir labels class_names [cardboard, paper, plastic, aluminum, glass, hardboard, bottle, plastic_bottle] cls_counter Counter() empty_files 0 bad_lines 0 out_of_range 0 too_small 0 total_boxes 0 for root, _, files in os.walk(label_dir): for f in files: if not f.endswith(.txt): continue p os.path.join(root, f) with open(p, r, encodingutf-8, errorsignore) as fh: lines [ln for ln in fh.read().splitlines() if ln.strip()] if not lines: empty_files 1 continue for ln in lines: parts ln.split() if len(parts) ! 5: bad_lines 1 continue try: cls_id int(float(parts[0])) cx, cy, w, h map(float, parts[1:5]) except ValueError: bad_lines 1 continue cls_counter[cls_id] 1 total_boxes 1 if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): out_of_range 1 if w 0.01 or h 0.01: too_small 1 print(各类别实例数) for i, name in enumerate(class_names): print(f {i:2d} {name:14s} {cls_counter.get(i, 0)}) print(空标签文件, empty_files) print(格式异常行, bad_lines) print(坐标越界框, out_of_range) print(极小框(宽或高1%图像尺寸), too_small) print(总框数, total_boxes)代码逻辑很简单但几个输出的含义值得逐条解释。类别实例数直接对标标题里的八类如果发现某个类完全没有样本或者某两类数量差一个数量级就要警惕类别不平衡。空标签文件分两种一种是这张图里确实没有垃圾属于负样本YOLO 也支持通常建议保留一定比例的纯背景图来压误检另一种是标注丢失常见于数据集由多批采集拼凑而成后期需要补标或剔除。坐标越界框说明标注源头用的可能不是 YOLO 约定也可能是标注时框出了图像边界。YOLO 训练时这类框不一定会报错但会影响 loss 回传的稳定性。极小框的判断阈值我取了 0.01也就是在 640x640 输入下只有 6 像素宽。这类目标通常是小塑料瓶盖、碎玻璃渣模型很难学到有效特征如果数量占比超过 5%要么考虑提高输入分辨率要么在转换时做过滤合并。还有一个检查项我放在脚本之外图像分辨率。13707 张图很可能不是统一尺寸。如果有的图是 1920x1080有的是 640x480训练时 YOLO 会强制缩放导致小目标被压得更小。我平时用下面这个命令看分辨率分布file images/*.jpg | sed -n 1,20p用file命令看前 20 张或者用 Python 的 PIL 全量统计。如果尺寸差异大建议先做一次统一 resize至少把短边统一到 640 以上再进训练流程。这里尤其要注意如果 zip 里自带的是未缩放原图就不要手动压缩保留原始分辨率让 YOLO 自己处理反而更稳。3. 把多源标注转成 YOLO 能直接吃的格式txt 归一化与 data.yaml3.1 从 VOC XML 到 YOLO txt归一化转换脚本很多这类垃圾检测数据集底层标注用的是 VOC XML 或 COCO JSONzip 里并不直接是 YOLO 的 txt 格式。如果你解压后看到的是Annotations加JPEGImages两个目录那就得先做格式转换。YOLO 和 VOC 的差异不只是文件后缀核心是坐标系的表达方式不同VOC 给的是xmin, ymin, xmax, ymax四个绝对像素坐标YOLO 给的是归一化后的cx, cy, w, h其中 cx、cy 是 bbox 中心点w、h 是宽高全部除以图像宽高。我一般用下面这段脚本把 VOC XML 批量转成 YOLO txtimport os import xml.etree.ElementTree as ET class_names [cardboard, paper, plastic, aluminum, glass, hardboard, bottle, plastic_bottle] def voc2yolo(xml_path, out_txt_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.findtext(name, ).strip() if name not in class_names: continue cls_id class_names.index(name) box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) xmin max(0, min(xmin, img_w)) ymin max(0, min(ymin, img_h)) xmax max(0, min(xmax, img_w)) ymax max(0, min(ymax, img_h)) w xmax - xmin h ymax - ymin if w 0 or h 0: continue cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h nw w / img_w nh h / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) with open(out_txt_path, w, encodingutf-8) as fw: fw.write(\n.join(lines) (\n if lines else ))逻辑说明分三块。第一块是类别映射class_names 的顺序就是最终训练时类别 id 的顺序这个顺序一旦确定就不要改否则权重和类别对不上。第二块是边界夹紧把标注框超出图像边界的部分裁掉避免出现 xmax 大于图宽的情况。第三块是坐标换算先算像素宽高再除以图像宽高得到归一化值保留六位小数就够用太多位只是徒增文件体积。转换脚本本身不复杂容易忽略的是 image 尺寸怎么来。有的 VOC 数据集在 XML 里有sizewidth...节点可以直接读如果没有就必须在同名 jpg 上用 PIL 读取。注意一定不能用缩略图读尺寸否则归一化坐标全错。这块我习惯写在主循环里from PIL import Image import glob for xml_path in glob.glob(Annotations/*.xml): img_path xml_path.replace(Annotations, JPEGImages).replace(.xml, .jpg) if not os.path.exists(img_path): img_path img_path.replace(.jpg, .png) with Image.open(img_path) as im: w, h im.size out_txt os.path.join(labels, os.path.basename(xml_path)[:-4] .txt) voc2yolo(xml_path, out_txt, w, h)这里读取原始图像尺寸的目的是确保归一化坐标计算基准正确。如果先 resize 再转标注就必须用 resize 后的尺寸重新计算不能混用。另一个细节如果转换后发现某个 txt 是空文件极可能是该图里所有目标类别都不在 class_names 里这种图要么补类、要么从训练集剔除不要让它带着空标签进入训练。3.2 划分 train/val 与 data.yaml随机种子和目录约定格式转换完成后下一个问题就是怎么划分训练集和验证集。如果 zip 里已经带了切分我会先按它走但会用脚本验一遍划分是否干净如果没有就自己切。常规比例是 8:2 或 9:1我一般取 85% 训练、15% 验证类别少时验证集多留一点。import os import random from sklearn.model_selection import train_test_split random.seed(42) img_dir images lbl_dir labels imgs [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] imgs.sort() train_imgs, val_imgs train_test_split(imgs, test_size0.15, random_state42) for split, split_imgs in [(train, train_imgs), (val, val_imgs)]: os.makedirs(fimages/{split}, exist_okTrue) os.makedirs(flabels/{split}, exist_okTrue) for img in split_imgs: os.rename(os.path.join(img_dir, img), os.path.join(fimages/{split}, img)) label img.rsplit(., 1)[0] .txt if os.path.exists(os.path.join(lbl_dir, label)): os.rename(os.path.join(lbl_dir, label), os.path.join(flabels/{split}, label))这段脚本有几个细节。imgs.sort()是为了固定文件顺序避免不同机器上 os.listdir 返回顺序不一样导致切分结果漂移。随机种子固定为 42这样同一份数据每次切分结果一致实验可复现。如果发现 val 图像没有对应 txt脚本里会静默跳过这种缺失不要直接忽略建议先统计缺多少超过 1% 就要回头查标注产出流程。切分完还需要在 ultralytics 的 data.yaml 里把路径和类别名声明清楚path: /home/user/datasets/garbage_data train: images/train val: images/val names: 0: cardboard 1: paper 2: plastic 3: aluminum 4: glass 5: hardboard 6: bottle 7: plastic_bottlepath我写的是绝对路径在服务器上换机器跑时路径变了要同步改 yaml。names顺序必须和 3.1 转换脚本里的 class_names 完全一致一个错位整个模型训练就等于在错误标签上学习。yaml 里没有显式写nc: 8也可以ultralytics 会自动从 names 长度推断但写出来更明确。启动训练的命令我一般会先跑一个低 epoch 的快速验证确认流程没问题再上全量yolo detect train datagarbage.yaml modelyolov8m.pt epochs3 imgsz640 batch16三个 epoch 只是为了验证数据加载、loss 曲线和类别 id 对应关系跑完看 train/val loss 是否下降如果 loss 直接 NaN 或 val 指标为 0一定是数据格式问题不值得等完整训练。确认无误后再把 epochs 改成 100 到 300 之间的实际值模型权重可以从 yolov8m.pt 或 yolov8s.pt 开始。4. 垃圾检测训练避坑小目标、类间相似与数据泄漏的三类翻车4.1 数据泄漏训练集和验证集里混进了同一个场景的连拍帧现象训练时 val mAP 很漂亮100 个 epoch 下来 mAP50 能到 0.9 以上可是把模型拿到真实场景一测误检和漏检都明显变多和验证指标完全不符。原因这份垃圾检测数据集不少图像来自监控视频截帧或手机连拍同一个瓶子、同一个纸箱会连续出现在多张相邻帧里。如果直接随机切分同一场景的相邻帧可能同时落在 train 和 val模型在验证集上“见过”目标指标虚高。这是目标检测数据集最隐蔽的一类数据泄漏。解决划分前先做去重。轻量做法是用感知哈希按相似度聚类把相似图像归到同一组再按组切分。我用过一个最小实现import os import imagehash from PIL import Image from collections import defaultdict hash_dict defaultdict(list) for f in os.listdir(images): if not f.lower().endswith((.jpg, .jpeg, .png)): continue with Image.open(os.path.join(images, f)) as im: h imagehash.phash(im.convert(RGB), hash_size16) hash_dict[str(h)].append(f) dup_groups {k: v for k, v in hash_dict.items() if len(v) 1} print(疑似重复/连拍组数, len(dup_groups))用 phash 比较时严格相同的哈希才归一组所以不会误伤正常相似图像。更严格的做法是用imagehash计算两两汉明距离但数据量大时 O(n^2) 太慢我的经验是先按帧号或文件名前缀粗分再在组内做 phash 比较。另一个替代方案如果 zip 里图像文件名带有序号特征比如frame_0001或20250101_120001按文件名字面顺序每隔若干张抽样成 val也能有效错开连拍但这种方法依赖文件名规律不通用只在没有更好办法时用。切分完以后我还会从 val 里抽 20 张图人工和 train 里的相似图对比一遍确认没有肉眼可见的重复场景。4.2 类间相似纸箱和硬纸板互相误检只能靠合并或重标现象训练收敛后混淆矩阵里cardboard和hardboard两个类别互相预测的格子数值特别高甚至bottle和plastic_bottle也在互相干扰导致 mAP50 不低但每个类单独看 AP 都很难看。原因纸箱和硬纸板在纹理、颜色、形状上高度重叠标注规范里“纸箱”和“硬纸板”的边界本身就模糊。同一个褐色瓦楞纸物体一个标注员标 cardboard另一个标 hardboard模型学到的类间差异就会被标注噪声淹没。瓶子同理玻璃瓶和塑料瓶在轮廓上几乎一样只有材质光泽有区别YOLO 靠 RGB 特征很难分开。解决先做类目合并再训练。我的做法是把高度混淆的类别合并成上级类比如cardboard hardboard → packagingbottle plastic_bottle → bottle然后看合并后的 mAP 是否显著上升。如果上升明显说明原标注的类别粒度对模型来说太细了强行分只会让每个类都学不好。合并操作在转换脚本里做就行映射表一改重新生成 txtmerge_map { cardboard: packaging, hardboard: packaging, bottle: bottle, plastic_bottle: bottle, }当然如果产品需求必须区分“纸箱”和“硬纸板”那就得回到数据层面补样本尤其是标注规范要重写明确“纸箱指瓦楞纸运输箱硬纸板指平板状纸板”并找同一标注团队返工。在这类需求下YOLO 模型不是瓶颈标注一致性才是。还有一个小技巧把最容易混淆的类别放在训练时做类别平衡采样让它们的样本数尽可能接近避免数量多的类把数量少的类“吃掉”。ultralytics 里可以用class_weights或在数据增强层面加重难例权重但最直接的办法还是保证样本数均衡。我每次跑完训练都会先看 confusion matrix 图如果某一对类别的互相混淆比例超过 20%就先合并不再继续调参。4.3 小目标远处的塑料瓶漏检mAP50-95 始终偏低现象mAP50 到 0.85 以上了但 mAP50-95 只停在 0.5 上下把测试图放大看远处的小塑料瓶、小易拉罐几乎全部漏检近处的大目标则很正常。原因垃圾回收场景的目标尺寸分布极不均匀部分图像里的瓶子只占图像面积 1% 不到。YOLO 默认输入 640x640小目标下采样到 80x80 特征图时可能只剩 3x3 像素特征基本消失。另外如果数据集里近距离样本占主导训练时模型对小目标的先验框匹配不上正样本分配不到。解决先看小目标占比第 2 章的体检脚本里那个too_small就能派上用场。如果极小框占比超过 3%优先把输入分辨率提到 960 或 1280这会直接增加小目标的像素占用yolo detect train datagarbage.yaml modelyolov8m.pt epochs100 imgsz960 batch8注意imgsz从 640 提到 960 后显存占用几乎翻倍batch 要相应调小不然会 OOM。另一种有效手段是开启更强的 mosaic 和 copy-paste 增强让每个 batch 里小目标的出现频率更高。ultralytics 默认已经开了 mosaic但可以通过mosaic0.8这类概率参数控制。如果分辨率已经拉到 1280 还是不够就要考虑检测头层面的改造比如给 YOLO 加 P2 检测层或换用专门优化过小目标的模型结构。这块成本较高我的建议是先在数据层面把问题量化确认小目标确实占比高再决定值不值得投时间。如果部署设备是边缘盒子分辨率撑不到 1280那就要接受小目标漏检的现实用业务规则兜底比如限制摄像头安装角度和检测距离范围让算法只负责它擅长的那一段。5. 用混淆矩阵和置信度阈值把模型推到能上线最后一步校验训练完不是看 mAP 就收工我最后还会做三件事。第一用一批完全不参与训练和验证的独立图像跑推理这批图像最好来自另一台设备或另一个时间段的拍摄避免和数据集同源。第二画出混淆矩阵逐个类别看互相污染的情况。第三针对实际场景调置信度阈值。yolo detect val datagarbage.yaml modelruns/detect/train/weights/best.ptval 结束后在 ultralytics 的输出目录里找到confusion_matrix.png如果某一行除了对角线以外还有明显亮点说明这个类别和另一个类别存在系统混淆。这时候不要急着调模型先回到 4.2 的合并思路验证一次合并后指标是否更好。部署时阈值的调节也很关键。自动回收箱场景里误检一个纸箱的成本比漏检一个纸箱高因为误检会触发错误的箱门开合我一般把置信度阈值调到 0.45 到 0.55如果是巡检记录场景漏检更不可接受阈值可以降到 0.25多出的误检交给人工复核。ultralytics 推理时直接改 conf 参数yolo detect predict modelbest.pt sourcetest_imgs/ conf0.35 imgsz960我的一个血泪经验是上线前一定要对“类目合并后的版本”和“原始 8 类版本”都做一次完整评测别因为原始版本指标好看就跳过。很多项目最后真正交到现场的模型类别数反而比数据集标题少一两个但这不代表模型变弱了而是类别定义和真实业务对齐之后的结果。另一个习惯是把验证集的预测结果和标注画到同一张图上特别是把误检和漏检的图单独存档。每轮迭代后翻出来对比能直观看到模型到底在哪些场景进步了。这个习惯帮我省下很多无效调参时间。希望帮到你。本文还有配套的精品资源点击获取