☰
茶叶病害数据集实战:883张单叶图与8类标注的YOLO训练指南
2026/10/10 6:38:19 网站建设 项目流程

简介:这份茶叶病害数据集面向农业图像识别、植物保护研究及深度学习目标检测方向的开发者与学习者,提供VOC与YOLO双格式标注,可直接用于训练和验证叶片病害检测模型。压缩包共约2000个文件,包含883张jpg图片、883个VOC格式xml标注、883个YOLO格式txt标注及少量说明文件,整体约200.5MB,图片与标注一一对应,省去格式转换的繁琐步骤。数据集覆盖8个类别,包括炭疽病、藻斑病、鸟眼斑、褐枯病、灰枯病、健康叶、红叶斑和白斑,总标注框数884个,每张图仅含单片叶子,适合单目标检测任务。所有标注均由labelImg工具以矩形框完成,类别分布相对均衡,其中红叶斑与白斑样本较多,健康叶相对偏少,便于研究者观察类别不平衡对模型的影响。目前已有887人学习下载,可作为茶叶病害识别、农业智能巡检等课题的可靠数据基础。

1. 茶叶病害数据集落地:883 张单叶图与 8 类标注怎么用

拿到一个标注好的茶叶病害数据集,第一反应往往不是「太好了」,而是「这 883 张图到底能不能直接喂给 YOLO」。这个包给的是 Pascal VOC 的 xml 和 YOLO 的 txt 双格式,883 张 jpg 对应 883 个 xml 和 883 个 txt,8 个类别,总框数 884。注意这个数字:图片 883 张,框 884 个,说明绝大多数图里只有一片叶子、一个病斑框,只有一张图出现了两个框。这个细节决定了它适合做单目标检测,而不是密集病斑分割。

它解决的是「没有现成茶叶病害标注数据」的问题。做农业视觉的团队,尤其是想验证 YOLO 在细粒度病害分类上表现的,可以直接拿它跑 baseline。适合谁?适合已经会跑 YOLOv5/v8 训练脚本、但缺数据的人;也适合想练手 VOC 转 YOLO、检查标注质量的新手。不适合谁?不适合想做病斑像素级分割的人,因为标注规则明确写了只画矩形框,没有分割路径的 txt。

类别名是英文小写:algalleaf、Anthracnose、birdeyespot、brownblight、graylight、healthy、redleafspot、whitespot。其中 healthy 只有 74 个框,redleafspot 有 143 个,whitespot 141 个,类别不均衡是明摆着的。后面训练时如果直接跑,healthy 的召回大概率会拖后腿。这个包的价值在于「省掉标注」,但代价是你要自己处理不均衡和单叶场景的泛化问题。

2. 拆包与格式核对:VOC 和 YOLO 双格式到底怎么对应

2.1 目录结构先理清,别急着写 data.yaml

拿到 zip 解压后,常见做法是看到 images 和 labels 两个文件夹,但 VOC 格式的 xml 往往单独放在 Annotations 里。这个包没有给分割路径的 txt,所以只有 jpg、xml、txt 三类文件。我一般先跑一遍文件计数,确认没有漏图或漏标注。

# 统计三类文件数量,确认是否一一对应 find . -name "*.jpg" | wc -l find . -name "*.xml" | wc -l find . -name "*.txt" | wc -l

逻辑说明:三个数字都应该是 883。如果 txt 少了几张,说明有图片没转成功;如果 xml 多出来,可能是重复标注。参数上没什么可调的,就是数数。这一步花不了一分钟,但能避免后面训练时突然报「找不到 label」。

2.2 VOC 的 xml 里到底存了什么

随便打开一个 xml,比如 firc_birdeyespot_49.xml,你会看到 size、object、bndbox 这些节点。size 里是宽高和通道数,object 里是 name 和 bndbox 的 xmin/ymin/xmax/ymax。这个包用的是 labelImg 标注,所以 xml 结构是标准的 Pascal VOC,没有旋转框,也没有 difficult 标记。

常见做法是写个脚本把 xml 里的类别名和框数抽出来,核对摘要里给的数字。比如 Anthracnose 框数 99,你就遍历所有 xml,统计 name 为 Anthracnose 的 object 数量。如果对不上,说明有 xml 被改过或者类别名拼写不一致。

