☰
番茄叶片病害数据集详解:YOLO/VOC标注格式与目标检测训练避坑指南
2026/10/1 3:26:58 网站建设 项目流程

简介:面向目标检测与深度学习场景的番茄叶片病害识别数据集,覆盖早疫病、花叶病毒、红蜘蛛感染三类常见病害,标注信息可供YOLO系列、Faster RCNN、SSD等主流目标检测模型直接训练使用。压缩包共包含2000个文件,其中1999个为YOLO格式的标签txt文件,另附1个指定三类病害类别信息的yaml配置文件,整体大小约247.69MB。每个txt标签文件均记录目标边界框与类别编号,yaml文件则定义了类别名称与索引映射,标签内容已预先划分出训练集、验证集和测试集,亦适配YOLOv5至YOLOv10等各版本YOLO算法,可实现开箱即用的训练流程。目前已有393人学习/下载,适合农业病害智能识别、深度学习目标检测实践以及算法精度对比等应用需求,能够有效缩短数据准备时间,帮助开发者快速开展模型训练与效果验证。

1. 番茄叶片病害识别数据集:三个类别、双格式标注,训练前先看清这几个细节

做番茄叶片病害识别模型,最怕的不是算法选型,而是拿到手的数据集标注格式混乱、类别对不上、训练集验证集混在一起。这份数据集把这些问题基本都避开了——6446张图片,覆盖blight-disease(晚疫病)、mosaic-virus(花叶病毒病)、redspider-infection(红蜘蛛/叶螨危害)三个类别,同时提供YOLO格式的txt标注和VOC格式的xml标注,并且已经按训练集、验证集、测试集划分完毕。我看了一下文件结构,txt标签、yaml配置、xml标签都齐全,理论上拿过来改一下路径就能直接喂给YOLOv5到YOLOv10系列,也能转给Faster R-CNN、SSD这类检测框架。不过文件名里带有一串哈希值和来自视频截帧的命名痕迹,说明数据来源比较杂,直接开训之前有几个坑需要先排掉。这篇文章就把我拆这份数据集时踩过的坑和验证过的用法完整写出来。

2. 数据格式与目录结构:YOLO和VOC双标注,从文件名到标签逐一对应

这份数据集的标注格式是比较标准的双轨制:每个图片对应一个txt文件(YOLO格式)和一个xml文件(VOC格式),外加一个记录了类别信息的yaml文件。先从文件命名说起。数据集里的文件名长这样:

How-to-control-spider-mites-in-your-garden---Tomato---Eggplant-Spider-Mite-Control_mp4-110_jpg.rf.59fe553941077ec2173939bc12448e3b.txt

2.1 先看懂文件名里藏的信息

这个命名规律其实透露了三件事。第一,部分图片来源于一个名为“How to control spider-mites in your garden”的视频截帧,mp4-110、mp4-98这些编号就是视频帧序号;第二,.rf.是Roboflow平台导出的标记;第三,后面的哈希字符串是Roboflow给每个文件生成的唯一标识,用来避免重名冲突。

这个命名方式在实际训练中会带来一个小麻烦:文件名过长,而且包含连续短横线和数字,如果直接放在Windows路径下,某些旧版数据处理脚本在解析时容易出问题。我的建议是,拿到数据集后先做一次重命名,统一改成tomato_leaf_0001.jpg这种简洁格式,同时把对应的txt和xml同步改名。别嫌麻烦,这一步能省掉后面很多莫名其妙的报错。

2.2 YOLO格式txt标注的内容拆解

YOLO格式的txt文件每一行代表一个目标框,标准结构是:

class_id x_center y_center width height

注意这里面的坐标都是归一化坐标,数值范围在0到1之间,用图片宽度和高度做了除法。给你看一个实际标注文件的内容示例:

0 0.432156 0.517284 0.214578 0.180235 2 0.687452 0.314587 0.152847 0.124569

第一列0表示类别ID,对应yaml文件里names列表的第一个类别blight-disease;2表示第三个类别redspider-infection。后面四个数字分别是目标框中心点的x坐标、y坐标、框宽度、框高度,全部归一化。读取这类文件时有个容易忽略的点:YOLO格式规定坐标是中心点加宽高,不是左上角加右下角,写可视化脚本时很多人习惯直接拿x_center减width/2算左上角,这个转换公式必须确认无误。

2.3 XML标注文件的结构解析

XML标注遵循VOC格式,根节点是<annotation>,内部核心字段包括<filename>、<size>和<object>。一个典型的标注文件结构如下:

