news 2026/10/17 4:29:31

垃圾分类数据集整理与YOLO训练避坑实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
垃圾分类数据集整理与YOLO训练避坑实战指南

简介:一份针对23年生活垃圾智能分类装置比赛的数据集,已完成四类垃圾的标注,可直接用于目标检测模型训练与验证,适合备赛团队和相关学习者。覆盖有害垃圾(电池、过期药品)、可回收垃圾(易拉罐、小矿泉水瓶)、厨余垃圾(小土豆、萝卜、胡萝卜)和其他垃圾(瓷片、鹅卵石、砖块)等类别,对应约3000张图片的标注信息,所选物品尺寸和形状贴近初决赛场景,能帮助模型适应抽签随机物品的识别需求。压缩包共2000个文件,主体为1998个XML标注文件,记录标注框类别与坐标,另含YOLOv5训练集与验证集的txt划分列表,包体约411.59MB,解压即可按列表组织训练和验证流程。已有1626人学习下载,适合正在备赛、需要高质量标注数据或快速搭建垃圾分类检测实验的选手与开发者。

1. 垃圾分类数据集:拿到3000张标注图之后的第一件事

打开压缩包先别急着开训练脚本。这份四类垃圾数据集总计三千张左右,标注框数量不小,但真正决定你这个垃圾分类检测项目能不能跑通的关键,不是图片数量,而是标注的一致性。四类垃圾说上去简单——可回收物、有害垃圾、厨余垃圾、其他垃圾——但每个类别是否分得够干净、有没有漏标和错标、train和val是否真正隔离,都要先靠代码验一遍。这篇文章按我平时接手一套标注数据的顺序来写:拆目录、验格式、做类别统计、整理成YOLO训练集、跑一轮训练,最后把四个最容易翻车的标注问题一次性讲透。新手能照着步骤复现,熟手可以直接跳到第4章避坑部分。

2. 数据再认识:四类边界、标注格式、目录结构与分布统计

2.1 四类垃圾怎么分:数据集里的类别定义与容易出现混淆的边界

拿到数据集第一步,先确认它采用的是国内最常见的四分类标准:可回收物、有害垃圾、厨余垃圾、其他垃圾。这套数据的类别文件夹或者标注标签名,大概率对应这四个中文类别或英文缩写。可回收物的典型对象是塑料瓶、纸箱、玻璃瓶、易拉罐;厨余垃圾是菜叶、果皮、剩饭;有害垃圾是废电池、灯管、过期药品;其他垃圾则是受污染的纸巾、一次性餐具、破旧陶瓷。类别边界看起来清楚,实际标注时却常常出现分歧:纸杯到底算可回收还是其他垃圾?沾了油的塑料盒算可回收还是厨余?这类样本在同一套数据里如果标注口径不一致,模型学到就是混乱的特征,这是后面训练翻车的最常见根源之一。

建议拿到数据先把类别映射表列出来,核对一遍每个类别对应的中英文标签名,因为后面写data.yaml和转换脚本时,名字错一个字符,模型就会多出一个你不想要的“幽灵类”。我整理数据时习惯建一张映射表,标注文件里的原始名称、用于训练的英文标签名、类别ID三者一一对应。以下是我笔下这类四类垃圾数据集的常见映射:

原始标注名训练用标签类别ID常见混淆对象
可回收物recyclable0纸杯、塑料餐盒
厨余垃圾kitchen1带汤汁剩饭、果皮
有害垃圾harmful2电池、灯管、药品
其他垃圾other3沾污纸巾、陶瓷碎片

映射表建好之后,接下来才是真正打开标注文件看格式。很多人觉得格式转换是小事,其实大多数数据集项目头一次训练失败,都栽在这个看似不起眼的环节上。

2.2 标注格式细看:VOC XML 与 YOLO txt 的差异及转换脚本

