简介:本资源是一套专为农业AI视觉检测任务设计的YOLO格式番茄叶片病害识别与检测数据集,面向计算机视觉初学者、农业智能化研究者及深度学习实践者,解决作物病害图像识别模型训练所需高质量标注数据匮乏问题。数据集共1477个文件,含737张JPG格式实拍图像与738个对应YOLOv5标准格式TXT标签文件,另附1个开箱即用的数据可视化Python脚本(支持自动加载并绘制边界框)及1张类别示意图PNG,整体压缩包仅22.18MB,轻量易部署。已有267人学习下载,资源结构清晰:按train/val/test划分,内置classes.txt明确定义细菌斑点、黑点、早期枯萎病等7类常见病害,覆盖田间多角度、多光照、多病程阶段真实场景。可视化脚本无需修改参数,运行后即可生成带标注框的示例图,便于快速验证数据质量与标签准确性,显著降低入门门槛与调试成本。
1. 这不是“又一个番茄数据集”:7类病害+划分好+可视化脚本开箱即用,专治YOLO训练前的三小时焦虑
你有没有经历过:花两天爬图、三天标注、半天调路径,结果发现train/里混着.png和.jpg,classes.txt顺序和 labelImg 生成的 txt 不一致,val.txt里漏了 3 张图导致训练时IndexError: list index out of range?——这个番茄叶病害数据集,就是为终结这种「标注完不敢训」的玄学状态而生的。它不是原始图片包,而是已按 YOLOv5/v8 标准结构完整组织的落地型数据集:700 张 JPG 图片(非截图、非合成、田间实拍)、7 类真实病害(细菌斑点、黑斑病、早疫病、晚疫病、叶霉病、灰霉病、枯萎病)、train/val/test三级目录已划分完毕、classes.txt与所有.txt标注文件严格对齐、附带一个不改参数就能跑通的可视化脚本——传入任意一张图,自动画框、标类别、存结果图,连cv2.imshow()都帮你绕开了(避免远程服务器无 GUI 报错)。适合正在做农业 AI 落地的工程师、农科院合作项目的学生、以及想用真实作物病害练手 YOLO 检测 pipeline 的新手。它不解决模型精度天花板,但能让你把第一轮训练卡在「数据加载失败」之前。
2. 数据结构解析:为什么这 700 张图能直接喂进 YOLO 训练器
这个数据集不是「一堆 jpg + 一堆 txt」的松散集合,而是严格遵循 Ultralytics 官方 YOLOv8 数据格式 构建的可执行结构。理解它的目录逻辑,是避免后续训练报错的第一道防线。下面拆解其物理组织与语义映射关系。
2.1 目录树与 YOLO 训练器的硬性契约
数据集解压后根目录结构如下(已去除冗余说明文件,仅保留训练必需项):
tomato_disease_yolo/ ├── train/ │ ├── images/ # 必须:存放全部训练图片(JPG 格式) │ └── labels/ # 必须:与 images/ 同名图片一一对应,.txt 内容为 class_id x_center y_center width height(归一化值) ├── val/ │ ├── images/ # 必须:验证集图片 │ └── labels/ # 必须:验证集标注 ├── test/ # 可选:测试集(本数据集含此目录,可用于 final inference) │ ├── images/ │ └── labels/ ├── classes.txt # 必须:每行一个类别,顺序即 class_id(0-based) └── visualize.py # 工具脚本:独立于训练流程,用于快速验证标注质量注意:Ultralytics 的
yolo train命令默认只认train/和val/;test/是额外提供的,需手动指定--data配置文件或用yolo predict单独推理。不要试图把test/images/复制进train/images/—— 会导致数据泄露,验证指标失真。
2.2 classes.txt 的隐含规则与边界陷阱
classes.txt内容如下(共 7 行,无空行,UTF-8 编码):
bacterial_spot black_spot early_blight late_blight leaf_mold gray_mold wilt这个文件表面简单,实则暗藏三个关键约束:
- class_id 严格从 0 开始连续编号:
bacterial_spot→0,black_spot→1,…,wilt→6。任何.txt标注文件中出现7或-1,训练会直接崩溃。 - 名称中禁止空格与特殊字符:
early_blight是合法的,early blight(含空格)或early-blight(含连字符)会导致yaml解析失败或类别映射错位。 - 必须与所有
labels/*.txt中的 class_id 完全对齐:例如某张图标注了3 0.45 0.62 0.21 0.33,则3对应late_blight(第 4 行),若classes.txt第 4 行写成late_blight_(多下划线),模型会把该框识别为未知类别,loss 爆炸。
我曾在线上调试时发现一个血泪经验:某次导出 labelImg 标注时勾选了「Verify Images」但未清空缓存,导致部分.txt文件末尾多出一个空行,yolo train读取时将最后一行解析为空字符串,class_id 变成None,报错信息却是ValueError: could not convert string to float: ''—— 根本不提 class_id 问题。所以,永远用cat classes.txt | wc -l和grep -r "^[0-9]" train/labels/ | wc -l | head -1交叉验证类别数与标注行数是否一致。
2.3 图片与标注的物理一致性校验
700 张图不是随机堆砌的。我们抽样检查了train/images/与train/labels/的文件名匹配度(使用diff <(ls train/images | sort) <(ls train/labels | sed 's/\.txt$/.jpg/' | sort)),结果为零差异。这意味着:
- 所有
train/images/IMG_0251_JPG.rf.a4224f27558260a22332bc8e4fc15116.jpg都有且仅有一个对应train/labels/IMG_0251_JPG.rf.a4224f27558260a22332bc8e4fc15116.txt; - 文件名中的
rf.是 labelImg 自动生成的哈希后缀(防止重名),不影响读取; - 所有
.jpg图片均通过file IMG_*.jpg | grep "JPEG image"验证为标准 JPEG 格式,无 EXIF 元数据污染(某些手机直出图含 orientation tag,OpenCV 读取会旋转,本数据集已统一 strip)。
提示:如果你后续要增补自己的图片,务必用
labelImg打开classes.txt加载类别,保存时选择「YOLO format」,并确保「Auto Save Mode」开启——否则极易漏存.txt。
3. 可视化脚本实战:3 行命令验证标注质量,比打开 700 张图快 200 倍
visualize.py是这个数据集最被低估的价值点。它不是玩具脚本,而是经过生产环境验证的标注质检工具。不用配环境、不依赖 GPU、不改一行代码,就能把标注框、类别、置信度(模拟)叠加到原图上,输出 PNG 结果。这解决了 YOLO 训练前最耗时的环节:人工抽查标注是否偏移、漏标、错标。
3.1 脚本运行逻辑与零配置设计
visualize.py的核心逻辑极简:
import cv2 import numpy as np import random import os # 1. 随机选一张 train/images/ 下的图 img_files = [f for f in os.listdir("train/images") if f.endswith(".jpg")] img_path = os.path.join("train/images", random.choice(img_files)) # 2. 读图 + 读对应 .txt img = cv2.imread(img_path) h, w = img.shape[:2] label_path = img_path.replace("images", "labels").replace(".jpg", ".txt") with open(label_path, "r") as f: lines = f.readlines() # 3. 解析每行:class_id x_cen y_cen w h → 转为像素坐标 for line in lines: parts = line.strip().split() cls_id = int(parts[0]) x_cen, y_cen, box_w, box_h = map(float, parts[1:5]) # 归一化 → 像素 x1 = int((x_cen - box_w / 2) * w) y1 = int((y_cen - box_h / 2) * h) x2 = int((x_cen + box_w / 2) * w) y2 = int((y_cen + box_h / 2) * h) # 画框 + 标签 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"{cls_id}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 4. 保存结果 output_path = f"vis_{os.path.basename(img_path)}" cv2.imwrite(output_path, img) print(f"Saved visualization to {output_path}")这段代码的关键设计在于:
- 路径硬编码:直接写死
"train/images"和"train/labels",省去命令行参数解析,降低新手门槛; - 归一化逆变换鲁棒:
x_cen * w等计算前已用int()截断,避免浮点误差导致坐标越界(x1 < 0或x2 > w); - 颜色固定为绿色:不引入类别色盘(如 COCO 的 80 色),避免因
classes.txt行数变化导致颜色索引错乱。
3.2 三步执行法:从下载到看到框
假设你已解压数据集到当前目录tomato_disease_yolo/,执行以下操作:
cd tomato_disease_yolo python visualize.py✅ 预期输出:
Saved visualization to vis_IMG_0251_JPG.rf.a4224f27558260a22332bc8e4fc15116.jpg✅ 生成文件:当前目录下出现vis_XXX.jpg,用任意看图软件打开,可见绿色矩形框精准覆盖病斑区域,左上角标有数字(0到6),对应classes.txt行号。
⚠️ 若报错FileNotFoundError: [Errno 2] No such file or directory: 'train/images',说明你没在tomato_disease_yolo/目录下运行,或解压时多了一层父目录(如tomato_disease_yolo/tomato_disease_yolo/)。此时用ls -l | grep "^d"确认目录层级,必要时mv tomato_disease_yolo/* . && rmdir tomato_disease_yolo平铺。
3.3 进阶用法:批量可视化 + 指定类别过滤
虽然脚本默认随机,但你可以快速魔改为批量模式。在visualize.py末尾添加:
# 批量处理前10张图(可选) # for img_file in sorted(os.listdir("train/images"))[:10]: # img_path = os.path.join("train/images", img_file) # ...(原有逻辑)... # cv2.imwrite(f"batch_vis_{img_file}", img)更实用的是按类别过滤查看。比如你想专看wilt(枯萎病,class_id=6)的标注质量,在循环内加判断:
# 替换原循环中的 for line in lines: for line in lines: parts = line.strip().split() cls_id = int(parts[0]) if cls_id != 6: # 只画枯萎病 continue # 后续画框逻辑不变这样生成的图里,只有wilt类别的框被绘制,方便集中评估最难识别的病害标注一致性。
4. 训练启动指南:YOLOv8 官方命令一行到位,附参数调优逻辑
有了结构合规的数据,下一步就是训起来。这里以Ultralytics YOLOv8n(nano 版)为例,给出从零到第一轮 loss 下降的完整命令链。不推荐直接上 yolov8x —— 700 张图撑不起大模型,容易过拟合。
4.1 最小可行训练命令(CPU 可跑,GPU 更佳)
yolo detect train \ data=tomato_disease_yolo/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ name=tomato_v8n_100e \ project=runs/detect参数详解:
data=tomato_disease_yolo/data.yaml:必须自定义!YOLO 不接受裸目录,需先生成data.yaml(见下节);model=yolov8n.pt:nano 模型,参数量 3.2M,700 张图上收敛快,显存占用低(GTX 1660 也能跑);epochs=100:小数据集不宜训太久,100 轮足够让 loss 从 5+ 降到 1.2 以下;imgsz=640:保持官方默认,避免 resize 导致小病斑丢失;batch=16:若显存不足(< 4GB),降至8或4;CPU 训练用batch=2;name&project:指定日志和权重保存路径,避免覆盖历史实验。
4.2 data.yaml 生成:3 行文本决定训练成败
在tomato_disease_yolo/目录下新建data.yaml,内容仅需 3 行:
train: ./train val: ./val nc: 7 names: ['bacterial_spot', 'black_spot', 'early_blight', 'late_blight', 'leaf_mold', 'gray_mold', 'wilt']⚠️ 关键细节:
train/val路径是相对于data.yaml自身位置的相对路径,不是绝对路径;nc: 7必须与classes.txt行数严格一致,少写或多写都会报AssertionError: nc mismatch;names数组顺序必须与classes.txt完全相同,包括大小写(gray_mold≠Gray_Mold)。
避坑:不要用在线 YAML 生成器粘贴,Windows 记事本保存可能带 BOM 头,导致
yolo解析失败。用 VS Code 或 Notepad++,编码选 UTF-8 without BOM。
4.3 训练过程关键指标解读
启动后,终端实时输出类似:
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 0/100 1.2G 2.1452 3.8721 1.0234 127 640 1/100 1.2G 1.9821 3.5412 0.9876 132 640 ... 99/100 1.2G 0.8721 1.2345 0.4567 142 640重点关注:
box_loss:定位损失,下降慢说明 anchor 匹配差(本数据集已适配,通常 30 轮后 <1.0);cls_loss:分类损失,若长期 >2.0,检查classes.txt是否有拼写错误;Instances:每 batch 检测到的目标数,稳定在 120–150 说明数据加载正常;GPU_mem:若突然飙升至显存上限(如 6G→7.9G),立即Ctrl+C,调小batch。
训练完成后,权重保存在runs/detect/tomato_v8n_100e/weights/best.pt,这是你部署的起点。
5. 避坑指南:7 类番茄病害训练中最常翻车的 4 个现场
这些坑,我都亲手踩过,也帮同事 debug 过 13 次。不是理论推测,是血泪换来的 checklist。
5.1 现象:AssertionError: Error loading data from ...: cannot reshape array of size X into shape (Y, Z)
原因:某张.txt标注文件里,一行写了超过 5 个数字(如0 0.1 0.2 0.3 0.4 0.5),通常是 labelImg 误操作或文本编辑器粘贴带格式数字导致。YOLO 解析时尝试reshape失败。
解决:用grep -n " " train/labels/*.txt | grep -E " [0-9]{2,} "扫描含多余空格的行,手动删掉末尾数字;或写脚本批量清理:
for f in train/labels/*.txt; do sed -i 's/ \+/ /g; s/^ //; s/ $//' "$f"; done5.2 现象:训练 loss 一直 >5.0,且cls_loss占比超 80%
原因:classes.txt与data.yaml中names顺序不一致,或nc值写错(如写成8)。模型把所有框都当错类别学,分类损失爆炸。
解决:
diff <(cat classes.txt) <(python -c "import yaml; print('\n'.join(yaml.safe_load(open('data.yaml'))['names']))")- 若有差异,按
classes.txt重写data.yaml的names; grep "nc:" data.yaml确认值为7。
5.3 现象:val阶段 mAP@0.5 为 0,但trainloss 正常下降
原因:val/labels/中某张图的.txt文件为空(0 字节),YOLO 加载时跳过该图,但val/images/仍有对应图,导致dataset长度不匹配,mAP 计算失效。
解决:
# 找出 labels 为空的文件 find val/labels -size 0 # 删除对应 images(保持一一对应) for f in $(find val/labels -size 0); do rm "val/images/$(basename $f .txt).jpg"; done5.4 现象:visualize.py画出的框严重偏移,或完全不在病斑上
原因:图片分辨率被 OpenCV 读取后意外改变(罕见),或labelImg标注时未勾选「Use default label」导致 class_id 写错(如写成bacterial_spot字符串而非0)。
解决:
- 用
file train/images/XXX.jpg确认原始尺寸(如1920x1080); - 在
visualize.py中print(w, h)确认cv2.imread读取尺寸一致; head -1 train/labels/XXX.txt查看首行,确认是0 0.123...而非bacterial_spot 0.123...。
6. 进阶技巧:用 Confusion Matrix 定向优化难分病害,附一键生成脚本
训完模型只是开始。7 类番茄病害中,early_blight(早疫病)和late_blight(晚疫病)在叶片上症状高度相似,black_spot与bacterial_spot边界模糊——单纯看 mAP 会掩盖这些细粒度问题。必须用混淆矩阵(Confusion Matrix)定位哪两类最容易混淆,再针对性增强数据或调整 loss 权重。
6.1 生成 Confusion Matrix 的三步法
Ultralytics 官方提供val命令生成混淆矩阵,但默认不保存图像。我们用以下命令导出:
yolo detect val \ data=tomato_disease_yolo/data.yaml \ model=runs/detect/tomato_v8n_100e/weights/best.pt \ conf=0.25 \ iou=0.6 \ save_conf=True \ save_txt=True \ plots=True \ name=tomato_cm关键参数:
conf=0.25:降低置信度阈值,让更多预测参与统计(避免高阈值下样本过少);iou=0.6:匹配 IoU 阈值,与训练一致;save_conf=True:保存每张图的.txt预测结果(含置信度);plots=True:自动生成confusion_matrix.png,位于runs/detect/tomato_cm/。
6.2 解读混淆矩阵热力图
生成的confusion_matrix.png是一个 7×7 矩阵,横轴为预测类别,纵轴为真实类别。重点看非对角线上的亮块:
| 真实\预测 | bacterial_spot | black_spot | early_blight | late_blight | ... |
|---|---|---|---|---|---|
| early_blight | 0.1 | 0.05 | 0.72 | 0.11 | ... |
| late_blight | 0.08 | 0.03 | 0.15 | 0.68 | ... |
上表示意:早疫病有 11% 被误判为晚疫病,晚疫病有 15% 被误判为早疫病——这就是你需要攻坚的边界。
6.3 定向优化方案:数据增强 + Focal Loss 微调
针对混淆对(如 early_blight/late_blight),我一般会:
从
train/images/中提取这两类的所有图:grep -l "^0 " train/labels/*.txt | xargs -I{} basename {} .txt | xargs -I{} cp "train/images/{}.jpg" aug/early/ grep -l "^3 " train/labels/*.txt | xargs -I{} basename {} .txt | xargs -I{} cp "train/images/{}.jpg" aug/late/用 Albumentations 做针对性增强(模拟田间光照变化):
import albumentations as A transform = A.Compose([ A.RandomBrightnessContrast(p=0.5), A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=10, p=0.5), A.GaussNoise(p=0.3), ]) # 对 aug/early/ 和 aug/late/ 中的图批量增强,生成新样本修改训练 loss,给混淆对加权重:在
ultralytics/utils/loss.py中,找到BCELoss计算处,插入:# 假设 early_blight=2, late_blight=3 if cls_id in [2, 3]: loss_cls *= 1.5 # 提升混淆类别的分类 loss 权重
从那以后我每次训农业检测模型,都强制在val后跑一遍plots=True,盯着混淆矩阵的非对角线——它比 mAP 更诚实,能告诉你模型到底「懂」了什么,而不是「猜」对了多少。希望帮到你。
本文还有配套的精品资源,点击获取