番茄病害识别数据集:2083张实拍图+YOLO/VOC双格式
2026/9/23 18:26:10 网站建设 项目流程

简介:本资源是一套专用于番茄病害智能识别的目标检测数据集,面向农业AI、计算机视觉初学者及深度学习模型开发者,解决农作物病害图像精准定位与分类的训练数据短缺问题。数据集涵盖斑萎病毒、早疫病、健康番茄、番茄赤霉病四类目标,共2083张高质量图像,已按YOLO与VOC双格式组织,包含1999个txt标签文件(对应YOLO格式)、1个类别定义yaml配置文件,以及配套xml标注,全部图片与标签均已划分训练集、验证集和测试集,可直接投入YOLOv5至YOLOv10、Faster R-CNN、SSD等主流检测模型训练。资源包共2000个文件,总大小139.11MB,结构规范、开箱即用。目前已有368人学习下载,适合开展农业病害检测项目实践、课程实验或科研baseline复现,显著降低数据预处理门槛。

1. 番茄病害识别数据集:2083张实拍图+YOLO/VOC双格式+4类细粒度标注,专治“模型认不出烂番茄”的临床焦虑

你是不是也遇到过这种场景:YOLOv8训练完,验证集mAP飙到85%,一上真实大棚图像——番茄叶子全被标成“早疫病”,连健康植株都逃不过;或者Faster R-CNN在测试集上框得挺准,但把“斑萎病毒”和“番茄赤霉病”反复混淆,最后发现不是模型不行,是数据集里两类病斑样本光照差异太大、标注边界模糊、甚至存在同图多标签错位。这个番茄病害识别数据集,就是为解决这类农业视觉落地最后一公里的标注失真问题而生的。它不是从公开图库爬取的“网图拼凑体”,而是基于真实温室与露天田块采集的2083张高分辨率JPG图像,覆盖晨雾、正午强光、阴天散射光三种典型农情光照条件;4个类别(斑萎病毒、早疫病、健康番茄、番茄赤霉病)全部由农科院植保专家逐图复核,每张图的txt(YOLO格式)与xml(PASCAL VOC格式)标签严格对齐,且已按7:2:1完成train/val/test划分——你解压即训,不用再花三天写脚本切分、校验、重映射类别ID。适合正在做智慧农业项目交付的算法工程师、高校农林AI方向研究生,以及需要快速验证小目标检测(病斑直径常<15px)鲁棒性的CV研究员。


2. 数据结构解析与YOLO训练直通配置:yaml定义、目录树、类别ID映射逻辑

2.1 文件组织与核心文件清单:看清“2083张图”如何被结构化管理

该数据集采用工业级目标检测数据集标准布局,解压后根目录下包含以下关键子目录:

tomato_disease_yolo/ ├── images/ # 所有原始JPG图像 │ ├── train/ # 训练集图片(1458张) │ ├── val/ # 验证集图片(417张) │ └── test/ # 测试集图片(208张) ├── labels/ # YOLO格式标签(.txt),与images/目录严格同名同级 │ ├── train/ │ ├── val/ │ └── test/ ├── annotations/ # PASCAL VOC格式标签(.xml),含完整坐标与类别语义 │ ├── train/ │ ├── val/ │ └── test/ ├── tomato_disease.yaml # YOLO系列训练必需的配置文件(关键!) └── README.md # 标注规范说明与采集参数(如相机型号、白平衡设置)

提示tomato_disease.yaml是整个训练流程的“宪法”,它不仅声明类别名称,更隐含了数据路径、类别ID顺序、颜色映射等元信息。很多初学者直接改names字段却忽略train/val路径指向,导致训练时报错FileNotFoundError: No such file or directory: 'train/images/xxx.jpg'——根源在于YOLO默认读取的是相对路径,而你的数据集可能放在/data/tomato/下,需同步修改yaml中的路径。

2.2tomato_disease.yaml深度拆解:为什么类别顺序不能乱、路径必须绝对化?

这是该数据集提供的tomato_disease.yaml原始内容(已脱敏处理,保留关键结构):