这套数据集的标注文件在压缩包里一般是两种形态之一:要么是每张图片对应一个XML文件,也就是VOC格式;要么已经是YOLO要求的txt文件。VOC格式存的是对象类别名和矩形框左上角、右下角的像素坐标,人眼看着直观,但YOLO训练不吃这套。YOLO需要的是归一化后的中心点坐标和宽高:x_center、y_center、width、height,四个值都除以图片宽高,取值范围在0到1之间,类别用整数ID表示。以下是我处理这种常见格式的转换脚本,直接把VOC XML转成YOLO txt:

import os import xml.etree.ElementTree as ET # 原始类别名到训练ID的映射 CLASS_MAP = { '可回收物': 0, '厨余垃圾': 1, '有害垃圾': 2, '其他垃圾': 3, } def xml_to_yolo(xml_path, img_w, img_h, out_txt_path): tree = ET.parse(xml_path) root = tree.getroot() # 取图片真实宽高,防止归一化时坐标系错位 size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) if w == 0 or h == 0: w, h = img_w, img_h lines = [] for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in CLASS_MAP: # 映射表之外的类别直接跳过 continue cls_id = CLASS_MAP[name] box = obj.find('bndbox') x1 = float(box.find('xmin').text) y1 = float(box.find('ymin').text) x2 = float(box.find('xmax').text) y2 = float(box.find('ymax').text) # 越界保护:超出图片边界的坐标强制裁剪到合法范围 x1 = max(0, min(x1, w)) x2 = max(0, min(x2, w)) y1 = max(0, min(y1, h)) y2 = max(0, min(y2, h)) if x2 <= x1 or y2 <= y1: continue # 退化框直接不写入 # VOC的像素坐标转YOLO归一化中心点格式 x_center = (x1 + x2) / 2.0 / w y_center = (y1 + y2) / 2.0 / h box_w = (x2 - x1) / w box_h = (y2 - y1) / h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(out_txt_path, 'w') as f: f.write('\n'.join(lines)) # 用法示例 xml_to_yolo('0001.xml', 640, 480, '0001.txt')

这段脚本的核心逻辑是把XML里的绝对像素坐标除以图片宽高,得到相对坐标。绝大多数转换脚本跑出来的坑都在两个地方:一是忘了处理坐标越界,标注员手滑拉出图片边界,坐标可能是负值也可能大于宽高,不裁剪的话训练时损失函数会算出一个离谱的loss;二是类别ID不连续,原始XML里存在一些没进映射表的干扰标签,不跳过的话会造成类别错位。脚本里保留class_id按CLASS_MAP重新编号的做法,比直接把原始类别文本写进txt要稳妥得多,因为后面换模型时不会因为标签文本大小写不一致而出错。

2.3 统计脚本:每类图片数、每类框数、小目标占比一次看清

把XML转成txt之后,先别急着训练,还要跑一个分布统计。这一份数据集约3000张图片,四类样本大概率不是平均分配的。常见的情况是厨余垃圾和可回收物样本多,有害垃圾因为实际场景里少见所以只有两三百张。这种分布如果不提前知道,后面训练时模型很容易把样本量小的类别彻底忽略掉,尤其是有害垃圾这种特征本身又杂又少的类别。

import os from collections import Counter label_dirs = ['labels/train', 'labels/val'] name_list = ['recyclable', 'kitchen', 'harmful', 'other'] cls_counter = Counter() small_box_counter = Counter() # 面积小于图片1%的框 for ld in label_dirs: for fname in os.listdir(ld): if not fname.endswith('.txt'): continue with open(os.path.join(ld, fname), 'r') as f: for line in f: parts = line.strip().split() cls_id = int(parts[0]) cls_counter[cls_id] += 1 # 记录小目标:宽或高小于0.1 box_w = float(parts[3]) box_h = float(parts[4]) if box_w * box_h < 0.01: small_box_counter[cls_id] += 1 total = sum(cls_counter.values()) print('总框数:', total) for i, name in enumerate(name_list): print(f'{name}: {cls_counter[i]} 框, 小目标占比 {100.0 * small_box_counter[i] / max(1, cls_counter[i]):.1f}%')

