拓冰建站拓冰建站
首页 / 资讯中心 / 正文

YOLO垃圾分类数据集实战:VOC/COCO/YOLO三格式转换、划分与训练避坑指南

简介本资源为面向目标检测学习者的YOLO垃圾分类检测数据集适用于课程设计、毕业设计及算法入门实战帮助解决真实场景下垃圾分类数据难获取、标注格式不统一的问题。压缩包共2000个文件约410.27MB以1985个xml标注文件为主另含少量txt、html与py脚本分别对应标签数据、教程说明和划分工具。数据集采用labelimg标注标注框质量高同时提供voc、coco和yolo三种格式标签分文件夹存放可直接接入YOLO系列模型训练。资源还附赠环境搭建与训练案例教程覆盖Windows与Linux版本并提供训练集、验证集、测试集划分脚本可按需自行切分数据。目前已有1346人学习下载适合希望快速跑通垃圾分类检测流程、掌握数据格式转换与训练配置的读者参考使用。1. 拿到一个 10000 张的 YOLO 垃圾分类数据集先别急着开训你从群里或者某个资源站下到一个压缩包名字叫「YOLO垃圾分类检测数据集(含10000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar」。解压之后大概率是一堆文件夹JPEGImages、Annotations、images、labels、train、val还有几个 .py 脚本和一个 Word 教程。很多人第一反应是直接yolo train dataxxx.yaml然后报一堆路径错误、类别错位、标签全 0 的玄学问题。这个标题真正值钱的地方不是那 10000 张图而是「同一批图同时给了 VOC、COCO、YOLO 三种格式标签」这件事。VOC 的 XML 是给人看的、方便改框COCO 的 JSON 是给 mmdetection、detectron2 这类框架用的YOLO 的 txt 是给 ultralytics 系直接吃的。三种格式并存意味着你可以一套数据跑通多个技术栈也意味着你必须先搞清楚它们之间的坐标换算关系否则划分脚本一跑标签和图片就对不上了。这篇笔记面向两类人手里已经有类似压缩包、想把它跑起来的新手以及想搞清楚「多格式标签共存时怎么组织目录、怎么校验、怎么避免训练时 mAP 异常」的熟手。下面按「先看懂结构 → 再动手转换和划分 → 再训练 → 再排坑 → 最后讲进阶校验」的顺序走一遍每一步都给能直接抄的命令和脚本。2. 三种标签格式到底差在哪坐标、类别与目录约定2.1 VOC、COCO、YOLO 的坐标定义差异先把最容易翻车的地方说清楚三种格式的框坐标定义完全不同混用必错。VOC 的 XML 里bndbox存的是xmin, ymin, xmax, ymax是绝对像素坐标原点在左上角且是包含边界的整数。一个典型文件长这样annotation folderJPEGImages/folder filename000001.jpg/filename size width640/width height480/height depth3/depth /size object nameplastic/name bndbox xmin112/xmin ymin96/ymin xmax308/xmax ymax402/ymax /bndbox /object /annotationCOCO 的 JSON 里bbox是[x, y, width, height]同样是绝对像素但注意是宽高而不是右下角坐标而且category_id通常从 1 开始0 一般留给 background。这一点和 YOLO 从 0 开始编号直接冲突是类别错位的高发区。YOLO 的 txt 每行是class_id cx cy w h全部是归一化到 0~1 的相对值cx cy是框中心点。换算公式cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h提示归一化时如果用了错误的 img_w/img_h比如读的是缩略图尺寸而不是原图框会整体偏移训练时 loss 能降但 mAP 上不去这是最隐蔽的坑之一。2.2 目录组织一套图喂三种格式的推荐结构压缩包里常见的目录是扁平的直接拿去训练会乱。我一般整理成下面这样图片只存一份标签分目录dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── annotations_voc/ # 原始 XML保留用于改框 ├── annotations_coco/ # 转换出的 COCO json ├── classes.txt # 类别名一行一个 └── data.yaml # ultralytics 训练配置关键点是images/train/000001.jpg必须对应labels/train/000001.txt文件名不含扩展名严格一致。YOLO 训练时是按文件名去 labels 目录找同名 txt 的找不到就当作负样本背景于是你会看到「训练正常但什么都检测不到」。classes.txt的顺序就是 class_id 的顺序第 0 行对应 id 0。这个文件一旦定了VOC 转 YOLO、COCO 转 YOLO 都必须按它来映射不能各转各的。2.3 类别映射表多格式共存时最容易错位的一环垃圾分类常见类别是 4 类可回收物recyclable、厨余kitchen、有害hazardous、其他other。但不同来源的 VOC XML 里name可能写的是中文、英文、甚至拼音。COCO 的categories里 id 又可能是 1~4。所以转换前必须先建一张映射表统一 class_id统一名称VOC name 可能写法COCO category_id0recyclablerecyclable / 可回收11kitchenkitchen / 厨余22hazardoushazardous / 有害33otherother / 其他4这张表要落到代码里而不是靠脑子记。下面转换脚本里会用到它。3. 从 VOC 转 YOLO转换脚本与四个边界坑3.1 转换脚本XML 批量转归一化 txt假设你的 XML 在annotations_voc/图片在images_all/输出到labels_all/。脚本如下import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射VOC 里的 name - 统一 class_id NAME2ID { recyclable: 0, 可回收: 0, kitchen: 1, 厨余: 1, hazardous: 2, 有害: 2, other: 3, 其他: 3, } VOC_DIR annotations_voc IMG_DIR images_all OUT_DIR labels_all os.makedirs(OUT_DIR, exist_okTrue) def convert(xml_path): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text # 优先用 XML 里记录的 size避免重新读图 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in NAME2ID: print(f[skip] unknown class {name} in {xml_path}) continue cls_id NAME2ID[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 边界裁剪防止越界 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue # 丢弃退化框 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_name os.path.splitext(filename)[0] .txt with open(os.path.join(OUT_DIR, out_name), w) as f: f.write(\n.join(lines)) for f in os.listdir(VOC_DIR): if f.endswith(.xml): convert(os.path.join(VOC_DIR, f)) print(done)逻辑说明脚本读 XML 的size拿原图宽高而不是重新打开图片这样即使图片被移动也能转。类别通过NAME2ID统一映射遇到未知类别打印并跳过避免静默丢数据。坐标做了裁剪和退化框过滤防止xmax xmin这种脏标注产生负宽高。参数说明NAME2ID必须和classes.txt顺序一致OUT_DIR输出的 txt 文件名取自 XML 里的filename所以 XML 里的 filename 必须和实际图片名一致否则对不上。3.2 坑一XML 里的 filename 和实际图片名不一致现象转换后 labels 目录里有一堆 txt但训练时提示找不到对应图片或者图片被当负样本。原因很多数据集在整理时改过图片名比如加了前缀但 XML 里的filename没同步改。解决转换前先做一次一致性检查用下面这段脚本列出不匹配项import os xml_names {os.path.splitext(f)[0] for f in os.listdir(annotations_voc) if f.endswith(.xml)} img_names {os.path.splitext(f)[0] for f in os.listdir(images_all) if f.lower().endswith((.jpg, .png, .jpeg))} print(XML 有但图片没有:, xml_names - img_names) print(图片有但 XML 没有:, img_names - xml_names)3.3 坑二图片被缩放但 XML 记录的是原尺寸现象转出来的框整体偏小或偏大训练 loss 正常但框位置系统性偏移。原因XML 的size是原图尺寸但实际喂给训练的图片被预处理缩放过或者反过来。解决以实际图片尺寸为准重算。把脚本里读size的部分改成用 PIL 打开图片取img.size并加一句断言from PIL import Image img Image.open(os.path.join(IMG_DIR, filename)) img_w, img_h img.size assert img_w int(size.find(width).text), fsize mismatch: {filename}3.4 坑三类别名带空格或大小写不一致现象NAME2ID查不到大量框被 skip转出来的 txt 是空的。原因XML 里写的是Plastic 带尾空格或Recyclable首字母大写。解决在name obj.find(name).text.strip()之后统一.lower()并把映射表的 key 也全部小写。中文类别不受影响但英文类别必须做这一步。3.5 坑四空标签文件与负样本的取舍现象某些图片确实没有目标转换后生成空 txt训练时这些图被当作纯背景。原因YOLO 允许空 txt 表示负样本但比例过高会拉低召回。解决统计空 txt 比例超过 10% 就考虑剔除或补充标注find labels_all -name *.txt -empty | wc -l find labels_all -name *.txt | wc -l4. 划分脚本怎么写train/val 不能只按 8:2 随机切4.1 为什么随机划分在垃圾分类上会翻车垃圾分类数据集的采集往往有场景聚集性同一批图可能来自同一个小区、同一次拍摄。如果纯随机按 8:2 切训练集和验证集里会出现几乎相同的背景验证 mAP 虚高上线后一塌糊涂。常见做法是按「采集批次」或「图片前缀」分组后再切保证验证集里的场景训练时没见过。如果压缩包没给批次信息退而求其次先按文件名排序再按固定间隔抽样做验证集而不是random.shuffle。这样至少保证分布均匀。4.2 划分脚本图片和标签同步搬移import os import shutil import random random.seed(42) # 固定种子保证可复现 IMG_SRC images_all LBL_SRC labels_all OUT dataset for split in [train, val]: os.makedirs(f{OUT}/images/{split}, exist_okTrue) os.makedirs(f{OUT}/labels/{split}, exist_okTrue) names sorted([os.path.splitext(f)[0] for f in os.listdir(IMG_SRC) if f.lower().endswith((.jpg, .png, .jpeg))]) # 按间隔抽样做验证集避免场景聚集 val_idx set(range(0, len(names), 10)) # 每 10 张取 1 张做 val约 10% for i, name in enumerate(names): split val if i in val_idx else train # 找图片扩展名可能不同 for ext in [.jpg, .png, .jpeg]: src_img os.path.join(IMG_SRC, name ext) if os.path.exists(src_img): shutil.copy(src_img, f{OUT}/images/{split}/{name}{ext}) break src_lbl os.path.join(LBL_SRC, name .txt) if os.path.exists(src_lbl): shutil.copy(src_lbl, f{OUT}/labels/{split}/{name}.txt) else: # 没有标签就写空文件表示负样本 open(f{OUT}/labels/{split}/{name}.txt, w).close() print(train:, len(os.listdir(f{OUT}/images/train))) print(val:, len(os.listdir(f{OUT}/images/val)))逻辑说明random.seed(42)保证每次划分结果一致方便复现实验。用range(0, len(names), 10)做等间隔抽样比随机抽样更能打散场景聚集。图片和标签同步搬移缺标签的补空文件避免训练时报「label missing」。参数说明间隔 10 约等于 9:1想改成 8:2 就把步长改成 5。seed换掉会得到不同划分但同一 seed 下结果稳定。4.3 data.yaml 的写法与路径陷阱ultralytics 系训练靠一个 yaml 描述数据位置和类别path: /abs/path/to/dataset train: images/train val: images/val nc: 4 names: 0: recyclable 1: kitchen 2: hazardous 3: other注意path建议写绝对路径。相对路径在不同工作目录下启动训练会解析成不同结果是「本地能跑、换台机器就报找不到文件」的常见原因。names的顺序必须和classes.txt、转换脚本里的NAME2ID完全一致。4.4 划分后必做的三项校验划分完别急着训先跑三个检查# 1. 图片和标签数量是否一致 ls dataset/images/train | wc -l ls dataset/labels/train | wc -l # 2. 有没有越界坐标大于 1 或小于 0 awk {if($21||$31||$41||$51||$20||$30||$40||$50) print FILENAME} dataset/labels/train/*.txt | head # 3. 类别 id 是否超出 nc 范围 awk {print $1} dataset/labels/train/*.txt | sort -u第 3 条输出的最大 id 必须小于nc否则训练时直接报 index 越界。5. 训练与排坑从 yaml 到第一个能用的权重5.1 最小训练命令与关键参数环境按 ultralytics 官方方式装好后最小训练命令yolo detect train \ data/abs/path/to/dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ projectruns/garbage \ nameexp1参数说明model用预训练权重比从头训收敛快得多垃圾分类这种中等规模数据强烈建议用预训练。imgsz640是通用起点如果小目标多比如瓶盖、电池可以提到 960但显存和速度要权衡。batch根据显存调OOM 就减半。workers在 Windows 上设 0 或 2设太高容易卡死。5.2 训练日志里该盯哪几个数box_loss、cls_loss、dfl_loss三条曲线要一起看。正常情况是前期快速下降后趋缓。如果cls_loss一直不降多半是类别映射错了或者标签里类别 id 混乱。mAP50和mAP50-95是验证指标mAP50涨但mAP50-95不涨说明框位置不够准可能是归一化尺寸问题。5.3 避坑与排查五条血泪记录现象一训练启动就报No labels found。原因data.yaml里train路径指向的目录下没有 labels或者图片和标签不在同一父目录结构下。 解决确认dataset/images/train和dataset/labels/train同级存在且 yaml 里写的是images/train而不是train。现象二训练正常但推理时框全在左上角。原因标签坐标没归一化或者归一化时除错了尺寸比如除了 255。 解决抽查几个 txt确认所有值都在 0~1 之间且cx cy大致在 0.5 附近而不是几百。现象三mAP 一直是 0。原因类别 id 从 1 开始编号COCO 习惯但nc和names从 0 开始导致所有框的类别都越界被忽略。 解决统一改成从 0 开始或在转换时做id - 1。现象四验证集 mAP 很高实际用起来一塌糊涂。原因划分时场景泄漏验证集和训练集背景几乎一样。 解决按采集批次分组划分或加大验证集比例并人工检查验证集图片是否和训练集重复。现象五训练到一半 loss 变 NaN。原因标签里有退化框宽或高为 0或坐标越界导致除零。 解决转换脚本里加退化框过滤前面已给训练前用 awk 检查越界。5.4 COCO 格式什么时候用得上如果你后面想换 mmdetection 或做实例分割COCO json 就派上用场了。从 YOLO txt 转 COCO 的核心是把归一化坐标还原成绝对[x, y, w, h]并维护images、annotations、categories三个数组。转换时注意category_id从 1 开始image_id唯一annotation id全局唯一。这块脚本较长思路和 VOC 转 YOLO 对称关键是别把bbox写成[xmin, ymin, xmax, ymax]——COCO 要的是宽高。6. 进阶用可视化校验把标签问题在上线前挖出来训练前最值钱的一步不是调参是把标签画到图上看一眼。我习惯写一个可视化脚本把 YOLO txt 还原成框画在原图上随机抽 20 张拼成网格。很多坐标错位、类别错标、框漂移的问题肉眼看一遍就出来了比盯着 loss 曲线猜快得多。import os import random import cv2 NAMES [recyclable, kitchen, hazardous, other] COLORS [(0,255,0), (255,0,0), (0,0,255), (255,255,0)] def draw(img_path, lbl_path): img cv2.imread(img_path) h, w img.shape[:2] if os.path.exists(lbl_path): for line in open(lbl_path): parts line.strip().split() if len(parts) ! 5: continue cid, cx, cy, bw, bh int(parts[0]), *map(float, parts[1:]) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) cv2.rectangle(img, (x1,y1), (x2,y2), COLORS[cid % len(COLORS)], 2) cv2.putText(img, NAMES[cid], (x1, max(0,y1-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, COLORS[cid % len(COLORS)], 2) return img names [f for f in os.listdir(dataset/images/train) if f.endswith(.jpg)] random.seed(0) sample random.sample(names, min(20, len(names))) rows [] for n in sample: stem os.path.splitext(n)[0] img draw(fdataset/images/train/{n}, fdataset/labels/train/{stem}.txt) img cv2.resize(img, (320, 320)) rows.append(img) # 拼成 4x5 网格 import numpy as np grid np.vstack([np.hstack(rows[i*5:(i1)*5]) for i in range(4)]) cv2.imwrite(check_grid.jpg, grid) print(saved check_grid.jpg)逻辑说明脚本把归一化坐标乘回原图宽高还原成像素框再按类别上色。拼成网格后一眼能看出框是否贴合目标、类别颜色是否和物体对得上。如果发现某类框系统性偏移基本就是归一化尺寸或坐标定义错了。参数说明COLORS按类别数扩展sample数量按需调。random.seed(0)保证每次抽同样的图方便对比修改前后的效果。除了可视化还有两个我常做的验证一是用yolo detect val在验证集上跑一遍看每类的 P/R 是否均衡某一类特别低通常是该类样本太少或标注质量差二是把训练好的权重在几张训练集图片上推理如果训练集都检不出来说明标签或类别映射有硬伤别急着调超参。我自己的习惯是拿到任何多格式数据集先花半小时做「一致性检查 可视化抽查」再开始训练。这半小时能省掉后面几小时的瞎调参。垃圾分类这类场景类别边界本来就模糊比如沾了油的纸盒算可回收还是其他标注一致性比模型结构重要得多。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门