# tomato_disease.yaml train: ../images/train # 注意:此处为相对路径,实际使用时需根据你的项目结构调整 val: ../images/val test: ../images/test nc: 4 # number of classes names: ['tomato_spotted_wilt_virus', 'early_blight', 'healthy_tomato', 'fusarium_oxysporum']

直接使用此yaml大概率翻车。原因在于:YOLOv5/v8/v9默认工作目录是yolov8/ultralytics/,而你的数据集很可能放在/home/user/datasets/tomato_disease_yolo/。此时../images/train会向上找两级,路径必然错误。

我一般会强制重写为绝对路径(以Ubuntu为例):

train: /home/user/datasets/tomato_disease_yolo/images/train val: /home/user/datasets/tomato_disease_yolo/images/val test: /home/user/datasets/tomato_disease_yolo/images/test nc: 4 names: ['tomato_spotted_wilt_virus', 'early_blight', 'healthy_tomato', 'fusarium_oxysporum']

参数说明

  • nc: 4:必须与names列表长度严格一致,否则模型加载权重时会报AssertionError: nc mismatch
  • names顺序决定类别ID映射:tomato_spotted_wilt_virus→ ID=0,early_blight→ ID=1,以此类推。所有.txt标签中的第一个数字必须是0~3之间的整数,否则YOLO解析器会静默跳过该行;
  • test字段虽非YOLOv8训练必需,但强烈建议保留——它让你能用yolo predict命令一键跑通测试集,避免训练完才发现测试路径没配。

2.3 YOLO格式标签(.txt)与VOC格式(.xml)的双向校验方法

每张图对应两个标签文件:xxx.jpgxxx.txt(YOLO) +xxx.xml(VOC)。二者必须几何一致,否则模型学到的是“错位知识”。我们用一个Python脚本做自动化校验:

# verify_label_consistency.py import xml.etree.ElementTree as ET from pathlib import Path def yolo_to_voc_bbox(yolo_line, img_w, img_h): """将YOLO格式归一化坐标转为VOC像素坐标""" parts = yolo_line.strip().split() if len(parts) < 5: return None cls_id, cx_norm, cy_norm, w_norm, h_norm = map(float, parts[:5]) cx, cy, w, h = cx_norm * img_w, cy_norm * img_h, w_norm * img_w, h_norm * img_h xmin = int(cx - w/2) ymin = int(cy - h/2) xmax = int(cx + w/2) ymax = int(cy + h/2) return (int(cls_id), xmin, ymin, xmax, ymax) def parse_xml(xml_path): """解析XML获取VOC坐标""" tree = ET.parse(xml_path) root = tree.getroot() bboxes = [] for obj in root.findall('object'): cls_name = obj.find('name').text bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) bboxes.append((cls_name, xmin, ymin, xmax, ymax)) return bboxes # 示例:校验一张图 img_path = Path("images/train/DSC03632_JPG.jpg") txt_path = Path("labels/train/DSC03632_JPG.txt") xml_path = Path("annotations/train/DSC03632_JPG.xml") # 读取YOLO标签 with open(txt_path) as f: yolo_lines = f.readlines() # 获取图像尺寸(真实像素) from PIL import Image img = Image.open(img_path) w, h = img.size # 转换YOLO坐标 yolo_boxes = [yolo_to_voc_bbox(line, w, h) for line in yolo_lines if yolo_to_voc_bbox(line, w, h)] # 解析XML voc_boxes = parse_xml(xml_path) print(f"YOLO detected {len(yolo_boxes)} boxes, VOC has {len(voc_boxes)} boxes") # 后续可比对坐标差值,阈值设为5px内视为一致

逻辑说明:该脚本核心是yolo_to_voc_bbox()函数,它执行YOLO归一化坐标的逆变换。YOLO的(cx,cy,w,h)是相对于图像宽高的比例值(0~1),而VOC是绝对像素值。若两者转换后坐标差值>10px,说明标注存在系统性偏移——常见于标注工具导出bug或图像预处理时未同步缩放标签。我曾在一个类似数据集中发现VOC标注框整体右偏12px,根源是采集时相机开启了数码变焦但XML导出未补偿。


3. VOC转YOLO实战:当你的模型只认txt,而手头只有xml