<annotation> <folder>images</folder> <filename>tomato_leaf_0001.jpg</filename> <size> <width>640</width> <height>640</height> <depth>3</depth> </size> <object> <name>blight-disease</name> <bndbox> <xmin>118</xmin> <ymin>203</ymin> <xmax>256</xmax> <ymax>319</ymax> </bndbox> </object> </annotation>

<name>字段的值必须和yaml文件里的names列表完全一致,否则Faster R-CNN这类基于VOC格式的框架在解析时会报KeyError或者类别数对不上。实际操作中我发现一个问题:部分XML文件里的<filename>字段可能和实际图片文件名不一致,比如缺少.jpg后缀或者带了路径前缀。如果要用VOC格式训练,建议写一个脚本统一校正这个字段。

2.4 yaml配置文件的类别顺序和路径

数据集的yaml文件是给YOLO系列训练用的配置文件,内容大概是这样的:

path: /path/to/tomato-disease-dataset train: images/train val: images/val test: images/test nc: 3 names: 0: blight-disease 1: mosaic-virus 2: redspider-infection

这里有几个关键点。nc: 3必须和names列表长度一致,否则训练时会报AssertionError: nc doesn't match len(names)。另外path字段建议改成绝对路径,或者在训练命令里用data=/your/absolute/path/data.yaml指定,别依赖相对路径——YOLO系列在解析相对路径时有时会找错目录,这是我自己跑YOLOv8时反复遇到的坑。train、val、test三个子路径是相对于path的,如果数据集目录结构不同,也需要同步调整。

常见的数据集划分比例是训练集70%、验证集20%、测试集10%,6446张图片大概对应训练集4500张左右、验证集1300张左右、测试集650张左右。这个划分对中等规模数据集来说比较合理,训练集足够让模型学习到三类病害的特征差异,验证集能稳定评估模型表现,测试集保留了独立评估空间。需要确认的是,这份数据集的划分是否按类别做了分层采样——如果某个类别的图片在测试集里占比异常,评估指标会失真。

3. 用YOLOv8训练番茄病害检测模型:配置修改、训练启动、结果验证

有了yaml文件,接下来就是实战环节。这一节用YOLOv8做示例,因为ultralytics框架的接口简单,代码改动最少,但同样的逻辑也适用于YOLOv5、YOLOv9、YOLOv10等版本。

3.1 环境准备与依赖安装

训练前先把环境装好。建议用Python 3.8到3.11版本,GPU显存8GB以上起步。安装ultralytics库:

pip install ultralytics

安装完成后验证一下CUDA是否可用:

python -c "import torch; print(torch.cuda.is_available())"

如果输出True,说明GPU环境正常,可以开始训练。如果输出False,检查CUDA版本和PyTorch版本是否匹配,常见的问题是PyTorch安装的CPU版本。

3.2 修改yaml路径并启动训练

把数据集yaml文件里的path改成你本机的实际路径,然后启动训练命令:

yolo train data=/path/to/data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0

如果你用的是YOLOv5,命令略有不同:

python train.py --data /path/to/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100

参数说明:model=yolov8s.pt是选择yolov8s预训练权重,s代表small版本,对6446张图片的数据集来说足够了,用yolov8x反而容易过拟合;epochs=100是训练轮数,我习惯先跑100轮看loss收敛趋势,如果验证集指标还在涨就继续加跑;imgsz=640是输入图片尺寸,番茄叶片病斑通常属于中小目标,640是可以接受的下限,显存足够的话建议尝试768或1024;batch=16是批大小,8GB显存跑yolov8s时16是安全值,如果报CUDA out of memory就降到8,或者加--cache参数但注意会额外占用内存。

代码背后的逻辑是:yaml文件告诉YOLO数据集从哪里读、类别有几个、类别名称是什么,预训练权重提供初始特征提取能力,训练过程中框架会自动按yaml里划分好的训练集和验证集做迭代和评估。这里有个容易忽略的点:yolo train命令会自动读取yaml里的val路径来算验证集指标,如果这个路径配错了,训练过程会直接报AssertionError。

3.3 训练过程中的关键指标解读

训练时终端会滚动输出loss值和指标,需要重点关注的是box_loss、cls_loss、dfl_loss和验证集的mAP50、mAP50-95。如果cls_loss降不下去,大概率是类别混淆问题,尤其blight-disease和mosaic-virus在颜色表现上接近时。

训练结束后,在runs/detect/train目录下会生成best.pt和last.pt两个权重文件。best.pt是验证集mAP最高的权重,last.pt是最后一轮训练结束时的权重。实际部署时优先用best.pt,但有个情况例外——如果训练过程出现后期过拟合,best.pt反而比last.pt更靠谱,因为它是早停之前的版本。

