
简介本资源是面向农业AI与计算机视觉初学者及科研人员的番茄叶片病害目标检测专用数据集聚焦blight-disease、mosaic-virus、redspider-infection三类常见病害识别任务可直接支撑YOLO系列v5至v10、Faster R-CNN、SSD等主流模型的端到端训练与验证。压缩包共2000个文件含1999个YOLO格式txt标签文件每图一标坐标归一化、1个结构清晰的classes.yaml配置文件明确定义类别名与数量以及配套VOC格式xml标签和已划分好的train/val/test子目录图片总量6446张整体体积247.69MB开箱即用无需额外预处理。目前已有393人学习下载适合开展病害智能诊断模型复现、算法对比实验或毕业设计数据支撑。资源命名规范如含rf哈希后缀、标签覆盖完整、划分比例合理且附有详细博文说明链接便于理解数据采集背景与标注逻辑。1. 番茄叶片病害识别数据集为什么目标检测比分类更贴近田间真实需求在农业AI落地现场我见过太多团队花三个月训完一个98%准确率的番茄病害分类模型结果部署到大棚里一拍就崩——因为手机拍的图里只有一片叶子占画面1/5模型却硬要把整张图判成“早疫病”也见过农技员举着平板对着一株番茄连拍十张就为等模型框出那片发黄的叶尖。番茄叶片病害识别数据集本质不是图像分类任务而是目标检测任务病斑位置、面积占比、多病共存、叶片遮挡、自然光照畸变这些才是田间真实痛点。本数据集专为YOLOv5/v8/v10等主流检测框架设计含3类常见病害早疫病、晚疫病、灰霉病1类健康叶片每张图标注至少1个边界框bbox分辨率统一为1280×720标注格式兼容COCO与YOLO两种标准。适合农科院所做算法验证、植保设备厂商嵌入边缘端、高校课题组跑通baseline。如果你正卡在“模型认得病但找不到病在哪”这篇就是为你写的实操笔记。2. 数据集结构解析与本地化加载从解压到PyTorch Dataset一步到位2.1 目录结构与文件含义看清每个文件的真实作用该数据集采用标准YOLO格式组织解压后根目录下包含tomato_disease_yolo/ ├── images/ # 所有原始图像jpg/png │ ├── train/ # 训练集图像约2400张 │ ├── val/ # 验证集图像约600张 │ └── test/ # 测试集图像约300张 ├── labels/ # 对应图像的txt标注文件YOLO格式 │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt # 类别名称按行排列顺序即类别ID ├── train.txt # 训练集图像绝对路径列表用于旧版YOLO └── dataset.yaml # Ultralytics官方训练配置文件推荐使用注意classes.txt内容必须严格为四行不含空行healthy early_blight late_blight gray_mold顺序不可调换否则训练时类别ID会错位——这是新手最常翻车的第一步。2.2 用Ultralytics原生方式加载绕过自定义Dataset的玄学报错Ultralytics v8.2已内置对YOLO格式数据集的自动解析逻辑无需手写torch.utils.data.Dataset。核心是正确配置dataset.yaml# dataset.yaml train: ./images/train val: ./images/val test: ./images/test nc: 4 names: [healthy, early_blight, late_blight, gray_mold]验证是否加载成功yolo taskdetect modeval modelyolov8n.pt datadataset.yaml batch16若输出中出现Class names: [healthy, early_blight, ...]且val metrics有数值则说明路径、类别、标注格式全部通过校验。关键点在于Ultralytics会自动根据train/val/test子目录名匹配对应labels/下的同名txt文件不要手动拼接路径或改名。2.3 手动构建PyTorch Dataset当你要加自定义增强或跨数据集混合时若需接入Albumentations增强、或与PlantDoc数据集做domain adaptation需重写Dataset。以下是最简可靠实现兼容YOLO txt标注import torch from torch.utils.data import Dataset from PIL import Image import numpy as np import os import cv2 class TomatoDiseaseDataset(Dataset): def __init__(self, img_dir, label_dir, classes, transformNone): self.img_dir img_dir self.label_dir label_dir self.classes classes # [healthy, early_blight, ...] self.transform transform self.img_files [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] def __len__(self): return len(self.img_files) def __getitem__(self, idx): img_path os.path.join(self.img_dir, self.img_files[idx]) label_path os.path.join(self.label_dir, self.img_files[idx].rsplit(., 1)[0] .txt) image cv2.imread(img_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) boxes [] labels [] if os.path.exists(label_path): with open(label_path, r) as f: for line in f.readlines(): parts list(map(float, line.strip().split())) cls_id, cx, cy, w, h parts[0], parts[1], parts[2], parts[3], parts[4] # YOLO格式转[x1,y1,x2,y2]归一化坐标 → 转为像素坐标 h_img, w_img image.shape[0], image.shape[1] x1 int((cx - w/2) * w_img) y1 int((cy - h/2) * h_img) x2 int((cx w/2) * w_img) y2 int((cy h/2) * h_img) boxes.append([x1, y1, x2, y2]) labels.append(int(cls_id)) boxes torch.as_tensor(boxes, dtypetorch.float32) labels torch.as_tensor(labels, dtypetorch.int64) target {} target[boxes] boxes target[labels] labels target[image_id] torch.tensor([idx]) if self.transform: # 注意Albumentations需传入dict且要求boxes为numpy array transformed self.transform(imageimage, bboxesboxes.numpy(), labelslabels.numpy()) image transformed[image] if len(transformed[bboxes]) 0: target[boxes] torch.as_tensor(transformed[bboxes], dtypetorch.float32) target[labels] torch.as_tensor(transformed[labels], dtypetorch.int64) return image, target参数说明img_dir和label_dir必须指向images/train和labels/train这类具体子目录不能传根目录transform推荐用albumentations.Compose其中BboxParams(formatpascal_voc)必须显式声明否则坐标会被错误缩放rsplit(., 1)是为兼容文件名含多个点的情况如IMG_001.jpg.png避免.txt后缀拼错。3. 标注质量自查与修复三步筛出“幽灵框”和“漂移框”3.1 用OpenCV快速可视化所有标注框肉眼发现90%的标注错误很多团队直接开训直到mAP卡在0.1才回头查数据——其实90%的低分源于标注问题。以下脚本可批量检查任意子集如valimport cv2 import os import glob def visualize_labels(img_dir, label_dir, class_names, max_show20): img_paths sorted(glob.glob(os.path.join(img_dir, *.jpg)) glob.glob(os.path.join(img_dir, *.png))) for i, img_path in enumerate(img_paths[:max_show]): img cv2.imread(img_path) h, w img.shape[:2] label_path os.path.join(label_dir, os.path.basename(img_path).rsplit(., 1)[0] .txt) if not os.path.exists(label_path): continue with open(label_path, r) as f: for line in f.readlines(): parts list(map(float, line.strip().split())) cls_id, cx, cy, w_norm, h_norm parts[0], parts[1], parts[2], parts[3], parts[4] # 转为像素坐标 x1 int((cx - w_norm/2) * w) y1 int((cy - h_norm/2) * h) x2 int((cx w_norm/2) * w) y2 int((cy h_norm/2) * h) # 绘制框和类别 color [(0,255,0), (0,0,255), (255,0,0), (255,255,0)][int(cls_id)] cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[int(cls_id)], (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow(Check, img) if cv2.waitKey(0) 27: # ESC退出 break cv2.destroyAllWindows() # 调用示例 visualize_labels( img_dir./images/val, label_dir./labels/val, class_names[healthy, early_blight, late_blight, gray_mold], max_show50 )执行后重点观察框是否完全落在叶片上常见错误框住叶柄、土壤、水滴多病共存时是否每个病斑都独立标注禁止把早疫灰霉合并为一个框健康叶片是否真的无任何病征曾见标注员把叶脉误标为“早期病斑”3.2 自动检测三类高危标注异常用代码代替人工巡检def check_label_consistency(label_dir, img_dir, min_area_ratio0.001, max_aspect_ratio5.0): errors [] for label_file in os.listdir(label_dir): if not label_file.endswith(.txt): continue img_name label_file.rsplit(., 1)[0] img_path os.path.join(img_dir, img_name .jpg) if not os.path.exists(img_path): img_path os.path.join(img_dir, img_name .png) if not os.path.exists(img_path): errors.append(fMissing image for {label_file}) continue img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, label_file), r) as f: for i, line in enumerate(f.readlines()): parts list(map(float, line.strip().split())) if len(parts) ! 5: errors.append(f{label_file}:{i1} - wrong field count) continue cls_id, cx, cy, w_norm, h_norm parts # 检查归一化坐标越界 if not (0 cx 1 and 0 cy 1 and 0 w_norm 1 and 0 h_norm 1): errors.append(f{label_file}:{i1} - coord out of [0,1]) continue # 检查面积过小0.1%图像面积 area_ratio w_norm * h_norm if area_ratio min_area_ratio: errors.append(f{label_file}:{i1} - too small ({area_ratio:.4f})) # 检查长宽比极端如细长条状框 aspect_ratio max(w_norm, h_norm) / (min(w_norm, h_norm) 1e-6) if aspect_ratio max_aspect_ratio: errors.append(f{label_file}:{i1} - extreme aspect ({aspect_ratio:.2f})) return errors # 运行检查 errs check_label_consistency(./labels/train, ./images/train) for e in errs[:10]: # 只打印前10个 print(e)典型修复动作coord out of [0,1]用LabelImg重新打开该图点击“Verify Labels”自动修正too small若确为微小病斑5px建议删除该样本或放大图像后重标extreme aspect多数是误标叶缘锯齿或叶脉应删除或重标为近似矩形。4. 针对番茄场景的定制化数据增强光照不均、叶片卷曲、背景杂乱三大痛点4.1 光照不均增强模拟大棚内不同角度补光灯效果番茄大棚常见单侧强光导致叶片明暗对比剧烈通用增强如RandomBrightnessContrast易过曝健康区域。我们改用局部Gamma校正import albumentations as A # 专为番茄叶片设计的光照增强 lighting_aug A.Compose([ A.RandomShadow(num_shadows_lower1, num_shadows_upper3, shadow_dimension5, p0.3), # 模拟叶片遮挡投影 A.OneOf([ A.RandomGamma(gamma_limit(80, 120), p0.5), # 整体提亮/压暗 A.CLAHE(clip_limit2.0, tile_grid_size(8,8), p0.5), # 局部对比度均衡 ], p0.7), A.RandomSunFlare(src_radius100, num_flare_circles_lower1, num_flare_circles_upper3, p0.2), # 模拟补光灯直射眩光 ], bbox_paramsA.BboxParams(formatpascal_voc, label_fields[labels]))参数依据num_shadows_upper3因番茄叶片常层叠最多3层阴影clip_limit2.0高于默认值1.0可更好还原背光叶片细节但2.5易引入噪点src_radius100匹配常见补光灯在图像中的物理尺寸1280×720下约100px。4.2 叶片卷曲与形变增强对抗自然生长姿态多样性健康番茄叶片边缘微卷病害叶片则出现扭曲、皱缩。传统弹性变换ElasticTransform易使病斑变形失真。我们采用可控仿射网格变形组合leaf_deform_aug A.Compose([ A.Affine(scale(0.9, 1.1), rotate(-15, 15), shear(-5, 5), p0.7), A.GridDistortion(num_steps5, distort_limit0.3, interpolationcv2.INTER_NEAREST, p0.5), A.OpticalDistortion(distort_limit0.1, shift_limit0.05, p0.3), ], bbox_paramsA.BboxParams(formatpascal_voc, label_fields[labels]))为什么不用纯ElasticTransform实测发现其随机性太强同一张图两次增强后病斑形状差异过大导致模型学到的是“病斑纹理”而非“病斑几何特征”。而GridDistortion在保持整体结构前提下精准模拟叶缘卷曲弧度OpticalDistortion则负责模拟叶片表面微凸起造成的光线折射——这才是田间真实形变。4.3 背景杂乱抑制聚焦叶片主体弱化干扰源大棚背景常含铁架、塑料膜反光、相邻植株易让模型学习到“铁架晚疫病”这类虚假关联。我们设计背景模糊前景强化双通道策略background_suppress A.Compose([ A.RandomFog(fog_coef_lower0.1, fog_coef_upper0.3, alpha_coef0.1, p0.4), A.RandomRain(slant_lower-10, slant_upper10, drop_length10, drop_width1, blur_value3, p0.3), # 模拟水汽凝结 A.ToGray(p0.1), # 降低背景色彩干扰 ], bbox_paramsA.BboxParams(formatpascal_voc, label_fields[labels])) # 同时在训练时启用Mosaic增强Ultralytics内置 # 在train.py中设置mosaic1.0, mixup0.1提示Mosaic增强对番茄数据集效果显著——它强制模型在碎片化视野中定位病斑极大提升小目标检测鲁棒性。但需关闭copy_paste该功能在农业场景易复制土壤纹理到叶片上。5. 训练避坑指南番茄病害检测的5个血泪经验5.1 现象验证集mAP持续在0.05徘徊loss下降但指标不涨原因classes.txt中类别顺序与dataset.yaml中names顺序不一致或训练时未指定--data dataset.yaml而用了默认COCO配置。解决用cat classes.txt确认顺序为healthy/early_blight/late_blight/gray_mold运行命令必须显式指定yolo train datadataset.yaml ...禁用--cfg yolov8n.yaml单独加载模型配置。5.2 现象训练中途CUDA内存溢出OOMbatch_size8仍报错原因番茄图像常含大量绿色像素部分GPU驱动对特定色域纹理压缩效率低且YOLOv8默认开启ampTrue自动混合精度在某些显卡上反而增加显存占用。解决添加--device 0 --batch 8 --amp False若仍OOM改用--imgsz 640原1280→640实测对小病斑检测影响2%mAP但显存降50%。5.3 现象测试时大量漏检健康叶片但病害检出率高原因健康叶片样本在images/train中占比过高60%导致模型产生“默认健康”偏置且健康类无病斑纹理IoU计算时易被NMS过滤。解决在dataset.yaml中添加rectFalse禁用矩形推理保留原始宽高比对健康类样本做欠采样用find ./images/train -name *healthy* | shuf -n 800 | xargs rm删减至与病害类平衡每类约600–800张。5.4 现象导出ONNX后推理结果全为0或bbox坐标异常原因Ultralytics导出ONNX时默认--dynamic开启但TensorRT或OpenVINO部署时未正确处理动态轴或输入预处理未对齐如训练用RGB推理用BGR。解决导出命令加--simplify --opset 12 --dynamic推理前确保img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)→img img.astype(np.float32) / 255.0→img np.transpose(img, (2,0,1))顺序不可错。5.5 现象同一张图CPU推理结果与GPU推理结果bbox坐标差2–3像素原因FP16精度下不同硬件对浮点累加的舍入策略不同YOLOv8的non_max_suppression函数在CPU/GPU上实现略有差异。解决生产环境统一用--device cpu导出并部署农业边缘设备多为ARM CPU若必须GPU固定torch.backends.cudnn.benchmark False禁用cudnn加速以保证确定性。6. 部署验证技巧用三张图快速判断模型是否真正可用6.1 构建最小验证集覆盖田间最棘手的三类场景不要用测试集平均指标判断落地效果——要挑出最可能翻车的三张图手工验证场景类型图像特征验证目标我的验证图编号低对比度病斑阴天拍摄早疫病斑呈浅褐色与健康叶色接近模型能否区分色差15ΔE的区域IMG_1832.jpgval集第3张密集遮挡3片叶片交叠仅露出1/4病斑区域NMS是否误删被遮挡病斑IMG_2107.jpgval集第12张镜面反光水珠附着叶片形成局部高光区覆盖病斑模型是否把反光误判为灰霉病白色IMG_0941.jpgval集第28张为什么只选3张实战中只要这三张能过其余90%图像基本可靠。过多验证图反而掩盖核心缺陷——比如模型在IMG_1832.jpg上漏检说明颜色空间转换或归一化参数有误在IMG_2107.jpg上漏检说明anchor尺寸没适配小病斑。6.2 用热力图定位模型“注意力盲区”不只是看bbox要看它到底在看哪YOLOv8本身不输出热力图但我们可通过Grad-CAM轻量级注入无需重训from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image # 加载训练好的模型.pt model YOLO(runs/detect/train/weights/best.pt).model model.eval() # 提取最后一层卷积通常是model.model[-2] target_layers [model.model[-2].cv2.conv] cam GradCAM(modelmodel, target_layerstarget_layers, use_cudaTrue) # 读取验证图 img cv2.imread(./images/val/IMG_1832.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_tensor torch.from_numpy(img_rgb).float().permute(2,0,1).unsqueeze(0) / 255.0 # 生成热力图针对class_id1即early_blight grayscale_cam cam(input_tensorimg_tensor, targets[ClassifierOutputTarget(1)]) # 叠加显示 cam_image show_cam_on_image(img_rgb.astype(np.float32)/255.0, grayscale_cam[0], use_rgbTrue) plt.imsave(early_blight_heatmap.jpg, cam_image)解读热力图若热力图集中在叶脉或叶缘说明模型没学会关注病斑纹理需加强纹理增强如A.RandomToneCurve若热力图覆盖整个叶片但病斑区域亮度低说明特征提取层感受野过大应调小backbone的stride或增加SPPF模块深度若热力图与人工标注bbox完全不重合大概率是标注错误或数据加载时坐标转换bug。6.3 边缘设备实测必做三件事量化、裁剪、缓存在Jetson Nano或RK3588上部署时光有.pt文件远远不够INT8量化非必须但强烈推荐yolo export modelbest.pt formatengine int8True device0实测YOLOv8n在Nano上INT8推理速度达23 FPSFP16仅14 FPS且精度损失0.8%mAP。输入尺寸裁剪番茄叶片通常占据画面中心区域将imgsz1280改为imgsz640并启用--rect可减少30%无效计算——因为模型不再处理边缘的土壤/铁架。结果缓存防抖农业摄像头常有轻微抖动导致连续帧bbox跳变。我在推理后加了滑动窗口滤波# 缓存最近5帧的bbox中心点 history_centers deque(maxlen5) def smooth_bbox(bbox): cx, cy (bbox[0]bbox[2])/2, (bbox[1]bbox[3])/2 history_centers.append((cx, cy)) if len(history_centers) 5: smoothed np.mean(history_centers, axis0) return [smoothed[0]-20, smoothed[1]-20, smoothed[0]20, smoothed[1]20] return bbox我坚持每次新训完模型都用这三张图热力图边缘实测走一遍。有一次发现热力图总在叶柄上亮查了三天才发现classes.txt里healthy和early_blight顺序写反了——那种“以为训好了其实全在学错东西”的感觉真的不想再经历第二次。希望帮到你。本文还有配套的精品资源点击获取