简介:这套茶叶病害检测数据集提供了9591张真实茶树叶图像,同时包含Pascal VOC与YOLO两种标注格式,适合目标检测算法训练、模型评估及农业AI场景落地,面向计算机视觉学习者和算法研发人员。压缩包整体约101.93MB,共2000个文件,其中1999个为VOC格式XML标注文件,另附1个TXT使用说明;对应每张图片的YOLO格式txt标注可用于YOLO系列等主流框架直接训练。数据覆盖8类常见病虫害与叶片状态,包括Black rot of tea、Brown blight of tea、Leaf rust of tea、Red Spider infested tea leaf、Tea Mosquito bug infested leaf、White spot of tea、健康Tea leaf等,类别标签系统完整,能支撑病害识别、检测精度对比与鲁棒性实验。已有1223人学习查看,数据规模与双标格式兼备,可为茶叶病害智能诊断、科研课题或毕业设计提供可靠的数据基础。
1. 搜到这个 7z 时,先想清楚你要的不是数据集,而是标注质量
做茶叶病害检测的同行,搜到这个标题时大多已经翻过好几轮数据集了。9591 张、8 个类别、VOC 和 YOLO 两套标注同时打包,看着是开箱即用,但我劝你先别急着解压训练。这类数据的标注质量参差,常见情况是:XML 和 txt 对不上数、类别顺序和文档不一致、部分图片带了 EXIF 方向信息导致框整体偏移。这篇笔记按我的落地流程走一遍——先读懂两套标注,再转格式、跑通 yolov8 训练,把训练中的坑和验证指标一起说清楚。适合手里有数据但还没跑通第一个模型的从业者,也适合准备在这个方向投入的人评估这套数据到底值不值得用。
2. VOC 与 YOLO 两套标注并存:先从目录结构读懂这份数据
2.1 解压后应该看到的目录布局
拿到压缩包第一件事不是双击解压,而是先看压缩包内部结构。.7z 格式在 Windows 下用 7-Zip 打开,Linux 服务器上装 p7zip 后执行:
7z l 茶叶病害检测数据集VOC+YOLO格式9591张8类别.7z列目录的作用是确认两套标注的存放路径,避免解压后和一堆无关文件混在一起。常见的打包方式有几种:VOC 部分按标准 VOC 结构放Annotations/、JPEGImages/、ImageSets/Main/,YOLO 部分按images/、labels/、classes.txt放,图片共用一份或各放一份都有可能。
我先说标准的 VOC 目录长什么样,因为这套数据集的 XML 标注几乎都沿用 VOC 约定:
VOC/ ├── Annotations/ # 每张图对应一个同名 .xml ├── JPEGImages/ # 原始图片,jpg/png 混合 ├── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txtYOLO 部分我建议解压后立即核对labels/下的文件数量和images/是否一一对应。一个很容易踩的坑是:原作者转 YOLO 格式时用了脚本,但部分 XML 解析失败,漏生成了几百个 txt。这个坑我在第 4 章专门展开,这里记住一个判断标准——labels目录文件数应该等于images目录文件数,最多允许极少量的空标签文件存在。
2.2 VOC 标注(XML)的读取顺序与关键字段
VOC 格式的核心是 XML 里<size>和<object>两块。我读取这类标注文件时,顺序永远是:先看<size>拿图片宽高,再看<object>列表拿每个目标的类别和边界框。一个典型结构:
<annotation> <filename>tea_disease_0042.jpg</filename> <size> <width>1280</width> <height>960</height> <depth>3</depth> </size> <object> <name>anthracnose</name> <difficult>0</difficult> <bndbox> <xmin>312</xmin> <ymin>405</ymin> <xmax>520</xmax> <ymax>633</ymax> </bndbox> </object> </annotation>三个值得注意的地方。第一,<filename>未必和实际图片名一致,有的数据集把文件名后缀写错,读取脚本必须以Annotations/*.xml的 stem 去JPEGImages/里找图,而不是直接信 XML 里的 filename。第二,<difficult>为 1 的目标在 Pascal VOC 评测里是要忽略的,转 YOLO 时默认跳过,但如果这个数据集的 difficult 占比很高,跳过会导致有效目标数量骤减,需要慎重。第三,<bndbox>里可能出现小数,这种一般是标注软件输出的浮点值,转整数还是保留浮点会影响后续转换精度,建议保留浮点。
2.3 YOLO 标注(txt)的归一化坐标换算
YOLO txt 每行是一个目标,格式固定为class_id x_center y_center width height,五个值全部相对图片宽高做了归一化,取值范围在 0 到 1 之间。比如一个目标在 1280×960 的图上,框左上角是 (312, 405),右下角是 (520, 633),换算过程如下:
x_center = (312 + 520) / 2 / 1280 = 0.325 y_center = (405 + 633) / 2 / 960 = 0.540 width = (520 - 312) / 1280 = 0.1625 height = (633 - 405) / 960 = 0.2375实际读取时经常遇到两类问题。一类是坐标没有归一化,整行写成0 312 405 520 633,这种文件混在数据集里,训练时边界框数值超过 1,轻则 Loss 异常,重则训练直接发散。另一类是类别 ID 从 1 开始而不是从 0 开始,转换脚本里没减 1,训练时 8 类数据被当成 9 类处理,类别数都对不上。这两个问题我都建议在转换后用一个独立脚本全量检查一遍,检查标准就是:每行 5 个数值,前四个都在 [0, 1] 区间,最后一个整数小于类别总数。
2.4 为什么同时给 VOV 与 YOLO 两份标注:互相兜底而不是数据冗余
不少朋友看到压缩包里两套标注,第一反应是“占空间”或“是不是重复了”。实际这两套格式服务的阶段完全不同。VOC 格式的信息密度更高,XML 里保存了 difficult、截断、遮挡等字段,适合做数据清洗、类别统计和二次筛选;YOLO 格式则被 ultralytics、Darknet 等训练框架直接消费,训练代码根本不去解析 XML。我一般拿到数据后的标准动作是:先用 VOC 的 XML 做统计,确认 8 个类别的样本数分布和每张图的平均目标数,判断这个数据集做检测任务的下限;再决定是把自带的 YOLO 标注直接用来训练,还是自己重新转一遍。多数情况下,我会选择自己重新转——原因是原作者的转换脚本可能和我本地的图片目录命名习惯不一致,重建一次虽然多花十分钟,但能保证训练链路完全可控。
3. 把这份数据跑通 yolov8 训练:从 VOC 转 YOLO 到最小训练命令
3.1 先统一 YOLO 目录布局:images/labels 的 train-val-test 拆分
ultralytics YOLO 对数据集目录的约定非常固定:images/train、images/val、images/test分别放原始图片,labels/train、labels/val、labels/test放同名 txt。图片文件名和标签文件名必须完全一致,比如tea_001.jpg对应tea_001.txt。
无论原始数据集自带的是ImageSets/Main/train.txt还是已经分好的目录,我习惯是重新做一次划分。9500 多张图的规模按 8:1:1 划分,train 大约 7600 张、val 和 test 各 950 张左右。顺序是先 shuffle 再切分,保证类别分布均匀:
mkdir -p datasets/tea/images/{train,val,test} mkdir -p datasets/tea/labels/{train,val,test}这里不急着复制文件。先确认一个关键问题:原数据集的 YOLO 标注是否已经被按某个比例分好了。如果labels/train已经存在,直接沿用原划分会省事,但风险是原作者可能没做同源去重——同一株茶树的连续拍摄帧可能同时进了 train 和 val,导致 mAP 虚高。我一般会强制重新划分,即使多花几分钟,也要保证验证集的可信度。
3.2 VOC 转 YOLO 脚本:读取 XML、忽略 difficult、写入 txt
当自带的 YOLO 标注可疑,或想自己控制清洗口径时,用 Python 把 VOC XML 转成 YOLO txt。这是整套流程里最值得花时间调试的一段,我直接给出可复用的脚本:
import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image, ImageOps voc_root = Path("VOC") yolo_root = Path("YOLO_clean") class_names = [] # 后面从 XML 中自动收集 for xml_path in sorted(voc_root.glob("Annotations/*.xml")): tree = ET.parse(xml_path) root = tree.getroot() # 找图:以 xml 的 stem 为准,不信任 filename 字段 img_path = voc_root / "JPEGImages" / f"{xml_path.stem}.jpg" if not img_path.exists(): img_path = voc_root / "JPEGImages" / f"{xml_path.stem}.png" if not img_path.exists(): print(f"[skip] missing image: {xml_path.stem}") continue # 用 PIL 读图并处理 EXIF 方向,避免坐标错位 img = ImageOps.exif_transpose(Image.open(img_path)) w_img, h_img = img.size lines = [] for obj in root.findall("object"): if int(obj.findtext("difficult", "0")) == 1: continue name = obj.findtext("name").strip() if name not in class_names: class_names.append(name) bbox = obj.find("bndbox") xmin = float(bbox.findtext("xmin")) ymin = float(bbox.findtext("ymin")) xmax = float(bbox.findtext("xmax")) ymax = float(bbox.findtext("ymax")) # VOC 是 x1,y1,x2,y2,YOLO 需要中心点 + 宽高,且必须归一化 x_center = (xmin + xmax) / 2.0 / w_img y_center = (ymin + ymax) / 2.0 / h_img w_norm = (xmax - xmin) / w_img h_norm = (ymax - ymin) / h_img lines.append( f"{class_names.index(name)} {x_center:.6f} {y_center:.6f} " f"{w_norm:.6f} {h_norm:.6f}" ) # 写入 txt,文件名与图片保持一致 out_txt = yolo_root / "labels" / f"{xml_path.stem}.txt" out_txt.parent.mkdir(parents=True, exist_ok=True) out_txt.write_text("\n".join(lines) + "\n" if lines else "")这段脚本里我做了三个关键决定。第一,用ImageOps.exif_transpose读取图片尺寸并转正方向,这样 XML 里的坐标和图片实际朝向一致,避免模型训练时框错位。第二,difficult=1的目标直接跳过,先保证训练目标都是可靠标注。第三,类别名动态收集,而不是硬编码 8 个名称——因为每个数据集的类别拼写习惯不同,动态收集后再统一排序成固定 ID,比直接套用原作者的 classes.txt 更稳妥。
脚本跑完后,用一行代码核对转换结果:
find YOLO_clean/labels -name "*.txt" | wc -l head -3 YOLO_clean/labels/xxx.txthead输出每行应该是类ID 浮点 浮点 浮点 浮点的格式。如果看到第二列或第三列大于 1 或小于 0,说明转换脚本的归一化除错了,或者原 XML 里混入了 non-VOC 格式的框。
3.3 写数据集 YAML:类别顺序就是 ID,别和 names 错位
yolov8 的数据集描述文件是 YAML,内容特别简单,但有一半的“训练后全错”问题出在这里。类别列表的顺序必须和 txt 里的 class_id 严格对应。
path: /home/user/datasets/tea # 数据集根目录,建议写绝对路径 train: images/train val: images/val test: images/test nc: 8 names: 0: tea_white_star 1: tea_anthracnose 2: tea_blister_blight 3: tea_brown_blight 4: tea_red_scab 5: tea_gray_blight 6: tea_caterpillar_damage 7: tea_mite_damage这里必须强调一个实践准则:names的索引号 0 到 7,必须和 3.2 脚本中class_names.index(name)的顺序一致。如果自带的 YOLO 标注已经被原作者按不同的字母顺序排过,而我又按自己的顺序生成 txt,就会发生“打出来的框是对的,但类别标签全错位”的恶性事故。我的办法是转换完直接打印class_names,然后照着这个列表一字不差地填进 YAML,并且用脚本校验一遍:读一个 txt 的 class_id,再和 YAML 的 names 对齐看是不是同一个病害名。
3.4 最小训练命令与三个关键参数
目录、标注、YAML 都就绪后,训练命令非常短:
yolo detect train \ data=tea_disease.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0model=yolov8s.pt是自动下载的预训练权重,首次运行会从官方仓库拉到本地。如果服务器不能直接访问下载地址,先在本地手动下载后放到当前目录,再换成model=./yolov8s.pt。imgsz=640是默认值,茶叶病斑有相当一部分是小目标,后面我会专门讲什么时候提高到 960。batch=16是 24GB 左右显存的安全值;如果是 V100 或更大显存,可以拉到 64,小显存降到 8 并把amp打开。epochs 不要一上来就 300,先用 100 跑通流程看 Loss 收敛趋势,再决定是否加。
训练完成后,runs/detect/train/weights/best.pt就是验证集上指标最好的权重。用这个权重对测试图片推理:
yolo predict model=runs/detect/train/weights/best.pt \ source=./sample_disease_imgs \ conf=0.25 \ save=True看到 test 目录下生成带框图片,整套流程就算打通了。到这里,新手已经能从原始压缩包跑出自己的第一个茶叶病害检测模型;熟手则应该关注下一章的坑——我几乎每套数据集都会踩到其中一两个。
4. 用这套数据集必踩的 5 个坑:现象、原因、解决
4.1 图片方向错位:EXIF 旋转让框全部偏移
现象:训练时 mAP 看起来正常,但推理时很多竖拍图片的检测框整体偏到画面角落,横拍图片正常。
原因:茶叶病害图片很多来自手机或无人机拍摄,JPEG 文件头里带 EXIF Orientation 字段,图片实际显示方向已经被播放器自动转正,但 OpenCV 的imread默认不处理这个字段。XML/TXT 里标注的坐标是在转正后的画面上标注的,训练框架却按原始像素方向读图,导致框和内容对不上。
解决:在数据预处理阶段统一转正并覆盖原图,或者在数据集准备脚本里用ImageOps.exif_transpose读取尺寸。我已经在新装数据集时把这一步写成预处理流水线的一环,不让 EXIF 问题进入训练链路。
4.2 类别 ID 错位:names 顺序和原发布者不一致
现象:训练正常、Loss 下降正常,但预测结果显示“炭疽病”的框在图上实际是“茶饼病”,并且所有类别整体偏移一位。
原因:原数据集的 YOLO 标注在打包前已经按原作者的类别顺序写死了 class_id。我自己转格式时如果按另一套顺序排名字,txt 里存的 0 到 7 就全指向了错误的病害。
解决:不要信任任何classes.txt,解压后立即扫描所有 XML 的<name>字段:
grep -h "<name>" VOC/Annotations/*.xml | sort | uniq -c得到真实的类别清单和数量后,再按字母序或按发病部位排序,固定成自己的names列表。这一步只要漏了,后面所有指标都不可信。
4.3 空标签文件被跳过:训练集悄悄少了几百张
现象:训练日志里显示train: 6200 images,但数据集的 train 目录明明有 7000 张图。
原因:部分 XML 里没有<object>节点,或者图片后缀缺失、转换脚本找不到图直接continue,导致这些图片没有对应的 txt。ultralytics 训练时会把没有标签的图片自动剔除。
解决:转换脚本里对无目标的样本也要生成空 txt,并在日志里单独统计:
find YOLO_clean/labels -name "*.txt" -size 0 | wc -l同时对比images和labels的文件数,差异超过 1% 就必须回到 3.2 的脚本排查到底是缺图还是缺标注。
4.4 混淆矩阵总和对不上:背景类混进了统计
现象:训练结束后看 confusion_matrix.png,矩阵所有行的和明显大于该类别真实目标数,甚至 val 集 GT 总共 5000 个目标,矩阵总和算下来有 7000。
原因:YOLO 的混淆矩阵默认把置信度低于阈值的预测框当作背景类统计,且每个 GT 只匹配一个预测框,多余的预测全部落进背景行。所以矩阵总和并不等于 GT 总数,而是一个和 conf 阈值、NMS 参数强相关的统计值。
解决:不要拿混淆矩阵的总和去反推数据集质量。要评估标注质量,直接看 val 集每个类别的recall列;如果某个类别 recall 低于其他类一大截,优先怀疑该类别标注框数量不足或框太小。
4.5 BN 崩溃:小 batch 加大学习率,Loss 直接 NaN
现象:第 0 个 epoch 的 Loss 直接显示nan,然后整轮训练都是nan,GPU 利用率还在正常跑。
原因:茶叶病害图像常见的深绿色背景像素值分布比较集中,若 batch 过小(比如 4),BN 的统计量不稳定,再加上默认lr0=0.01偏大,很容易在第一个 step 数值溢出。
解决:先把lr0降为 0.001,batch 提到 16 以上,并显式关闭混合精度试跑一次:
yolo detect train data=tea_disease.yaml model=yolov8s.pt \ epochs=50 imgsz=640 batch=16 lr0=0.001 amp=False能正常跑完前三个 epoch 再把amp=True打开。BN 崩溃不能靠换预训练权重解决,这是超参和 batch 的匹配问题。
5. 跑验证集之前先校准这两件事:mAP 与漏检排查
5.1 先看 val_batch 图,再看指标
训练目录下会生成val_batch0_pred.jpg这类带预测框的拼接图。我要求自己先看这张图,再看 mAP。原因很简单:mAP 是个聚合指标,它会把“某个类别整体漏检”和“某个类别框偏了”平均掉;图片能直接暴露系统性问题。重点看三类痕迹:一是大量 GT 框完全没被预测覆盖,二是同一位置框非常小,三是两个相邻病斑被并成一个框。
茶叶病害检测里最典型的现象是“小病斑漏检”——一个叶片上有 20 个几像素的早期病斑,预测只框出了其中 3 个大的。这类问题 mAP 下降幅度不大,但实际田间使用就是不可用。看完图再去翻指标,才知道下一步调什么。
5.2 用混淆矩阵定位漏检类别
yolov8 训练完会输出confusion_matrix.png,横轴是预测类别,纵轴是真实类别。这里的坑在 4.4 已经讲清楚:矩阵总和不是 GT 总数,但矩阵里的相对分布仍然有效。具体怎么看,我给出一个固定流程:
先看对角线,找到明显偏暗的类别,那一类就是漏检重灾区;再看该类别所在行落在“background”列的值,如果这个值和对角线差不多甚至更大,说明模型对该类别的区分度严重不足。在 8 类茶叶病害里,茶饼病和炭疽病早期病斑形态极像,这类混淆在矩阵里会表现为两个类别互相有大量串框。能用矩阵看出哪两类在打架,比看十项指标都有用。
5.3 用 F1-conf 曲线定置信度阈值
yolov8 会自动画出 F1-Confidence 曲线,它表达的是“置信度阈值从 0 到 1 变化时 F1 怎么变”。这个曲线的用途不是在训练时看,而是决定推理时的conf参数。
| 置信度阈值 | 适用场景 | 实际效果 |
|---|---|---|
| 0.25(默认) | 实验对比、论文报告 | 平衡,可复现性好 |
| 0.10~0.18 | 大田普查、早期预警 | 召回提升明显,误检增加 |
| 0.35~0.45 | 专家复核、药效评估 | 误检大幅减少,漏检上升 |
病害检测和车辆检测不同,漏检一个早期病斑可能意味着整片茶园防治延迟,所以我把推理阈值普遍设在 0.15 左右,代价是背景误检变多。如果是做病害分级或出具报告,再拉高到 0.35。这里没有绝对正确,只有场景合适。
5.4 按采集批次划分,避免跨组泄漏
打个比方:同一片茶园的连续 10 帧照片,光照、角度、病斑形态几乎没变化,如果随机打散后一部分进 train 一部分进 val,val 的精确率会虚高到接近 1.0,一到新茶园立刻打回原形。解决方法是按采集批次分组,组内整体划分:
from sklearn.model_selection import GroupShuffleSplit # images 列表每个元素是 (图片路径, 批次ID),批次ID来自文件夹或文件名前缀 split = GroupShuffleSplit(n_splits=1, test_size=0.15, random_state=42) train_idx, val_idx = next(split.split(images, groups=批次ID列表))这个处理对茶叶病害尤其重要,因为大田采集普遍是“一小时连拍几百张”的模式,不做分组划分,后续任何模型上线都会有严重的泛化落差。我一般在解压后就把图片按文件名前缀或目录还原出批次信息,再走上面这段逻辑。
6. 让 9591 张图发挥更大价值:切片、半监督与部署验证
6.1 从 640 到 960:切片推理救回小目标病斑
茶叶早期病斑常小于 32×32 像素,直接imgsz=640训练会把小目标下采样到 8×8 以下,特征基本损失。我的做法是先把训练尺寸提为imgsz=960,显存不够就batch=8;如果还是不够,对原图做无重叠切片后单张推理再合并结果。
6.2 用 best.pt 给未标注图打伪标签
田间采集容易但标注贵。拿best.pt对未标注茶园图片推理,筛选出置信度高于 0.5 的预测框写成 txt,作为伪标签加入训练集。重复两轮,能有效扩充到超过 1.5 万张训练图。只做一轮就会引入噪声,我已经把“两轮伪标签 + 每轮人工抽检 30 张”作为固定流程。
6.3 部署验证用 FP16 后必须重测 recall
导出 TensorRT 或 OpenVINO 模型后,不要只看导出成功就上线。FP16 推理会改变精度分布,病斑边缘的小框可能因为数值误差被 NMS 误删。导完模型后用同一组 test 图对比 PT 模型和导出模型的 mAP,差值超过 0.01 就退回 FP32。
我现在的习惯是:每拿到一套新数据,先花半小时把第 2、3 章的预处理脚本完整跑一遍,再谈训练。这个习惯帮我省掉过不止一次“训练三天发现标签错位”的返工。数据集的真正价值从来不在压缩包的大小,而在标注能否经得起训练流程的检验。希望帮到你。
本文还有配套的精品资源,点击获取