这段统计脚本我在每个数据集上都强制跑一遍,输出结果能直接看出四类数据是否存在严重不均衡,也能提前知道这批数据里小目标占比高不高。小目标占比高的数据集,训练时的imgsz就不能设太小,否则瓶子上的小标签、远处的垃圾袋根本看不清。统计完把类别分布记到一张表里,这个表格后面做样本均衡和数据增强时要用,如果某一类框数只有另一类的五分之一,训练参数里就得考虑针对性调整。

3. 训练YOLO系列:目录整理、train/val划分、超参设置与输出验证

3.1 把数据集整理成YOLO标准目录结构

拿到这套数据后,最稳妥的做法是把它规整成YOLO官方的目录形态:images目录下分train和val,labels目录下也分train和val,两边的文件名一一对应,图片是jpg或png都行,标签必须是与图片同名的txt。目录结构如下:

dataset/ ├── images/ │ ├── train/ # 约2400张 │ └── val/ # 约600张 ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

data.yaml是YOLO读取数据集配置的关键文件,内容包括训练和验证图片路径、类别数量、类别名称列表。路径建议用相对路径,这样整个dataset文件夹拷到其他机器上不会因为绝对路径不同而报错。一个可以用的data.yaml是这样:

train: ./images/train val: ./images/val nc: 4 names: ['recyclable', 'kitchen', 'harmful', 'other']

这里nc必须与names列表长度一致,写错会导致训练时标签索引越界,报错信息又会指向莫名其妙的IndexError。同时注意names的顺序必须与第2章里CLASS_MAP的ID保持一致,否则模型训练时看到的类别和推理时预测的类别就错位了。我自己见过太多次names顺序和标签ID对不上,训练集mAP看着挺高,实际推理全在乱猜。

3.2 train/val划分:按场景批次划分,而不是随机洗牌

多数人拿到数据集直接random split,按9:1随机切。这个做法在场景单一的数据集上问题不大,但如果这套垃圾数据本身就是按多个拍摄场景采集的,比如室内垃圾桶、户外环卫、食堂餐盘区各拍了一批,随机切分很容易让同一个场景的图片同时出现在train和val里,验证集指标虚高,部署到新场景直接失灵。我处理这种数据集时更倾向于按文件名前缀或目录批次来划分,例如图片名中带有scene1、scene2、scene3前缀,就按前缀分组切。

import os import shutil import random img_root = 'source_images' train_dir = 'dataset/images/train' val_dir = 'dataset/images/val' label_root = 'source_labels' train_lbl = 'dataset/labels/train' val_lbl = 'dataset/labels/val' os.makedirs(train_dir, exist_ok=True) os.makedirs(val_dir, exist_ok=True) os.makedirs(train_lbl, exist_ok=True) os.makedirs(val_lbl, exist_ok=True) # 按图片前缀分组,确保同一场景的图片不跨集合 groups = {} for fname in os.listdir(img_root): prefix = fname.split('_')[0] # 例如 'scene1' groups.setdefault(prefix, []).append(fname) val_prefixes = set(random.sample(list(groups.keys()), k=max(1, len(groups) // 5))) for prefix, files in groups.items(): for fname in files: is_val = prefix in val_prefixes img_dst = val_dir if is_val else train_dir lbl_dst = val_lbl if is_val else train_lbl shutil.copy(os.path.join(img_root, fname), os.path.join(img_dst, fname)) lbl_name = fname.replace('.jpg', '.txt').replace('.png', '.txt') lbl_src = os.path.join(label_root, lbl_name) if os.path.exists(lbl_src): shutil.copy(lbl_src, os.path.join(lbl_dst, lbl_name))

