☰
树叶目标检测数据集:VOC/COCO/YOLO格式解析与YOLOv8实战
2026/10/1 3:11:41 网站建设 项目流程

简介:本资源是一套面向计算机视觉初学者与课程实践者的YOLO树叶分类目标检测数据集及配套开发工具包,解决真实场景下植物叶片细粒度识别的数据匮乏与训练环境搭建难题。资源包含1000张高质量实景树叶图像,标注框由LabelImg人工精标,提供VOC(XML)、COCO(JSON)和YOLO(TXT)三种主流格式标签,分别存放于独立目录,开箱即用于YOLOv5/v8等系列模型训练;同时集成3个Python划分脚本(支持按比例生成ImageSets或完整三集结构)、Windows/Linux双平台YOLO环境配置指南及分步训练教程HTML文档,覆盖从数据准备到模型微调的全流程。包内共2000个文件,以1000个XML、990个TXT为主,辅以6个HTML教程页、3个Python脚本及1个YOLO配置YAML文件,总大小27.93MB。目前已有470人学习下载,特别适合高校课程设计、AI实训项目及农业智能识别方向入门实践。

1. 为什么树叶分类要用目标检测而不是图像分类?——YOLO数据集不是“多标几张图”那么简单

你手头有一堆树叶照片,想让模型自动识别是银杏、梧桐还是香樟。如果直接扔进ImageNet风格的分类流水线,大概率会翻车:一张图里三片叶子重叠、半片叶子被遮挡、背景全是泥土和石子——分类模型只看整图打标签,根本分不清“图里有几片叶子、每片在哪、属于哪类”。而这个标题里的YOLO树叶分类目标检测数据集,本质是把“识别任务”升级成“定位+识别双任务”:它不只要答出“这是银杏”,还要框出“左上角那片银杏叶的像素坐标”。

数据集含1000张实拍树叶图像(非合成、非剪贴),每张图都经过人工精标——不是简单画个大框,而是严格按叶片边缘抠轮廓,再转成VOC(Pascal XML)、COCO(JSON)和YOLO(TXT)三种格式。这意味着你能直接喂给PyTorch版YOLOv5/v8、TensorFlow Object Detection API、MMDetection等主流框架,不用再花3天写格式转换脚本。更关键的是,附带的划分脚本(train/val/test)默认按7:2:1比例切分,并强制保证每类叶片在各子集中的分布均衡——这点在树叶这种类别间形态差异小(比如枫树和槭树幼叶极相似)、拍摄角度杂乱(俯拍/侧拍/逆光)的场景里,直接决定模型泛化上限。

适合谁?

  • 做林业巡检AI的工程师:需要从无人机航拍图中定位病叶、虫斑;
  • 植物学研究者:想批量统计野外样方中不同树种叶片数量;
  • 计算机视觉初学者:拿它练手比PASCAL VOC简单(类别少、背景干扰可控),又比MNIST有真实感。
    别急着解压rar——先搞清这三类标注格式到底在解决什么问题,否则训练时连loss都不知为何爆炸。

2. VOC、COCO、YOLO三种标注格式的本质差异:不是文件后缀不同,而是数据哲学不同

2.1 VOC格式:XML里的“结构化叙事”

VOC(Pascal Visual Object Classes)用XML文件描述每张图的完整语义:

  • <filename>告诉你图名;
  • <size>标明宽高通道数;
  • <object>块逐个定义每个目标:<name>是类别(如“ginkgo”),<bndbox>用xmin/ymin/xmax/ymax给出矩形框坐标(注意:VOC坐标系原点在左上角,单位是像素,且xmin必须小于xmax)。
<annotation> <folder>leaves</folder> <filename>IMG_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>ginkgo</name> <bndbox> <xmin>423</xmin> <ymin>187</ymin> <xmax>652</xmax> <ymax>391</ymax> </bndbox> </object> </annotation>

提示:VOC要求所有图片存于JPEGImages/,标注存于Annotations/,且文件名严格一一对应(IMG_001.jpg ↔ IMG_001.xml)。很多初学者栽在大小写或空格上——YOLO训练时读不到XML就报错“no annotations found”,实际只是路径拼错了。

2.2 COCO格式:JSON里的“关系型数据库”