import os import xml.etree.ElementTree as ET from collections import Counter # 遍历 xml,统计每个类别的框数 counter = Counter() for f in os.listdir('Annotations'): if not f.endswith('.xml'): continue tree = ET.parse(os.path.join('Annotations', f)) root = tree.getroot() for obj in root.findall('object'): name = obj.find('name').text counter[name] += 1 print(counter)

逻辑说明:ET.parse 读 xml,findall('object') 拿到所有标注框,name 就是类别。参数上注意,如果 xml 里有 pose 或 truncated 节点,不影响统计。跑完对照摘要里的框数表,Anthracnose 99、algalleaf 114、birdeyespot 100、brownblight 113、graylight 100、healthy 74、redleafspot 143、whitespot 141,加起来 884。如果某个类别差一两个,可能是某张图里同一个类别画了两个框,属于正常。

2.3 YOLO txt 的归一化坐标怎么读

YOLO 格式的 txt 每行是class_id x_center y_center width height,全部归一化到 0-1。这个包里的 txt 和 xml 是一一对应的,所以你可以用 xml 的 bndbox 反推归一化坐标,验证 txt 有没有写错。常见坑是:有些转换脚本会把 xmin/ymin 直接除以宽高,忘了加一半的宽高得到中心点。我一般抽三张图手动算一遍。

# 从 xml 的 bndbox 算 YOLO 归一化坐标,和 txt 对比 import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text bnd = obj.find('bndbox') xmin = float(bnd.find('xmin').text) ymin = float(bnd.find('ymin').text) xmax = float(bnd.find('xmax').text) ymax = float(bnd.find('ymax').text) xc = (xmin + xmax) / 2.0 / img_w yc = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{name} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") return lines

逻辑说明:img_w 和 img_h 从 xml 的 size 节点里读,不要硬编码。参数上,归一化保留 6 位小数就够了,YOLO 训练时不在乎再多几位。如果算出来的 xc 和 txt 里的对不上,大概率是转换时用错了图片尺寸,或者 xml 里的 size 和实际 jpg 尺寸不一致。后者在数据增强过的包里很常见,但这个包是原始标注,应该一致。

2.4 类别名映射表要自己建

YOLO 训练需要把类别名转成 0 到 7 的整数。这个包没有给 names 文件,所以你得自己按字母序或按摘要里的顺序建。我一般按摘要里的顺序:algalleaf=0, Anthracnose=1, birdeyespot=2, brownblight=3, graylight=4, healthy=5, redleafspot=6, whitespot=7。注意大小写,Anthracnose 首字母大写,其他都是小写,写 data.yaml 时别抄错。

# data.yaml 示例 path: ./tea_dataset train: images/train val: images/val nc: 8 names: ['algalleaf', 'Anthracnose', 'birdeyespot', 'brownblight', 'graylight', 'healthy', 'redleafspot', 'whitespot']

逻辑说明:path 是数据集根目录,train 和 val 是相对路径。nc 是类别数,必须和 names 长度一致。参数上,如果后面要做交叉验证,可以把 train 和 val 都指向同一个 images 文件夹,用 YOLO 的 split 参数自动划分,但常见做法还是手动分好 train/val,避免数据泄漏。

3. 训练前处理:划分、增强与类别不均衡的应对

3.1 训练集验证集怎么分才不翻车

883 张图,按 8:2 分,训练集 706 张,验证集 177 张。但要注意:同一个叶子的不同病害图不能同时出现在训练和验证里,否则验证指标会虚高。这个包的命名有规律,firc_birdeyespot_49.jpg、firc_Anthracnose_70.jpg,前缀 firc 加类别名加序号。我一般按类别分层抽样,保证每个类别在验证集里都有至少 10% 的图。

import os import random from collections import defaultdict # 按类别分层划分 train/val files = [f for f in os.listdir('images') if f.endswith('.jpg')] cls_files = defaultdict(list) for f in files: cls = f.split('_')[1] # 从文件名提取类别 cls_files[cls].append(f) train, val = [], [] for cls, flist in cls_files.items(): random.shuffle(flist) split = int(len(flist) * 0.8) train.extend(flist[:split]) val.extend(flist[split:]) print(len(train), len(val))