这段脚本的核心是按前缀分组后抽20%的组进val,而不是随机抽20%的图片。典型的数据泄露场景是同一批垃圾放在同一个背景里拍了多张,随机切分后,模型在训练时记住了背景纹理,而不是垃圾本身的特征,val数据里又出现同一背景,mAP虚高到0.9以上,换真实场景就崩。按前缀分组能在一定程度上避免这种信息泄露。

3.3 训练脚本与关键超参:epoch、batch、imgsz与增强配置

整理好目录就可以开训练了。我用YOLOv8n起步,因为这份数据规模只有3000张左右,属于中小规模数据集,用轻量模型足以判断数据质量,也方便快速迭代。如果一上来就用YOLOv8x,训练时间拉长几倍,翻车后排查成本太高。以下是训练脚本框架:

from ultralytics import YOLO # 加载预训练权重,用COCO上的通用特征做迁移学习 model = YOLO('yolov8n.pt') # 训练参数说明: # epochs: 数据量中等,100轮足够,配合早停防止过拟合 # batch: 16,显存不够就降到8 # imgsz: 640,若小目标占比高可考虑800,但会明显变慢 # patience: 15轮val指标无提升就早停 model.train( data='dataset/data.yaml', epochs=100, batch=16, imgsz=640, patience=15, device='0', workers=4, lr0=0.01, augment=True, cache=True, )

参数最关键的是patience和imgsz。patience设太小,比如5,模型还没收敛就被早停掐掉,val指标看着还行,实际上泛化根本没到最好;设太大,又会浪费算力。一般中等数据集patience取15到20比较合适。imgsz方面,如果统计脚本里发现小目标占比超过20%,把imgsz提到800能明显提升小目标的召回率,代价是训练时间增加一半以上。augment默认开启,它会自动做随机翻转、缩放、色彩抖动等操作,这相当于变相扩充了垃圾样本的多样性。

训练结束后,YOLO会在runs/detect/train目录输出weights/best.pt和weights/last.pt,还会生成confusion_matrix.png、results.png等图表。results.png里的val曲线是判断有没有翻车的第一道关:正常情况下mAP50应该在20轮之后逐步爬升到0.7以上,如果曲线一直贴着底部,大概率是标签格式或类别映射出了问题,直接去跑第4章的排查脚本。

4. 避坑:四类垃圾标注数据集里最常见的四个翻车点

4.1 现象:训练loss很漂亮,验证mAP却几乎为零

训练过程中loss从2.0降到0.05,损失曲线非常干净,但验证集mAP50一直在0.01附近打转。整体看好像没学进去,但训练集本身也似乎没拟合。这个问题尤其隐蔽,因为loss和mAP的反馈完全矛盾。

原因出在类别ID错位。数据集原始标注里有害垃圾可能是第2类,但转换脚本或data.yaml里名字顺序写成了recyclable、kitchen、other、harmful,于是原来ID为2的类别在训练时被当作other,预测结果和真实标签永远对不上。模型所有类别都学到了,但名字是错位的,表现在指标上就是mAP零。

解决方法是训练前先跑一次分布对照:把labels/train里每个txt的第一列ID统计出来,再与data.yaml的names逐一对应,人工抽查十张图确认ID2确实是电池或灯管照片。我在转换脚本里额外输出一个类别分布日志,复制到训练机上一核对,所有ID错位当场现形。

4.2 现象:有害垃圾类别在预测中完全消失,混淆矩阵里那一行列全是0

另一个高发问题是有害垃圾样本太少,四类总计三千张,有害垃圾只有两百张,其中电池占一半,灯管、药品占另一半。模型训练时把有害垃圾当成背景噪声忽略掉,推理时无论输入什么,都只输出可回收物和厨余垃圾。