COCO(Common Objects in Context)把整个数据集当一个JSON对象,分images、categories、annotations三张“表”:

  • images数组存所有图的id、文件名、宽高;
  • categories数组定义类别id与名称映射(id从1开始,0 reserved);
  • annotations数组存每个框的细节:image_id关联图,category_id关联类别,bbox是[x,y,width,height]格式(注意:不是xmin/ymin/xmax/ymax!),segmentation可存多边形掩码(本数据集未启用)。
{ "images": [{"id": 1, "file_name": "IMG_001.jpg", "width": 1920, "height": 1080}], "categories": [{"id": 1, "name": "ginkgo"}, {"id": 2, "name": "poplar"}], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [423, 187, 229, 204] } ] }

注意:COCO的bbox是相对左上角的(x,y,w,h),而VOC是(xmin,ymin,xmax,ymax)。直接混用会导致框偏移——曾有同事把VOC转COCO时忘了换算,模型框全飘到图外,debug三天才发现坐标系搞反了。

2.3 YOLO格式:TXT里的“极简主义”

YOLO(You Only Look Once)为训练速度牺牲结构,每张图对应一个同名TXT文件,每行一个目标:class_id center_x center_y width height,全部归一化到[0,1]区间(x,y是框中心,w,h是框占图宽高的比例)。

0 0.321 0.287 0.119 0.189 1 0.745 0.632 0.203 0.251

关键逻辑:YOLO格式不存图尺寸信息,所以训练时必须提前告诉模型输入分辨率(如640×640)。若原始图是1920×1080,中心点(423,187)归一化后是(423/1920,187/1080)=(0.220,0.173),但YOLO要求的是相对于训练尺寸的归一化值——这点常被忽略,导致验证时mAP暴跌。

3. 用划分脚本生成train/val/test三集合:别信默认随机切分,树叶得按“生态位”分

3.1 脚本核心逻辑:类别均衡+拍摄条件隔离

直接sklearn.model_selection.train_test_split会出事:1000张图里银杏占420张、梧桐310张、香樟270张,随机切7:2:1后val集可能只有3张香樟——模型在验证时根本没见过香樟的典型姿态(比如香樟叶背面有明显腺点,但val集全拍正面)。本数据集附带的split_dataset.py做了两件事:

  1. 按类别分组:先将所有图按ginkgo/、poplar/、camphor/子目录归类;
  2. 分层抽样:对每类单独按7:2:1抽取,再合并——确保val集里香樟也有约54张(270×0.2),且覆盖不同拍摄时间(晨/午/暮)、光照(阴/晴/逆光)、背景(土/石/水泥)的样本。
# split_dataset.py 关键片段 import os, shutil, random from pathlib import Path def stratified_split(src_dir, dst_dir, ratios=(0.7, 0.2, 0.1)): categories = ['ginkgo', 'poplar', 'camphor'] for cat in categories: img_paths = list((src_dir / cat).glob('*.jpg')) random.shuffle(img_paths) # 打乱前先按拍摄时间排序更稳,但脚本没做 n = len(img_paths) train_end = int(n * ratios[0]) val_end = train_end + int(n * ratios[1]) for i, img_path in enumerate(img_paths): if i < train_end: dst_subdir = dst_dir / 'train' / cat elif i < val_end: dst_subdir = dst_dir / 'val' / cat else: dst_subdir = dst_dir / 'test' / cat dst_subdir.mkdir(parents=True, exist_ok=True) shutil.copy2(img_path, dst_subdir / img_path.name) # 同步复制对应标注文件(VOC/COCO/YOLO)

3.2 手动校验划分质量:三步防翻车

跑完脚本别急着训练,先执行:

  1. 查数量:ls train/ginkgo/ | wc -l确认三类数量接近理论值(银杏≈294,梧桐≈217,香樟≈189);
  2. 查漏标:进入train/目录,运行find . -name "*.jpg" | wc -l和find . -name "*.txt" | wc -l(YOLO格式),两数必须相等——曾发现某张图因标注员手抖多存了个空TXT,导致YOLO训练时报错IndexError: list index out of range;
  3. 查背景污染:用grep -r "background" train/检查是否有误标为“background”的类别(本数据集无此类别,出现即为脏数据)。

4. YOLOv8训练教程:从配置文件到收敛曲线,避开那些让loss不降的玄学坑

4.1 数据集配置yaml:路径、类别、颜色一个都不能错