逻辑说明:从文件名提取类别依赖命名规范,这个包是 firc_类别_序号.jpg,所以 split('_')[1] 能拿到类别。参数上,random.shuffle 前设个种子,保证每次划分一致。如果文件名不规范,就得从 xml 里读类别再分层。

3.2 数据增强别乱开,单叶场景有讲究

这个包所有图都是单叶,背景相对干净。常见做法是开 mosaic、mixup、HSV 抖动、随机翻转。但要注意:mosaic 会把四张图拼成一张,如果四张都是单叶,拼出来就是四片叶子,模型可能学到「一张图多片叶子」的模式,而实际推理时你给的是一张图一片叶子。我一般把 mosaic 的概率调低到 0.3 左右,或者干脆关掉,用 copy-paste 增强代替。

# YOLOv8 训练时关闭 mosaic 的配置片段 from ultralytics import YOLO model = YOLO('yolov8n.pt') model.train( data='data.yaml', epochs=100, imgsz=640, mosaic=0.0, # 关闭 mosaic mixup=0.0, # 关闭 mixup hsv_h=0.015, # 色调抖动 hsv_s=0.7, # 饱和度抖动 hsv_v=0.4, # 亮度抖动 fliplr=0.5, # 水平翻转 flipud=0.0 # 垂直翻转关掉,叶子方向有意义 )

逻辑说明:mosaic 和 mixup 对小数据集通常有帮助,但单叶场景下可能引入分布偏移。参数上,hsv_h 控制色调,茶叶病害的颜色是重要特征,别调太大;flipud 关掉是因为叶子正反面在图像里方向固定,垂直翻转会造出不存在的姿态。

3.3 类别不均衡:healthy 只有 74 个框怎么办

healthy 74 个框,redleafspot 143 个,差了近一倍。直接训练,模型会偏向多数类。常见做法有三种:过采样少数类、在 loss 里加类别权重、或者用 focal loss。YOLOv8 默认的分类 loss 是 BCE,不直接支持类别权重,但你可以通过复制 healthy 的图片来平衡。

# 过采样 healthy 类,复制到临时文件夹再合并 mkdir -p oversample/healthy for f in images/*healthy*.jpg; do cp "$f" oversample/healthy/ cp "${f%.jpg}.txt" oversample/healthy/ 2>/dev/null done # 把 oversample/healthy 里的图复制回训练集,重复 2 次

逻辑说明:复制图片和对应的 txt,让 healthy 的样本数接近其他类。参数上,复制倍数看差距,74 到 140 左右需要复制 2 次。注意别复制到验证集里,否则验证指标失真。另一种做法是在 data.yaml 里给每个类别设权重,但 YOLOv8 不直接支持,得改源码,不推荐新手折腾。

3.4 输入尺寸选 640 还是 1024

这个包的图片是单叶,叶子在画面里占比较大。640 的输入下,病斑可能只有几十个像素,小目标检测会吃力。我一般先跑 640 看 baseline,如果 redleafspot 和 whitespot 的召回低,再换 1024。但 1024 的显存占用是 640 的 2.5 倍左右,batch size 要相应调小。

# 切换输入尺寸 model.train(data='data.yaml', epochs=100, imgsz=1024, batch=8)

逻辑说明:imgsz 是训练和推理的输入尺寸,必须是 32 的倍数。参数上,batch 根据显存调,8G 显存跑 1024 大概能到 batch=8,再大就 OOM。如果显存不够,可以用梯度累积模拟大 batch。

4. 避坑与排查:标注、转换、训练里的五个血泪经验

4.1 现象:训练报「Label class 8 is invalid」

原因:data.yaml 里 nc 写成了 8,但 names 列表只有 7 个,或者某个 txt 里的 class_id 是 8。这个包的类别是 0 到 7,如果转换脚本把类别名映射错了,比如把 whitespot 映射成 8,就会报这个。

解决:先检查 data.yaml 的 nc 和 names 长度是否一致,再遍历所有 txt,看有没有大于等于 nc 的 class_id。

