news 2026/10/5 5:54:54

YOLO垃圾检测数据集实战:13707张图从解压到训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO垃圾检测数据集实战:13707张图从解压到训练避坑指南

简介: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", encoding="utf-8", errors="ignore") 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.yaml

3.1 从 VOC XML 到 YOLO txt:归一化转换脚本

很多这类垃圾检测数据集,底层标注用的是 VOC XML 或 COCO JSON,zip 里并不直接是 YOLO 的 txt 格式。如果你解压后看到的是Annotations加JPEGImages两个目录,那就得先做格式转换。YOLO 和 VOC 的差异不只是文件后缀,核心是坐标系的表达方式不同:VOC 给的是xmin, ymin, xmax, ymax四个绝对像素坐标;YOLO 给的是归一化后的cx, cy, w, h,其中 cx、cy 是 bbox 中心点,w、h 是宽高,全部除以图像宽高。

我一般用下面这段脚本把 VOC XML 批量转成 YOLO txt:

import 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", encoding="utf-8") as fw: fw.write("\n".join(lines) + ("\n" if lines else ""))

逻辑说明分三块。第一块是类别映射,class_names 的顺序就是最终训练时类别 id 的顺序,这个顺序一旦确定就不要改,否则权重和类别对不上。第二块是边界夹紧,把标注框超出图像边界的部分裁掉,避免出现 xmax 大于图宽的情况。第三块是坐标换算,先算像素宽高,再除以图像宽高得到归一化值,保留六位小数就够用,太多位只是徒增文件体积。

