首页 / 资讯中心 / 文章详情

3000张YOLO垃圾分类数据集:从格式转换到模型训练全指南

3000张YOLO垃圾分类数据集:从格式转换到模型训练全指南 ★ FEATURED ARTICLE
简介一份面向垃圾分类目标检测与智能分拣竞赛的数据集标签体系严格对应赛题要求覆盖有害垃圾、可回收垃圾、厨余垃圾和其他垃圾四类包含电池、过期药品、易拉罐、矿泉水瓶、小型蔬菜、瓷片与砖块等常见物品适合参赛学生、指导教师及YOLO检测开发者使用。压缩包共2000个文件其中1998个为XML标注文件内含目标框与类别信息与LabelImg等标注工具及YOLO训练流程兼容另有两个txt文件给出YOLOv5训练集与验证集划分整体约411.59MB对应约3000张垃圾图片可直接用于模型训练与评测。该资源已有1626人学习比赛导向性强、上手门槛低。入手后可免去自行拍摄、清洗与标注的繁琐流程按赛题类别快速开展训练、调参与精度验证决赛中垃圾种类、形状及投放数量随机变化该数据集还可作为基准扩展数据增强、测试多目标检测能力适合课程设计、毕业设计与竞赛备赛。1. 3000张图片够吗一份四类垃圾标注数据集能支撑起什么大家做智能垃圾箱、社区回收站视觉识别项目时最常问的一句话就是“垃圾分类打完标签的数据集四类垃圾共计3000张左右到底能不能用”我的回答是能而且3000张是小型CV项目里一个非常典型的分水岭——用来验证方案、训练出可演示的模型、给甲方看效果都足够但想直接扛住复杂光线和摄像头低画质的线上环境还没到那个量级。这篇就用这样一份数据集把从格式检查、训练验证到踩坑修复的完整路径讲清楚。适合谁做毕设、产品Demo、算法岗练手的人以及刚接手某小区智能垃圾桶项目的一线开发者。2. 这份数据集怎么用类目定义、目录组织与YOLO格式转换拿到一个数据集最忌直接开训。数据集的格式、类目边界和文件组织决定后续训练能不能顺利推进。我一般先做三件事确认四类垃圾的定义是否清晰、梳理原始标注格式、把数据切分成训练集、验证集和测试集。2.1 四类垃圾的类目边界比格式更重要垃圾分类数据集的类目通常按“可回收、有害、厨余、其他”划分也有按“塑料、纸张、金属、玻璃”划分的。如果标题里明确写了“四类垃圾”你要先搞清楚是哪套定义。因为这两套定义直接影响标注框的内容前者按垃圾的去向分类后者按材质分类同一个“沾了油的塑料饭盒”在这两套体系下会落入完全不同的框。看了实际数据后我发现不少打标数据存在类目歧义比如“纸杯”被部分人归为可回收被另一些人归为其他垃圾因为纸杯内壁有塑料淋膜严格来说确实属于其他垃圾。这种歧义在3000张规模下会造成约3%到5%的框错配训练时反映为验证集mAP不升反降、混淆矩阵里有固定的一对类互相渗透。处理方式把数据集里所有图片按类别抽查一遍生成一份类目说明明确“每类包含什么、不包含什么”。例如可回收只包含洁净的瓶罐纸箱厨余包含剩饭果皮茶叶渣有害包含电池灯管药品其他包含污染塑料、烟蒂、破旧陶瓷等。然后写一个脚本扫描所有标签文件把不符合规则的类别批量替换再把无法确定的样本单独挪出来人工复核。3000张数据量不大半天能清完这个成本非常值得。2.2 从Labelme的JSON到YOLO训练所需的TXT常见的数据集打标工具是Labelme产出的是一堆JSON文件里面记录了每个目标的类别和多边形点坐标。而YOLO系列模型训练需要的是TXT格式每行一个目标内容是“类别id 中心点x 中心点y 宽度w 高度h”且坐标要归一化到0到1之间。这段转换脚本我每次都会用到import json import os from PIL import Image CLASS_MAP {可回收: 0, 有害: 1, 厨余: 2, 其他: 3} def convert_json_to_yolo(json_path, output_dir): # 读取Labelme产出的JSON with open(json_path, encodingutf-8) as f: label_data json.load(f) # 读原图尺寸坐标归一化用 image_path label_data[imagePath] base_img Image.open(os.path.join(os.path.dirname(json_path), image_path)) img_w, img_h base_img.size txt_name os.path.basename(json_path).replace(.json, .txt) txt_lines [] for shape in label_data[shapes]: label_name shape[label] if label_name not in CLASS_MAP: continue class_id CLASS_MAP[label_name] points shape[points] # Labelme的矩形框用两点表示多边形取外接矩形 if shape[shape_type] rectangle: x1, y1 points[0] x2, y2 points[1] else: xs [p[0] for p in points] ys [p[1] for p in points] x1, x2 min(xs), max(xs) y1, y2 min(ys), max(ys) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h txt_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(os.path.join(output_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(txt_lines))这个脚本的逻辑不复杂先读JSON里的shapes再用原图的宽高做归一化最后拼成YOLO的标准格式。有两个参数要特别注意CLASS_MAP的顺序决定了训练时类别的索引必须和你后面写的数据配置文件里的names顺序保持一致否则模型训练时会把类别错位另外如果多边形标签的形状特别细长直接取外接矩形会框进很多背景这种样本建议回去重新标成矩形框或者直接放弃。清洗完标注后还要把坐标值限制在0到1之间。有些标签框可能因为人工打标时手抖出了图片边界归一化后超过1.0训练时YOLO会直接报错或者忽略这个框。标准做法是# 对超出边界的坐标做裁剪防止训练时加载报错 x_center min(max(x_center, 0.0001), 0.9999) y_center min(max(y_center, 0.0001), 0.9999) box_w min(max(box_w, 0.0001), 0.9999) box_h min(max(box_h, 0.0001), 0.9999)2.3 训练集、验证集、测试集的切分与比例3000张左右的数据集如果做目标检测我通常按80%、12%、8%来切。很多人习惯只用train和val测试集直接用验证集代替这在调参阶段没问题但最后汇报指标的时候会高估模型表现因为你已经在验证集上反复调过很久了验证集已经“脏了”。import os import random from shutil import copyfile random.seed(42) # 固定种子确保切分结果可复现 img_dir images label_dir labels train_dir train val_dir val test_dir test images [f for f in os.listdir(img_dir) if f.endswith(.jpg) or f.endswith(.png)] random.shuffle(images) train_cnt int(len(images) * 0.8) val_cnt int(len(images) * 0.12) # 剩余约8%作为测试集只在最终评估时使用 for i, img_name in enumerate(images): base_name os.path.splitext(img_name)[0] label_file os.path.join(label_dir, base_name .txt) # 有图无标签的样本训练时会直接报错必须跳过 if not os.path.exists(label_file): continue if i train_cnt: dest train_dir elif i train_cnt val_cnt: dest val_dir else: dest test_dir copyfile(os.path.join(img_dir, img_name), os.path.join(dest, img_name)) copyfile(label_file, os.path.join(dest, base_name .txt))随机种子42是固定值这样每次跑脚本切分结果完全一样方便复现。切分后要检查一下各类目标框的数量在三个子集里的比例。比如有害垃圾样本总量只有400张切分后测试集里可能只有20来个目标框数量太少评估结果波动会非常大。这种情况下可以先按类别做分层抽样而不是纯随机切分。3. 把3000张数据跑成模型YOLO训练配置与参数实测3.1 先写对数据配置文件再谈训练YOLO训练第一步不是敲训练指令而是把数据配置文件写对。数据配置文件本质上就是告诉模型“数据在哪、有几个类别、类别叫什么”。# garbage4.yaml # 数据集根目录下面的路径都基于这个目录 path: ../datasets/garbage_4cls # 图片目录YOLO会自行找同级的labels目录 train: images/train val: images/val test: images/test # 类别数量 nc: 4 # 类别名称顺序和TXT标签第一列的id一一对应 names: 0: recyclable 1: harmful 2: kitchen_waste 3: other_waste这个文件里有三个容易踩坑的地方一是path路径建议写成相对路径不要写绝对路径方便换机器训练二是训练时YOLO的DataLoader会根据图片路径自动找对应的labels目录要求目录名是labels而不是labellabels/train里的TXT文件名必须和images/train里的图片文件名一模一样三是names的索引顺序要和数据集标签里的分类索引完全对齐这个顺序一旦错了模型的预测结果就全都乱了而且很难从Loss上看出问题只有在最终预测时才会发现画出来的框类别是错的。3.2 训练命令与五个影响结果的关键参数数据配置写好之后训练命令本身其实就一长串参数的事。我拿YOLO系列来举例数据量只有3000张模型选择以轻量为主s级别就够不用硬上l或者x级否则很容易过拟合。yolo train \ modelyolov8s.pt \ datagarbage4.yaml \ epochs150 \ imgsz640 \ batch16 \ patience20 \ device0这五个参数是影响结果最直接的model用预训练权重启动而不是从零训练因为3000张的数据量不足以让模型从头学出好的特征提取能力epochs设150轮但配合patience20也就是连续20轮验证集指标不提升就提前停你不需要真的等150轮跑完imgsz设为640如果目标普遍比较大512也可以但640对垃圾分类这种物体尺寸不极端的场景更稳batch的默认值会因显存而变如果显卡告急降到8或4Loss曲线会更抖需要多跑一些轮次才能收敛device0是指定第一张GPU只有CPU的机器要去掉这个参数但训练速度会慢很多。一个真实的经验是3000张数据在yolov8s下单卡训练大约2小时能完成。训练结束后去runs/detect/train/目录下看结果重点看results.png里的Box Loss和Cls Loss曲线如果两条曲线都平稳下降说明模型在学习如果Cls Loss降到很低但Val Loss反弹就是过拟合。3.3 Loss掉了但效果不行检查这四类指标很多新手只看LossLoss降到0.02就觉得自己成功了结果拿去跑视频检测全是漏检。我不能说Loss不重要但训练结束必须看Precision、Recall、mAP50、mAP50-95这四个指标。这四个指标在验证集的val阶段已经算好了最终评估时再看测试集的结果。3000张数据的mAP50-95一般不会太高垃圾目标很多是曲面反光、半透明塑料袋这类数据连人眼看都费劲。常见水平是mAP50在0.8左右mAP50-95在0.5上下就已经可用了。如果mAP50不到0.6先别急着调训练参数回去看标注问题。另外注意混淆矩阵它能直接告诉你模型把哪两类搞混了比Loss曲线信息量大得多。如果训练结束后发现验证集指标不错但跑真实场景视频时翻车严重大概率是数据集里的图片都来自于网络截图跟目标部署环境差异太大。这种问题靠调参解决不了只能补充拍摄数据做增量训练。4. 四类数量不均衡的修法增强、过采样与类别权重4.1 先量化不均衡程度再决定修法四类垃圾在实际场景里数量天然不均衡可回收垃圾的瓶子瓶子很常见有害垃圾的电池灯管相对少见。如果数据集是随机采集的大概率“可回收”和“厨余”样本量偏多“有害”偏少。这种不均衡如果不处理模型会偏向多数类表现为有害垃圾的Recall特别低。修法分为数据层面和损失函数层面。动手之前先统计每个类别实际有多少个目标框注意是统计框的数量不是图片的数量因为一张图片里可能包含多个同类目标import os label_dir labels class_counts [0, 0, 0, 0] for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file), r, encodingutf-8) as f: for line in f: class_id int(line.strip().split()[0]) class_counts[class_id] 1 total_boxes sum(class_counts) for i, cnt in enumerate(class_counts): print(fclass {i}: {cnt} boxes, ratio{cnt / total_boxes:.2%})统计结果如果显示多数类和少数类的比例超过2:1就必须干预。我见过最夸张的一组数据四类里面“可回收”占了总数的一半“有害”只占一成训练出来的模型对有害垃圾几乎全漏。4.2 数据增强与过采样怎么配才不过拟合对于3000张规模的数据集数据增强是必须开的一环。YOLO自带一套增强参数可以在数据配置里直接控制强度# garbage4_augment.yaml 里追加这些配置 augment: true hsv_h: 0.02 hsv_s: 0.5 hsv_v: 0.3 degrees: 15 flipud: 0.5HSV色域扰动对垃圾检测很有用因为不同场景下灯光颜色差异很大黄色灯光和白色灯光下同一个塑料瓶的颜色差异可能超过模型能泛化的范围。翻转要分场景如果部署摄像头是固定朝下的俯拍上下翻转就没有意义把flipud设为0嵌入式或移动端手持拍照的场景上下翻转可以保留。degrees设为15度而不是更大因为垃圾在画面里的姿态通常不会完全颠倒。过采样的常见做法是把少数类图片复制几份放进训练目录。但如果只是纯粹复制模型很容易对同一批图产生记忆训练Loss低验证Loss高。建议复制后搭配轻微的几何变换比如随机裁切、平移几个像素再写回训练集。这样本质上是“离线增强”比复制原图效果更好。4.3 类别权重计算与训练时如何传入数据层面处理完还不够的话可以在损失函数上做文章。类别权重就是给样本少的类别更大的Loss权重让模型更重视学习这一类。计算方式一般有两种一是反频率权重样本越多权重越小二是用中位数频率权重避免极端不平衡让权重差距过大。这里给一个简单的反频率版本import os label_dir labels class_counts [0, 0, 0, 0] for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file), r, encodingutf-8) as f: for line in f: class_id int(line.strip().split()[0]) class_counts[class_id] 1 max_count max(class_counts) weights [max_count / cnt for cnt in class_counts] print(类别权重建议:, weights)得到的结果类似[1.0, 3.2, 1.4, 2.5]意思是“有害”类别的Loss放大3.2倍。在YOLO训练命令里可以通过cls等参数调节分类损失的权重这个值默认是0.5你可以按需设置。但要注意类别权重设置过大模型会对少数类产生过度拟合验证集上的多数类mAP反而下降。所以这是个“按需调参”的过程不要一上来就上权重先尝试数据层的过采样看效果不够再加权重。5. 数据集避坑指南我在这3000张图上踩过的四个坑这章全是血泪经验。我接手过一份别人打好标签的四类垃圾数据集文件名写得很正规图片质量看起来也正常但直接训练后折腾了整整两周才定位到问题。5.1 一个图多个目标却只标了一个框现象是最直观的训练集的Loss正常下降验证集mAP50在0.5左右就上不去了而且召回率Recall特别低。我检查标签文件发现很多图片里明明有三四个垃圾袋标签里却只有一个框。原因是打标员在切图时只标注了核心目标或者目标之间有遮挡标注时嫌麻烦只标了最明显的那个。解决方法是做一次“漏标自查”用训练好的模型对训练集做一遍预测找出那些置信度高但和原标签没有重叠的框把图片抽出来人工复核并补标。这个流程在3000张数据量下大约需要半天到一天时间。5.2 标签框离目标太近导致高置信度误检现象是模型推理时框普遍比目标稍大或稍小一点但就是贴不准目标边缘。更麻烦的是如果目标边缘有一点阴影或反光模型会在目标附近再产生一个多余的框。原因是打标规范要求“框要贴合目标边缘”这个做法在分类任务里没问题但在检测任务里框太紧会让模型学不到目标周围的上下文信息。垃圾和背景在颜色上高度相似完全没有上下文的模型很难稳定识别。解决方法是训练前对标签框做轻微外扩把标注框按中心点扩展5%到10%给模型一点“背景缓冲区”。如果数据集的框已经是紧贴的可以用脚本批量把框宽和高乘1.05再写回标签文件。5.3 压缩图和带水印图让Loss下不去现象是训练到前20轮时Loss在0.06附近震荡怎么调学习率都下不去。检查数据后发现数据集里混入了不少从网上爬来的缩略图分辨率只有300x300甚至更低还有些图片带了平台水印和边框标语。原因是模型把这行水印文字当作特征学进去了水印出现的位置、颜色各不相同导致Loss震荡。解决方法是先做数据清洗筛掉分辨率低于200x200的小图删除文件大小异常的损坏图import os from PIL import Image img_dir images min_size 200 for img_name in os.listdir(img_dir): path os.path.join(img_dir, img_name) try: with Image.open(path) as img: w, h img.size except Exception: print(f损坏图片直接移除: {img_name}) os.remove(path) continue if w min_size or h min_size: print(f分辨率过低需人工确认: {img_name} {w}x{h})注意一点分辨率低的图不一定都要删。如果图片内容真实且目标大而清楚可以保留作为困难样本只是对纯文本水印、分辨率极低的图片应当直接移除。我的习惯是先筛选出来人工过目一遍再批量处理。5.4 类目歧义一次性餐具算可回收还是其他现象是混淆矩阵显示“其他”和“可回收”两类的互相错检率明显高于其他类对。仔细翻标签发现同一场景下“一次性塑料餐盒”有的标为可回收有的标为其他。原因是打标阶段没有统一的判定标准打标员按自己的直觉理解类目。解决思路是重新定义各类的边界并批量修正。例如明确写死“塑料材质且目视洁净的瓶、盒、杯为可回收粘有食物残渣、油污或曾经装过液体的为其他。”然后写个脚本按图片文件名列表批量替换TXT里的类目id。这个动作必须在训练前完成否则模型学到的类别边界是模糊的。6. 3000张不够用切片、伪标签与增量训练三步数据量到了3000张训练一个Demo模型已经够用但真要部署到生产环境往往还需要继续扩展。这里有一个三步走的策略是我在多个项目里反复验证过的做法。6.1 大图切块把小目标变中目标如果原图是大分辨率手机拍摄图2048x1536这种直接缩放到640会让小目标变得非常小漏检率会明显上升。常见做法是把大图切成四个或九个重叠的块每块独立训练和推理。切图时必须同步切标签切块后目标框如果被截断需要重新计算被截断部分的坐标只保留面积占比超过50%的目标。这个处理能让模型在小图上学到更多细节推理时再把各块的检测结果拼回原图坐标。6.2 伪标签策略让模型帮你审核数据3000张之外的扩充最省力的来源是现场摄像头直接截帧。截帧后的画面没有标签如果全人工打标成本很高。我一般用训练好的模型先做预测把置信度高于0.6的目标框导成TXT作为伪标签再由人工快速审核。审核的重点不是“框有没有打对”而是“有没有明显错标”把明显错误的部分删掉剩下的就并入训练集。这个流程对刚起步的小数据集极为有效。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourceunlabeled_images/, conf0.6, save_txtTrue, save_confTrue, ) # 预测结果生成到runs/predict/labels/每行格式: class conf cx cy w h # 人工用可视化工具批量检查后把符合规范的txt同步进labels目录伪标签的阈值0.6是个保守值。生产环境里我见过有人用0.3结果错误标签太多增量训练后模型性能反而下降了。伪标签的准则很简单宁可多漏不要错标。6.3 增量训练时需要注意的旧类遗忘问题用原有best.pt作为预训练权重在新旧混合数据上继续训练这是一个标准的增量训练流程但有一个坑模型会在新数据上拟合得太快把旧数据里的特征“忘掉”。所以我混合数据时会保留至少三成旧数据并且把初始学习率调低大约是正常训练的十分之一让模型在新旧数据之间慢慢平衡。我当时做类似方案时最大的教训是别急着删掉旧模型它不只是你之前的成果更是增量训练的保护性起点。每次增量训练后都要单独在旧测试集上跑一遍指标如果旧类mAP下降超过两个点说明遗忘严重需要回调学习率或增加旧数据比例。这是3000张数据往更多数据走的一个必要习惯。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站