import os nc = 8 for f in os.listdir('labels'): if not f.endswith('.txt'): continue with open(os.path.join('labels', f)) as fh: for line in fh: cid = int(line.split()[0]) if cid >= nc: print(f, cid)

4.2 现象:验证集 mAP 很高,但推理时什么都检测不到

原因:验证集和训练集划分时,同一个叶子的不同病害图被分到了两边,导致验证集泄漏。或者验证集的图片做了增强,而推理时没有。

解决:按文件名前缀分组,确保同一个叶子的图只出现在一边。这个包的命名里,firc_类别_序号,序号不同的图可能是同一片叶子的不同角度,但摘要说「所有图片都是一个图片包含一个叶子」,所以不同序号大概率是不同叶子。保险起见,按序号分组,同一序号的图只放一边。

4.3 现象:healthy 类几乎全漏检

原因:healthy 只有 74 个框,训练时被多数类压制。模型倾向于把 healthy 也预测成有病斑的类别。

解决:过采样 healthy,或者在推理时对 healthy 的置信度阈值单独调低。常见做法是先把 healthy 的图复制到训练集里,让它的框数到 140 左右,再重新训练。

4.4 现象:xml 里的 size 和 jpg 实际尺寸不一致

原因:标注时图片被缩放或裁剪过,但 xml 里记的是原始尺寸。这个包是 labelImg 标注,一般不会出现,但如果你自己做了预处理,就可能引入。

解决:用 PIL 读 jpg 的尺寸,和 xml 里的 width/height 对比,不一致的图要么重新标注,要么在转换时用实际尺寸归一化。

from PIL import Image import xml.etree.ElementTree as ET img = Image.open('images/firc_birdeyespot_49.jpg') w, h = img.size tree = ET.parse('Annotations/firc_birdeyespot_49.xml') root = tree.getroot() size = root.find('size') print(w, h, size.find('width').text, size.find('height').text)

4.5 现象:YOLO txt 里的坐标全是 0 或 1

原因:转换脚本把 xmin/ymin 直接除以宽高,没有加一半的宽高得到中心点,或者把 xmax/xmin 搞反了。

解决:用 2.3 节的脚本重新算一遍,和现有 txt 对比。如果对不上,就批量重新生成 txt。注意备份原始 txt,别直接覆盖。

5. 进阶用法:用这个数据集验证 YOLO 的细粒度分类能力

这个包最值钱的地方不是「能跑通 YOLO」,而是「能验证 YOLO 在细粒度病害上的边界」。8 个类别里,birdeyespot 和 redleafspot 在颜色上接近,Anthracnose 和 brownblight 在形状上接近,healthy 和 graylight 在纹理上容易混。你可以拿它做消融实验:换不同的 backbone、换不同的 head、加不加注意力机制,看哪一类的 mAP 提升最明显。

我一般会先跑一个 baseline,记录每个类别的 AP。然后改 YOLOv8 的 head,比如换成 efficient head,再跑一遍,对比 redleafspot 和 whitespot 的 AP 变化。如果提升集中在多数类,说明模型只是记住了频率,没有学到细粒度特征。这时候可以试试 focal loss,或者把输入尺寸从 640 提到 1024。

# 按类别输出 AP 的验证脚本片段 from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') metrics = model.val(data='data.yaml', split='val') for i, name in enumerate(metrics.names): print(name, metrics.box.ap[i])

逻辑说明:metrics.box.ap 是每个类别的 AP 数组,顺序和 names 一致。参数上,split='val' 指定验证集。如果某个类别的 AP 低于 0.3,就重点看它的混淆矩阵,确认是被哪个类吃掉了。

另一个进阶用法是把这个数据集当预训练,再迁移到更大的茶叶病害数据集上。因为它是单叶、干净背景,学到的特征偏向病斑本身,而不是背景。迁移时冻结 backbone,只训 head,往往比从头训收敛快。

从那以后我每次拿到新数据集,都强制先跑一遍文件计数和类别统计,再抽三张图手动核对坐标。这个习惯帮我省掉了至少两次通宵排查标注错误。希望帮到你。

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

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

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

立即咨询