VOC垃圾分类检测数据集解析:从XML标注到YOLO训练全流程
简介面向YOLO垃圾分类检测任务的数据集全部由真实场景拍摄的高质量jpg图片构成并使用LabelImg标注软件完成类别框选与标签定义。整体约一万五千张覆盖纸张、塑料、果皮、玻璃杯、易拉罐、厨余垃圾等常见生活垃圾类别明暗、角度、摆放状态多样可直接用于垃圾分类模型训练、验证与效果评估。压缩包内共58921个文件含jpg原图19640张、xml格式标注19640个、txt格式标注19641个两种标注分别存放于不同文件夹无需格式转换即可接入YOLO或VOC流程。压缩包约994.56MB目录结构清晰便于划分训练集与验证集目前已有1334人浏览学习。适合入门垃圾分类检测的学生、算法工程师及竞赛选手作为数据基底也可用于不同检测模型的横向对比。1. VOC垃圾分类检测数据集先看懂它再决定怎么训练VOC垃圾分类检测数据集本质是一套用VOC格式打包的垃圾分类图片集专门给目标检测模型做训练和验证用。我第一次拿到这类数据集时最直观的感觉是它不像网上随手爬的图片包而是把原始图片、XML标注文件、训练验证划分全部按PASCAL VOC的目录规范整理好了。这意味着它可以直接喂给Faster R-CNN、SSD也可以转成YOLO来训练。对正在做垃圾分类落地、毕业设计或者想在小区、园区部署识别终端的人来说它能替你把最麻烦的标注环节省掉。下面就从格式怎么读、怎么转成YOLO训练、参数怎么设、哪些地方容易翻车这几点讲透。2. VOC标注格式拆解垃圾分类数据集里的目录与XML到底怎么读2.1 从文件夹结构看懂VOC2007的约定JPEGImages、Annotations、ImageSets/Main拿到VOC垃圾分类检测数据集第一件事不是急着训练而是先摸清目录结构。PASCAL VOC是目标检测领域的老约定后来很多数据集包括一些商业标注平台导出的数据都沿用这套骨架。常见做法是解压后看到这样一层结构VOC_Garbage/ ├── JPEGImages/ │ ├── garbage_000001.jpg │ ├── garbage_000002.jpg │ └── ... ├── Annotations/ │ ├── garbage_000001.xml │ ├── garbage_000002.xml │ └── ... └── ImageSets/ └── Main/ ├── train.txt ├── val.txt ├── trainval.txt └── test.txtJPEGImages里放的是原始图片Annotations里是与图片同名的XML标注文件ImageSets/Main里是划分清单每行一个不带扩展名的图片名。这套结构的意义在于把训练、验证、测试的名单和标注内容分离模型不知道划分时你可以自己改Main里的txt来重做。很多入门朋友以为只有JPEGImages和Annotations就够了其实ImageSets里的清单同样重要因为检测模型评估时经常要按这里的名单逐张对齐。另外要提醒的是同一个类别数据可能被不同来源混编图片命名前缀不一定全是garbage。有的会带拍摄场景编号比如street_0234.jpg、balcony_0001.jpg也有的是纯数字编号。如果你打算按文件名前缀做数据集划分这一层必须先看清楚。拿到数据集后的标准动作是统计文件数量检查图片与XML是否一一对应ls JPEGImages | wc -l ls Annotations | wc -l两边数量不一致的话说明有图片没有标注或者标注没有对应图片这类脏数据会在后面转换时引发找不到XML或标签为空的情况。我一般还会用find命令确认目录层数免得训练时路径多写一级少写一级。2.2 annotation XML的关键字段name、bndbox、difficult与truncated的含义VOC的XML标注看着啰嗦但每个字段都有实际用途。以最常见的结构为例annotation folderJPEGImages/folder filenamegarbage_000001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameplastic_bottle/name bndbox xmin312/xmin ymin240/ymin xmax580/xmax ymax860/ymax /bndbox difficult0/difficult truncated0/truncated /object /annotationname是类别名bndbox是真实框的四个角点坐标difficult标记难例truncated表示目标是否被图像边界截断。很多初学朋友只关心name和bndbox忽略了difficult结果转换后把大量难例当成普通样本导致模型被模糊目标带偏。在垃圾分类场景里difficult通常用来标注那种人眼也要凑近才能确认的目标比如被压扁的塑料瓶、沾满油污的纸盒。实际训练前建议把difficult1的样本剔除或者单独放到验证集里观察模型对难例的表现。这里有一个读取细节同一个XML里可能有多个object解析时必须用root.iter(object)遍历全部而不能用find(object)因为find只返回第一个目标。另外bndbox的坐标在VOC原版里是整数但有些标注工具导出的是浮点数所以转换脚本里最好统一用float()解析避免类型错误。还有一个隐蔽坑XML里的filename字段可能与实际文件名大小写不一致或者带了下划线后缀读取的时候要以JPEGImages目录里的实际文件为准而不是盲目相信XML里的filename。2.3 数据集的类别体系常见垃圾分类标签与背景类别问题VOC垃圾分类检测数据集的类别设计一般围绕垃圾回收的实用分类展开。常见标签有塑料瓶、玻璃瓶、易拉罐、纸盒、纸箱、塑料袋、厨余袋、电池等。这个数据集的标注对象往往是垃圾个体而不是垃圾袋整体因此类别名对应的是具体物品。要注意不同版本的标注粒度差别很大有的把塑料瓶和塑料桶统一标成plastic有的严格区分plastic_bottle、plastic_bag有的把厨余垃圾标成kitchen_waste有的只标food_waste。拿到数据集后第一步应该统计所有XML里实际出现过哪些name值而不是看README里写了几个类。很多翻车事故就发生在这里README说6类实际XML里有9个不同名字其中三个是同一类在不同光照下的别名。统计类别的命令很直接grep -h name Annotations/*.xml | sort | uniq -c输出的频次列表能同时看出两个信息类别到底有几种以及每类的样本量是否均衡。如果某一类只有个位数目标后面训练时基本会被模型忽略。解决办法是建立一份class_mapping把同义词归并比如把plastic和plastic_bottle统一成plastic_bottle。注意合并后要重新检查有没有框重叠同一张图里如果一个目标被两个name各标一次合并后会出现双框这个在后面转换里要主动去重。如果数据集里出现undefined或者background这类标签我的建议是直接剔掉给检测模型设置一个杂物类只会增加输出头的负担不如把不属于你业务目标的框全部忽略。2.4 用一个小脚本快速校验XML与图片一致性前面说了文件数量要一致但数量一致不代表名字对得上。最稳妥的校验是遍历JPEGImages目录逐个检查同名XML是否存在。下面这个脚本可以直接跑不需要第三方库from pathlib import Path img_dir Path(VOC_Garbage/JPEGImages) ann_dir Path(VOC_Garbage/Annotations) missing_ann [] for img_path in img_dir.iterdir(): stem img_path.stem if not (ann_dir / f{stem}.xml).exists(): missing_ann.append(img_path.name) if missing_ann: print(缺少标注文件, missing_ann[:10], 共, len(missing_ann), 张) else: print(所有图片都有对应XML校验通过)这个脚本的逻辑很简单用图片文件名去查同名XML。如果存在缺失后续YOLO转换时就会产生只有图片没有标签的训练项ultralytics会跳过这些图片或报labels not found警告。反过来也要查一下有没有多余的XML即没有对应图片的标注文件那些XML通常来自误拷贝或裁剪前的旧图同样要清理掉。整个校验过程建议在转换脚本之前做因为一旦你发现缺失最好的处理方式是去补标注而不是在转换时容忍空标签。3. 把VOC垃圾分类数据集转成YOLO训练格式脚本与参数设计3.1 为什么要转YOLO的txt标签与VOC的XML差异YOLO系模型训练时并不直接读取VOC的XML而是要求每个图片对应一个同名的txt文件。txt的每一行是类别ID 中心点x 中心点y 宽 高且所有坐标都归一化到0到1之间。这与XML里用像素角点的表示完全不同。归一化的好处是不管图片缩放成640还是1280标签都不受影响。如果你直接把XML喂给YOLO它根本无法解析这就是为什么所有YOLO训练教程都要求先做数据转换。另一个关键差异是类别信息。XML里写的是类别名字符串而YOLO的txt只认整数ID。转换脚本的核心工作就是把name字符串映射成int再把(xmin, ymin, xmax, ymax)换算成(x_center, y_center, width, height)最后除以图片宽高。说起来简单但最容易出错的地方是坐标换算时边界处理不当。某些标注的xmax恰好等于图片宽度归一化后会等于1.0而YOLO标签规范要求坐标严格在0到1区间内训练时会出现标签越界的警告。为了避免这个我会在归一化后面加一道min截断。3.2 转换脚本VOC XML转YOLO txt含归一化坐标计算我一般写一个最小转换脚本不依赖额外库只用Python标准库里的xml.etree.ElementTree。下面是可以直接改用的版本import xml.etree.ElementTree as ET from pathlib import Path def voc_xml_to_yolo_txt(xml_path: Path, out_txt_path: Path, class_map: dict): class_map: {plastic_bottle: 0, glass_bottle: 1, ...} 解析单个XML输出一行一个目标的YOLO txt标签。 tree ET.parse(xml_path) root tree.getroot() # 图片宽高必须从XML的size节点读不能自己猜 img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text # 类别名不在映射表里就跳过避免训练时崩 if name not in class_map: print(f[warning] {xml_path.name}: 未知类别 {name}) continue class_id class_map[name] # 过滤difficult难例按需开启 difficult int(obj.find(difficult).text) if difficult 1: 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.0 / img_width y_center (ymin ymax) / 2.0 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height # 越界保护防止坐标恰好等于1.0 x_center min(x_center, 1.0) y_center min(y_center, 1.0) box_width min(box_width, 1.0) box_height min(box_height, 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) if lines: out_txt_path.write_text(\n.join(lines), encodingutf-8)这段代码的逻辑说明先读XML里的图片宽高再遍历所有object节点按class_map映射类别ID过滤difficult1的目标最后把四个角点换算成中心点加宽高的归一化格式。越界保护用min截断是为了防止坐标等于1.0导致ultralytics报警。注意difficult的过滤是可选策略如果你数据集的难例比例很高全过滤掉会让样本变少可以改成不过滤只用truncated字段做标记。参数说明class_map必须与后面训练时data.yaml里的names顺序完全一致。最稳妥的做法是先统计XML里所有类别再按字母或者按业务重要性排号不要随手写dict。如果class_map和names错位模型会把塑料瓶学成玻璃瓶训练过程不报错但结果完全没意义。脚本里的float转换也有讲究统一使用float之后遇到整数字符串也可以正常处理。批量转换时再套一层循环from pathlib import Path base Path(VOC_Garbage) xml_dir base / Annotations out_txt_dir Path(yolo_labels) out_txt_dir.mkdir(exist_okTrue) class_map {plastic_bottle: 0, glass_bottle: 1, aluminum_can: 2, carton: 3} for xml_file in sorted(xml_dir.glob(*.xml)): txt_name xml_file.stem .txt voc_xml_to_yolo_txt(xml_file, out_txt_dir / txt_name, class_map) print(f转换完成共处理 {len(list(xml_dir.glob(*.xml)))} 个XML)这段代码会为每个XML生成同名txt但没有复制图片图片仍留在JPEGImages里训练时通过data.yaml指向图片目录即可。sorted排序是为了保证输出顺序稳定方便排查问题尤其是当某个xml转换失败时你能快速定位是第几个文件。3.3 数据集划分train/val/test的两种做法与ImageSets/Main的复刻VOC格式自带train.txt、val.txt但直接用它们训练YOLO时我建议重新划分。原因很简单原划分可能针对分类任务设计检测任务需要保证同一个场景的不同角度图片不能全部落在训练集而另外一组高度相似的场景落在验证集里。方法一是直接读取原ImageSets/Main下的txt做划分用train.txt里的名字作为训练集文件名val.txt作为验证集。这种方法适合原划分质量较高、图片量大的情况。方法二是按图片编号重新随机划分常见比例是train:val:test8:1:1。随机划分前先shuffle并且固定随机种子。如果图片命名里有场景编号比如同一批连续编号来自同一段拍摄序列我建议按编号前几位分组防止同一物体的连续帧同时出现在训练和验证集里否则验证精度会虚高。下面是固定随机种子并生成YOLO需要的train.txt和val.txt的片段import random from pathlib import Path random.seed(42) all_images sorted(Path(VOC_Garbage/JPEGImages).glob(*.jpg)) random.shuffle(all_images) n len(all_images) train_names [p.stem for p in all_images[:int(n*0.8)]] val_names [p.stem for p in all_images[int(n*0.8):int(n*0.9)]] test_names [p.stem for p in all_images[int(n*0.9):]] Path(yolo_train.txt).write_text(\n.join(train_names), encodingutf-8) Path(yolo_val.txt).write_text(\n.join(val_names), encodingutf-8) Path(yolo_test.txt).write_text(\n.join(test_names), encodingutf-8)随机种子固定为42只是示例实际项目里我习惯用版本号比如101保证别人能复现你的划分。这里要注意的是all_images的glob只匹配jpg如果JPEGImages里混有png或jpeg会漏掉。更稳的写法是用后缀集合过滤例如suffix in {.jpg, .png, .jpeg}。另外如果你希望把划分信息写回VOC的ImageSets/Main直接覆盖原txt即可但记得备份。3.4 转换后的校验可视化标注与类别检查转换完不能直接开训至少要做两层校验。第一层是统计数量把每个XML里object的总数和对应txt的行数做对比差值应该是被difficult过滤掉的个数。简单做法是写一个小脚本遍历XML和txt分别统计object数量和行数。如果出现txt为空但XML里明明有没被过滤的目标说明转换脚本有bug。第二层是可视化把YOLO txt画回原图。方法是从txt读归一化坐标乘回图片宽高再用OpenCV画框。下面是一个快速检查脚本import cv2 from pathlib import Path def draw_yolo_box_on_image(image_path: str, txt_path: str, out_path: str): img cv2.imread(image_path) h, w img.shape[:2] for line in Path(txt_path).read_text(encodingutf-8).strip().splitlines(): if not line: continue parts line.split() cls_id int(parts[0]) xc, yc, bw, bh map(float, parts[1:]) x1 int((xc - bw/2) * w) y1 int((yc - bh/2) * h) x2 int((xc bw/2) * w) y2 int((yc bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls_id), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(out_path, img)这段代码把归一化坐标还原成像素框并画在原图上检查重点是框是否贴住目标、有没有标错位置、有没有出现整张图的超大框。如果发现某张图框全乱了优先回去看这个XML的size字段是否和实际图片尺寸一致。可视化检查不需要全部看x在训练集里抽20张左右覆盖不同背景和类别就够。4. 用YOLOv8在本地跑通垃圾分类检测最小命令与必调参数4.1 准备data.yaml路径与类别映射转换完标签后下一步就是写ultralytics需要的data.yaml。这个文件指定图片目录、标签目录和类别名。很多朋友在这里踩坑要么路径写错要么names顺序跟class_map不一致。一个最小可用的data.yaml长这样path: VOC_Garbage train: yolo_train.txt val: yolo_val.txt test: yolo_test.txt names: 0: plastic_bottle 1: glass_bottle 2: aluminum_can 3: carton这里train和val指向的是包含图片文件名的txt而不是图片目录。ultralytics支持两种写法一种是train/images目录另一种是train.txt每行写图片路径。我一般用txt方式因为划分文件已经在前面生成好了而且与VOC的ImageSets/Main风格一致。data.yaml里的path是基础路径所有相对路径都以它为基准。如果你的JPEGImages目录和yolo_labels目录不在同一层级建议用绝对路径或者把路径写成相对path的相对路径。另一个容易忽略的点是test字段。训练阶段data.yaml不写test也能跑但如果你要用同一份配置做最终评估建议把test也写上。另外names的编号必须从0开始且连续不能跳号。如果你在class_map里把塑料瓶编为0玻璃瓶编为2跳了一个1ultralytics会在训练时报类别数量不匹配。4.2 训练命令与关键超参数imgsz、epochs、batch配置好data.yaml后用YOLOv8的最小命令就能跑yolo detect train \ dataVOC_Garbage/voc_garbage.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ patience20 \ cacheTrue逻辑说明modelyolov8n.pt表示加载预训练权重yolov8n是轻量版在CPU上也能勉强跑民用显卡上能有几十帧的速度。epochs150是训练轮数imgsz640是训练分辨率batch16是批次大小patience20表示20轮不涨mAP就早停cacheTrue会把图片缓存到内存或磁盘加速读取。参数说明batch的选择取决于显存8GB显存用batch16基本合适16GB可以用32。如果训练时OOM优先降batch而不是降imgsz因为降imgsz会影响小目标检测。垃圾分类里塑料瓶、易拉罐在画面里经常很小低分辨率会把它们彻底抹掉。imgsz建议不低于480正式实验用640部署时再按硬件压到416或320。patience20在这里特别重要VOC垃圾分类数据集如果包含大量难例mAP曲线会出现平台期没有早停会白耗很多时间。如果你不想用命令行也可以用Python脚本启动训练方便后面接自己的回调from ultralytics import YOLO model YOLO(yolov8n.pt) model.train( dataVOC_Garbage/voc_garbage.yaml, epochs150, imgsz640, batch16, patience20, cacheTrue, )4.3 迁移学习与类别不均衡从预训练权重开始用yolov8n.pt而不是从零训练是垃圾分类检测落地中最该坚持的一步。垃圾分类图片的背景很杂但目标本身瓶、盒、罐与COCO的常见物体有大量重叠预训练权重已经学会了稳定的边缘特征和纹理特征微调能省很多数据和算力。如果你拿到的VOC垃圾分类检测数据集只有几百张冷启动基本训练不出可用模型。类别不均衡是垃圾分类数据集的常态。厨房场景里厨余袋样本很多但电池样本可能只有几十张模型会偏向大类。解决方案有三个层次。第一先看类别分布训练前统计每个类别的目标数量决定是否要加重少数类。第二如果类别数量差距超过5倍可以考虑对少数类图片做复制粘贴增强或者在Loss里给少数类加权。ultralytics没有直接暴露class_weight常见做法是调整数据集本身比如对电池类图片做水平翻转、HSV调整增加它在每个epoch里的出现次数。第三最有效的还是补数据哪怕是网上爬来的同类图片自己标一下也能缓解。这里我建议在训练时开启mosaic和mixup增强尤其是对小目标多的垃圾分类数据yolo detect train \ dataVOC_Garbage/voc_garbage.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ mosaic1.0 \ mixup0.2mosaic1.0表示每批都有一张由四张图拼接的训练图能帮助模型适应不同光照和遮挡mixup0.2表示按0.2的概率做图像混合。这两个参数对提升少样本类别的鲁棒性有实际帮助但注意mosaic开启时如果数据集中小目标特别多框的分布会被拼接边缘截断导致部分标注失效。所以遇到大量小目标时反而要把mosaic降到0.5附近。4.4 训练后评估与导出部署模型训练完成后第一件事是跑验证集而不是直接看train folder里的result.png。验证命令yolo detect val \ dataVOC_Garbage/voc_garbage.yaml \ modelruns/detect/train/weights/best.pt \ imgsz640这个命令会输出mAP0.5、mAP0.5:0.95、每类AP并生成混淆矩阵图。混淆矩阵是判断类别混淆最直观的工具如果塑料瓶和玻璃瓶的格子特别亮说明这两个类的特征没有学好回到第5.2节的思路处理。评估没问题后导出部署模型。一般先导出ONNX再做一步量化yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出的ONNX可以用ONNX Runtime在CPU上跑也可以转成TensorRT在Jetson上跑。导出时imgsz要和你部署时的输入尺寸一致否则重新缩放会损失精度。另外如果导出后推理结果坐标偏了多半是代码里没有做letterbox的原始尺寸还原这个跟YOLO训练脚本无关是推理后处理的常见坑。5. 垃圾分类检测数据集避坑指南标注噪声、类别混淆与脏数据5.1 现象训练时损失下降但mAP始终在0.5附近打转经常有朋友来问我loss曲线明明很漂亮从2点几一路降到0.2但mAP0.5始终不超过0.55。这种情况在VOC垃圾分类检测数据集里很常见原因通常是数据集里有大量极小的目标而且标注框比实际目标大了一圈。VOC标注如果由人工手标小物体在低分辨率下边缘模糊标出来的框很容易包住背景。YOLO的损失函数对框尺寸误差比较敏感这些胖框让模型学到错误的边界模型宁可预测一个保守的大框也不愿精确贴合目标。解决方法是转换时加一个面积过滤去掉相对图片而言过小的框。我一般用框面积占图片面积小于0.001的样本剔除但这个阈值取决于拍摄距离不能拍脑袋。先统计一下框面积分布再决定过滤范围。另外也可以只过滤difficult1的XML里的框保留其他数据不变。如果过滤后样本量明显减少说明这个数据集的拍摄距离普遍很远此时应提高imgsz而不是继续过滤。5.2 现象塑料瓶和玻璃瓶互相误检塑料瓶和玻璃瓶形状相似、材质接近在VOC垃圾分类检测数据集里经常被标注混淆。现象是验证集里塑料瓶被预测成玻璃瓶玻璃瓶被预测成塑料瓶尤其在没有瓶盖和标签的侧面视角下。原因是类别定义在实际标注时没有统一标准有的标注员把透明PET瓶归为塑料瓶有的因为玻璃质感强而标成玻璃瓶。另外数据集里如果glass类别样本比plastic少很多模型会把一切透明瓶形都倾向预测为大类。解决分三步第一检查XML里这两类目标是否有大量重叠框如果同一张图里的同一个瓶子同时被标了plastic和glass直接去掉这属于标注错误。第二在class_mapping里做严格定义比如塑料瓶必须能看到瓶盖或标签才算否则标为difficult。第三训练时给玻璃瓶类别增加样本或者用cutout、mosaic增强让模型更关注材质纹理而不是单靠形状。如果交叉误检仍然严重考虑把这两个类别合并成瓶罐类再在后续分类器里细分。5.3 现象训练直接报IndexError: list index out of range启动YOLOv8训练时出现index 4 is out of bounds for axis 0 with size 4这类报错原因非常直接data.yaml里的names数量是4但某个txt标签文件里出现了类别ID 4。这种问题往往不是转换脚本写错而是脚本循环时漏掉了某个XML里的类别名导致该目标没有被写入txt但你另一个步骤又把旧标签混进来了。还有一种常见情况是你先用旧class_map转换了整个数据集后来改了类别数只剩部分txt重新转换新旧标签混在yolo_labels目录里。解决写一个校验脚本读取所有txt文件统计类别ID的最小和最大值确认最大ID不超过names长度减1。下面是我每次训练前必跑的代码。from pathlib import Path max_id -1 for txt in Path(yolo_labels).glob(*.txt): for line in txt.read_text(encodingutf-8).strip().splitlines(): if line: max_id max(max_id, int(line.split()[0])) print(最大类别ID:, max_id)如果输出大于names列表长度减1就去检查对应txt对应的XML里有没有你遗漏的类别。class_map的修改必须同步到data.yaml这两处是绑定关系改了一边忘了另一边就会变成这样的问题。另外也要检查文件里是否混入了空行或者只有一列的空标签YOLO不允许没有框的txt存在。5.4 现象验证集mAP很高但换一批真实场景图就拉胯这种情况十有八九是数据泄漏。很多VOC垃圾分类检测数据集在划分train/val时是按文件列表直接随机分的没有考虑同一来源的图片会在相似背景、相似摆放方式下重复出现。random.shuffle后某一张图的临近拍摄帧可能一张落在train一张落在val。模型等于看着答案做验证mAP虚高。换到真实场景背景变了、角度变了立刻原形毕露。解决按图片ID前缀划分也就是把同一个拍摄批次作为一个整体。如果文件名是scene01_001.jpg、scene01_002.jpg这样的结构把scene01作为一个整体归入train或val不要拆散。另一个办法是直接用摄像机序列抽帧保证同一个视频源的所有帧只进一个集合。这个检查做完后你会发现真实mAP比原来低5到10个点但这才是部署时真正能用的数。以后再看到高mAP先别高兴先确认划分方式是否严格按场景隔离。5.5 现象训练时出现大量labels not found警告ultralytics训练时刷出WARNING labels not found in ... image或者干脆一个标签都不读。常见原因是你把train.txt里的图片路径写成了绝对路径但标签目录的相对位置对不上。YOLOv8默认标签路径与图片路径同目录或者由data.yaml里的label字段指定。如果你把txt标签放在yolo_labels而data.yaml没有给label字段ultralytics会默认去图片同目录找同名txt自然找不到。解决在data.yaml里显式加一行label: yolo_labels前提是yolo_labels与path的相对位置正确。或者把yolo_labels做成JPEGImages的同级目录ultralytics会优先读取图片同目录的下级labels目录。我更推荐显式指定逻辑清楚排查也快。如果显式指定后仍然找不到检查路径大小写和目录名是否多了空格。这些细节在Windows上尤其容易踩坑因为Windows的大小写不敏感但Linux对大小写敏感同一份配置在Windows上能跑换到Linux上就报错。6. 用10分钟验证数据集质量一个顺手的目标分布可视化技巧最后一个建议是不要拿到数据集就训先用可视化脚本判断它值不值得投入。我每次处理新的VOC垃圾分类检测数据集都会先统计类别频次和目标尺寸分布把两个指标画在同一张图上。import matplotlib.pyplot as plt from pathlib import Path from collections import Counter sizes [] cnt Counter() for txt in Path(yolo_labels).glob(*.txt): for line in txt.read_text(encodingutf-8).strip().splitlines(): if not line: continue parts line.split() cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) cnt[cls_id] 1 sizes.append((w h) / 2) # 用归一化宽高的均值近似目标占比 fig, ax1 plt.subplots() ax1.bar(cnt.keys(), cnt.values(), colorskyblue) ax1.set_xlabel(class id) ax1.set_ylabel(count, colorskyblue) ax2 ax1.twinx() ax2.hist(sizes, bins50, alpha0.5, colororange) ax2.set_ylabel(box size distribution, colororange) plt.savefig(dataset_health.png, dpi150)这段代码复用第3.2节的标签目录统计每个类别的目标数量同时画归一化目标尺寸的直方图。如果发现某一类只有个位数目标或者大量目标尺寸都集中在0.01以下就要考虑删类、补数据或者换高分辨率训练。类别频次柱状图能让你一眼看出不均衡程度目标尺寸分布则告诉你当前的imgsz是否足够。比如大多数目标归一化宽高都在0.1以下说明目标整体偏小用640训练也是勉强可能要考虑更高分辨率或者切图训练。这个技巧治好了我很多次盲目开训的毛病。以前拿到这种数据集第一反应是赶紧跑YOLO结果训到一半发现电池类只有11个实例模型每次都漏检最后还是要回头补数据。现在我会花10分钟先看这个分布图再决定要不要继续。如果你也想快速判断手里的VOC垃圾分类检测数据集够不够用建议先从这一张图开始。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →