可回收废物数据集YOLO训练实战:标签清洗、增强与部署

发布时间:2026/10/13 10:55:02
可回收废物数据集YOLO训练实战:标签清洗、增强与部署
简介面向需要训练YOLO系列目标检测模型的开发者与算法研究人员这份可回收废物数据集聚焦玻璃瓶、纸瓶、塑料瓶、塑料袋等常见垃圾类别为垃圾分类回收场景提供可直接训练和验证的标注数据。资源内含2000个文件以xml标签文件为主要类型整体压缩包约229.98MB配套提供yolo格式txt与voc格式xml两种标签分别存放在独立文件夹中并详细说明了类别、中心点坐标、宽高等归一化字段含义便于快速接入YOLOv5/YOLOv8/YOLOv9/YOLOv10/YOLO11等主流算法。数据集已按训练与测试需求划分好省去手动整理标注的步骤可帮助用户节省数据预处理时间直接开展模型训练与效果对比。目前已有47人学习/下载适合刚接触YOLO检测任务或需要扩充回收物品种类样本的实践者快速上手。1. 拿到 6000 张可回收废物带标签数据集先判断它能不能闭眼开训你可能在某个资源包里看到过这样一句话标题写着 yolo 算法、可回收废物数据集、6000 张带标签图像玻璃瓶、纸瓶、塑料瓶、塑料袋四类齐全。第一反应往往是解压、打开训练脚本、跑起来然后盼着 mAP 冲上 0.9。真实情况是这类数据集的“带标签”不等于“标签可靠”四类名字也不等于四类边界清楚。玻璃瓶和塑料瓶在图片里经常只有反光和高光差异纸瓶一压就皱塑料袋更是软趴趴地叠在一起YOLO 很难只靠颜色把它们分开。这篇笔记不是教你重新发明检测网络而是围绕这个 6000 张数据集把解压、校验、划分、增强、训练、排错、导出一整套流程讲清楚。适合正在做垃圾分类、回收分拣或物品识别 Demo 的从业者和学生照着走能少踩大半坑。2. 解压后的第一道工序把 YOLO 标签、图片路径和训练/验证集一次理顺2.1 老规矩YOLO 读的是 images 与 labels 成对目录不是 zip 里的原样YOLO 系列训练时找数据靠的是两个目录图片目录和标签目录。标签文件必须是与图片同主文件名的.txt每行五个值类别编号、目标中心点 x、目标中心点 y、目标宽度 w、目标高度 h坐标全部归一化到 0 到 1。这个约定听起来简单但回收物数据集常常不是这个结构尤其从网上下载的打包资源可能是 labelme 格式、VOC 格式、甚至是直接把 txt 和 jpg 混在一个文件夹里。我一般会先在干净目录里解压然后看一眼文件分布不急着改代码mkdir -p ~/recycle_project cd ~/recycle_project unzip ~/Downloads/yolo算法-可回收废物数据集-6000张图像带标签-玻璃瓶纸瓶塑料瓶塑料袋.zip find . -maxdepth 2 | sort | head -40这段命令做的事很简单建立项目目录、解压 zip、打印前 40 条文件路径。用head -40而不是直接看全部是因为这类压缩包里经常带着预览图、说明文档、标注工具的备份文件一次性输出几百条反而找不到重点。解压后你大概率会看到的几种情况是图片在images下、标签在labels下或者图片和标签都在同一层又或者标签是 XML 格式。前两种我们还能救第三种就得先做格式转换。判断完目录接下来要做的不是立刻复制粘贴脚本而是把图片和标签的主文件名对齐。常见问题是图片是glass_001.jpg标签却叫glass_001.txt这种情况没问题但有些资源包是图片1.jpg、标签002.txt这种编号错位会在训练时静默丢样本YOLO 不会报错只是把你当成没有标注图的样本跳过。如果你看到文件名完全对不上先写一个重命名脚本把两边统一成同一套编号否则后面所有验证指标都失真。2.2 用一条脚本扫描 6000 张图的标签空文件、越界坐标、类别编号错位清洗标签这件事是最枯燥但回报最高的一步。6000 张带标签图像听起来很多但里面只要有几十个空 txt训练时模型就会少学十几张关键场景只要有几个坐标写成百分比而不是归一化值loss 曲线就会出现莫名其妙的尖峰。我写过一个扫描脚本逻辑是遍历标签目录逐行检查五字段、类别编号、坐标范围import os from collections import Counter from pathlib import Path label_root Path(labels) class_names [glass_bottle, paper_bottle, plastic_bottle, plastic_bag] for split in [train]: total_boxes 0 empty_files [] bad_lines [] cls_counter Counter() for txt_path in (label_root / split).glob(*.txt): content txt_path.read_text(encodingutf-8, errorsignore).strip() lines [ln.strip() for ln in content.splitlines() if ln.strip()] if not lines: empty_files.append(txt_path.name) continue for ln in lines: parts ln.split() if len(parts) ! 5: bad_lines.append((txt_path.name, f字段数不对: {ln})) continue cls_id int(parts[0]) try: coords [float(v) for v in parts[1:]] except ValueError: bad_lines.append((txt_path.name, f坐标不是数字: {ln})) continue if cls_id 0 or cls_id len(class_names): bad_lines.append((txt_path.name, f类别越界: {cls_id})) continue if any(v 0 or v 1 for v in coords): bad_lines.append((txt_path.name, f坐标越界: {ln})) continue cls_counter[class_names[cls_id]] 1 total_boxes 1 print(split, 框数:, total_boxes) print(类别分布:, dict(cls_counter)) print(空文件:, len(empty_files), 异常行:, len(bad_lines)) for name, problem in bad_lines[:10]: print( , name, problem)脚本里值得注意的几个点第一errorsignore处理中文编码标签文件这类资源包里偶尔有 UTF-8 带 BOM 的 txt直接按 utf-8 读会抛异常。第二坐标越界判断用的是any(v 0 or v 1)如果标签是像素坐标比如一张 640x640 的图标注了320 240 100 150这四个值全小于 1只有320会报越界所以这类问题比想象中更隐蔽。第三cls_id必须从 0 开始如果你的资源包用 1 到 4 表示四类训练时会直接认为第 4 类不存在mAP 会低到像随机预测。扫描完不用着急把坏文件全删掉。空文件多半是采集时漏标但如果整张图本来就是背景删掉没问题坏行能修就修不能修就把对应图片和标签一起丢进一个discarded文件夹而不是直接删。真训到一半才发现数据少了再去找原因很麻烦。2.3 划分 train/val 时别让小类目消失按类别比例做一次简单分层数据清洗完下一个翻车点是训练集和验证集划分。很多人图省事直接random.shuffle后按比例切结果验证集里纸瓶类只有两三张训练出来的模型在验证集上召回率全是 0还以为是模型没收敛。我处理可回收废物数据集时会先跑一个带类别覆盖检查的划分脚本import random, shutil from collections import Counter from pathlib import Path labels_src Path(labels_all) # 清洗后的标签目录 images_src Path(images_all) val_ratio, seed 0.15, 42 random.seed(seed) all_stems [p.stem for p in labels_src.glob(*.txt)] random.shuffle(all_stems) val_n int(len(all_stems) * val_ratio) val_stems set(all_stems[:val_n]) train_stems [s for s in all_stems[val_n:]] # 检查验证集的类别覆盖小类目在 val 里至少要留 8 个框 val_cls Counter() for stem in val_stems: for ln in (labels_src / f{stem}.txt).read_text().splitlines(): if ln.strip(): val_cls[int(ln.split()[0])] 1 min_per_cls 8 for cls_id in range(4): while val_cls[cls_id] min_per_cls: if not train_stems: break moved train_stems.pop() val_stems.add(moved) for ln in (labels_src / f{moved}.txt).read_text().splitlines(): if ln.strip(): val_cls[int(ln.split()[0])] 1 for split, stems in [(train, train_stems), (val, val_stems)]: img_out Path(fimages/{split}) lbl_out Path(flabels/{split}) img_out.mkdir(parentsTrue, exist_okTrue) lbl_out.mkdir(parentsTrue, exist_okTrue) for stem in stems: for ext in [.jpg, .jpeg, .png]: src_img images_src / f{stem}{ext} if src_img.exists(): shutil.copy(src_img, img_out / src_img.name) break shutil.copy(labels_src / f{stem}.txt, lbl_out / f{stem}.txt) print(train, len(train_stems), val, len(val_stems))这个脚本不是严格意义的分层抽样但足够解决垃圾数据集中最常见的小类目缺失问题。它先随机切 85% 到 train15% 到 val然后检查 val 里每一类框的数量少于 8 个就从 train 里搬样本过去再重新统计。min_per_cls可以根据你的标签总量调整6000 张图里有 2 万个框时8 个框的阈值偏保守如果某类总共只有 80 个框验证集里留 8 到 10 个已经算有代表性。搬样本时有一个细节要注意判断类别只看一行标签里第一个字符但这张图可能同时包含玻璃瓶和纸瓶。你把一张多类别的图挪进 val会让多个类别计数同时增加这是好事不用额外处理。最后复制阶段我按jpg/jpeg/png顺序找图片避免有些资源包把 png 图写成 jpg 后缀导致文件不存在。3. 训练配置这一步决定玻璃瓶、纸瓶、塑料瓶、塑料袋能不能分得开3.1 类间相似所以光靠网络容量不够得靠增强策略这四类垃圾的视觉特征比想象中更接近。玻璃瓶和塑料瓶在照片里都是透明或半透明圆柱体高光位置接近纸瓶虽然自带印刷图案但被压扁之后轮廓很像塑料瓶塑料袋是最难的它没有固定形状透明材质会让背景透出来YOLO 抓到的往往是一团乱麻。想让模型区分它们不能只靠把 YOLO 网络撑大还要让训练数据覆盖“形状扭曲、光照变化、背景干扰”这些真实情况。我处理这个数据集时的原则是旋转幅度要小色彩扰动要狠尺度抖动要适中。原因很实际回收流水线上的瓶子基本都是直立或接近直立验证集和测试集也大多是直立图你如果做 90 度旋转增强相当于让模型学一堆它根本不会遇到的角度但同一类瓶子在白天和夜间的色差很大塑料袋在室内灯和自然光下的透明程度完全不同所以 HSV 扰动比旋转更有价值。尺度方面6000 张图里瓶子大小差异本来就大有的占满画面有的只占中间一小块这类尺度变化应该交给scale参数而不是靠 resize。另一个常见误区是迷信 mosaic。Mosaic 把四张图拼在一起确实能提升小目标检测能力但如果你四个类别本身就有三个像mosaic 很容易把玻璃瓶的一半和塑料瓶的一半粘成一张新图模型学到的是“瓶子上半部分是玻璃、下半部分是塑料”这种错误特征。所以我会把 mosaic 概率降到 0.8 左右并且打开close_mosaic训练最后几个 epoch 把它关掉让模型回到正常图片尺度上精修。3.2 data.yaml 和增强超参遮罩度、旋转角度与色彩抖动怎么设进入训练之前先写数据配置文件。类别的顺序必须和标签里的编号完全一致这是我最强调的一句话。data.yaml 里names的顺序就是模型输出层 0、1、2、3 的映射顺序如果标签里的 0 是 glass_bottle而 data.yaml 里第 0 位写成了 plastic_bottle整个训练看起来正常但推理时框和类别全部错位。我一般这样组织# recycle.yaml path: /home/youraccount/recycle_project train: images/train val: images/val names: 0: glass_bottle 1: paper_bottle 2: plastic_bottle 3: plastic_bagpath字段我强烈建议写绝对路径不要写~/recycle_project。YOLO 训练入口读配置文件时对波浪号展开的支持有好有坏一旦路径解析不到报错信息会指向“找不到 train 目录”但实际上你的 train 目录就在那里。写过相对路径踩过坑的人都知道这不是网络问题是路径玄学。增强参数我单独放到一个 hyp 文件里训练时用cfg指过去# recycle_hyp.yaml hsv_h: 0.02 hsv_s: 0.6 hsv_v: 0.4 degrees: 5.0 translate: 0.1 scale: 0.4 shear: 2.0 perspective: 0.0 flipud: 0.0 fliplr: 0.5 mosaic: 0.8 mixup: 0.1 copy_paste: 0.0这几个值不是随便拍的。degrees: 5应对立式瓶子的轻微倾斜shear: 2模拟镜头畸变flipud我设为 0因为垃圾图片里瓶子瓶口朝下和朝上都可能出现但如果你的数据集里所有瓶子都是瓶口朝上垂直翻转会让模型学习“瓶口方向可以互换”的错误知识mixup: 0.1是让透明塑料袋能稍微透一点背景概率太高会让边框和类别都模糊化。如果你的显存有限把copy_paste关掉它会额外增加内存占用。比较难定的是mosaic和scale的组合。mosaic: 0.8意思是 80% 的 epoch 使用四图拼接剩余 20% 使用原图。我建议在第 3.3 节的冒烟测试里先用mosaic: 0.5确认 loss 能收敛再按正式值跑。3.3 先用百来张跑通 pipeline 的最小命令正式训练前一定要做一次冒烟测试。所谓冒烟测试就是用极小 epoch、极小 batch 把整个流程跑一遍确认数据路径、标签文件、增强参数、输出目录这些环节没有断裂。你不想在 100 个 epoch 训练到第 70 个时才发现验证集路径写错那比重新下数据集还让人崩溃。命令我建议这样写cd ~/recycle_project yolo train \ modelyolov8n.pt \ datarecycle.yaml \ cfgrecycle_hyp.yaml \ epochs5 \ imgsz320 \ batch4 \ workers0 \ cacheFalse这里故意把imgsz降成 320batch降成 4workers设 0。320 是为了让流程快速跑完同时验证标签框在缩小后是否还落在合理位置batch 4 对显存压力最小即使只用 CPU 也能几分钟跑完workers 0 是 Windows 和部分 Linux 环境下的后悔药避免多进程读取图片时因为路径编码问题直接卡死。跑完这次冒烟测试重点看两个地方。第一控制台输出的类别数量如果打印出来的类别数和 4 不一致回头查标签清洗。第二训练结束后的runs/detect/train/目录里有没有生成val_batch0_labels.jpg和val_batch0_pred.jpg这两张图直接展示了训练前标签框和训练后预测框的可视化。如果标签可视化里框和物体明显不贴合说明你前面的归一化坐标可能把 w、h 和 x、y 写反了再调 data 清洗脚本比调网络参数有用得多。冒烟测试用yolov8n.pt是因为它最小最快但真正跑正式训练时我建议换成yolov8m.pt。可回收废物四类的形状差异不大边界靠细节纹理模型太小特征图分辨率不够玻璃和塑料的区分会很吃力。4. 看懂训练输出损失曲线、mAP 和推理阈值别只盯着 mAP504.1 三种 loss 曲线怎么读常见的“表面收敛”是什么样YOLO 训练时记录在results.csv里的指标很多常见的三种 loss 是train/box_loss、train/cls_loss、train/dfl_loss。box_loss 管预测框位置cls_loss 管分类是否正确dfl_loss 管边框分布。分类任务里最值得盯的是 cls_loss因为玻璃瓶和塑料瓶这类形态接近的类别分类损失下降慢是正常的。我一直的习惯是训练结束后不看总和而是把后几十个 epoch 的 val loss 单独画出来看趋势import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) loss_cols [c for c in df.columns if val/box_loss in c or val/cls_loss in c] df[loss_cols].tail(50).plot(figsize(10, 4)) plt.xlabel(epoch) plt.ylabel(loss) plt.show()正常情况是前 20 个 epoch 所有 loss 快速下降之后缓慢下行后 30 个 epoch 呈锯齿状但整体不抬升。如果你看到 val loss 在最后 10 个 epoch 明显掉头向上而 train loss 还在下降这就是过拟合了解决方案不是加数据而是调低lr0或增加patience提前终止。还有一种伪装成收敛的情形train loss 和 val loss 都很平滑但 cls_loss 始终高于 box_loss 一段距离这说明模型能框住物品但不确定它是什么。对垃圾四分类这种模型虽然 mAP 不难看实际部署时会把玻璃瓶和塑料瓶来回跳。patience我通常设 30。6000 张图跑 100 个 epoch一般 60 到 80 个 epoch 就收敛patience 设太大会浪费算力设太小会在 loss 正常波动时提前截断。如果你用默认值发现训练经常在 40 多轮就停了把 patience 调大到 50。4.2 mAP50 与 mAP50-95 在回收分拣场景里的不同含义训练完成后控制台会打印一张指标表mAP50、mAP50-95 是最显眼的两个。mAP50 只在意预测框和真值框的 IoU 是否超过 0.5对框的位置要求相对宽松mAP50-95 则把 IoU 从 0.5 到 0.95 按步长计算后取平均越往上要求框越准。在回收分拣场景如果你是做提示或者计数mAP50 够用如果你要把结果给机械臂去抓那就必须看 mAP50-95。机械臂抓瓶子靠的是中心点和抓取半径IoU 0.5 的预测框中心可能偏了半个瓶身抓过去就是一手空。我的经验是mAP50 在 0.85 以上的模型mAP50-95 能不能到 0.6很多时候不取决于网络而取决于标注框边缘是否贴着瓶身。资源包里的标签如果画得偏大YOLO dfl loss 会一直降不下来此时再调网络结构也没用。另一个容易忽略的指标是各类别分开的 mAP。训练入口默认打印的是整体 mAP低于阈值的类会被其他类的成绩盖过去。你可以用yolo val时打开plotsTrue生成的混淆矩阵 PNG 会直观看出来纸瓶到底是被认成玻璃瓶多还是被认成塑料瓶多。4.3 用 yolo val 重新评估再决定推理时把 conf 放 0.25 还是 0.5正式模型训练好后别急着用训练日志里的数字对外汇报单独跑一次验证yolo val \ model/home/youraccount/recycle_project/runs/detect/train/weights/best.pt \ datarecycle.yaml \ splitval \ conf0.25 \ iou0.5 \ plotsTrue这里我把conf显式写成 0.25是因为训练入口默认的置信度阈值和推理时不一定一致。很多数据集的 mAP 是在置信度为 0 到 1 的 PR 曲线下积分得到的你真正部署时阈值一提高召回率立刻掉。400 张 val 图像里如果预测结果中塑料袋数量明显偏少大概率不是模型没学过而是 accepted。在回收物检测里我一般把 glass_bottle 和 plastic_bottle 的 conf 设置不同玻璃瓶因为反光容易误检阈值抬高一点塑料袋真心难检阈值放低。这种方式需要看混淆矩阵来决定我在最后一章详细说。5. 避坑指南这个回收物数据集最容易翻车的 5 个地方5.1 路径与文件层路径玄学、空标签、中文文件名坑 1训练时报image not found但图片明明在目录里。现象启动训练不到 10 秒日志抛出找不到某张图片文件。原因最常见的是 data.yaml 里的path用了相对路径而训练时工作目录不在项目根目录其次是图片文件后缀名和标签前缀不一致比如标签是001.txt、图片是001.JPG大写后缀在 Linux 上严格区分。解决data.yaml 改绝对路径图片后缀统一转小写用第 2 章的对齐脚本检查同名文件是否同时在 images 和 labels 里。坑 2标签文件是空的数量还不少。现象训练能跑但每个 epoch 的数据量比预期少打印出的类别框数里某一类明显掉线。原因采集端标注遗漏或者转换标注格式时空文件备份。这类问题 YOLO 不会报错只会静默跳过。解决在训练前跑一次 2.2 节的扫描脚本统计空 txt 数量把对应的图片从训练目录中移走。6000 张带标签图像里移走 50 张不会显著影响模型但保留空标签会让验证时的 mAP 计算出现偏差。坑 3中文目录和中文文件名。现象Windows 上可能正常Linux 上训练或推理时偶尔报编码错误或者可视化时标签变成乱码。原因zip 包从 Windows 压缩内含 GBK 编码文件名而 Python 默认按 UTF-8 解码路径。解决解压后立刻把所有目录名、文件名改成 ASCII 拼音或数字编号。这里不要偷懒越早改越省事。为类目起英文名是之后校验类别映射的关键。5.2 标签与模型层类别顺序错位、塑料袋漏检、验证集太小坑 4整体 mAP 不错但所有预测框的类别整体往后偏一位。现象例如把 glass_bottle 的图全部预测成 paper_bottle。原因data.yaml 的names列表顺序和打标签工具导出时的类别 ID 不一致。资源包里标注的 1 可能代表 glass_bottle而你的 data.yaml 第 1 位是 paper_bottle。解决打印验证集 val 标签里每个 cls_id 的框数确认 0、1、2、3 分别对应哪一类再回填 data.yaml。这个坑不检查的话第 2 章再干净也白搭训练过程完全无感知。坑 5塑料袋漏检严重conf 降到很低还是找不全。现象其他三类 mAP 都能到 0.8 以上塑料袋只有 0.4而且验证集上预测框大量漏掉折叠的袋子。原因塑料袋透明、边缘模糊和背景缺乏对比模型学到的是“高光和纹理”不是“袋子形状”另外如果数据集里塑料袋大多是平整展开测试时遇到折叠袋也会漏检。解决增强参数里加大hsv_h和hsv_v让袋子在不同颜色背景上更醒目mosaic 保持开启让袋子在拼接边界上被部分遮挡迫使模型学形状特征最后隐私面较窄的错检通过按类别调阈值补回来。6. 最后一招用混淆矩阵和 ONNX 重做一次部署前验证6.1 给每个类单独设置信度而不是只调一个全局 conf训练入口打印的整体指标会掩盖单类问题。我拿到一个可回收废物模型第一步是看confusion_matrix.png。这张图横轴是真值类别纵轴是预测类别。如果玻璃瓶和塑料瓶的交叉点数值很大说明模型在两者之间犹豫不决。此时全局 conf 只能一刀切调高会让塑料瓶漏检调低会让玻璃瓶误检一堆。正确做法是按类设置阈值。推理代码里对预测结果逐一判断类别 ID 是 0 时要求 conf 0.4类别 ID 是 3 时允许 conf 0.15。这样玻璃瓶宁可漏也不误检因为玻璃误检成塑料瓶会直接影响后续分类回收的纯度塑料袋本来难检阈值放低一点反而能多捞回来几个。你会发现在回收分拣业务里准确率比召回率更能体现价值。6.2 导出 ONNX 后再跑一遍 val而不是训练框架里自嗨训练完.pt文件看起来好用但部署时大多会用 ONNX 或 TensorRT 加速。问题在于PyTorch 模型导出 ONNX 后前处理、坐标换算和输出解析可能存在细微差别这些差别在可视化上不明显但一旦跑批量验证mAP 可能下降 2 到 5 个点。我现在的习惯是训练完先做一次导出yolo export \ modelruns/detect/train/weights/best.pt \ formatonnx \ imgsz640导出后再用 ONNX 模型对 val 集重新预测一次打印混淆矩阵和各类别 AP。如果导出前后指标差异超过 2%就检查导出时的imgsz是否和训练一致。很多导出翻车不是因为算子不支持而是推理时少了训练时用的 padding 方式。这一步做完模型才算真正能交出去。我最后一次踩这个坑是在一周内连续改了三次标签顺序、两次增强参数最后发现导出模型和验证模型的前处理不一致。从那以后我的习惯是先跑数据校验再跑冒烟训练最后导出重验三步缺一不可。希望这篇 6000 张带标签可回收废物数据集的落地流程能帮你少走这一段弯路。本文还有配套的精品资源点击获取