
简介茶叶病害检测数据集采用Pascal VOC与YOLO双格式组织包含883张茶叶叶片图像及一一对应的xml标注和txt标注文件面向目标检测初学者、算法工程师及农业智能化项目开发者可直接用于模型训练、验证与算法调优。压缩包共2000个文件以jpg图片、xml标注和txt标注为主整体约200.5MB标注类别涵盖algalleaf、Anthracnose、birdeyespot、brownblight、graylight、healthy、redleafspot、whitespot共8种叶片状态累计884个矩形框。所有图像均以单片叶子为主体标注规则统一由labelImg工具手工完成边界框贴合目标适合快速评估YOLO、Faster R-CNN等检测器性能。已有888人学习下载对于需要标准数据集开展实验或验证模型的读者解压即可使用省去筛选图片与格式转换的时间。1. 这份茶叶病害数据集拿到手先别急着训练做目标检测最怕的不是模型选型翻车而是数据集本身埋着雷。这份茶叶病害数据集包含883张单叶图片、8个病害类别每个类别附有VOC格式xml和YOLO格式txt两份标注总标注框数884个。我最早做茶叶病害检测时先用这份数据跑通了完整的训练验证流程也踩过不少坑。别看883张图比动辄上万张的公开数据集小单叶检测的场景里每张图只有一个完整叶片目标占比大、背景相对干净配合合理的划分和增强策略它的实战可用度比很多凑数的千张级数据集要高。适合三类人第一次跑YOLO流程的初学者做茶叶病害检测专项的从业者以及需要一份多类别小数据集来验证训练脚本是否写对的开发者。2. 数据集结构拆解VOC与YOLO双格式背后的坐标逻辑2.1 目录结构、类别分布与单叶检测的边界条件这份资源解压后是典型的图像分类检测目录图片jpg文件、VOC格式xml文件、YOLO格式txt文件三者数量完全对应都是883个。图片命名直接带了类别前缀比如firc_birdeyespot_49.jpg、firc_Anthracnose_43.jpg这一点非常友好做数据划分时可以直接按文件名前缀分组不需要额外读标注文件判断类别。8个类别分别是algalleaf藻斑病、Anthracnose炭疽病、birdeyespot鸟眼斑、brownblight赤枯病、graylight灰斑、healthy健康叶、redleafspot赤叶斑、whitespot白斑。每个类别的框数如下类别标注框数占比Anthracnose9911.2%algalleaf11412.9%birdeyespot10011.3%brownblight11312.8%graylight10011.3%healthy748.4%redleafspot14316.2%whitespot14116.0%注意这里有个细节图片883张总框数884个说明有一张图里标了两个目标。这是单叶检测场景里的边界情况后面划分数据集和计算mAP时要单独留意不能默认“一张图等于一个目标”。2.2 坐标体系换算xml与txt的第一性原理VOC格式的xml里存的是绝对像素坐标核心字段是bndbox下的xmin、ymin、xmax、ymax单位是像素。YOLO格式的txt里存的是归一化坐标class_id x_center y_center width height前三个是浮点数因为要除以图片宽高。我一般不建议手工做这种转换因为884个框手工算一次能算到怀疑人生。这个数据集既然两边都给了训练YOLO直接用txt后续如果要做分割或可视化再用xml。唯一要小心的是坐标出界问题LabelImg标注时偶尔会把框画到图片边界外一点点def voc_to_yolo(xml_file, img_w, img_h): import xml.etree.ElementTree as ET tree ET.parse(xml_file) root tree.getroot() boxes [] for obj in root.iter(object): cls obj.find(name).text bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界约束防止坐标越界导致训练时loss炸掉 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) w xmax - xmin h ymax - ymin # 归一化到[0,1] cx (xmin w / 2) / img_w cy (ymin h / 2) / img_h bw w / img_w bh h / img_h boxes.append((cls, cx, cy, bw, bh)) return boxes这段代码的逻辑分三层解析xml拿类别和坐标做边界裁剪最后做归一化。边界裁剪那两行max和min是重点因为后续训练时如果坐标里出现负数或者大于1的值损失函数很容易出现NaN排查起来相当费时间。图片宽高可以先用PIL读一遍原始图片拿真实尺寸不要直接信xml里记录的值。2.3 标注规则的隐含信息为什么说它是“单叶检测”而非“通用检测”描述里写得很清楚所有图片都是单叶包含针对单个叶子做检测。这意味着模型学到的是“叶片内的病斑位置”复杂度低于田间自然场景的多叶重叠检测。这个特性决定了后续的数据增强不能乱开——比如mosaic增强会把四张图拼在一起虽然增强效果明显但对这种单叶数据集来说拼接后目标分布和真实场景差异很大反而可能拉低验证mAP。提示单叶数据集的测试指标天然偏高因为背景干扰少、叶子形状规则。评估模型好坏时建议额外关注每一类的独立AP而不只看整体mAP否则你很难发现某个病害类别已经被模型忽略了。3. 训练前必须做的一致性校验图片、xml、txt三方对应3.1 校验脚本文件名、类别集合、坐标越界一次查完很多资源下载下来直接用结果训练到一半报错“找不到标签”或者某个类别的AP一直是0。这类问题大多源于三个地方xml和txt文件名对不上、类别名拼写不一致比如Anthracnose首字母大写和小写混用、某个xml里的坐标超出图片尺寸。我习惯拿到数据集先跑一遍三方一致性校验脚本如下import os from PIL import Image img_dir images xml_dir labels_voc txt_dir labels_yolo imgs set(f.split(.)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)) xmls set(f.split(.)[0] for f in os.listdir(xml_dir) if f.endswith(.xml)) txts set(f.split(.)[0] for f in os.listdir(txt_dir) if f.endswith(.txt)) # 1. 三方文件名集合完全一致 print(缺失图片:, xmls - imgs) print(缺失xml:, imgs - xmls) print(缺失txt:, imgs - txts) # 2. 类别集合检查 all_classes set() for f in os.listdir(xml_dir): import xml.etree.ElementTree as ET root ET.parse(os.path.join(xml_dir, f)).getroot() for obj in root.iter(object): all_classes.add(obj.find(name).text) print(类别集合:, all_classes) # 3. 坐标边界检查 for f in sorted(os.listdir(xml_dir)): img Image.open(os.path.join(img_dir, f.replace(.xml, .jpg))) w, h img.size root ET.parse(os.path.join(xml_dir, f)).getroot() for obj in root.iter(object): b obj.find(bndbox) xmin, ymin float(b.find(xmin).text), float(b.find(ymin).text) xmax, ymax float(b.find(xmax).text), float(b.find(ymax).text) if xmin 0 or ymin 0 or xmax w or ymax h: print(f{f}: 越界 {xmin},{ymin},{xmax},{ymax})这段脚本覆盖三类问题文件名缺失意味着训练时数据加载会跳过某张图、类别集合检查能看出有没有标签名被写错、坐标越界检查则会直接给出问题文件。实际跑这套数据时最常出现的是第三类——LabelImg画框时不小心拖出边界尤其叶子边缘贴着图片底部时操作容易带出。3.2 数据划分按文件名分组别让同一来源泄露到验证集这是这份数据最容易踩的坑。图片命名前缀是病害类别名比如firc_Anthracnose_70.jpg后跟序号。如果直接用train_test_split随机划分同一个类别内部即使有相似的背景图片也有不小概率被同时分到训练集和验证集造成指标虚高。我常见的做法是优先按“叶子批次”或者“图片来源”分组但这个数据集没有提供来源字段那么退而求其次按类别分层的随机划分并且保证验证集里类别分布和训练集接近。import random from collections import defaultdict files [f.split(.)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)] cls_to_files defaultdict(list) for f in files: cls f.rsplit(_, 1)[0] # 文件名形如 firc_Anthracnose_70 cls_to_files[cls].append(f) train_files, val_files [], [] for cls, flist in cls_to_files.items(): random.seed(42) random.shuffle(flist) split int(len(flist) * 0.8) train_files.extend(flist[:split]) val_files.extend(flist[split:]) print(len(train_files), len(val_files)) print({c: sum(1 for f in val_files if f.rsplit(_, 1)[0] c) for c in cls_to_files})划分逻辑里最核心的是rsplit(_, 1)[0]取类别前缀这比读xml再解析name要快得多而且前几行注释写清楚了文件名格式。验证集那行打印是给你自己看的如果某个类别在验证集里只有一两张图那这个类别的AP指标会极其不稳定训练时就要考虑类别加权或者干脆做smote之类的不平衡处理。3.3 标注规则矩形框在单叶检测里的边界情况描述里写明使用LabelImg画矩形框。矩形框本身没有方向属性所以对叶片边缘的病斑画框时通常有两种取向紧贴病斑边缘画或者把病斑连同周围的变色区域一起画入。这个数据集没有严格说明但从框数统计看健康叶只有74个框明显少于whitespot的141个说明健康叶类别在采集时更保守——没把握的区域宁可标成背景。这一点对训练有实际影响如果后续你自己补充标注别把健康叶的框画得太随意否则模型会把正常叶脉纹路误判成病斑。提示拿到任意数据集第一步永远是一致性校验和分布检查不要跳过。我见过不少训练事故最终都追溯到某个xml里类别名多加了一个空格或者某张图片在exif里有旋转信息导致宽高读取错误。校验脚本虽然简单但它是给整个训练流程兜底的。4. YOLO系列训练配置883张图也能训练出可用的模型4.1 模型选型为什么从nano起步而不是直接上large很多人拿到数据集第一反应是上大模型觉得模型大一定准。单叶检测场景下目标尺寸在整张图里的占比通常超过30%属于“大目标高分辨率”场景模型容量需求远低于遥感小目标检测。883张图喂给yolov8x这种大模型遇到的最大问题不是精度而是过拟合——训练集loss能降到0.01验证集mAP却在第三十个epoch开始断崖式下跌。我一般建议用yolov8n或yolov8s起步。nano和small在单叶这种目标占比大的场景里精度差距通常不超过2个百分点但训练速度和显存占用差距明显。如果把200个epoch的训练成本都花在调参上比盲目换大模型划算得多。先用nano跑通全流程再考虑要不要换s。4.2 配置文件与训练命令一份能直接抄的yaml# tea_disease.yaml path: /path/to/tea_disease_dataset train: images/train val: images/val nc: 8 names: 0: algalleaf 1: Anthracnose 2: birdeyespot 3: brownblight 4: graylight 5: healthy 6: redleafspot 7: whitespot这个配置里最需要注意的是names的顺序要和txt标注里的class_id完全一致。txt里的class_id是整数如果你在训练脚本里改了顺序或者误把类别名首字母大小写改掉了训练出来的模型预测结果就会整体错位——检测框位置是对的但类别标签全是乱的。这也是为什么校验脚本里必须打印一遍all_classes集合一次对齐所有类别。训练命令用ultralytics的标准接口from ultralytics import YOLO model YOLO(yolov8n.pt) model.train( datatea_disease.yaml, epochs150, imgsz640, batch16, lr00.01, augmentTrue, patience30, seed42 )参数说明imgsz640对单叶检测是够用的再提分辨率到1280会显著增加训练时间对病斑这种毫米级目标提升有限batch16在中等显存下能跑显存紧张就降到8patience30表示连续30个epoch验证集指标不上升就早停。这套参数配合883张图通常能在80到120个epoch之间收敛。4.3 数据增强策略单叶数据集的mosaic开关别乱开YOLO默认开启mosaic增强把四张图缩放到一个画布上训练对通用目标检测提升明显。但这份数据是单叶场景训练时报错不会报在mosaic上问题出在验证集指标上开mosaic后验证集mAP可能比不开还低两个点。原因是mosaic引入了“多叶拼接”的分布而验证集是纯单叶分布训练和推理时域不一致。对于这份数据我的做法是用mosaic但只开低强度mosaic0.2同时把close_mosaic10设上表示最后10个epoch关掉mosaic回到纯单叶训练让模型在最后阶段适应真实分布。平移和旋转增强可以适当开大degrees15、translate0.1因为单叶图片叶子位置不一定居中这种变换能模拟更多拍摄角度。HSV增强建议保守hsv_h0.015就够调太高会让叶子的绿色失真模型反而学会依赖颜色噪声而不是纹理特征。5. 避坑指南单叶检测数据集的五个高频翻车点5.1 验证集指标高、实测效果差置信度阈值没调现象验证集mAP有0.85拿到田间试拍图树上一堆病斑被漏检或者健康叶子被不停误报。原因验证集的ground truth本身就是人工画框单叶图片背景干净模型学到的是“找一个叶子上的斑块”。而实际拍摄中叶片边缘阴影、露珠反光、叶脉纹路都会造成误检。验证集没有覆盖这些负样本所以指标虚高。解决训练完不要直接拿默认置信度用。我一般把conf_thres从默认的0.25提高到0.45再看效果。如果误报还压不住就把背景图加入训练集做一点负样本微调。这是单叶检测最常见但最容易被忽略的一步。5.2 类别错乱txt的class_id和yaml的names对不上现象训练loss正常下降但打印验证集预测结果时whitespot的框标成了algalleaf。原因txt里的class_id是在标注时按类别顺序写死的而tea_disease.yaml里的names列表是手工写的。只要有一个类别的顺序手滑写错全部预测都会错位。解决在训练前写一个三行脚本做映射校验读取任意一张txt的class_id对应到yaml的names再打开同名的xml检查name是否一致。这个校验只需一次但能防住整个训练流程报废。从那以后我每次新建数据集配置都会强制走一遍这个映射校验。5.3 早停触发太快模型没收敛就停了现象训练只跑到第40个epoch就早停验证集mAP停在0.72明明后面还有上升趋势。原因patience30在训练初期波动大时容易被误触发。前几十个epoch的验证指标震荡明显连续30个epoch不涨不代表真的收敛。解决把patience调大到50或60同时配合一个最低epoch下限比如epochs150起步时前60个epoch不允许早停。或者干脆先跑一版完整的150epoch看loss曲线确定大概在第几个epoch收敛再设定合理的patience。5.4 一张图两个标注框导致的数据统计误导现象数据描述里写着883张图、884个框有人认为没有空标注也没有重复标注其实有一张图是两个病斑。原因单叶检测里一张叶子出现多个独立病斑很常见标注员画了两个框。这不算错误但如果训练代码里按图片编号去重统计框数就会少算一个目标。解决训练时按txt行数累加计算总标注数不要按图片数量乘以平均框数估算。在划分验证集时确保带有两个框的那张图被分到训练集避免验证集里出现“单张图多目标”没有覆盖的情况。5.5 图片尺寸不统一导致预处理耗时长现象训练日志里每个epoch的数据加载时间从20秒涨到60秒GPU利用率上不去。原因这份数据的图片既有大尺寸也有小尺寸虽然都能训练但ultralytics每次都要动态resize。如果图片尺寸悬殊会频繁触发缓存重算训练时间拉长。解决训练前统一把图片resize到一个固定尺寸比如长边1280、短边至少640另存到新目录。这一步能明显缩短数据加载时间。如果担心resize造成病斑纹理丢失用letterbox填充而不是直接拉伸。6. 进阶操作单叶检测的置信度阈值调优与类别级指标复盘模型训练完不要只看一行mAP就收工。单叶检测的场景里不同病害类别的AP差异可以很大因为部分病斑很小且与健康叶颜色接近。我习惯把验证集预测结果导出为json按类别统计AP并画混淆矩阵然后针对AP偏低的两三类单独调阈值。具体做法是先跑一次批量推理统计每个类别的置信度分布。对于whitespot这类高置信度类别可以适当把阈值提高到0.5以上压低误检对于graylight这类低置信度类别阈值降到0.3宁多检几个框也不要漏掉病斑。注意classes不要笼统统一调而是按类别分别设conf_thres如果用的是ultralytics的predict方法可以写个小循环分别处理。另外还有一个实用验证方法做一次留一类别交叉验证。把某个类别从训练集中完全拿掉只保留在验证集里看模型能否靠其他类别的特征迁移识别它。这个测试能看出哪些病害类别的特征外观相似、模型是否真正学到了区分边界。实测下来这份数据里Anthracnose和brownblight容易混淆两者都表现为主脉附近的深色斑块区别只在于边缘是否带黄色晕圈这种细粒度区分对输入分辨率比较敏感复现时引入裁剪后放大局部病斑的推理策略能明显提升这两个类别的召回。从那以后我每次拿到新的检测数据集都会强制走一遍流程一致性校验、按来源划分、训练前映射对齐、后端按类别调阈值。这套流程看起来慢实际跑通后比返工省时间得多希望帮到你。本文还有配套的精品资源点击获取