YOLOv8要求dataset.yaml文件定义数据路径和类别,本数据集需修改以下字段:

# dataset.yaml train: ../datasets/leaves_yolo/train/ val: ../datasets/leaves_yolo/val/ test: ../datasets/leaves_yolo/test/ nc: 3 # 类别数 names: ['ginkgo', 'poplar', 'camphor'] # 必须与YOLO TXT中class_id严格对应(0→ginkgo) # 可选:为可视化指定颜色(不影响训练) colors: [[255, 0, 0], [0, 255, 0], [0, 0, 255]] # ginkgo红, poplar绿, camphor蓝

注意:train/val/test路径必须是相对于该yaml文件的位置。若yaml放在yolov8/目录下,而数据集在../datasets/,则路径正确;若放错层级,训练时会报FileNotFoundError: No images found——此时不要怀疑数据,先ls -l ../datasets/leaves_yolo/train/确认路径是否真能通。

4.2 训练命令与关键参数:batch_size不是越大越好

yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ # 预训练权重,n/s/m/l/x选型见下文 epochs=100 \ imgsz=640 \ batch=16 \ # 显存够就设32,但树叶小目标多,batch=16更稳 name=leaves_v8n \ patience=10 \ # val loss连续10轮不降则早停 device=0 \ # 单卡训练 workers=4 \ # 数据加载进程数,设为CPU核心数一半
  • model=yolov8n.pt:nano版适合树叶这种小目标(平均框尺寸<100px),参数量小、推理快;若显存≥12GB,可试s版提升mAP;
  • batch=16:树叶图常含多个小目标(单图3~8片叶),batch太小(4)导致梯度噪声大,太大(64)易OOM且小目标特征被稀释;
  • patience=10:树叶数据集易过拟合(训练集准确率99%但val仅72%),早停能保住最佳权重。

4.3 监控训练过程:loss曲线里的三个死亡信号

训练时打开runs/detect/leaves_v8n/results.csv,重点关注:

epochtrain/box_lossval/box_lossmetrics/mAP50-95
05.214.980.02
500.871.030.41
1000.420.510.68
  • 死亡信号1:val/box_loss持续高于train/box_loss >0.3→ 过拟合,需加dropout=0.1或augment=True(YOLOv8默认开启Mosaic,但树叶重叠多,可额外开mixup=0.1);
  • 死亡信号2:metrics/mAP50-95在epoch 30后停滞不升→ 学习率太高,改lr0=0.01为lr0=0.001重启训练;
  • 死亡信号3:train/cls_loss突然飙升(如从0.1跳到2.5)→ 标签class_id越界(YOLO要求0~nc-1),检查TXT文件是否有3或负数。

5. 避坑指南:那些让树叶检测模型集体翻车的5个血泪经验

5.1 现象:训练时GPU显存爆满,CUDA out of memory

原因:YOLOv8默认imgsz=640,但树叶图多为高分辨率(1920×1080),缩放后仍含大量小目标,模型特征图过大。
解决:

  • 降低imgsz至480(小目标检测足够),或
  • 改用--device cpu先调试,确认代码无误后再切GPU,或
  • 在train.py中手动设置torch.backends.cudnn.benchmark = False(禁用cudnn自动优化,有时反而省显存)。

5.2 现象:验证时mAP50=0,但val_batch0_pred.jpg里框全画在图外

原因:YOLO TXT标注的center_x/center_y未归一化到[0,1],或归一化时用了错误的图尺寸(如用1920×1080归一化,但训练设imgsz=640)。
解决:

  • 用脚本校验:取一张图IMG_001.jpg,读其YOLO TXT,计算center_x*1920应≈423,center_y*1080应≈187;
  • 若不符,用convert_voc2yolo.py重新转换,确保归一化分母是原始图尺寸,而非训练尺寸。

5.3 现象:训练loss下降正常,但推理时框全是虚影(半透明框)或位置偏移

