ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

树叶分类检测数据集:VOC/COCO/YOLO格式转换与YOLOv8训练全流程

树叶分类检测数据集:VOC/COCO/YOLO格式转换与YOLOv8训练全流程 简介这是一份面向目标检测初学者与课程实践者的YOLO树叶分类检测数据集针对真实场景下树叶目标识别任务解决自建数据集标注成本高、格式转换繁琐的问题。资源包共2000个文件约27.93MB包含1000张真实场景图片及对应标注标签覆盖vocxml、cocojson与yolotxt三种格式另有yaml配置文件、划分脚本与教程页面可直接接入YOLO系列模型训练。附带的Python脚本支持按需求划分训练集、验证集与测试集并生成ImageSets索引文件教程涵盖Windows与Linux环境搭建、Ubuntu安装及基于案例修改训练自己数据集的完整流程。目前已有471人学习下载适合课程设计、毕业项目或算法入门者快速获得一份标注规范、格式齐全、可复现的树叶检测数据基础。1. 树叶分类检测数据集到底解决什么问题从 1000 张图到三种标签格式做树叶识别这件事坑不在模型而在数据。我见过太多人拿着 YOLO 官方权重直接去拍校园里的树叶结果模型把银杏认成枫叶把梧桐认成杨树——不是模型不行是它压根没见过你场景里的叶子。这个数据集标题里最值钱的部分不是「1000 张图片」而是「voc、coco 和 yolo 三种格式标签 划分脚本 训练教程」这一整套流水线。它解决的是从原始标注到可训练数据集的最后一公里问题你拿到手的是已经标注好的图片但不同框架吃不同格式的标签VOC 是 XML、COCO 是 JSON、YOLO 是 TXT三者之间的转换和划分才是真正卡住新手的地方。适合谁做植物分类毕设的学生、想跑通目标检测全流程的入门者、需要快速验证树叶检测方案的工程师。1000 张图不算多但足够你把「数据准备→格式转换→训练→推理」这条链路完整走一遍知道每一步在干什么、哪里会翻车。2. 三种标签格式的差异与转换VOC、COCO、YOLO 到底怎么选2.1 三种格式的坐标系与文件结构对比VOC、COCO、YOLO 这三种格式最核心的差异在坐标表示方式和文件组织上。VOC 用绝对像素坐标左上角和右下角两个点确定一个框每张图对应一个 XML 文件标签和图片分开存放。COCO 把所有图片的标注塞进一个 JSON 文件坐标是绝对像素的 [x, y, width, height]还带 categories 和 images 两个索引段。YOLO 最简洁每张图一个 TXT每行是类别索引 中心x 中心y 宽 高全部归一化到 0 到 1 之间。格式坐标类型文件组织类别存储典型框架VOC绝对像素 (xmin,ymin,xmax,ymax)每图一个 XMLXML 内 name 字段Faster R-CNN、SSDCOCO绝对像素 (x,y,w,h)单个 JSONcategories 数组Detectron2、MMDetectionYOLO归一化 (cx,cy,w,h)每图一个 TXTclasses.txt 或 data.yamlUltralytics YOLO 系列选哪个取决于你用什么框架。跑 YOLOv8 或 YOLOv11 就用 YOLO 格式用 MMDetection 或 Detectron2 就转 COCO用老版 TensorFlow 的 object detection API 就转 VOC。这个数据集三种都给了省掉了你自己写转换脚本的麻烦但理解转换逻辑仍然重要——因为你的类别名和类别数可能和数据集不一样需要自己改。2.2 用 Python 做 VOC 到 YOLO 的格式转换假设你拿到的是 VOC 格式的 XML要转成 YOLO 的 TXT核心就是坐标归一化和类别映射。下面这段脚本我用了很多次改一下路径和类别列表就能跑import xml.etree.ElementTree as ET import os # 类别列表顺序决定 YOLO 标签里的类别索引 classes [ginkgo, maple, camphor, poplar, willow] def voc_to_yolo(xml_path, img_w, img_h, output_dir): tree ET.parse(xml_path) root tree.getroot() txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt txt_path os.path.join(output_dir, txt_name) with open(txt_path, w) as f: for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) 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) # 归一化中心点坐标和宽高都除以图片尺寸 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h f.write(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n)这段代码的逻辑很直接解析 XML找到每个 object 的类别名和边界框把绝对坐标转成归一化的中心点加宽高。classes列表的顺序必须和你在data.yaml里写的 names 顺序一致否则模型学到的类别会错位。img_w和img_h从图片本身读取可以用 PIL 或 OpenCV 获取。转换完记得检查有没有空 TXT 文件——如果某张图里所有类别都不在classes列表里就会生成空文件训练时可能报错。2.3 COCO JSON 转 YOLO 的关键字段提取COCO 格式转 YOLO 稍微绕一点因为所有信息都在一个 JSON 里。你需要先建立 image_id 到文件名的映射再遍历 annotations 数组import json import os def coco_to_yolo(json_path, output_dir): with open(json_path, r) as f: data json.load(f) # 建立 image_id 到 (文件名, 宽, 高) 的映射 img_info {} for img in data[images]: img_info[img[id]] (img[file_name], img[width], img[height]) # 建立 category_id 到连续索引的映射 cat_map {} for idx, cat in enumerate(data[categories]): cat_map[cat[id]] idx # 按图片分组写入 from collections import defaultdict img_anns defaultdict(list) for ann in data[annotations]: img_anns[ann[image_id]].append(ann) for img_id, anns in img_anns.items(): fname, w, h img_info[img_id] txt_name os.path.splitext(fname)[0] .txt with open(os.path.join(output_dir, txt_name), w) as f: for ann in anns: cls_id cat_map[ann[category_id]] x, y, bw, bh ann[bbox] cx (x bw / 2.0) / w cy (y bh / 2.0) / h nw bw / w nh bh / h f.write(f{cls_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n)这里最容易翻车的地方是category_id不连续。COCO 的 category_id 可能是 1、3、7 这种跳号但 YOLO 要求类别索引从 0 开始连续。所以必须用cat_map做一次重映射。另外 COCO 的 bbox 格式是[x, y, width, height]x 和 y 是左上角坐标不是中心点转换时要注意。3. 数据集划分脚本怎么写训练集、验证集、测试集的比例与坑3.1 划分比例的选择与分层抽样1000 张图怎么分常见做法是 7:2:1 或 8:1:1。树叶分类这种类别可能不均衡的场景我一般用 7:2:1并且尽量做分层抽样——保证每个类别在训练集和验证集里的比例大致相当。如果某个类别只有 50 张图随机划分可能导致验证集里一张都没有那验证指标就完全不可信了。下面这个划分脚本按类别分层同时处理图片和标签的对应关系import os import random import shutil from collections import defaultdict def split_dataset(img_dir, label_dir, output_dir, ratios(0.7, 0.2, 0.1)): random.seed(42) # 固定随机种子保证可复现 # 按类别分组读取每个标签文件取第一个类别作为该图的主类别 cls_imgs defaultdict(list) for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue with open(os.path.join(label_dir, txt)) as f: lines f.readlines() if not lines: continue main_cls lines[0].split()[0] img_name os.path.splitext(txt)[0] .jpg cls_imgs[main_cls].append(img_name) # 对每个类别分别划分 for split in [train, val, test]: os.makedirs(os.path.join(output_dir, images, split), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, split), exist_okTrue) for cls, imgs in cls_imgs.items(): random.shuffle(imgs) n len(imgs) n_train int(n * ratios[0]) n_val int(n * ratios[1]) splits { train: imgs[:n_train], val: imgs[n_train:n_train n_val], test: imgs[n_train n_val:] } for split, files in splits.items(): for img in files: txt os.path.splitext(img)[0] .txt shutil.copy(os.path.join(img_dir, img), os.path.join(output_dir, images, split, img)) shutil.copy(os.path.join(label_dir, txt), os.path.join(output_dir, labels, split, txt))random.seed(42)这行别省否则每次跑出来的划分结果不一样实验没法复现。按主类别分组是为了保证每个类别在三个子集里都有样本。如果某张图有多个类别取第一个作为主类别就够了不影响整体均衡。3.2 划分后必须检查的三件事划分完不是就完事了我每次都会检查三件事。第一图片和标签是否一一对应——有没有图片没有对应 TXT或者 TXT 没有对应图片。第二每个子集的类别分布是否合理用collections.Counter统计一下每个类别的数量。第三有没有空标签文件混进去空文件会让训练时的损失计算出问题。# 检查图片和标签是否一一对应 for split in [train, val, test]: imgs set(os.path.splitext(f)[0] for f in os.listdir(fdataset/images/{split})) labels set(os.path.splitext(f)[0] for f in os.listdir(fdataset/labels/{split})) only_img imgs - labels only_label labels - imgs print(f{split}: 只有图片无标签 {len(only_img)}, 只有标签无图片 {len(only_label)})如果输出不是 0 和 0就得回去查是哪一步出了问题。常见原因是图片扩展名不统一有的.jpg有的.JPG在 Linux 下大小写敏感就会漏掉。4. 用 YOLOv8 跑通树叶检测训练配置文件与关键参数4.1 data.yaml 的写法与路径陷阱Ultralytics YOLOv8 和 YOLOv11 都吃data.yaml这个配置文件。路径问题是新手翻车最多的地方——yaml 里的path是数据集根目录train、val、test是相对于path的子路径。如果你写绝对路径换台机器就跑不了写相对路径又容易搞错基准目录。# data.yaml path: ./dataset # 数据集根目录 train: images/train val: images/val test: images/test nc: 5 # 类别数 names: 0: ginkgo 1: maple 2: camphor 3: poplar 4: willownc必须和names的长度一致names的索引必须从 0 开始连续。如果你的类别名里有中文建议改成英文或拼音避免编码问题。这个 yaml 文件放在项目根目录训练时用yolo detect train datadata.yaml指定。4.2 训练命令与 batch size、imgsz 的取值训练命令本身很简单关键是参数怎么设。1000 张图属于小数据集我一般用 YOLOv8n 或 YOLOv8s 这种小模型从预训练权重开始微调yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectleaf_detection \ nameexp1imgsz640是 YOLO 系列的默认输入尺寸树叶目标通常不大640 够用。batch16取决于你的显存8G 显存跑 YOLOv8n 用 16 没问题跑 YOLOv8m 可能得降到 8。lr00.01是初始学习率微调预训练模型时这个值比较稳。patience20表示 20 个 epoch 验证指标不提升就早停防止过拟合。epochs100对小数据集来说够了再多容易过拟合。4.3 训练过程中的指标解读与早停判断训练时终端会输出每一轮的 box_loss、cls_loss、mAP50、mAP50-95。box_loss 是边界框回归损失cls_loss 是分类损失两个都应该随着训练下降。mAP50 是 IoU 阈值 0.5 时的平均精度mAP50-95 是 0.5 到 0.95 多个阈值下的平均值后者更严格。如果 box_loss 下降但 mAP 不涨可能是标注框质量有问题或者类别不均衡导致模型偏向多数类。如果训练集 mAP 很高但验证集 mAP 很低那就是过拟合了需要加数据增强或者减少模型参数量。YOLOv8 默认开启了 mosaic、mixup 等增强小数据集上这些增强有帮助但如果你的树叶图片背景很单一mosaic 可能反而引入噪声可以在data.yaml同级加一个augment.yaml关掉部分增强。5. 树叶检测的避坑与排查标注、类别、显存、过拟合5.1 标注框贴边导致归一化坐标越界现象训练时报错Label format error或者坐标超出 0 到 1 范围。原因VOC 转 YOLO 时如果标注框的 xmax 等于图片宽度归一化后 cx w/2 可能刚好等于 1.0浮点精度问题导致变成 1.000001。解决在转换脚本里加一行裁剪cx min(max(cx, 0), 1)四个值都做一遍。另外标注时尽量让框比叶子边缘大两三个像素别贴着图片边界画。5.2 类别不均衡导致模型只认多数类现象训练完推理时模型把所有叶子都预测成数量最多的那个类别。原因1000 张图里如果银杏有 600 张、枫叶只有 50 张模型只要全猜银杏就能拿到 60% 的准确率梯度被多数类主导。解决一是划分时做分层抽样保证验证集均衡二是训练时用cls损失的类别权重YOLOv8 可以在data.yaml里加cls_pw参数或者用过采样把少数类复制几份。我一般先看混淆矩阵确认是不是真的偏向多数类再决定要不要加权。5.3 显存不足导致 batch size 被迫降到 1现象训练启动时报CUDA out of memory。原因batch16加imgsz640对 8G 显存来说在 YOLOv8m 上就撑不住了。解决优先降 batch 到 8 或 4其次降 imgsz 到 512最后才考虑换更小的模型。降 batch 会影响 BN 层的统计量如果降到 1 或 2建议把model换成 YOLOv8n 并开启ampTrue混合精度训练。另外可以在训练命令里加cacheFalse避免把图片全部缓存到显存。5.4 验证集 mAP 虚高因为数据泄露现象验证集 mAP 到了 0.95但拿新拍的树叶照片测试效果很差。原因划分数据集时没有按图片去重同一片叶子的不同角度照片被分到了训练集和验证集模型实际上见过验证集的图。解决划分前先对图片做感知哈希去重或者按拍摄批次划分——同一批次拍的叶子全部放进训练集或全部放进验证集。这个坑很隐蔽因为指标看起来很美只有真正部署时才暴露。5.5 推理时类别名显示为数字而不是树叶名现象用yolo detect predict推理结果图上框旁边显示的是0、1而不是ginkgo、maple。原因推理时没有加载data.yaml里的 names 映射或者加载的 yaml 和训练时不一致。解决推理命令里显式指定datadata.yaml或者用 Python API 时把model.names打印出来确认。如果 names 是空的说明模型文件里没保存类别信息需要重新训练或者手动设置。6. 小数据集涨点技巧从 1000 张图榨出更高 mAP 的实操1000 张图在目标检测里算小数据集但树叶检测这个场景有它的特殊性——同类树叶的形状、纹理、颜色变化有限模型其实不需要太多样本就能学到关键特征。我试过几个有效的涨点手段按性价比排序。第一个是 Copy-Paste 增强。把一张图里的叶子抠出来随机粘贴到其他图的背景上相当于免费扩充了样本。树叶的边界比较清晰用简单的颜色阈值或者 GrabCut 就能抠出来。粘贴时注意缩放和旋转别让叶子大小和背景明显不搭。这个操作能把有效训练样本翻两三倍mAP50 通常能涨 3 到 5 个点。第二个是调整锚框尺寸。YOLOv8 是 anchor-free 的但如果你用的是 YOLOv5 或更早版本默认锚框是基于 COCO 数据集聚类的和树叶的尺寸分布不匹配。用kmeans对自己的标注框做一次聚类把得到的锚框尺寸写进配置文件小目标召回率会明显提升。第三个是测试时增强TTA。推理时把图片水平翻转、多尺度缩放各跑一遍把结果做 NMS 融合。Ultralytics 支持augmentTrue开启 TTA代价是推理速度慢两三倍但 mAP 能涨 1 到 2 个点。如果部署环境对延迟不敏感这个开关值得打开。第四个是冻结骨干网络微调。先用 COCO 预训练权重冻结 backbone 训练 20 个 epoch让检测头适应树叶类别再解冻全部网络用更小的学习率训练 50 个 epoch。这样比直接端到端微调收敛更稳尤其当你的数据集类别和 COCO 差异较大时。最后说一个我自己的习惯每次训练完我都会把验证集里 mAP 最低的那几张图单独拿出来看分析是标注问题、遮挡问题还是类别混淆。大部分时候改几张标注比调参涨点更明显。树叶检测这个方向数据质量的上限远高于模型结构的上限。希望帮到你。本文还有配套的精品资源点击获取
返回列表