原因是类别不均衡外加特征差异不够明显。电池与可回收物里的易拉罐形状相似,灯管与玻璃瓶都是长条形,在浅层特征上很难区分。解决思路有两个方向:一是从数据侧做样本均衡,对有害垃圾图片做copy-paste增强或Mosaic增强时提高参与概率;二是从损失侧入手,给YOLO的cls_loss按类别比例加权。我一般先在train阶段打开增强,然后单独把有害垃圾类别的图片重复采样一份做二次微调。最直接有效的还是回源头补拍一批有害垃圾照片,但数据集已定死的情况下,先把增强参数拉满再说。

4.3 现象:训练报错IndexError或RuntimeError,提示标签索引大于类别数

模型训练到第一个epoch就崩,报错信息指向某个txt里的类别ID是6,而data.yaml里nc=4。打开那个txt文件看到第一列写的是6,位置信息却正常。出现这个现象的原因一般是数据集里混入了不属于四类的其他标签,比如标注员在标注时顺手标了树枝、烟头这种对象,转换脚本CLASS_MAP没覆盖,默认落到了其他ID上。

解决方法是把所有标签文件整体扫描一遍,找出不在0到nc-1范围内的ID,打印出对应的图片文件名和原始标注文本,逐条确认是否可以归类到四类之中。无法归类的直接删除该标签行,图片保留,作为负样本参与背景训练。自那以后我每个数据集训练前都强制跑一遍越界ID扫描,几秒钟的事能省下半天的排错时间。

4.4 现象:同一个类别标注框出现两种尺度,e.g. 瓶子被标成大框套小框

可视化val预测结果时发现,同一个塑料瓶图片上出现了两个重叠预测框,IoU很高但置信度都差不多,非极大值抑制压不掉。往深里追,训练过程中的标注就存在重复框:数据集里某一部分图片的瓶子既有旧标注员拉的大框,又有后来补标的小框,两套框同时存在。

这类问题在训练指标上表现得并不明显,mAP可能还挺高,因为两个框至少有一个命中,但它会在真实推理时让NMS输出不稳定的结果,置信度被两框均分。通用解决思路是清洗重复框,用两框IoU超过0.7且类别相同就合并,保留面积较大的那个。注意不能简单删除重叠框,因为有些场景下合格框就是一大一小,比如大框是包装盒,小框是盒子上的商标,这属于真实语义差异,只有在同一目标同一语义层级下的框才需要合并。从这个角度下来,我一般清洗逻辑加一个约束:只有两个框中心点距离小于较小框宽度的20%才判定为重复框,避免误合并。

4.5 现象:混淆矩阵显示“其他垃圾”和“可回收物”互相误判,比例高达三成

四类数据里,其他垃圾和可回收物这两个类别之间的误判率最高。原因不难理解:纸巾、陶瓷碎片、一次性餐具,在视觉上和塑料瓶、纸壳的纹理背景重叠太多,尤其是低光或杂乱背景下,边界本身就不清晰。如果标注阶段对“沾了食物残渣的塑料瓶”这种模糊样本处理不一致,模型学到的就更是两头摇摆。

解决这类问题不能只靠数据增强,需要回头审视数据集是否在标注说明里给了明确的判定规则。比如把“是否污染”作为区分可回收物和其他垃圾的第一准则,再检查图片中是否存在污染痕迹。如果数据集本身没有这种规则,工程上的做法是选出误判最集中的一批样本,重新修正标签,然后把修正后的单独划成一个小验证组,用来观察这个类别对的混淆比例是否下降。数据清洗阶段反复打磨出来的边界质量,比模型换什么结构都管用。

5. 把模型的输出再逼一把:用验证批次图与置信度阈值做人工挑错

训练结束不代表交付完成。我拿到best.pt后第一件事不是去部署,而是先跑一轮带保存的预测,把标注框、类别名、置信度打印在图上,然后人工看一批推理结果。YOLO提供了现成的val批处理,通过命令行或Python都能产出带框的png:

yolo predict model=runs/detect/train/weights/best.pt source=dataset/images/val conf=0.35 save=True