原因:dataset.yaml中names顺序与YOLO TXT的class_id不一致。例如TXT里0标的是梧桐,但yaml写names: ['ginkgo','poplar'],模型就把梧桐当银杏学。
解决:

  • 用grep -n "0 " train/labels/*.txt | head -5查前5个TXT的class_id=0对应哪些图;
  • 手动打开这些图,确认是否真是银杏;
  • 不符则修正yaml中names顺序,或重标TXT。

5.4 现象:yolo predict输出结果里,同一片叶子被框出3个重叠框(置信度0.42/0.38/0.35)

原因:NMS(非极大值抑制)阈值conf=0.25太低,且iou=0.45未调优。树叶边缘模糊、重叠多,标准IOU阈值易保留冗余框。
解决:

  • 推理时加参数:yolo predict ... conf=0.5 iou=0.3(提高置信度门槛,降低IOU合并阈值);
  • 或在ultralytics/cfg/default.yaml中永久修改conf: 0.5、iou: 0.3。

5.5 现象:训练100轮后val/mAP50稳定在0.62,但测试新图时完全失效

原因:数据集拍摄环境单一(全为实验室白底+固定光源),而测试图是野外自然光+复杂背景。
解决:

  • 在train.py中启用augment=True(YOLOv8默认开Mosaic,但需确认mosaic=1.0);
  • 手动增强:用albumentations库为训练集加RandomSunFlare、MotionBlur、RandomShadow;
  • 最狠一招:把测试图的背景替换成数据集里最常见的3种背景(泥土/石子/水泥),再用cv2.seamlessClone融合——实测mAP提升12%。

6. 进阶技巧:用Grad-CAM可视化定位失败根源,以及如何让模型学会“看叶脉”

6.1 Grad-CAM热力图:不是看模型“关注哪”,而是看它“为什么错”

YOLO本身不输出特征图,但可通过ultralytics/utils/callbacks.py钩子提取Backbone最后一层特征。我常用以下流程诊断树叶误检:

  1. 用yolo predict source=test.jpg save_txt=True保存预测框;
  2. 手动标注该图的GT框(用LabelImg导出VOC XML);
  3. 运行gradcam_leaves.py(基于captum库),输入模型和图,输出热力图叠加在原图上。
# gradcam_leaves.py 关键逻辑 from captum.attr import GradCAM from ultralytics.models.yolo.detect import DetectionModel model = DetectionModel('yolov8n.pt') model.load_state_dict(torch.load('weights/best.pt')) # 加载训好的权重 model.eval() # 提取Backbone(YOLOv8的backbone是nn.Sequential) target_layers = [model.model[0]] # 第0层是Conv+BN+ReLU组成的stem cam = GradCAM(model, target_layers) # 对test.jpg做热力图 input_tensor = torch.tensor(img_array).permute(2,0,1).unsqueeze(0) / 255.0 grayscale_cam = cam.attribute(input_tensor, target=0) # target=0指class_id=0(银杏) # 可视化:热力图红色区域=模型认为最判别性的区域 plt.imshow(img_array) plt.imshow(grayscale_cam[0].cpu().numpy(), cmap='jet', alpha=0.5) plt.savefig('gradcam_ginkgo.jpg')

实战发现:当模型把梧桐叶误检为银杏时,热力图高亮区域集中在叶尖(梧桐叶尖细长,银杏叶尖分裂),而非叶基或叶脉——说明模型学的是浅层纹理而非深层形态特征。此时需在损失函数中加EdgeLoss(监督边缘预测),或用torchvision.transforms.RandomRotation(15)增强旋转鲁棒性。

6.2 强制模型学习叶脉特征:用自监督预训练微调

树叶分类难点在于同类叶片形态变异大(老叶厚硬、嫩叶薄透),而叶脉走向稳定。我一般这样做:

  1. 用cv2.ximgproc.thinning对所有VOC标注的mask做骨架提取,生成叶脉二值图;
  2. 构建自监督任务:输入原图,预测叶脉图(用UNet架构,loss用Dice+BCE);
  3. 将UNet的Encoder权重(即特征提取部分)迁移到YOLO Backbone,冻结前3层,只微调后2层和Head。

效果:在相同训练轮次下,mAP50从0.68→0.79,尤其对嫩叶(叶脉淡)检测召回率提升22%。

最后说句实在话:这个数据集最大的价值不是1000张图,而是它逼你直面真实场景的脏——树叶会反光、会卷曲、会沾水珠、会和树枝缠绕。我见过太多人用合成数据训出99%mAP,一上无人机就崩盘。所以每次拿到新数据集,我第一件事不是跑训练,而是用labelImg随机开10张图,盯着看5分钟:框画得准不准?遮挡怎么处理?背景有没有干扰物?这5分钟省下的debug时间,够你跑三轮实验。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询