
简介这份资源面向从事目标检测学习与课程实践的学生、教师及算法入门者提供一套真实场景下的树叶分类目标检测数据集可直接用于YOLO系列模型的训练与验证。压缩包共约2000个文件以1000个xml标注文件、990个txt标签文件为主另含少量html教程页面、py脚本与yaml配置文件整体约27.93MB体积轻便便于快速下载与本地部署。数据经labelimg标注标注框质量较高同时提供voc、coco和yolo三种格式标签分别存放于不同文件夹省去格式转换环节。资源还附赠数据集划分脚本可按需生成训练集、验证集与测试集并配套Windows与Linux环境搭建、训练案例教程帮助读者从环境配置到模型训练完整跑通流程。目前已有471人学习下载适合作为课程设计、毕业设计或目标检测入门练手的数据基础。1. 树叶分类检测数据集1000 张图、三种标签格式和一套划分脚本到底怎么用手上接到一个树叶识别的小项目客户扔过来一个压缩包名字长得像绕口令YOLO树叶分类目标检测数据集(含1000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar。这种资源在目标检测圈子里很常见但真正让人头疼的不是模型选哪个而是拿到手之后发现标签格式对不上、划分脚本跑不通、训练时类别数写错。树叶分类这个场景本身不算复杂难点在于叶片重叠、背景杂乱、同类叶片形态差异大1000 张图的量级刚好卡在“够用但不够富裕”的区间。这篇文章面向的是手里已经拿到类似数据集、准备用 YOLO 跑通训练和推理的从业者从目录结构、三种标签格式的差异、划分脚本的写法到 YOLOv8/v11 训练参数和避坑一步步拆开讲。新手能照着命令跑熟手能直接看参数边界和踩坑记录。2. 树叶数据集目录结构与三种标签格式的取舍2.1 拿到压缩包先别急着解压训练先看清目录很多人拿到 .rar 之后直接解压看到 images 和 labels 就开始写 data.yaml结果训练时发现标签全是 VOC 的 XMLYOLO 根本不认。常见做法是先列一遍目录树确认图片数量、标签格式、是否有划分文件。一个典型的树叶数据集解压后大概长这样# 查看压缩包内容不解压 unrar l YOLO树叶分类目标检测数据集.rar # 解压后进入目录列出两级结构 tree -L 2 -d leaf_dataset/预期输出类似leaf_dataset/ ├── images/ # 1000 张 jpg/png ├── annotations/ # VOC 格式 XML ├── labels/ # YOLO 格式 txt ├── coco/ # COCO 格式 json ├── split.py # 划分脚本 └── train_tutorial.md这里的关键是images 目录下可能混放了所有图片而 labels 目录下可能只有部分图片有对应 txt。先统计一下图片和标签的数量是否一致这是后面排查“训练时找不到标签”的第一步。# 统计图片数量 find leaf_dataset/images -type f \( -name *.jpg -o -name *.png \) | wc -l # 统计 YOLO 标签数量 find leaf_dataset/labels -type f -name *.txt | wc -l # 统计 VOC 标签数量 find leaf_dataset/annotations -type f -name *.xml | wc -l如果三个数字不一致说明数据集本身有缺失需要先补齐或剔除无标签图片。我一般会写个脚本把没有对应标签的图片移出去避免训练时反复报 warning。2.2 VOC、COCO、YOLO 三种格式到底差在哪树叶分类检测里三种格式的核心差异在于坐标表示和文件组织方式。VOC 用 XML每个目标一个object节点坐标是左上角和右下角的绝对像素值COCO 用单个 JSON所有图片的标注集中管理坐标是 [x, y, width, height] 绝对像素YOLO 用每张图一个 txt每行一个目标格式是class_id x_center y_center width height全部归一化到 0-1。格式文件组织坐标类型类别表示适合场景VOC每图一个 XML绝对像素 xmin,ymin,xmax,ymax字符串类别名传统检测框架、标注工具导出COCO单 JSON绝对像素 x,y,w,h数字 category_id多任务、分割、评估YOLO每图一个 txt归一化中心点宽高数字 class_idYOLO 系列训练树叶数据集的类别通常不多比如“银杏叶、枫叶、梧桐叶”三类但有些数据集会把“叶片”和“叶柄”分开标导致类别数比预期多。拿到 COCO JSON 后第一件事是看 categories 字段确认类别数和顺序因为 YOLO 训练时 data.yaml 里的 names 顺序必须和 class_id 对应否则推理结果全是错的。import json with open(leaf_dataset/coco/annotations.json, r) as f: coco json.load(f) # 打印类别信息 for cat in coco[categories]: print(cat[id], cat[name]) # 统计每类目标数量 from collections import Counter cat_counter Counter(ann[category_id] for ann in coco[annotations]) print(cat_counter)这段代码的作用是确认类别 id 和名称的映射关系。参数上注意COCO 的 category_id 不一定从 0 开始也不一定连续而 YOLO 的 class_id 必须从 0 开始连续。如果 COCO 里是 1、2、3转 YOLO 时要减 1。树叶数据集里如果出现“背景”类通常要删掉因为 YOLO 不把背景当正类。2.3 格式转换VOC 转 YOLO 的脚本与边界处理如果数据集只给了 VOC 格式需要自己转 YOLO。转换的核心是读取 XML 里的 size 和 object 坐标做归一化。下面这个脚本处理了树叶数据集常见的几个边界图片宽高为 0、坐标越界、类别名不在预设列表里。import os import xml.etree.ElementTree as ET # 类别列表顺序决定 class_id CLASSES [ginkgo, maple, phoenix] # 按实际数据集修改 def voc_to_yolo(xml_path, img_w, img_h, output_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue # 跳过未定义类别 cls_id CLASSES.index(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) # 裁剪到图像边界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 跳过无效框 if xmax xmin or ymax ymin: continue # 归一化 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(output_path, w) as f: f.write(\n.join(lines))逻辑说明先按 CLASSES 列表过滤类别不在列表里的目标直接丢弃避免 class_id 错乱。坐标裁剪是必须的树叶数据集里经常有标注框超出图片边缘的情况不裁剪会导致归一化后坐标小于 0 或大于 1YOLO 训练时虽然不报错但会引入噪声。参数上img_w和img_h从 XML 的size节点读取不要硬编码。如果 XML 里没有 size 信息需要用 PIL 或 OpenCV 读图片获取。提示转换完成后随机抽 5 张图用 labelImg 或 CVAT 可视化检查确认框的位置和类别都对。我见过太多转换后类别偏移一位的情况训练 loss 降不下去排查半天才发现是 class_id 从 1 开始了。3. 划分脚本怎么写训练集、验证集、测试集的比例与随机种子3.1 为什么不能直接随机 split要先按类别分层树叶数据集的类别分布往往不均衡比如银杏叶有 600 张枫叶只有 200 张梧桐叶 200 张。如果直接random.shuffle然后按 8:1:1 切验证集里可能一个银杏叶都没有导致评估指标失真。常见做法是按类别分层抽样保证每个类别在训练集、验证集、测试集里的比例一致。import os import random from collections import defaultdict from sklearn.model_selection import train_test_split def split_dataset(label_dir, img_dir, output_dir, train_ratio0.8, val_ratio0.1, seed42): random.seed(seed) # 收集每张图的类别集合 img_classes {} for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue img_name txt_file.replace(.txt, ) with open(os.path.join(label_dir, txt_file)) as f: classes set(line.split()[0] for line in f if line.strip()) img_classes[img_name] list(classes) # 按主类别分组 class_to_imgs defaultdict(list) for img_name, classes in img_classes.items(): for c in classes: class_to_imgs[c].append(img_name) train_imgs, val_imgs, test_imgs [], [], [] for cls, imgs in class_to_imgs.items(): imgs list(set(imgs)) # 去重 train, temp train_test_split(imgs, test_sizeval_ratio (1 - train_ratio - val_ratio), random_stateseed) val, test train_test_split(temp, test_size0.5, random_stateseed) train_imgs.extend(train) val_imgs.extend(val) test_imgs.extend(test) # 去重避免多类别图片被重复划分 train_imgs list(set(train_imgs)) val_imgs list(set(val_imgs) - set(train_imgs)) test_imgs list(set(test_imgs) - set(train_imgs) - set(val_imgs)) # 写入文件 for split_name, split_imgs in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: with open(os.path.join(output_dir, f{split_name}.txt), w) as f: for img in split_imgs: f.write(f{img}\n) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)}, test: {len(test_imgs)})逻辑说明先统计每张图包含的类别按类别分组后分别做 train_test_split最后合并去重。参数上seed固定为 42 保证可复现train_ratio和val_ratio按需调整。树叶数据集如果某类样本少于 50 张建议只做 train/val 两分测试集从验证集里抽否则测试集太小没有统计意义。3.2 划分后的目录组织与 data.yaml 写法划分脚本输出的是三个 txt 文件里面是图片文件名不带扩展名。YOLO 训练时可以直接用这些 txt 作为索引也可以把图片和标签复制到 train/val/test 三个子目录。我一般倾向于后者因为 Ultralytics 的 YOLO 对目录结构有默认约定复制过去省去改代码的麻烦。# 按划分文件复制图片和标签 for split in train val test; do mkdir -p leaf_dataset/splits/$split/images mkdir -p leaf_dataset/splits/$split/labels while read img; do cp leaf_dataset/images/$img.jpg leaf_dataset/splits/$split/images/ cp leaf_dataset/labels/$img.txt leaf_dataset/splits/$split/labels/ done leaf_dataset/splits/$split.txt done对应的 data.yamlpath: /absolute/path/to/leaf_dataset/splits train: train/images val: val/images test: test/images names: 0: ginkgo 1: maple 2: phoenix注意path要写绝对路径Ultralytics 在不同版本里对相对路径的处理不一致写绝对路径最稳。names的顺序必须和转换脚本里的 CLASSES 完全一致否则训练出来的模型会把银杏叶识别成枫叶。注意如果数据集里图片是 png 格式复制时记得改扩展名或者在 data.yaml 里不写扩展名让 YOLO 自动匹配。我遇到过 jpg 和 png 混放的情况YOLO 默认只找 jpg导致一半图片被忽略。4. YOLOv8/v11 训练树叶检测模型的参数与显存控制4.1 从预训练权重开始不要从零训树叶数据集只有 1000 张图从零训练几乎不可能收敛到可用精度。常见做法是加载 COCO 预训练的 yolov8n.pt 或 yolo11n.pt冻结 backbone 前几层只微调检测头。Ultralytics 的命令行接口很直接yolo detect train \ dataleaf_dataset/splits/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ warmup_epochs3 \ patience20 \ device0 \ projectleaf_runs \ nameexp1参数说明imgsz640是 YOLO 的标准输入尺寸树叶目标通常不大640 够用如果叶片在图中占比很小可以提到 1024但显存翻倍。batch16在 8GB 显存上跑 yolov8n 没问题如果 OOM 就降到 8 或 4。lr00.01是初始学习率微调时如果 loss 震荡厉害降到 0.001。patience20表示 20 个 epoch 验证指标不提升就早停树叶数据集容易过拟合早停能省时间。4.2 显存不够时的三个降级方案树叶数据集虽然只有 1000 张但如果用 yolov8m 或 yolov8l显存很容易爆。我一般按这个顺序降级先降 batch再降 imgsz最后换更小的模型。下面这个表格是实测的显存占用参考RTX 3060 12GBAMP 开启模型imgszbatch显存占用训练速度yolov8n64016~4.2GB快yolov8s64016~6.8GB中yolov8m6408~9.5GB慢yolov8n10248~7.1GB中如果显存还是不够可以开启梯度累积用batch4配合accumulate4模拟 batch16 的效果但训练时间会拉长。另外cacheTrue可以把图片缓存到内存加快数据加载但 1000 张图缓存后大概占 2-3GB 内存内存小的机器慎用。# 显存不足时的保守配置 yolo detect train \ dataleaf_dataset/splits/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch8 \ accumulate2 \ lr00.005 \ cos_lrTrue \ device0cos_lrTrue启用余弦退火学习率对小数据集更友好避免后期 loss 震荡。accumulate2表示每 2 个 batch 更新一次权重等效 batch16。4.3 训练过程中的关键指标怎么看训练日志里重点看三个指标box_loss、cls_loss 和 mAP50。树叶检测的 box_loss 通常在前 10 个 epoch 快速下降如果 20 个 epoch 后还在 0.5 以上说明标注框质量有问题或者学习率太大。cls_loss 反映分类难度树叶类别之间如果形态相似比如不同品种的枫叶cls_loss 会偏高这时候可以考虑增加类别间的区分度比如加入叶脉纹理特征。# 训练结束后用验证集评估 yolo detect val \ modelleaf_runs/exp1/weights/best.pt \ dataleaf_dataset/splits/data.yaml \ imgsz640 \ batch16验证输出里关注 mAP50-95 和每类的 AP。如果某一类 AP 明显低于其他类大概率是样本量不足需要针对性补充该类图片或做数据增强。树叶数据集常用的增强包括随机旋转、颜色抖动、马赛克Ultralytics 默认开启 mosaic如果叶片重叠严重可以关掉 mosaic 改用 mixup。提示训练时把plotsTrue打开结束后会生成混淆矩阵和 PR 曲线能直观看到哪两类容易混淆。我见过银杏叶和梧桐叶互相误检的情况后来发现是标注时把两者标反了。5. 树叶检测避坑从标签错位到推理翻车的 5 个血泪记录5.1 现象训练 loss 正常下降但推理结果全是乱框原因data.yaml 里的 names 顺序和标签文件里的 class_id 不对应。比如转换脚本里 CLASSES 是 [ginkgo, maple, phoenix]但 data.yaml 写成了 [maple, ginkgo, phoenix]模型学到的“0”是银杏叶推理时却按枫叶输出。解决训练前用脚本校验一遍随机抽 10 张图把标签里的 class_id 映射回类别名和图片肉眼比对。5.2 现象验证集 mAP 很高但测试集一塌糊涂原因划分脚本没有按类别分层验证集和测试集分布不一致。树叶数据集里如果某类只出现在验证集测试集里没有mAP 虚高。解决用分层抽样重新划分确保每个类别在三个集合里的比例接近。另外检查是否有同一片叶子的多张照片被分到了不同集合这会导致数据泄漏。5.3 现象训练到一半突然 OOM之前几个 epoch 都正常原因Ultralytics 默认开启 mosaic 增强某些 batch 里拼接了 4 张高分辨率图片显存峰值飙升。解决设置mosaic0.5降低 mosaic 概率或者close_mosaic10在最后 10 个 epoch 关闭 mosaic。另外检查imgsz是否被自动调整YOLO 在训练时会按 32 的倍数取整如果原始图片尺寸不规则可能被放大。5.4 现象推理时检测框重叠严重同一片叶子出多个框原因NMS 的 IoU 阈值设得太高或者模型对重叠目标过拟合。树叶数据集里叶片重叠是常态NMS 阈值默认 0.7如果叶片密集降到 0.5 能减少重复框。解决推理时加iou0.5参数或者训练时增加重叠样本的标注让模型学会区分相邻叶片。yolo detect predict \ modelleaf_runs/exp1/weights/best.pt \ sourcetest_images/ \ imgsz640 \ conf0.25 \ iou0.5 \ saveTrue5.5 现象模型在训练集上表现完美换一张新图片就检测不到原因过拟合加上数据增强不足。1000 张图对 YOLO 来说偏少如果类别又多模型容易记住训练集的背景而不是叶片特征。解决增加数据增强强度比如degrees15随机旋转、hsv_h0.015色调抖动、flipud0.5上下翻转。另外可以冻结 backbone 的前 5 层减少可训练参数。如果还是不行考虑用半监督方式加入未标注的树叶图片。6. 用 TensorRT 加速树叶检测从 640 分辨率到多路视频的实测技巧训练完模型只是第一步真正落地时往往要在边缘设备上跑实时检测。树叶分类的场景可能是果园巡检、无人机航拍或者固定摄像头监控对帧率有要求。我一般会把 PyTorch 权重导出成 TensorRT engine在 T4 或 Jetson 上跑。导出命令yolo export \ modelleaf_runs/exp1/weights/best.pt \ formatengine \ imgsz640 \ halfTrue \ device0 \ workspace4halfTrue启用 FP16 精度T4 上 640 分辨率的 yolov8n engine 单帧推理大概 2-3ms理论上能跑 300 FPS。但实际多路视频时瓶颈往往在解码和预处理不在推理。我实测过 T4 上 1080p25 帧的视频用 TensorRT 跑 640 分辨率检测单路大概占用 15% GPU理论上能支持 6-8 路但加上解码和跟踪实际稳定在 4-5 路。如果要做多路建议用 DeepStream 或者自己写批处理把多路帧拼成一个 batch 送进 engine。import tensorrt as trt import pycuda.driver as cuda import numpy as np # 加载 engine 并创建执行上下文 logger trt.Logger(trt.Logger.WARNING) with open(best.engine, rb) as f, trt.Runtime(logger) as runtime: engine runtime.deserialize_cuda_engine(f.read()) context engine.create_execution_context() # 分配输入输出显存 input_shape (1, 3, 640, 640) output_shape (1, 6, 8400) # 4 坐标 2 类别按实际调整 d_input cuda.mem_alloc(np.prod(input_shape) * 4) d_output cuda.mem_alloc(np.prod(output_shape) * 4) # 预处理resize 归一化 NCHW def preprocess(img): img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, HWC to CHW img np.ascontiguousarray(img, dtypenp.float32) / 255.0 return img[np.newaxis, ...] # 推理 def infer(img): inp preprocess(img) cuda.memcpy_htod(d_input, inp) context.execute_v2([int(d_input), int(d_output)]) out np.empty(output_shape, dtypenp.float32) cuda.memcpy_dtoh(out, d_output) return out这段代码的关键是预处理要和训练时完全一致包括颜色通道顺序、归一化方式、resize 的插值算法。我踩过的坑是训练时用了 letterbox 保持长宽比推理时直接 resize 导致框偏移。解决方法是把 letterbox 的缩放比例和 padding 记录下来后处理时还原到原图坐标。注意TensorRT engine 和硬件绑定T4 上导出的 engine 不能直接拿到 Jetson 上用需要在目标设备上重新导出。另外 TensorRT 版本和 CUDA 版本要匹配版本不对会报 deserialize 失败。最后一个技巧如果树叶检测的类别不多比如 3-5 类可以把分类头砍掉只保留一个二分类的“叶片/非叶片”检测然后用一个轻量级分类网络做细分类。这样检测模型更小推理更快分类网络可以单独优化。我做过一个类似方案yolov8n 检测 mobilenetv3 分类T4 上 640 分辨率能跑到 500 FPS精度比端到端多分类只低 1-2 个点。这个思路适合对帧率要求高、类别又多的场景。希望帮到你。本文还有配套的精品资源点击获取