ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLO垃圾分类检测:VOC转YOLO格式与训练避坑指南

YOLO垃圾分类检测:VOC转YOLO格式与训练避坑指南 简介面向垃圾分类与目标检测研究的标注数据集适配YOLO等主流目标检测算法覆盖纸箱、纸张、塑料、铝、玻璃、硬纸板、瓶子与塑料瓶等常见生活垃圾类别适合算法入门者、环保项目开发者以及需要标准标注语料的研究人员用于模型训练、调参、评估与效果验证。压缩包共2000个文件均为XML标注文件总大小348.31MB符合VOC格式规范可直接用于YOLO系列模型训练也可通过脚本将XML转换为TXT等其他标注格式。XML内容记录目标类别与边界框坐标便于直观理解数据标注结构辅助垃圾分类检测中的定位与分类任务。目前已有346人学习资源目录组织清晰能够帮助使用者在较少时间内构建垃圾分类训练集显著减少人工标注成本同时支撑铝制品、玻璃瓶等易混淆类别的精细化检测调优兼顾教学与实战是垃圾检测领域实用的基础数据。1. 拿 YOLO 做垃圾分类检测13707 张带标签图像能直接开训吗做垃圾检测项目时最头疼的往往不是模型选型而是数据从哪来。自己拍、自己标几千张图就得耗掉一两周标注规范稍不统一后面训练时类别错乱、bbox 偏移全来了。这份 YOLO 垃圾检测数据集共 13707 张图像覆盖纸箱、纸张、塑料、铝、玻璃、硬纸板、瓶子、塑料瓶八个常见可回收类别标签文件齐全解压就能接入训练管线。适合正在做智能回收箱、环卫巡检或垃圾分类竞赛的开发者也适合刚接触目标检测、想用一份规范数据集跑通 YOLO 全流程的新手。规模上比公开的 TrashNet约 2500 张大不少且类别贴近国内回收场景实用价值更高。拿到压缩包后第一件事不是急着训练而是先确认标签格式能不能被 YOLO 直接消费。文件名以img_0305_11337.xml这种形式命名说明采用的是 PASCAL VOC 的 XML 标注格式需要先做格式转换和数据集划分才能交给 YOLO 训练。2. 解析标注文件先从 XML 里把类别和边界框读出来2.1 看清楚 XML 里到底存了什么解压后随便打开一个 XML能看到典型的 VOC 结构folder、filename、size里是图像宽高和通道数object节点里包含name类别名和bndboxxmin、ymin、xmax、ymax。这里的坐标是像素绝对值左上角原点。转换前先统计全部 XML 的类别分布能提前发现两类问题一是标签名拼写不一致比如cardboard和card_board混用二是某些类别样本数过少直接训练会导致 AP 很低。我一般会写个快速脚本跑一遍全量统计确认无误再做转换。import xml.etree.ElementTree as ET import glob from collections import Counter xml_files glob.glob(annotations/*.xml) class_counter Counter() for xml_file in xml_files: tree ET.parse(xml_file) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() class_counter[name] 1 print(fXML 文件总数: {len(xml_files)}) for cls, cnt in class_counter.most_common(): print(f{cls}: {cnt})这段代码用glob匹配所有 XML用Counter统计类别频次。如果发现某一类只有几十张后续可以考虑做数据增强或者干脆合并到相近类别。类别名中的空格和大小写也要统一否则转换后的 class id 会错位。2.2 找出无效标签宽高为 0 的 bbox 是训练毒药还有一种情况很隐蔽XML 解析正常但xmax和xmin相等或者ymax小于ymin。这类无效框在训练时会导致损失函数计算异常轻则训练发散重则直接报错。检查逻辑很简单遍历每个bndbox验证xmax xmin且ymax ymin同时框面积不要小于 10 像素太小的框对下采样后的特征图几乎没有贡献。invalid_files [] for xml_file in xml_files: tree ET.parse(xml_file) root tree.getroot() for obj in root.findall(object): bbox obj.find(bndbox) xmin int(float(bbox.find(xmin).text)) ymin int(float(bbox.find(ymin).text)) xmax int(float(bbox.find(xmax).text)) ymax int(float(bbox.find(ymax).text)) if xmax xmin or ymax ymin: invalid_files.append((xml_file, xmin, ymin, xmax, ymax)) print(f无效标注文件数: {len(invalid_files)}) for item in invalid_files[:10]: print(item)跑完后如果 invalid 列表为空说明这份数据集的标注质量比较靠谱。如果存在少量无效框直接过滤掉对应目标即可不建议手动去改坐标——量大的时候改不过来而且容易引入新错误。3. 把 VOC 转成 YOLO 格式转换脚本与四个边界坑3.1 YOLO 标签格式为什么是归一化坐标YOLO 训练需要的是每张图对应一个同名的.txt文件每行格式为class_id x_center y_center width height坐标全部归一化到[0, 1]。归一化的好处是模型不受输入分辨率影响训练时任意缩放图片都不会破坏标签的有效性。转换公式很简单x_center (xmin xmax) / 2 / widthwidth (xmax - xmin) / widthy 方向同理。注意这里除以的是图像原始宽高不是 bbox 的宽高。3.2 转换脚本与类别映射类别映射我建议做成字典方便后续调整顺序。YOLO 的 class id 从 0 开始所以第一个类别就是 0。转换时要把 label 目录名和图像目录名严格对起来否则训练时找不到对应标注。import os import glob import xml.etree.ElementTree as ET class_mapping { cardboard: 0, paper: 1, plastic: 2, aluminum: 3, glass: 4, metal: 5, bottle: 6, plastic_bottle: 7 } def convert_voc_to_yolo(xml_file, output_dir): tree ET.parse(xml_file) root tree.getroot() img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) base_name os.path.splitext(os.path.basename(xml_file))[0] output_txt os.path.join(output_dir, base_name .txt) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_mapping: print(f跳过未知类别 {name} 在 {xml_file}) continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height lines.append(f{class_mapping[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(output_txt, w) as f: f.write(\n.join(lines)) xml_files glob.glob(annotations/*.xml) os.makedirs(labels, exist_okTrue) for xml_file in xml_files: convert_voc_to_yolo(xml_file, labels) print(f转换完成共处理 {len(xml_files)} 个 XML 文件)这里几个参数要留意class_mapping的 key 必须和 XML 中的name完全一致大小写敏感img_width和img_height取的是 XML 里size节点的值不要自己去读图片算避免因图片元信息不一致导致归一化出错。.6f格式保留 6 位小数足够满足 YOLO 的精度要求。3.3 边界坑之一图片和 XML 文件名不匹配这是最常见的翻车点。有时 XML 里filename写的是img_0305_11337.JPG但实际文件是.jpg小写有时annotations目录里有 XML但images目录里根本没有对应图片。转换之后一定要做一次交叉校验确保labels下每个 txt 都能找到对应图片否则训练时Dataset会静默跳过导致实际参与训练的图片数远小于预期。3.4 边界坑之二图像尺寸读取位置不对有些 XML 的size节点里宽高是反的或者用depth标记通道数时类型不对。严格来说训练时会重新读取图片尺寸做 resize标签里的归一化坐标只要按 XML 里的尺寸计算就不会错。但如果 XML 和图片真实尺寸不一致看起来不明显的偏差会在缩放后被放大。3.5 边界坑之三类别 id 不连续如果原 XML 中有 12 个类别但某些类别被过滤掉后剩余 8 个转换成 YOLO 格式时 class id 必须重新按 0-7 连续编号不能保留原 id。YOLO 的类别数是写死在配置文件里的如果标签里出现class_id9而nc8训练时索引越界或错位损失曲线会直接乱掉。3.6 边界坑之四多类别目标的输出顺序一张图里多个目标时txt 文件里每行一个目标行与行之间没有顺序要求。但要注意不能出现空行否则load_dataset解析时可能报错。转换脚本里用lines列表收集再有条件写入能确保文件格式干净。4. 训练配置与模型选型13707 张图该用哪个 YOLO 版本4.1 数据集划分训练、验证、测试比例怎么定13707 张图规模不算小按照 8:1:1 划分能得到约 10965 张训练图、1370 张验证图、1372 张测试图。划分时要保证类别分布均衡不能简单随机切——如果随机切完发现某个类别在测试集里只有个位数那评测结果就不具备参考意义。我用sklearn的train_test_split加stratify参数按类别分布做分层划分。import glob import random from sklearn.model_selection import train_test_split image_files glob.glob(images/*.jpg) random.shuffle(image_files) train_files, test_files train_test_split( image_files, test_size0.2, random_state42 ) val_files, test_files train_test_split( test_files, test_size0.5, random_state42 ) def write_split_file(files, output_path): with open(output_path, w) as f: for file_path in files: f.write(file_path \n) write_split_file(train_files, train.txt) write_split_file(val_files, val.txt) write_split_file(test_files, test.txt) print(f训练集: {len(train_files)}) print(f验证集: {len(val_files)}) print(f测试集: {len(test_files)})random_state42固定随机种子保证多次执行划分结果一致。test_size0.2先切出 20%再对这部分二等分得到验证集和测试集最终比例是 80/10/10。分层划分需要自己写更细节的代码train_test_split的stratify参数接收的是标签数组这里按图片级别不太好直接传所以我通常先按图片的“主类别”做简化分层或者干脆人工检查分布。4.2 YOLOv5 还是 YOLOv8小数据集上的选择逻辑13707 张的规模YOLOv5s 和 YOLOv8s 都能跑得动。YOLOv8 在 C2f 模块和 Anchor-Free 头上有优势小目标检测效果略好YOLOv5 胜在生态成熟文档和预训练权重更丰富部署时量化工具链也更完整。如果你后续要部署到 Jetson 或嵌入式设备YOLOv5s 转 TensorRT 的坑相对少如果追求精度上限且 GPU 显存不算紧张YOLOv8s 更合适。数据集的data.yaml这样配置train: train.txt val: val.txt test: test.txt nc: 8 names: 0: cardboard 1: paper 2: plastic 3: aluminum 4: glass 5: metal 6: bottle 7: plastic_bottletrain和val字段可以写绝对路径也可以写 txt 文件路径YOLO 会按其中的内容逐行读取图片路径。nc必须为 8顺序要和转换脚本里的class_mapping保持一致顺序错了模型训练出来 AP 高也没用推理时类别全对不上。4.3 超参数的起点imgsz 和 batch_size我一般先用imgsz640、batch_size16起步。如果 GPU 显存只有 8Gbatch_size8也是可以的。初始学习率保持默认的lr00.01不用动跑完前 50 个 epoch 看 loss 下降趋势再决定是否调低。数据增强方面YOLOv8 默认开启 mosaic对小目标检测有帮助但 mosaic 在训练后期偶尔会导致目标被裁掉一半如果发现验证集的 mAP 在中间 epoch 波动异常可以尝试在最后 20 个 epoch 关闭 mosaic。yolo train datadata.yaml modelyolov8s.pt epochs100 imgsz640 batch16 device0这条命令里modelyolov8s.pt会从官方地址下载 COCO 预训练权重用它做迁移学习的初始权重比随机初始化收敛快得多。device0指定第一块 GPUCPU 训练的话改成devicecpu但 13707 张图在 CPU 上跑 100 epoch 会等到怀疑人生。4.4 训练过程监控loss 和 mAP 该看哪一个训练时终端输出的box_loss、cls_loss、dfl_loss是三个核心指标。box_loss下降说明定位在收敛cls_loss下降说明分类在收敛。如果两者一个降一个不降大概率是某一类别的样本太少或者标注噪声大。验证集 mAP 每 10 个 epoch 看一次趋势不用每个 epoch 都盯避免被单次波动干扰判断。5. 踩坑记录垃圾检测数据集训练中的五个典型翻车现场5.1 类别不平衡导致 mAP 虚高现象训练完成后整体 mAP0.5 达到 0.85看起来不错但查看每个类别的 AP 后发现aluminum只有 0.3glass只有 0.2。原因数据集中paper和cardboard占了大头模型对高频类别过拟合低频类别几乎没有学到有效特征。整体 mAP 被高频类别拉高。解决先统计类别分布对低频类别做复制增强或在线增强调高hsv_h、hsv_s、flipud概率。我在这个数据集上把aluminum和glass的翻转概率从默认的 0.5 提到 0.8同时关闭了 mosaic 中的随机擦除有效框保留率明显提升。5.2 XML 中类别名带空格和特殊字符现象转换后的标签文件里有一行class_id0开头正常但训练时提示Invalid class id。原因某张图的name是plastic bottle带空格和映射表里的plastic_bottle不匹配被跳过后写入默认值。解决在转换脚本里加一层别名归一化把所有常见写法统一。常见做法是建立一个aliaes字典把plastic bottle、PlasticBottle、plasticbottle都映射到同一个 key。5.3 训练时图片加载报错且进程直接退出现象训练刚开始不到 10 分钟进程崩溃日志里出现Image decoding error或FileNotFoundError。原因数据集中混入了少量损坏的 JPEG 文件或者某些图片路径有特殊字符。YOLO 的数据加载器遇到这种文件默认抛出异常。解决训练前先跑一遍全量图片的完整性校验用PIL打开并load()一遍。损坏的直接移动到corrupted/目录同时把对应的 XML 和转换后的 txt 一起移除保证三个目录始终一一对应。from PIL import Image import glob corrupted [] for img_path in glob.glob(images/*.jpg): try: img Image.open(img_path) img.load() except Exception: corrupted.append(img_path) print(f损坏图片: {len(corrupted)}) for path in corrupted: print(path)这段代码对每个图片做一次完整的解码验证。之所以检测速度慢但值得是因为损坏图片在训练中出现的时机不确定一旦中途崩掉前面的 epoch 全部白跑。把corrupted列表里的路径保存下来再写个小脚本同步删除对应 XML 和 txt 文件。5.4 验证集 mAP 高但实际推理效果差现象验证集 mAP0.50.9但把训练好的模型放到真实场景的图片上测试发现漏检严重尤其是小瓶子和小纸片。原因验证集和训练集同分布模型学的是数据集里的“环境特征”而不是真正的物体特征。例如背景是传送带模型对传送带上的物体检测效果好但换到室外地面就失效。解决这本质上需要更多场景多样性的数据。从工程角度讲先把imgsz从 640 提到 960小目标在放大后特征更明显AP 往往能涨 3~5 个点。另外可以单独标注一批现场图做二次微调比盲目加训练 epoch 有效得多。5.5 训练 loss 正常下降但 mAP 始终在低位徘徊现象训练 100 epoch 后box_loss降到 0.5 左右但验证 mAP0.5 只有 0.4。原因最常见的是类别数或 class id 错位。比如data.yaml写了 8 个类别但标签文件里实际只有 6 个类别的数据空闲的两类在验证时拉低整体指标。解决训练前用脚本统计labels/下所有 txt 中出现的class_id集合和data.yaml里的nc比对。不一致时优先检查是转换脚本漏了类别还是映射表写错了。这类问题属于结构性 bug不是调参能救回来的。6. 验证与进阶用 mAP 曲线和推理测试把效果坐实训练结束后第一件事是检查runs/detect/train/下的results.csv看mAP0.5和mAP0.5:0.95的最终数值。mAP0.5对定位要求宽松适合快速判断模型是否“学到东西”mAP0.5:0.95更严格更能反映实际部署感受。垃圾分类场景mAP0.5能到 0.85 以上基本可用mAP0.5:0.95至少要到 0.6。第二个动作是挑几张验证集图片跑推理用conf0.25的阈值看可视化效果重点观察漏检和误检。如果在实际测试中发现低阈值下大量误检把conf提到 0.4 能显著降低假阳性。垃圾检测这种场景误检代价不高但漏检会导致回收分类错误所以宁可选偏低的置信度阈值配合后续人工复核。进阶可以试一下将 YOLOv8s 剪枝后转成 TensorRT FP16在 RTX 3060 上 640 分辨率推理延迟大概能从 8ms 降到 3ms。如果目标是嵌入式设备先用torch.quantization做 PTQ 量化注意校准集一般选 500 张就够选多了反而可能过拟合到校准集的分布。做这份数据集的时候最让我头疼的其实不是模型而是标注文件里那些不一致大小写、空格、坐标顺序每一个都能让训练静默崩溃。从那以后我每次拿到别人的数据集都强制先跑一遍格式校验、类别统计和图片完整性检查三关通过才允许进入训练流程。这个习惯帮我省下了至少一周的调试时间希望也能帮到你。本文还有配套的精品资源点击获取
返回列表