3.1 为什么必须自己动手转?现有工具的三大失效场景

虽然网上有xml_to_txt.py脚本,但直接套用这个番茄数据集大概率失败,原因有三:

  1. 类别名映射断裂:多数脚本硬编码{'cat':0, 'dog':1},而本数据集类别名含下划线与长单词(如fusarium_oxysporum),需精准匹配names列表索引;
  2. 坐标越界不修复:VOC标注中常出现xmin=0xmax=width,YOLO要求0 < cx < 10 < w < 1,直接转换会导致cx=0.0被截断为0,训练时loss爆炸;
  3. 多目标漏检:部分XML中<object>节点嵌套在<group>下,通用脚本只遍历一级object,导致病斑簇标注丢失。

因此,我重写了适配本数据集的转换器,核心逻辑如下:

# voc2yolo_tomato.py import xml.etree.ElementTree as ET from pathlib import Path import os # 类别名到ID的映射(必须与yaml中names顺序一致) CLASS_NAMES = [ 'tomato_spotted_wilt_virus', 'early_blight', 'healthy_tomato', 'fusarium_oxysporum' ] NAME_TO_ID = {name: i for i, name in enumerate(CLASS_NAMES)} def convert_voc_to_yolo(xml_path, img_w, img_h, output_txt_path): tree = ET.parse(xml_path) root = tree.getroot() with open(output_txt_path, 'w') as f: for obj in root.findall('.//object'): # 使用'.//object'确保捕获嵌套object name = obj.find('name').text.strip() if name not in NAME_TO_ID: print(f"Warning: unknown class '{name}' in {xml_path}") continue cls_id = NAME_TO_ID[name] bbox = obj.find('bndbox') if bbox is None: continue try: xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) except (TypeError, ValueError): continue # 修复越界:clamp到[1, img_w-1]避免cx=0或w=0 xmin = max(1, min(xmin, img_w - 1)) ymin = max(1, min(ymin, img_h - 1)) xmax = max(xmin + 1, min(xmax, img_w - 1)) ymax = max(ymin + 1, min(ymax, img_h - 1)) # 转YOLO格式 x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 再次clamp确保归一化后合法 x_center = max(0.001, min(0.999, x_center)) y_center = max(0.001, min(0.999, y_center)) width = max(0.001, min(0.999, width)) height = max(0.001, min(0.999, height)) f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n") # 批量转换示例 voc_dir = Path("annotations/train") img_dir = Path("images/train") txt_dir = Path("labels/train") txt_dir.mkdir(exist_ok=True) for xml_file in voc_dir.glob("*.xml"): img_name = xml_file.stem + ".jpg" img_path = img_dir / img_name if not img_path.exists(): print(f"Missing image {img_path}") continue # 获取图像尺寸 from PIL import Image with Image.open(img_path) as img: w, h = img.size txt_path = txt_dir / f"{xml_file.stem}.txt" convert_voc_to_yolo(xml_file, w, h, txt_path)

参数说明

  • max(1, min(...)):强制坐标不贴边,避免YOLO计算cx=0导致梯度异常;
  • :.6f:保留6位小数,YOLOv8内部解析精度要求;
  • .//object:XPath语法,深度优先搜索所有object节点,解决嵌套标注漏检。

3.2 从VOC到YOLO的不可逆损失:哪些信息被丢弃了?

转换不是无损的。VOC XML中以下信息在YOLO txt中完全消失:

VOC字段是否保留在YOLO txt丢失影响补救建议
<pose>(拍摄姿态)无法建模不同角度病斑形变在数据增强中加入RandomRotate
<truncated>(截断标志)模型对边缘病斑鲁棒性下降训练时启用mosaic=False,单独评估边缘性能
<difficult>(难例标记)难例参与loss计算权重相同ClassBalanceLoss或手动给难例txt加权重字段

血泪经验:我在用该数据集训练YOLOv8s时,发现验证集对“健康番茄”类别召回率仅62%。排查发现VOC中标记为<difficult>的37张健康图(叶片反光严重)全部被转为普通txt,模型将其当作易例学习,导致泛化失效。最终方案是:先运行上述脚本生成基础txt,再用正则匹配<difficult>1</difficult>,对对应txt追加一行# difficult注释,训练时用自定义Dataloader读取该标记并提升loss权重。


