ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

垃圾分类数据集与YOLOv8训练实战:自检、转换与避坑指南

垃圾分类数据集与YOLOv8训练实战:自检、转换与避坑指南 简介面向垃圾分类入门学习与小型试验场景这份资料整合了六类常见垃圾硬纸板、纸、塑料瓶、玻璃瓶、铜制品、不可回收物的图像数据及配套分类代码适合刚接触深度学习图像识别的开发者也可作为课程设计或毕业设计的参考实现。资源共7个文件包含5个程序脚本、1个模型权重文件与1个数据集压缩包脚本覆盖图像预处理、模型训练、类别预测与结果可视化等环节权重文件直接用于加载已训练模型进行推理。目前已有30634人学习下载热度较高。借助该资源读者可在本地快速跑通“输入图片路径—输出垃圾类别”的预测流程同时通过源码理解数据读取、模型搭建与预测逻辑便于后续迁移到更多类别或改进模型结构。1. 垃圾分类数据集与代码拿到手先自检再进训练垃圾分类识别真正跑起来的人大多不是被模型难倒而是被数据困住图片散落在不同来源标注格式各写各的类别定义经常冲突凑出一版后一训练就翻车。这份垃圾分类数据集及代码把整理好的图片、标注、类别配置和训练脚本打包在了一起——拿到手先自检再训练省掉的是以周计的脏活。它适合这两类人刚接触目标检测、需要一套完整流程照着跑的初学者以及做竞赛或毕业设计、想快速出一版可用基线的工程师。对只想看看示例代码讲解和参数效果的人来说后半部分的转换脚本与调优思路也足够当参照。2. 数据集结构自检目录、类别与标注分布一次看清拿到任何一份数据集第一步不是急着开训练而是花二十分钟把目录结构、类别体系、标注范围三者摸清楚。这一步省掉的是后面几次无谓的重跑。垃圾分类数据集的常规组织方式是 images 和 labels 两个大目录下面按 train、val 拆分再配一个描述类别的 yaml 文件。这份资源的目录可能不完全同名但大概率沿用的是这套约定我按这个结构演示自检流程。2.1 目录与文件角色先看清 images 和 labels 的对应关系拿到资源后我先跑两条命令确认结构find . -maxdepth 2 -type d ls images/train | head -n 5find 列出两层目录ls 看一眼训练图片的文件名规律。YOLO 数据集的约定是图片放在 images/train对应的标注 txt 放在 labels/train文件名完全相同、扩展名不同。图片是 001.jpg标签就是 001.txt。多花两分钟确认这个对应关系能避免后面处理数据集时路径写错。如果目录组织得规整通常会看到这样一层结构garbage-dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── data.yaml └── README.md有图无标注是更常见的问题。我一般会顺手跑一条命令把缺标签的图片列出来comm -23 \ (ls images/train | sed s/\.jpg$/.txt/ | sort) \ (ls labels/train | sort)这条命令的输出是“存在于图片目录但不存在于标签目录”的文件名。comm 默认输出三列-23 参数只看第二和第三集合的差集。如果资源里混入了没有标注的图片训练时 YOLO 会把它当背景图处理数量多时会拉低召回率所以缺标注的图片先记下来后续决定是补标还是剔掉。2.2 类别配置与标注格式yaml 里的顺序是数字标签的翻译表这份资源的类别体系应该集中在 data.yaml 里。以常见四分类为例结构大致是这样path: /workspace/garbage-dataset train: images/train val: images/val names: 0: recyclable 1: kitchen 2: harmful 3: otherpath 建议改成绝对路径很多人在训练时报 dataset not found问题就出在相对路径上。names 是一个从 0 开始的字典它的顺序直接决定 txt 标注里第一列数字的含义第一行标注的第一列是 0对应 recyclable是 2对应 harmful。这个顺序一旦定下来整个训练周期就不要动否则旧标签和旧权重全部错位。看完 yaml 再看一条实际标注内容正常情况长这样0 0.482813 0.356250 0.161667 0.095830 2 0.776563 0.742578 0.093750 0.121880每行五列类别 ID、中心点 x、中心点 y、框宽、框高。后四列都是相对图片宽高的浮点数范围 0 到 1。第一行意思是一个 recyclable 目标中心点在图片宽 48.28%、高 35.63% 的位置宽度占整张图的 16.17%高度占 9.58%。如果看到某行坐标大于 1 或者宽高为负那这份标注大概率是从别的格式转过来时没做归一化。2.3 自检脚本类别分布与空标签数一次看清处理数据集用于 yolov8 训练之前我会先把标签整体统计一遍而不是靠眼睛翻。下面这个脚本是我每次的固定动作import os from collections import Counter label_root labels/train # 换成资源实际路径 class_counter Counter() empty_files [] for fname in os.listdir(label_root): if not fname.endswith(.txt): continue path os.path.join(label_root, fname) with open(path, encodingutf-8) as f: lines f.read().strip().splitlines() if not lines: empty_files.append(fname) continue for line in lines: parts line.split() if len(parts) ! 5: print(f非标准行: {fname}: {line}) continue class_counter[int(parts[0])] 1 print(各类别框数量:, dict(sorted(class_counter.items()))) print(空标签文件:, len(empty_files), empty_files[:5])逻辑不复杂逐行读 txt每行切出五列把第一列类别 ID 累加进 Counter。空标签文件单独计数因为 YOLO 训练时无标注图片会被当作纯背景。如果空标签文件占比超过 1%我建议先回去补标注或者删掉这些图不然模型会学到“这张图里没有目标”的倾向。需要注意上面统计的是框数量不是图片数量。要判断一个类别是否缺样本更可靠的口径是“至少出现一次该类别的图片数量”。改法也很简单把class_counter[int(parts[0])] 1换成按图片维度先分组对每张图片记录出现过哪些类别最后再数集合长度。框数量能反映标注工作量图片数量才反映类别代表性。提示跑训练前先记住两个数字——每类的框量级和空标签文件数。前者暴露样本不均衡后者暴露标注质量。3. 跑通 YOLOv8 训练环境、配置与参数选型数据自检没问题接下来就是训练本身。这套流程在 yolov8 训练自己的数据集时最常见如果你用的是 yolov5 训练自己的数据集流程几乎一样只是少数参数名不同。我以 YOLOv8 为例把环境、配置和参数一次讲清楚。3.1 环境安装与版本验证训练环境我习惯用 conda 隔离避免把系统 Python 搞乱conda create -n garbage python3.10 -y conda activate garbage pip install ultralyticspip 安装 ultralytics 时会自动带上匹配的 torch 版本。装完先验证两件事一是 YOLO 命令行可用二是 GPU 能被 torch 识别yolo version python -c import torch; print(torch.cuda.is_available(), torch.__version__)输出 True 说明 GPU 可用。如果你的机器 CUDA 版本比较旧pip 默认装的 torch 可能不匹配常见报错是 CUDA driver 版本不够此时先按官方命令单独装对应版本的 torch再装 ultralytics。CPU 也能跑但训练速度差一个量级垃圾分类这类中等规模数据集在 CPU 上跑 100 轮不太现实建议至少找一张 4GB 显存以上的卡。3.2 数据集配置与第一条训练命令yaml 在第 2 章已经写过训练前只需要确认 path 指向真实目录。第一条训练命令我用的是yolo detect train \ datagarbage.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0modelyolov8n.pt 表示加载 COCO 预训练权重做迁移学习这是最快出效果的路径。如果不想引入 COCO 的先验知识可以改成 yolov8n.yaml 从零初始化但训练时间会明显变长前期 loss 也高。epochs 给 100 是一个折中配合早停机制模型不涨了会自动停。imgsz640 对多数垃圾分类场景够用如果原图里目标普遍很小可以试 1280代价是显存和训练时间翻倍。batch16 在 3060 或 4090 级别显卡上是常规取值显存小就降到 8。跑起来之后每个 epoch 末尾会打印 box_loss、cls_loss、dfl_loss 和验证集 mAP。第一个 epoch 耗时明显更久是正常的因为要做图片预加载和缓存不表示卡死。训练日志和权重存放在 runs/detect/train 目录best.pt 就是验证集表现最好的权重。3.3 关键参数选型imgsz、batch、epochs 从哪开始调新手最容易把每个参数都调到最大值实际上这几个参数之间有明显的优先级。我先按经验列一张起始配置表参数起始值说明imgsz640越大显存开销越高小目标越容易保留batch16显存不够就降影响训练平稳性epochs100配合早停不必手动卡轮数patience20验证集 mAP 连续 20 轮不涨就停cacheFalse小数据集可开 True 加速吃内存mosaic1.0接近收敛时关掉可减少波动最值得先试的是 imgsz 和 batch因为它们直接决定显存占用和训练能否跑完。垃圾分类场景里一张图可能同时出现一个塑料袋和一颗电池大小差距悬殊640 的归一化缩放对两种尺寸都不太友好。我的做法是先用默认参数跑一版记录每类 AP再看瓶颈在哪。如果小目标类别普遍低上调 imgsz如果 loss 反复横跳先降 batch 而不是降学习率。还有一类情况是数据集本身带有多边形标注或者混合了多种来源的图片这类数据不能直接丢给 YOLO你需要先做一轮格式和尺度上的统一也就是第 4 章要讲的转换。4. 标注格式互转VOC XML 与 labelme JSON 转 YOLO txt 的脚本从网上下载的数据集标注格式五花八门。这份垃圾分类资源里的原始标注可能是 txt但你手里其他来源的图片可能是 VOC XML 或 labelme 的 JSON。格式不统一时转换脚本就是绕不开的工序。互转的关键点只有一个把绝对像素坐标换算成归一化的中心点和宽高。4.1 三种标注格式的差异与互转的关键点先说清楚三种格式的差异格式单位形状描述常见来源YOLO txt相对坐标 0~1中心点 宽高各类 YOLO 项目VOC XML绝对像素左上角 右下角老式目标检测数据集labelme JSON绝对像素多边形顶点数组人工标注工具VOC 和 labelme 都基于绝对像素所以转 YOLO 时必须除以图片宽高做归一化。另一个容易踩坑的是类别映射VOC 里的 name 是字符串YOLO txt 里是数字 ID两者之间需要一张手工维护的映射表。映射表一旦错转换出来的标签不会报错但训练时就会出现第 5 章那种“loss 正常但 mAP 为零”的诡异现象。4.2 VOC XML 转 YOLO txtVOC XML 转 YOLO 是出现频率最高的转换需求脚本可以直接拿来改import xml.etree.ElementTree as ET class_map {recyclable: 0, kitchen: 1, harmful: 2, other: 3} def convert_xml(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue 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) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))脚本先从 size 节点读图片宽高这是归一化的分母取错后面全错。随后遍历每个 object把左上角和右下角坐标换算成中心点加宽高的形式。if name not in class_map: continue这行是有意保留的遇到映射表外的类别直接跳过比写入一个未知 ID 更安全。注意如果 xml 里出现 xmax 小于 xmin 的脏数据说明原标注本身有问题转换前应该先清洗。4.3 labelme JSON 转 YOLO txtlabelme 导出的是 JSON每个标注对象是一组多边形点。垃圾图片里很多物体斜着放多边形转外接矩形会损失一部分精度但检测任务一般能接受。脚本如下import json class_map {glass: 0, paper: 1, metal: 2, plastic: 3} def convert_json(json_path, out_path): with open(json_path, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] if not points: continue xs [p[0] for p in points] ys [p[1] for p in points] xmin, xmax min(xs), max(xs) ymin, ymax min(ys), max(ys) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_map[label]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))取 min 和 max 得到外接矩形是工程上的折中对形状接近矩形的垃圾袋、纸箱效果好对延展性强的塑料袋会框进大量背景。如果 JSON 里存在 group_id最好先按 group_id 去重避免同一物体被画了两遍导致重复框。转换完成后不要直接开训先做一步抽查。4.4 转换结果抽查五分钟验证脚本坐标越界是最常见的转换事故我用一个短脚本快速扫一遍import os label_root labels/train for fname in os.listdir(label_root): if not fname.endswith(.txt): continue with open(os.path.join(label_root, fname), encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f格式错: {fname}: {line}) continue cls, cx, cy, w, h parts cx, cy, w, h float(cx), float(cy), float(w), float(h) if w 0 or h 0 or cx 0 or cx 1 or cy 0 or cy 1: print(f坐标越界: {fname}: {line})YOLO txt 每行固定五列数值范围 0 到 1。如果出现 cx 大于 1 或 w 为负值问题基本出在归一化分母取错或者源标注单位不是像素。越界检查跑完再随机抽十张图用 OpenCV 把框画出来看一眼这一步能暴露 80% 的标注问题。示例代码讲解到这里足够覆盖大多数格式转换场景。5. 训练避坑指南五个最常见的翻车点与排查手段数据转换完、训练跑起来坑才真正开始。下面五条是我在多次垃圾分类训练里踩过的典型问题每条按“现象 → 原因 → 解决”写可以直接对照排查。5.1 loss 降了但 mAP 几乎为零多是类别 ID 与 yaml 错位现象训练 loss 正常下降每个 epoch 结束时输出的 mAP 是 0 或者极低个别类别 AP 显示 nan。原因标注 txt 第一列的类别 ID 和 data.yaml 的 names 索引对不上。例如 yaml 从 0 开始但标注里混着从 1 开始的 ID模型把第 0 类和第 1 类全部错位学习。解决不要急着调模型先跑第 2.3 节的自检脚本打印出标注里实际出现的全部类别 ID再和 yaml 的 names 长度、顺序逐一对照。发现错位就建立映射表批量改写所有 txt 的第一列。这条问题最能安静地吞噬训练周期我当时浪费了两整轮训练才定位到。5.2 验证集 P/R 震荡剧烈先看图片尺寸分布现象验证 mAP 忽高忽低同一轮次两次评估的差距超过 0.1训练曲线锯齿明显。原因数据集中图片分辨率跨度太大。实拍图 1920×1080手机截图 312×512统一 resize 到 640 之后小图被暴力拉伸目标尺度和标注框比例对不上模型在验证集上的表现自然剧烈波动。解决写脚本统计所有图片的宽高过滤掉短边小于 200 像素的图或者把这类小图单独分出来用更小的 imgsz 单独训练。如果不想删数据至少要保证同一批次内尺寸分布接近否则数据增强带来的随机性会叠加到指标波动里。5.3 个别类别 AP 极低样本不均衡先于调参处理现象大类 mAP 有 0.8小类比如电池、灯泡始终在 0.1 以下调参也没用。原因标注样本数量相差十几倍模型在遇到不确定目标时倾向于归类到多数类。解决先看自检脚本输出确认比例。对多数类做欠采样对少数类做复制增强比如上下翻转、随机旋转 90 度。如果条件允许针对少数类补充真实样本效果最好。复制增强时注意生成的增强图片不能同时出现在 train 和 val否则 val 的 mAP 虚高部署到真实场景立刻现原形。5.4 训练中途 OOM现象前几个 epoch 正常跑到第五六个 epoch 时突然报 CUDA out of memory再跑又是在接近的位置崩。原因mosaic 增强开启时前几个 epoch 还在缓存边界试探后面几个 epoch 图像组合变复杂显存占用水涨船高。解决batch 减半是先手操作从 16 降到 8显存压力立刻下来。如果还不行把 cacheFalse 关掉图片缓存或者把 imgsz 从 640 降到 480。YOLOv8 支持 batch-1 自动探测最大 batch第一次跑建议用这个值。注意不要同时把 imgsz 和 batch 都拉满两个参数互相乘关系显存是乘积增长。5.5 推理结果框住整张图回看标注本身现象训练指标正常推理时一张图只输出一个大框把画面里几个目标全包进去。原因标注阶段就不精细很多图被标成一张图一个框或者类别定义太粗比如把所有垃圾统称“垃圾”模型学到的就是框住整张图而不是局部目标。解决可视化抽查训练标签用 labelimg 或者 OpenCV 画框确认框是否贴合物体边缘。如果要细粒度识别需要重新标注部分样本至少把同一画面的多个目标拆开。这一步没有后悔药标注质量问题不能靠调参补救发现晚了只能回去补标。6. 验证进阶用混淆矩阵和置信度阈值吃透结果训练跑完不等于工作结束多花半小时分析验证结果能省掉后面大量回调时间。YOLOv8 训练结束后runs/detect/train 目录下会生成一张 confusion_matrix.png。看混淆矩阵对角线之外的亮块就是模型最常搞混的类别对。垃圾分类里最典型的易混对是外形接近的“可回收塑料瓶”和“其他类塑料容器”两类互相污染说明类别定义本身缺少区分度。处理方式有两种把两个容易混淆的类别合并成一个或者单独为易混对补充负样本让模型看到更多边界案例。6.1 用验证命令重新评估不同阈值训练时默认的置信度阈值是 0.25这个阈值偏保守漏检率可能被压得很高。对垃圾识别场景漏检的代价往往高于误检我习惯在验证阶段把阈值降到 0.1 重新评估from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datagarbage.yaml, conf0.1) print(metrics.box.map) # mAP50-95 print(metrics.box.maps) # 每个类别的 APconf 只影响验证时的正负样本判定不改训练权重。降阈值后能看到精度下降、召回上升的完整曲线如果召回明显提升而误检还在可接受范围说明模型本身学得不错只是输出阈值压得太狠。6.2 调阈值的收益有限真正差异在类别定义调 confidence 阈值本质上只是在同一个模型输出上重新切一刀收益有限。如果降阈值后召回还是上不去问题在特征层面接着用各类别 AP 列表横向对比定位是哪一类拖后腿再回到数据层面处理。从我自己的经验看拿到一份陌生数据集时最忌讳直接开训。现在的固定动作是先跑第 2 章的自检脚本看一眼类别分布再跑一次快速验证确认标签框都能打开最后才是训练。这个习惯救过我好几轮训练周期每次以为能跳过自检直接出结果最后都老老实实回来补数据。希望帮到你。本文还有配套的精品资源点击获取
返回列表