3.4 推理验证模型效果

用训练好的权重做推理测试:

yolo predict model=/path/to/runs/detect/train/weights/best.pt source=/path/to/test/images save=True conf=0.25

推理结果会保存到runs/detect/predict目录,打开图片逐一检查三个类别的检出情况。我一般会挑几张验证集图片,把真实标注和预测结果叠在一起对比,重点看红蜘蛛感染这类小目标的检出率。如果漏检严重,说明模型对这个小类别学习得还不够好,下一节会讲怎么针对性调整。

4. 把数据迁移给Faster R-CNN和SSD:XML标注的完整解析与使用

YOLO格式的txt标注对单阶段检测器很友好,但如果你要跑Faster R-CNN、SSD这些框架,就需要VOC格式的XML标注。这也是这份数据集保留XML文件的价值所在。

4.1 为什么不能直接把txt喂给Faster R-CNN

Faster R-CNN的标准训练流程是先读VOC格式的XML,解析出<name>和<bndbox>,然后构造Ground Truth边界框。YOLO格式的txt虽然也包含边界框信息,但缺了图片尺寸信息,坐标归一化后必须结合原图宽高才能还原。大多数Faster R-CNN实现(比如detectron2、mmdetection)没有内置对YOLO格式的支持,需要自己写数据加载器来转换。XML恰好把图片尺寸<size>和目标框绝对坐标都存好了,可以直接被VOC格式的数据集类读取。

4.2 XML解析的最小实现代码

如果你要写自定义数据加载器,或者想把XML转成COCO等其他格式,可以先用这个Python脚本解析:

import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree = ET.parse(xml_path) root = tree.getroot() filename = root.findtext('filename') size = root.find('size') width = int(size.findtext('width')) height = int(size.findtext('height')) objects = [] for obj in root.iter('object'): name = obj.findtext('name') bbox = obj.find('bndbox') xmin = int(bbox.findtext('xmin')) ymin = int(bbox.findtext('ymin')) xmax = int(bbox.findtext('xmax')) ymax = int(bbox.findtext('ymax')) objects.append({ 'name': name, 'bbox': [xmin, ymin, xmax, ymax] }) return { 'filename': filename, 'width': width, 'height': height, 'objects': objects } xml_data = parse_voc_xml('tomato_leaf_0001.xml') print(xml_data)

代码逻辑说明:ET.parse加载XML文档,findtext提取filename和尺寸信息,root.iter('object')遍历所有目标对象,最后把边界框转成[xmin, ymin, xmax, ymax]的绝对坐标格式。这种格式是Faster R-CNN和SSD系框架的标准输入。

参数说明:xmin、ymin、xmax、ymax的单位是像素,直接从XML读取时是整数。在不同框架中要注意的是边界值是否包含在内,Pascal VOC约定是闭区间,也就是xmax这个像素本身包含在框内,如果你转成COCO格式,COCO的边界框定义是[x, y, width, height],且width和height需要做xmax - xmin计算而不是xmax - xmin + 1。

4.3 用mmdetection训练Faster R-CNN时的数据集配置

用mmdetection这类框架跑Faster R-CNN时,需要把XML标注组织成标准Pascal VOC目录结构,然后注册数据集。典型的配置方式:

dataset_type = 'VOCDataset' data_root = '/path/to/dataset/' data = dict( samples_per_gpu=2, workers_per_gpu=2, train=dict( type='RepeatDataset', times=3, dataset=dict( type=dataset_type, ann_file=data_root + 'VOC2007/ImageSets/Main/train.txt', img_prefix=data_root + 'VOC2007/JPEGImages/')), val=dict( type=dataset_type, ann_file=data_root + 'VOC2007/ImageSets/Main/val.txt', img_prefix=data_root + 'VOC2007/JPEGImages/'), test=dict( type=dataset_type, ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt', img_prefix=data_root + 'VOC2007/JPEGImages/'))

ann_file指向的是ImageSets/Main目录下的txt文件,每个文件里逐行列出图片文件名(不带扩展名),框架根据文件名去JPEGImages目录找图片、去Annotations目录找XML。所以在迁移这份数据集到mmdetection时,要先做三个准备:把XML文件归入Annotations目录、把图片归入JPEGImages目录、生成train.txt、val.txt、test.txt三个清单文件。清单文件的生成方式很简单:

ls /path/to/images/train | sed 's/\.jpg$//' > /path/to/dataset/VOC2007/ImageSets/Main/train.txt

把这种txt和图片的路径对应关系理顺了,Faster R-CNN就能直接用。数据集规模和类别数是三个,所以num_classes配置为3 + 1,多出来的背景类由框架自动处理。

5. 避坑与常见问题:三个类别的边界、文件匹配和训练不收敛的排查记录

拆这份数据集和实际训练过程中,我遇到了不少问题,按“现象 → 原因 → 解决”的格式记录几个最典型的,方便你对照排查。

5.1 图片数量和txt标注文件数量不一致

现象:解压后统计图片文件有6446张,但txt标注文件只有6320个,少了一百多个;训练时YOLO报错找不到某些图片的标注文件。

原因:数据集部分图片来自视频截帧,在导出过程中可能有个别帧没有被目标框标注工具处理,Roboflow导出数据集时默认会跳过未标注的图片,但图片文件本身被保留了。另外文件命名里的哈希串有极小概率导致工具解析重名文件时覆盖了标注。

解决:先用脚本做一次匹配检查,找出哪些图片缺txt,哪些txt缺图片。我一般写这样一个核对脚本:

import os from collections import Counter img_dir = 'images' txt_dir = 'labels' img_files = [f for f in os.listdir(img_dir) if f.endswith('.jpg')] txt_files = [f for f in os.listdir(txt_dir) if f.endswith('.txt')] img_names = set(os.path.splitext(f)[0] for f in img_files) txt_names = set(os.path.splitext(f)[0] for f in txt_files) print('图片数:', len(img_names)) print('标注数:', len(txt_names)) print('缺标注的图片:', len(img_names - txt_names)) print('缺图片的标注:', len(txt_names - img_names))

把缺标注的图片从训练集里剔除,或者用相邻帧的标注手动补上。稳妥做法是剔除,一百多张图片对整体训练影响不大,手动补标反而容易出错。

5.2 redspider-infection类别几乎检不出来

现象:训练完成后mAP整体还行,但单独看redspider-infection类别的AP值非常低,推理时大量漏检,有的图片里红蜘蛛明明一片一片的,模型就是检测不到。

原因:红蜘蛛感染的视觉特征是叶片表面出现许多微小的黄白色斑点,单只红蜘蛛本身只有零点几毫米,在640×640分辨率下,每只蜘蛛可能只有几个像素大小。目标的像素面积太小,属于典型的小目标检测难题,而且标注时如果只有整片叶子的框而没有逐个小斑点框,模型学到的特征不够精细。

解决:有几个方向可以选。第一,把imgsz从640提高到1280或1536,小目标在放大后更容易被检测头部捕捉到,但这会增加显存占用,对应的batch要减半;第二,YOLOv8里可以对小目标针对性开启数据增强,把mosaic和copy_paste参数调大来增加小目标的多样性;第三,考虑修改损失权重,给redspider-infection这个类别更高的cls_loss权重,强迫模型更关注它。我实际测试下来,最直接有效的还是提高imgsz,1280下这个类别的AP能提升十几个点。

5.3 blight-disease和mosaic-virus两个类别互相误检

现象:推理结果里,把mosaic-virus误检成blight-disease的情况很常见,反之也有,混淆矩阵里这两个类别的交叉项特别多。

原因:叶片感染晚疫病后会出现水渍状暗褐色病斑,花叶病毒病则表现为叶片黄绿相间的花叶斑纹。二者在低分辨率下颜色分布有一定相似性,特别是早期病斑都不明显时,标注者自己都很难准确区分,导致部分训练图片的标注本身就是错的。

解决:这个问题的根源在数据标注质量。建议拿到数据集后先做一次标注可视化抽查,把标注框和类别标签画在图片上逐张看,确认明显标注错误的图片再处理。处理方式不是改标注(容易引入新错误),而是把这些模棱两可的图片单独分出来,留到做了充分的数据增广后再加入训练。同时可以考虑把类别名称改为更明确的表示,不直接叫“blight”而是“late-blight-severe”,减少语义歧义。

5.4 训练过程中loss值周期性跳动、不收敛

现象:训练到第30轮左右,loss不是平稳下降,而是每隔几轮就突然跳高,然后又降回去,验证集指标也跟着波动。

原因:这种情况多半是数据加载顺序导致的。如果batch里某个batch恰好包含大量标注质量差的图片或者极端光照条件下的图片,梯度就会异常增大。还有一个常见原因是数据增强的超参数设置过于激进,比如hsv_h、hsv_s的增强幅度太大,番茄叶片的颜色被改得失真,模型学到的特征不稳定。

解决:把数据增强参数往回收,YOLOv8默认的hsv_h=0.015、hsv_s=0.7对病害检测来说可能偏大。我建议改成hsv_h=0.01, hsv_s=0.4,同时把degrees=0关掉旋转增强,因为番茄叶片的病斑特征受角度影响较大,旋转增强容易把特征学歪。另外可以设置随机种子:python train.py --seed 42,保证每次实验可复现,方便判断问题是数据还是模型引起的。

6. 训练前跑一遍数据自检脚本:十分钟排查完所有潜在问题

在正式训练之前,花十分钟跑一遍数据自检脚本,能避免训练过程中断在莫名其妙的位置。这是我处理这类数据集时的一个习惯做法。自检脚本覆盖四个方面:数量匹配、格式校验、坐标范围、类别边界。

import os import numpy as np from PIL import Image img_dir = 'images/train' label_dir = 'labels/train' class_names = ['blight-disease', 'mosaic-virus', 'redspider-infection'] issues = [] for img_file in os.listdir(img_dir): if not img_file.endswith('.jpg'): continue img_stem = os.path.splitext(img_file)[0] label_file = os.path.join(label_dir, img_stem + '.txt') if not os.path.exists(label_file): issues.append(f'Missing label: {img_file}') continue img_path = os.path.join(img_dir, img_file) img = Image.open(img_path) img_w, img_h = img.size with open(label_file, 'r') as f: for line_num, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: issues.append(f'{img_file}:{line_num} - expected 5 fields, got {len(parts)}') continue cls_id = int(parts[0]) if cls_id < 0 or cls_id >= len(class_names): issues.append(f'{img_file}:{line_num} - class id {cls_id} out of range') x_center, y_center, width, height = map(float, parts[1:]) if not (0 <= x_center <= 1 and 0 <= y_center <= 1): issues.append(f'{img_file}:{line_num} - x_center out of [0,1]: {x_center}') if not (0 <= width <= 1 and 0 <= height <= 1): issues.append(f'{img_file}:{line_num} - width/height out of [0,1]: {width},{height}') if width <= 0 or height <= 0: issues.append(f'{img_file}:{line_num} - non-positive width/height') print(f'Checked {len(os.listdir(img_dir))} images') if issues: print(f'Found {len(issues)} issues:') for issue in issues[:50]: print(issue) else: print('All checks passed')

这个脚本的逻辑按顺序做四件事:检查每个jpg文件是否都有对应的txt标注文件;检查txt每行的字段数量是否为5(类别ID加四个坐标);检查类别ID是否在合法范围内;检查归一化坐标是否在0到1之间,以及框宽高是否为正数。这四个检查覆盖了训练时最容易报错的几类数据问题。

跑完自检后再做一次可视化标注验证,用OpenCV把边界框画到图片上:

import cv2 import os img_dir = 'images/val' label_dir = 'labels/val' class_names = ['blight-disease', 'mosaic-virus', 'redspider-infection'] colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)] for img_file in os.listdir(img_dir)[:20]: img_stem = os.path.splitext(img_file)[0] label_file = os.path.join(label_dir, img_stem + '.txt') if not os.path.exists(label_file): continue img = cv2.imread(os.path.join(img_dir, img_file)) h, w = img.shape[:2] with open(label_file, 'r') as f: for line in f: parts = line.strip().split() cls_id = int(parts[0]) x_center, y_center, box_w, box_h = map(float, parts[1:]) x1 = int((x_center - box_w / 2) * w) y1 = int((y_center - box_h / 2) * h) x2 = int((x_center + box_w / 2) * w) y2 = int((y_center + box_h / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls_id], 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls_id], 1) cv2.imwrite(f'vis_{img_file}', img)

代码说明:这个脚本把YOLO归一化坐标还原成像素坐标的完整过程是,x_center * w得到中心点像素x坐标,再± box_w / 2 * w得到左右边界,y方向同理。这里注意box_w是归一化宽度,必须乘以图片宽度w才能还原成像素值。

可视化输出的图片里,红框是blight-disease、绿框是mosaic-virus、蓝框是redspider-infection。重点检查三件事:框是否贴合病斑实际范围;是否存在明显的大面积背景被框进去的情况;同一张图片里是否存在不同类别的框重叠面积过大。

我自己的习惯是,每次拿到新的数据集先跑这两段脚本,全部通过后再进入训练环节。从那以后我处理任何数据集都强制走一遍这个自检流程,还真靠它提前拦下来过不少问题——最夸张的一次是数据集里混进来几十张用错了标注文件的图片,类别ID越界导致训练直接崩溃。自检脚本十分钟能跑完的事,就不要让训练过程用三小时去试错。希望帮到你。

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

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

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

立即咨询