4. 避坑指南:番茄病害数据集的5个高发翻车点与现场急救

4.1 现象:训练loss震荡剧烈,100轮后仍不收敛

原因:数据集中存在少量图像尺寸异常(如1280×720混入300×400小图),YOLO默认rect=True进行矩形推理,但训练时augment=True会强制resize到640×640,导致小图被过度拉伸,病斑纹理失真,梯度方向混乱。
解决:运行python utils/general.py --check-dataset tomato_disease.yaml(YOLOv8内置校验),删除images/train/下所有短边<400px的图像,并同步清理对应txt/xml。我删掉了12张,loss曲线立刻平滑。

4.2 现象:验证集mAP@0.5正常,但mAP@0.5:0.95暴跌至12%

原因:番茄赤霉病与早疫病在早期症状高度相似(均为褐色小斑点),但数据集中两类标注边界模糊,VOC的<bndbox>常将相邻病斑合并为一个大框,YOLO txt继承此错误,导致模型学不会细粒度区分。
解决:用labelImg打开问题图像,人工修正XML中重叠病斑的独立<object>节点,再重新运行3.1节转换脚本。重点修正IMG_20221001_085521_jpg.rf.7b25e280e9a0884f573233b267898917.jpg等17张高混淆图。

4.3 现象:yolo predict输出大量低置信度框(0.05~0.15),且集中在叶脉区域

原因:数据集采集时部分图像白平衡未校准,叶脉在RGB通道呈现与病斑近似的灰度值,YOLO底层卷积误提取叶脉纹理为病斑特征。
解决:在train.py中插入HSV空间增强:transform = A.Compose([A.RGBShift(r_shift_limit=10, g_shift_limit=10, b_shift_limit=10, p=0.5), A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, val_shift_limit=10, p=0.5)]),抑制通道偏移干扰。

4.4 现象:测试集出现“健康番茄”被标为“斑萎病毒”的批量误判

原因tomato_spotted_wilt_virus类别在训练集中仅有217张图(占比10.4%),远少于early_blight(723张),模型产生类别偏好。YOLO默认class_weights为1,未做平衡。
解决:计算类别权重:weight = total_samples / (num_classes * class_samples),得[2.4, 0.7, 1.8, 1.1],在train.py中传入--class_weights "[2.4,0.7,1.8,1.1]"

4.5 现象:模型在测试集上对小病斑(<20px)检测率为0

原因:YOLOv8默认neck层P3输出步长为8px,而小病斑在640×640输入下仅占2~3个像素,低于P3感受野下限。
解决:修改models/yolov8.yaml,在backbone末尾添加Conv层输出P2(stride=4),并在head中增加对应分支。实测小病斑召回率从0→68%。


5. 进阶技巧:用Grad-CAM可视化定位“模型到底在看哪里”,揪出标注污染源

5.1 为什么番茄病害场景必须做可解释性分析?

在农业场景中,“模型为什么这么判”比“判得准不准”更重要。例如:当模型将一张健康番茄图判为“早疫病”,是因叶片水渍状反光被误认为病斑?还是背景杂草纹理触发了误检?传统指标(mAP)无法回答。Grad-CAM通过反向传播热力图,直观显示模型决策依据区域,是定位数据集污染、优化标注质量的核心手段。

5.2 YOLOv8+Grad-CAM零代码接入:三步注入热力图生成能力

YOLOv8官方未内置Grad-CAM,但可通过captum库轻量集成。以下是经过生产环境验证的接入方案:

# gradcam_yolov8.py from ultralytics import YOLO from captum.attr import GradCAM from captum.attr import visualization as viz import torch import numpy as np from PIL import Image import cv2 # 1. 加载模型并设为eval模式 model = YOLO("yolov8s.pt") model.model.eval() # 必须!否则BatchNorm层导致热力图噪声 # 2. 定义前向钩子:捕获最后一层特征图 target_layer = model.model.model[-1].cv2[0] # YOLOv8s的head中最后一个Conv层 activations = {} gradients = {} def forward_hook(module, input, output): activations['value'] = output.detach() def backward_hook(module, grad_input, grad_output): gradients['value'] = grad_output[0].detach() target_layer.register_forward_hook(forward_hook) target_layer.register_backward_hook(backward_hook) # 3. 图像预处理(与YOLO训练一致) def preprocess_image(img_path): img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img_tensor = torch.from_numpy(img).float().permute(2,0,1) / 255.0 img_tensor = img_tensor.unsqueeze(0) # 添加batch维度 return img_tensor # 4. 生成热力图 def generate_cam(model, img_tensor, target_class=1): # target_class=1对应early_blight img_tensor.requires_grad = True pred = model(img_tensor)[0] # yolov8返回list,取第一个tensor # 提取预测框中置信度最高的early_blight框 boxes = pred.boxes.xyxy.cpu().numpy() confs = pred.boxes.conf.cpu().numpy() classes = pred.boxes.cls.cpu().numpy() early_blight_boxes = boxes[(classes == target_class) & (confs > 0.5)] if len(early_blight_boxes) == 0: return None # 取置信度最高框的中心点,作为Grad-CAM目标 best_box = early_blight_boxes[np.argmax(confs[classes == target_class])] center_x = int((best_box[0] + best_box[2]) / 2) center_y = int((best_box[1] + best_box[3]) / 2) # 构造目标:让模型强化该位置的early_blight响应 target = torch.zeros_like(pred.boxes.cls) target[0] = 1.0 # 假设第一个预测框为目标 # 反向传播 model.zero_grad() pred.boxes.cls.sum().backward(retain_graph=True) # 简化:对所有类别求和回传 # 计算热力图(简化版,实际用activations/gradients加权平均) weights = torch.mean(gradients['value'], dim=(2,3), keepdim=True) cam = torch.relu(torch.sum(weights * activations['value'], dim=1, keepdim=True)) return cam.squeeze().cpu().numpy() # 执行示例 img_tensor = preprocess_image("images/test/IMG_20221001_085527_jpg.rf.b362b0854ce45701111b74df27bf0316.jpg") cam_map = generate_cam(model, img_tensor) if cam_map is not None: # 叠加热力图到原图 cam_resized = cv2.resize(cam_map, (640,640)) cam_normalized = (cam_resized - cam_resized.min()) / (cam_resized.max() - cam_resized.min() + 1e-8) heatmap = cv2.applyColorMap(np.uint8(255*cam_normalized), cv2.COLORMAP_JET) original = cv2.imread("images/test/IMG_20221001_085527_jpg.rf.b362b0854ce45701111b74df27bf0316.jpg) superimposed = cv2.addWeighted(original, 0.5, heatmap, 0.5, 0) cv2.imwrite("gradcam_result.jpg", superimposed)

关键参数说明

  • target_layer = model.model.model[-1].cv2[0]:YOLOv8s的head中负责分类的卷积层,其输出特征图最能反映类别决策;
  • cv2.COLORMAP_JET:热力图配色,红色区域=模型最关注区域;
  • addWeighted(..., 0.5, 0.5, 0):原图与热力图5:5混合,避免遮盖病斑真实形态。

5.3 用热力图反哺数据集:发现并清洗3类隐蔽污染

运行上述脚本分析100张测试图后,我发现三类需清洗的污染样本:

污染类型热力图表现样本数量清洗操作
背景误激活热力图高亮在图像四角(大棚支架、土壤块)23张labelImg扩展<segmentation>掩膜,排除背景干扰
标注漂移热力图中心与XML标注框中心偏移>15px17张人工重标,确保框紧贴病斑边缘
多病共存混淆单图热力图出现2个以上分离高亮区,但XML只标1个框9张拆分为多个<object>,每个病斑独立标注

**从那以后我每次拿到新农业数据集,都强制走一遍Grad-CAM分析:先抽50张图生成热力图,用OpenCV自动计算热力图质心与标注框中心距离,距离>10px的样本全部打标待复核。这套流程帮我避开了3个交付项目的数据集返工,节省了至少80人时。希望帮到你。

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

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

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

立即咨询