用Python接口可以让保存路径和打印内容更可控:

from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') # conf阈值默认0.25,对四类垃圾场景调到0.35能明显减少误报 results = model.predict( source='dataset/images/val', conf=0.35, iou=0.5, save=True, project='runs/val_samples', )

conf调高后,低置信度的误检框会被过滤掉,但也会漏掉一些真实的小目标,所以这里需要一个平衡点。我通常先在conf=0.25跑一遍,看误检多不多,如果垃圾袋上出现大量把褶皱识别成瓶子或盒子的框,就往上调到0.35再跑一遍,对比两批图判断哪一批更符合实际场景的容错要求。

人工挑错时重点看图里两类问题:一是同类标注框漏检,例如一排垃圾桶中只检出了前面的两个;二是类别误判,例如玻璃瓶被标成其他垃圾。yaml里的类别分布表还要结合第4章无人值守时无法规避的概率问题考虑——最终使用环境里背景变了,模型输出的类别置信度还是会跟着变。一个常用的做法是统计一定数量真实现场抓拍的置信度分布,选择一个让漏检率最低的阈值,而不是直接用训练时默认的0.25。

那里面的验证批次图往往最能暴露问题。如果发现某几类在真实场景里集体误判,先别急着调模型结构,回到训练数据和增强参数里查类别边界定义是否一致。从那以后我再拿到任何标注数据集,都会强制走一遍校验脚本加可视化挑选的流程,确认四类垃圾的判别边界和输出质量经得起真实场景检验,才敢交付给下游。慢是慢一点,但至少不会在关键时刻翻车。希望帮到你。

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

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

SVM实战调参指南:从医疗影像到工业质检的高维小样本分类

1. 这不是教科书里的SVM&#xff0c;而是我调参调到凌晨三点后写下的实战笔记支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;这六个字&#xff0c;我在十一年前第一次看到时&#xff0c;以为它只是统计学习里一个带点几何美感的数学玩具——画几条超…

作者头像 李华
网站建设 2026/10/17 4:29:25

artcraft:参数化设计如何让非设计师稳定产出视觉资产

1. 从“artcraft”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“artcraft”这个项目标题&#xff0c;我的直觉是&#xff1a;这大概率是一个把“艺术创作”和“工程实现”缝合在一起的东西。art 是艺术&#xff0c;craft 是手艺、工艺&#xff0c;合在一起就是“艺…

作者头像 李华
网站建设 2026/10/17 4:29:15

2026毕设实战:SSM+Vue酒店员工管理系统完整实现指南

每年到了毕业季&#xff0c;都会有人在技术群里问同一个问题&#xff1a;“SSM Vue 这套组合现在还能不能选&#xff1f;”我的回答始终是&#xff1a;能&#xff0c;而且很能。尤其是酒店员工管理系统这种成熟度极高的业务题&#xff0c;它既不炫技&#xff0c;也不至于让你在…

作者头像 李华
网站建设 2026/10/17 4:27:47

Python股票数据管道:生产级爬虫、清洗与回测闭环

简介&#xff1a;本资源是一套面向Python初学者与金融数据分析入门者的实战教学包&#xff0c;聚焦股票大数据采集、清洗、分析与可视化全流程&#xff0c;解决金融领域数据获取难、处理杂、分析浅等实际问题。压缩包为7z格式&#xff0c;大小4.47MB&#xff0c;虽未提供具体文…

作者头像 李华
网站建设 2026/10/17 4:27:41

拖拽式AI应用工厂实战:从智能表单到插件开发

我第一次用拖拽式AI应用工厂干活&#xff0c;是在一个被需求方催得很紧的智能表单项目上。把ModelEngine的节点一个个拖进画布、连上线、配置好参数&#xff0c;原本计划两周的交付&#xff0c;三天就跑到提测。这个反差让我对应用编排这件事的态度从怀疑变成了深度体验。后来我…

作者头像 李华