转换脚本本身不复杂,容易忽略的是 image 尺寸怎么来。有的 VOC 数据集在 XML 里有<size><width>...节点,可以直接读;如果没有,就必须在同名 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_size=0.15, random_state=42) for split, split_imgs in [("train", train_imgs), ("val", val_imgs)]: os.makedirs(f"images/{split}", exist_ok=True) os.makedirs(f"labels/{split}", exist_ok=True) for img in split_imgs: os.rename(os.path.join(img_dir, img), os.path.join(f"images/{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(f"labels/{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_bottle

path我写的是绝对路径;在服务器上换机器跑时,路径变了要同步改 yaml。names顺序必须和 3.1 转换脚本里的 class_names 完全一致,一个错位,整个模型训练就等于在错误标签上学习。yaml 里没有显式写nc: 8也可以,ultralytics 会自动从 names 长度推断,但写出来更明确。

启动训练的命令,我一般会先跑一个低 epoch 的快速验证,确认流程没问题再上全量:

yolo detect train data=garbage.yaml model=yolov8m.pt epochs=3 imgsz=640 batch=16

三个 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_size=16) 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 → packaging,bottle + plastic_bottle → bottle,然后看合并后的 mAP 是否显著上升。如果上升明显,说明原标注的类别粒度对模型来说太细了,强行分只会让每个类都学不好。合并操作在转换脚本里做就行,映射表一改,重新生成 txt:

merge_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 data=garbage.yaml model=yolov8m.pt epochs=100 imgsz=960 batch=8

注意imgsz从 640 提到 960 后,显存占用几乎翻倍,batch 要相应调小,不然会 OOM。另一种有效手段是开启更强的 mosaic 和 copy-paste 增强,让每个 batch 里小目标的出现频率更高。ultralytics 默认已经开了 mosaic,但可以通过mosaic=0.8这类概率参数控制。

如果分辨率已经拉到 1280 还是不够,就要考虑检测头层面的改造,比如给 YOLO 加 P2 检测层,或换用专门优化过小目标的模型结构。这块成本较高,我的建议是先在数据层面把问题量化,确认小目标确实占比高,再决定值不值得投时间。如果部署设备是边缘盒子,分辨率撑不到 1280,那就要接受小目标漏检的现实,用业务规则兜底,比如限制摄像头安装角度和检测距离范围,让算法只负责它擅长的那一段。

5. 用混淆矩阵和置信度阈值把模型推到能上线:最后一步校验

训练完不是看 mAP 就收工,我最后还会做三件事。第一,用一批完全不参与训练和验证的独立图像跑推理,这批图像最好来自另一台设备或另一个时间段的拍摄,避免和数据集同源。第二,画出混淆矩阵,逐个类别看互相污染的情况。第三,针对实际场景调置信度阈值。

yolo detect val data=garbage.yaml model=runs/detect/train/weights/best.pt

val 结束后,在 ultralytics 的输出目录里找到confusion_matrix.png,如果某一行除了对角线以外还有明显亮点,说明这个类别和另一个类别存在系统混淆。这时候不要急着调模型,先回到 4.2 的合并思路,验证一次合并后指标是否更好。

部署时阈值的调节也很关键。自动回收箱场景里,误检一个纸箱的成本比漏检一个纸箱高,因为误检会触发错误的箱门开合,我一般把置信度阈值调到 0.45 到 0.55;如果是巡检记录场景,漏检更不可接受,阈值可以降到 0.25,多出的误检交给人工复核。ultralytics 推理时直接改 conf 参数:

yolo detect predict model=best.pt source=test_imgs/ conf=0.35 imgsz=960

我的一个血泪经验是:上线前一定要对“类目合并后的版本”和“原始 8 类版本”都做一次完整评测,别因为原始版本指标好看就跳过。很多项目最后真正交到现场的模型,类别数反而比数据集标题少一两个,但这不代表模型变弱了,而是类别定义和真实业务对齐之后的结果。

另一个习惯是把验证集的预测结果和标注画到同一张图上,特别是把误检和漏检的图单独存档。每轮迭代后翻出来对比,能直观看到模型到底在哪些场景进步了。这个习惯帮我省下很多无效调参时间。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 5:52:01

JavaWeb学生宿舍管理系统实战:数据库设计、事务与部署全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 5:49:38

AVM环视系统搭建全流程:从硬件选型到图像拼接与标定

AVM环视系统这几年已经快成新车标配了&#xff0c;从十万级家用车到高端智能电动车&#xff0c;都能看到这个功能。所谓AVM&#xff0c;就是通过装在车身前后左右的四路鱼眼摄像头&#xff0c;实时采集车辆周围的图像&#xff0c;经过畸变矫正、俯视变换、拼接融合等一系列处理…

作者头像 李华
网站建设 2026/10/5 5:48:00

Redis 影子数据隔离实操:统一命名空间注入与自动化 TTL 生命周期管理

Redis 影子数据隔离实操&#xff1a;统一命名空间注入与自动化 TTL 生命周期管理在双 11 全链路压测的存储隔离设计中&#xff0c;很多团队把绝大部分精力放在了 MySQL 的影子库表上&#xff0c;却常常忽视了作为前置高频缓存的 Redis 集群。与关系型数据库可以通过创建物理独立…

作者头像 李华
网站建设 2026/10/5 5:46:34

Hoeffding与Chernoff不等式:高维统计的尾部控制基石

1. 这两个不等式不是“工具”&#xff0c;而是高维统计的呼吸节奏你翻开任何一本现代高维统计教材&#xff0c;翻到前五十页&#xff0c;几乎必然撞见 Hoeffding 和 Chernoff。但绝大多数人——包括刚学完概率论、信心满满来啃 MATH567 的同学——会把它们当成两张“查表用的公…

作者头像 李华
网站建设 2026/10/5 5:45:44

车载视觉技术落地指南:从算法选型到量产验证的完整路径

简介&#xff1a;这是一份面向机器视觉入门者及汽车制造工艺人员的PPT资料&#xff0c;系统讲解机器视觉在汽车行业中的检测、装配、测量、机器人引导、OCR/OCV、读码与分类等核心应用&#xff0c;并覆盖冲压、白车身、油漆、总装、动力总成等典型工位场景。资源为1个PPT文件&a…

作者头像 李华