
简介这份番茄叶病害目标检测数据集面向农业智能化与计算机视觉方向的研究者、学生及算法工程师用于训练和验证番茄叶片病害的自动识别与定位模型可服务于智慧农业、病害防治等实际场景。资源包共1438个文件压缩后约23.27MB包含716张jpg图像、358个xml标注、362个txt标签以及1个yaml配置和1个cache缓存文件同时提供VOC与YOLO两套格式VOC以JPEGImages配Annotations的xml边界框标注YOLO以images配labels的txt标签并已划分train.txt与val.txt便于直接投入训练与验证。目前已有130人学习下载。数据集覆盖多种番茄叶病害类型标注信息完整兼容主流深度学习框架与YOLO等实时检测算法读者可快速搭建训练流程、验证模型泛化能力省去自行采集与标注的成本适合作为目标检测入门与农业病害识别项目的实用数据基础。1. 番茄叶病害目标检测数据集从拿到压缩包到跑通第一轮训练番茄叶片上那点病斑看着简单真做起来比想象中磨人。早疫病的同心轮纹、晚疫病的水渍状边缘、叶霉病的黄绿斑块在自然光下互相重叠背景还有土壤、支架、反光膜。你手里这个「番茄叶病害目标检测数据集.zip」本质是一批已经标好边界框的叶片图像目标类别通常是早疫病、晚疫病、叶霉病、健康叶这几类。它解决的不是「分类这张图是什么病」而是「病斑在哪、有多大、有几处」——这对精准施药、病情分级、机器人巡检才是真正有用的输出。适合谁做农业视觉的算法同学、想拿真实数据练 YOLO 系列目标检测的工程新手、以及需要给温室巡检设备做病害定位的开发者。这篇不聊虚的从解压后的目录长什么样一路讲到训练、验证、踩坑和进阶技巧。2. 解压后先别急着训练目录结构、标注格式与数据体检2.1 一个典型番茄叶病害数据集的目录长什么样拿到压缩包第一件事不是unzip完就丢进 YOLO而是先看清楚它到底怎么组织的。农业类目标检测数据集常见的两种结构一种是已经切好 train/val/test 的另一种是全部图片堆在一个目录、标注单独放。番茄叶病害数据集多数属于后者解压后大致是这样tomato_leaf_disease/ ├── images/ │ ├── IMG_0001.jpg │ ├── IMG_0002.jpg │ └── ... ├── annotations/ │ ├── IMG_0001.xml # 或 .json / .txt │ ├── IMG_0002.xml │ └── ... └── classes.txt # 类别名列表有时叫 labels.txt先跑一条命令把家底摸清楚别凭感觉# 统计图片数量、格式分布、总大小 find tomato_leaf_disease/images -type f | wc -l find tomato_leaf_disease/images -type f -name *.jpg | wc -l find tomato_leaf_disease/images -type f -name *.png | wc -l du -sh tomato_leaf_disease/images # 看标注文件数量和扩展名 find tomato_leaf_disease/annotations -type f | sed s/.*\.// | sort | uniq -c这几条命令的含义很直白第一条数图片总数第二三条分别数 jpg 和 png第四条看总体积最后一条统计标注文件扩展名分布。为什么要先做这个因为番茄叶病害数据集经常是多个来源拼的jpg 和 png 混着来标注有的是 Pascal VOC 的 xml有的是 COCO 的 json甚至有的子集只有图片没标注。你不先体检后面转换脚本一跑就报错还找不到原因。提示如果classes.txt里类别名是中文先确认训练框架的编码支持YOLO 系列对中文类别名在部分版本会出问题建议映射成英文或拼音。2.2 标注格式判断VOC、COCO 还是 YOLO txt判断格式不用猜打开一个标注文件看头部就行head -30 tomato_leaf_disease/annotations/IMG_0001.xml如果看到annotationobjectbndboxxmin这种结构就是 Pascal VOC。如果标注是.json且里面有images、annotations、categories三个顶层键那是 COCO。如果标注是.txt且每行是class_id x_center y_center width height五个归一化到 0~1 的数那已经是 YOLO 格式省事了。番茄叶病害数据集里 VOC 格式最常见因为很多标注是用 LabelImg 做的默认导出就是 VOC xml。VOC 的坐标是绝对像素值YOLO 要的是归一化中心点加宽高转换时必须除以图像宽高。这里有个高频翻车点xml 里的size字段写的宽高和实际图片的宽高不一致。标注工具偶尔会记录错或者图片被后期裁剪过但 xml 没更新。所以转换前一定要用真实图片尺寸不要信 xml 里的 size。from PIL import Image import os, xml.etree.ElementTree as ET def check_size_mismatch(img_dir, ann_dir): 检查 xml 记录的尺寸和真实图片尺寸是否一致 mismatches [] for xml_file in os.listdir(ann_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, xml_file)) root tree.getroot() size root.find(size) xml_w int(size.find(width).text) xml_h int(size.find(height).text) img_name os.path.splitext(xml_file)[0] .jpg img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue real_w, real_h Image.open(img_path).size if (xml_w, xml_h) ! (real_w, real_h): mismatches.append((xml_file, (xml_w, xml_h), (real_w, real_h))) return mismatches result check_size_mismatch(tomato_leaf_disease/images, tomato_leaf_disease/annotations) print(f尺寸不一致的标注文件数: {len(result)}) for r in result[:5]: print(r)这段代码遍历所有 xml读取里面记录的宽高再用 PIL 打开对应图片拿真实宽高不一致就记下来。参数上没什么可调的img_dir和ann_dir换成你的实际路径即可。跑完如果发现几十上百个不一致别慌转换时统一用真实图片尺寸就行xml 里的 size 直接忽略。这一步做完你对数据质量就有了底。2.3 数据体检类别分布、框大小与漏标排查训练之前还有三件事必须做类别是否均衡、框的尺寸分布、有没有明显漏标。番茄叶病害数据集里健康叶样本往往偏多病斑样本偏少直接训练模型会偏向预测健康叶。先统计一下import xml.etree.ElementTree as ET import os from collections import Counter def count_classes(ann_dir): counter Counter() box_sizes [] for f in os.listdir(ann_dir): if not f.endswith(.xml): continue root ET.parse(os.path.join(ann_dir, f)).getroot() for obj in root.findall(object): name obj.find(name).text counter[name] 1 bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) box_sizes.append((w, h)) return counter, box_sizes counter, sizes count_classes(tomato_leaf_disease/annotations) print(类别分布:, counter) areas [w * h for w, h in sizes] print(f框面积 中位数: {sorted(areas)[len(areas)//2]:.0f} 像素) print(f最小框面积: {min(areas):.0f} 像素)类别分布直接告诉你要不要做重采样或加权。框面积中位数和最小值告诉你 anchor 或输入分辨率该怎么设——如果大量框面积只有几百像素输入分辨率设 416 就太小了病斑会糊成一团建议 640 起步。漏标排查没有全自动的可靠办法常见做法是随机抽 20 张图用脚本把标注框画上去人眼过一遍import cv2, os, xml.etree.ElementTree as ET def draw_boxes(img_path, xml_path, out_path): img cv2.imread(img_path) root ET.parse(xml_path).getroot() for obj in root.findall(object): b obj.find(bndbox) x1, y1 int(b.find(xmin).text), int(b.find(ymin).text) x2, y2 int(b.find(xmax).text), int(b.find(ymax).text) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, obj.find(name).text, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img) # 抽查前 20 张 for f in sorted(os.listdir(tomato_leaf_disease/annotations))[:20]: name os.path.splitext(f)[0] draw_boxes(ftomato_leaf_disease/images/{name}.jpg, ftomato_leaf_disease/annotations/{f}, fcheck_{name}.jpg)画框脚本的逻辑就是把 xml 里的坐标读出来用 OpenCV 画矩形和类别名输出到新文件。重点看两类问题病斑明明在图上但没框或者框把整片叶子都圈进去了。前者是漏标后者是标注粒度过粗都会拉低模型精度。这一步花半小时能省后面几天的调参时间。3. 转成 YOLO 格式并跑通训练脚本、配置与参数3.1 VOC 转 YOLO txt 的完整脚本与四个边界坑格式转换是绕不过去的。下面这个脚本把 VOC xml 转成 YOLO 需要的 txt同时生成 train/val 划分文件import os, random, xml.etree.ElementTree as ET from PIL import Image CLASSES [early_blight, late_blight, leaf_mold, healthy] # 按你的 classes.txt 实际内容改顺序决定 class_id def voc_to_yolo(xml_path, img_path, out_txt): tree ET.parse(xml_path) root tree.getroot() # 关键用真实图片尺寸不用 xml 里的 size img_w, img_h Image.open(img_path).size lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) b obj.find(bndbox) x1 float(b.find(xmin).text) y1 float(b.find(ymin).text) x2 float(b.find(xmax).text) y2 float(b.find(ymax).text) # 边界裁剪防止坐标越界 x1, y1 max(0, x1), max(0, y1) x2, y2 min(img_w, x2), min(img_h, y2) # 转归一化中心点 宽高 cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 过滤掉宽高为 0 的脏框 if w 0 or h 0: continue lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) return len(lines) def build_dataset(img_dir, ann_dir, out_dir): os.makedirs(f{out_dir}/images/train, exist_okTrue) os.makedirs(f{out_dir}/images/val, exist_okTrue) os.makedirs(f{out_dir}/labels/train, exist_okTrue) os.makedirs(f{out_dir}/labels/val, exist_okTrue) names [os.path.splitext(f)[0] for f in os.listdir(ann_dir) if f.endswith(.xml)] random.seed(42) random.shuffle(names) split int(len(names) * 0.8) for i, name in enumerate(names): subset train if i split else val img_src f{img_dir}/{name}.jpg if not os.path.exists(img_src): continue # 复制图片 import shutil shutil.copy(img_src, f{out_dir}/images/{subset}/{name}.jpg) # 生成标注 voc_to_yolo(f{ann_dir}/{name}.xml, img_src, f{out_dir}/labels/{subset}/{name}.txt) build_dataset(tomato_leaf_disease/images, tomato_leaf_disease/annotations, tomato_yolo)逻辑说明voc_to_yolo负责单个文件的坐标转换build_dataset负责整体目录搭建和 8:2 划分。四个边界坑分别是——第一坐标越界xml 里 xmax 可能超过图片宽度必须裁剪第二宽高为 0 的脏框除下来是 0YOLO 训练时会报 nan必须过滤第三类别名不在 CLASSES 里的目标直接跳过否则 class_id 会错位第四图片扩展名不一定是 jpg脚本里写死了.jpg如果你的数据集有 png要把这行改成动态匹配。参数上random.seed(42)保证划分可复现0.8是训练集比例小数据集可以调到 0.85。3.2 data.yaml 怎么写路径、类别数与常见报错YOLO 训练靠一个 yaml 文件告诉它数据在哪、有几类。在tomato_yolo同级建一个tomato.yamlpath: /absolute/path/to/tomato_yolo train: images/train val: images/val nc: 4 names: 0: early_blight 1: late_blight 2: leaf_mold 3: healthypath必须是绝对路径这是最高频的报错来源写相对路径 YOLO 会找不到。nc是类别数必须和 names 的条目数一致多一个少一个都会在训练启动时抛错。train和val是相对path的子路径。names 的键从 0 开始顺序必须和转换脚本里 CLASSES 的顺序完全一致否则模型学到的类别会张冠李戴。注意如果你的数据集类别名是中文yaml 里建议用英文或拼音训练完再在可视化时映射回中文。YOLO 部分版本读取中文 names 会乱码导致验证时类别显示异常。3.3 用 YOLOv8 跑第一轮训练命令行与关键参数环境配置不展开假设你已经装好 ultralytics。第一轮训练不要一上来就调参先用默认配置跑通确认数据管道没问题yolo detect train \ datatomato.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ projecttomato_runs \ namebaseline逐参数说modelyolov8n.pt用 nano 版本参数量小先验证流程别一上来就上 x 版本浪费时间。epochs100对番茄叶病害这种几千张规模的数据集够用看验证集 mAP 什么时候平了就停。imgsz640是前面数据体检得出的结论病斑小分辨率不能低。batch16看显存8G 显存跑 640 的 nano 模型 16 没问题爆显存就降到 8。project和name决定输出目录方便你对比多次实验。训练启动后重点盯三个输出box_loss是否稳定下降、mAP50是否上升、验证集 loss 有没有反弹。如果 box_loss 一直不降八成是标注格式有问题回去检查 txt 里的坐标是不是归一化到了 0~1。如果 mAP50 卡在很低的值不动检查类别顺序和 yaml 是否对得上。第一轮跑完你会得到一个best.pt这就是后续推理和继续训练的基础。4. 番茄叶病害检测的避坑与排查那些让我返工的细节4.1 现象训练 loss 正常但验证 mAP 极低原因通常有两个。一是训练集和验证集的类别分布差异太大比如验证集里几乎全是健康叶模型在验证集上等于没学到病斑。二是标注框和图片没对齐转换时用了 xml 里的错误尺寸导致框整体偏移。解决先跑一遍 2.3 的类别统计确认两个子集分布接近再用 2.3 的画框脚本抽查验证集图片肉眼确认框位置正确。这两个动作做完问题基本能定位。4.2 现象模型把健康叶也框出病斑这是典型的假阳性番茄叶病害数据集里健康叶样本多模型容易学到「叶子有病」的捷径。原因往往是负样本健康叶在训练中被过度采样或者病斑框标注过粗把整片叶子都圈进去了。解决检查健康叶的标注如果健康叶也被标了框确认类别是否正确在训练时适当降低健康叶的采样权重或者增加病斑样本的增强。另一个办法是提高置信度阈值从默认 0.25 提到 0.4 再观察。4.3 现象小病斑完全检测不到早疫病早期的小斑点可能只有十几个像素640 分辨率下经过多次下采样就没了。原因是模型的下采样倍率太高小目标特征丢失。解决把imgsz提到 1024 甚至 1280代价是显存和训练时间增加或者改用带 P2 小目标检测层的模型结构。另一个实用技巧是在数据增强里关掉 mosaic 的最后几轮让模型在真实尺度上收敛。4.4 现象训练到一半显存爆了原因通常是 batch 设太大或者 dataloader 的 worker 数太多导致内存泄漏。解决先把 batch 减半观察是否还爆如果还爆把workers从默认 8 降到 4 或 2。番茄叶病害数据集图片分辨率高640 输入下 batch 16 在 8G 显存上已经接近上限别硬撑。4.5 现象推理时框重叠严重同一病斑出多个框这是 NMS非极大值抑制阈值的问题。默认iou0.7对密集病斑偏松。解决推理时把iou降到 0.5 左右重叠框会被合并。如果病斑本身就很密集降太多会漏检需要在验证集上试几个值找平衡。这个参数没有万能值跟你的病斑密度直接相关。5. 从能跑到好用验证策略、阈值调优与一个提点技巧第一轮训练跑通只是起点真正决定这个番茄叶病害检测方案能不能落地的是验证和调优。我一般会固定一个验证集每次改完参数都在同一批图上对比避免「这次好像好一点」的玄学判断。验证时不要只看 mAP 一个数把 precision、recall 和每类的 AP 都拉出来看。番茄叶病害里晚疫病和早疫病视觉上接近经常互相误判看混淆矩阵能直接暴露这个问题。阈值调优有个实用做法在验证集上跑一遍推理把置信度从 0.1 到 0.9 按 0.05 步长扫一遍画 precision-recall 曲线选 F1 最高的点作为部署阈值。代码不长from ultralytics import YOLO import numpy as np model YOLO(tomato_runs/baseline/weights/best.pt) results model.val(datatomato.yaml, conf0.001, iou0.6) # results 里含 curves可直接看 PR 曲线 # 实际部署阈值建议在 F1 最大处取这里conf0.001是为了让验证时保留所有低置信度预测方便画完整 PR 曲线部署时再按 F1 最大点设阈值。iou0.6是验证时的 NMS 阈值比推理时略松保证不漏掉真实框。最后一个提点技巧也是我踩坑踩出来的番茄叶病害数据集的病斑在颜色上和健康组织差异明显可以在训练前对图像做一次颜色增强比如随机调整 HSV 的饱和度和亮度让模型对光照变化更鲁棒。但别过度饱和度调太狠会让病斑颜色失真反而掉点。我一般把hsv_h设 0.015、hsv_s设 0.7、hsv_v设 0.4这是 YOLO 默认增强里比较稳的一组。另外如果你的数据集里不同来源的图片色温差异大先做一次白平衡统一比任何增强都管用。说到底番茄叶病害目标检测这件事数据质量的决定性远大于模型选型。我见过太多人花一周调 YOLOv8 和 YOLOv11 的差异结果标注里一堆漏标和错框换什么模型都白搭。先把 2.3 的数据体检做扎实把 3.1 的转换脚本跑对再谈调参。希望帮到你。本文还有配套的精品资源点击获取