简介土豆病害检测数据集说明文档面向计算机视觉与农业智能化方向的研究者、算法工程师及高校学生用于解决土豆生长、储存过程中的常见病害识别与目标检测训练需求。资源包由 1 个 docx 文档构成整包约 3.71MB便于查阅与归档文档围绕 8908 张图片展开图片与对应标注均为 8908 个覆盖 5 类土豆状态总标注框数达 30854 个。类别包括损伤土豆、缺陷土豆、真菌性病害土豆、正常土豆与萌芽土豆同时提供 Pascal VOC 与 YOLO 两种标注格式VOC 侧以 jpg 与 xml 记录目标框YOLO 侧以 jpg 与 txt 供检测网络直接训练兼顾传统模型与 YOLO 系列模型使用习惯。各类别框数分布清晰例如正常土豆 9868 框、真菌性病害土豆 7674 框、损伤土豆 4850 框等便于按需采样或做样本均衡标注使用 labelImg 完成并配有图片预览与标注示例可帮助读者快速确认图像质量、类别分布与格式细节降低数据准备门槛。目前已有 68 人学习下载适合需要构建土豆病害检测模型、评估训练数据或开展农业 AI 实验的读者参考。1. 拿到这份土豆病害检测数据集为什么先看标注格式再谈模型效果很多人第一次跑马铃薯叶部病害检测看到“土豆病害检测数据集VOCYOLO格式8908张5类别”这份数据第一反应是找模型、调参数结果训练一直掉点翻车后才发现问题出在数据格式和类别分布上。这个标题能给你的东西很具体8908张真实田间图像、VOC和YOLO两套标注、5个病害类别。它能解决的是训练数据从哪来、拿来之后怎么整理的问题适合刚转向目标检测的新手也适合要做病斑计数、田间巡检测产的从业者。读完你会知道如何把这份双格式数据集变成YOLO可训练的结构以及在哪一步最容易踩坑。2. 数据集格式搞清楚VOC和YOLO到底差在哪五类样本为什么这么拍2.1 两个格式的坐标体系左上角右下角 vs 归一化中心点宽高VOC格式的核心是每个图像对应一个同名XML文件里面把整张图的尺寸和每个目标框都写清楚。下面是很常见的一段VOC标注annotation filenameimg_2024_0731_1024.jpg/filename size width1024/width height768/height depth3/depth /size object namelate_blight/name bndbox xmin312/xmin ymin204/ymin xmax587/xmax ymax489/ymax /bndbox /object /annotationsize记录的是整张图的宽高object代表一个目标框name是类别名bndbox里是像素坐标xmin/ymin是左上角xmax/ymax是右下角。这种格式的优点是可读性强适合人工校对绝大多数标注工具导出的也是这种结构缺点是文件多、字段冗余训练框架加载起来不够直接。YOLO格式就不一样了它每个图像对应一个同名TXT文件每行代表一个目标框1 0.439 0.451 0.269 0.3715个字段分别是类别索引、目标框中心点x坐标、中心点y坐标、框宽度、框高度其中坐标全部是相对于原图宽高的归一化值范围在0到1之间。拿上面的XML数据算一下W1024H768xmin312ymin204xmax587ymax489那么x_center(312587)/(2×1024)0.439y_center(204489)/(2×768)0.451w(587-312)/10240.269h(489-204)/7680.371正好对应这一行。对比项VOC格式XMLYOLO格式TXT坐标体系左上角右下角像素整数中心点宽高归一化浮点类别表达字符串名称整数索引可读性适合人工检查适合训练加载存储位置xmin, ymin, xmax, ymaxclass_id, x, y, w, h做病害检测我一般建议把VOC当作中间格式保留标注工具导出、人工校对都用它训练时转到YOLO格式。不要觉得自己只用YOLO训练就把VOC丢弃后期如果发现某类标注错了改XML再重新转换比直接改几千个TXT可靠得多。2.2 五类病害的样本量分布训练验证划分不能只看总数拿到数据集第一件事不是训练而是统计各类别的框数量。标题说5个类别具体是哪5类以数据里的类别清单为准常见是早疫病、晚疫病、健康叶片、疮痂病、黑痣病这类组合但不同采集批次命名可能不一样。先写一段统计脚本from pathlib import Path from collections import Counter labels_dir Path(labels/train) # 把转换后的txt放这里统计 total Counter() files_with_boxes 0 for txt in labels_dir.glob(*.txt): lines [line.strip() for line in txt.read_text().splitlines() if line.strip()] if not lines: continue files_with_boxes 1 for line in lines: cls_id int(line.split()[0]) total[cls_id] 1 print(f有标注的图数: {files_with_boxes}) for cls_id in sorted(total): print(f类别{cls_id}: {total[cls_id]}框)这段脚本对每行取第一个字段作为类别索引累加统计。8908张是总图像数不是总框数一张叶片上可能同时有几处病斑所以“5个类别”是否均衡必须按框数看才知道。python count_labels.py常见的真实情况是晚疫病样本量很大健康叶片或者某些早期症状样本量很小比例可能到10比1。如果按总数随机划分训练集和验证集小类别在验证集里可能一张图都分不到直接导致该类别AP为0。我习惯先统计再做分层划分按图像ID分桶保证每个类别在每个桶里的框数比例大致接近总体比例。另外建议按采集批次或者地块划分而不是纯随机划分。同一块田同一天拍的图像背景、光照、品种都高度相似随机切会让验证集虚高换到其他地块就掉点。后面避坑章节还会专门说这个。2.3 数据增强在病害数据上的用与不用哪些增强合适哪些会导致误检病害检测的数据增强不能照着通用目标检测的配置直接抄。叶片病害有自己的特点病斑形态是有生物学约束的不是任意翻转都合理。我一般会在训练阶段用Mosaic增强、HSV色域抖动、轻度缩放和平移垂直翻转和大幅旋转在叶片病害上要慎重。倒不是说绝对不能用而是旋转90度后叶尖朝向变了模型会花更多容量去拟合这些在真实俯拍图里不会出现的形态。在YOLO的配置里增强参数集中在训练阶段mosaic: 1.0 fliplr: 0.5 flipud: 0.0 degrees: 0.0 scale: 0.5 translate: 0.1 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4fliplr水平翻转对叶片左右对称的病斑形态没有影响可以用flipud垂直翻转会让叶片上下倒置真实田间俯拍不会出现这种角度建议关掉。hsv_h色相扰动设小值因为早疫病和晚疫病的颜色本身接近扰动太大会让模型更难区分。scale保持0.5即可病斑大小跨度本来就大不用额外做多尺度采样。Mosaic增强对小样本类别很有用它把4张图拼在一起训练变相增加了小目标数量。但训练后期如果发现某个类别的AP一直不涨可以尝试把mosaic降到0.5甚至0这类增强会引入拼接边缘的伪特征反而干扰细粒度病斑分类。验证阶段不要做任何随机增强只做resize和归一化否则指标会骗人。3. 把VOC标注批量转成YOLO格式转换脚本与坐标校验3.1 数据结构先按YOLO惯例重排images和labels两个根目录拿到手的数据集可能是一个文件夹里混着jpg和xml也可能jpg在images下、xml在annotations下先统一成YOLO训练的标准结构dataset/ ├─ images/ │ ├─ train/ │ │ ├─ img_0001.jpg │ │ └─ ... │ └─ val/ │ ├─ img_0002.jpg │ └─ ... └─ labels/ ├─ train/ │ ├─ img_0001.txt │ └─ ... └─ val/ ├─ img_0002.txt └─ ...每条图像在同级目录下有一个同名TXT图片和标签文件名完全一致只是扩展名不同。YOLO系模型的加载器扫描images目录找图再根据同名规则去labels目录找对应TXT图里没有任何病斑时可以不放TXT也可以放空文件两种做法都有人用。建议不放空文件因为统计标签时方便区分“无标注图”和“漏标图”。划分逻辑我习惯先按图名排序再去重、打乱、按8比2切分。如果不放心随机种子可以固定seed保证每次划分结果一致方便复现训练。3.2 读取VOC XML并生成YOLO TXT完整转换脚本转换的核心就一句话从XML里读出像素坐标除以图片宽高得到归一化值。但工程实现里有几个细节需要注意直接给完整脚本import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image CLASSES [early_blight, late_blight, healthy, scab, black_scurf] def voc2yolo_one(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() size_node root.find(size) if size_node is not None: W int(size_node.findtext(width)) H int(size_node.findtext(height)) else: img_path xml_path.with_suffix(.jpg) with Image.open(img_path) as im: W, H im.size lines [] for obj in root.findall(object): name obj.findtext(name) if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) xmin max(0, min(xmin, W - 1)) xmax max(0, min(xmax, W - 1)) ymin max(0, min(ymin, H - 1)) ymax max(0, min(ymax, H - 1)) if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / W y_center (ymin ymax) / 2.0 / H box_w (xmax - xmin) / W box_h (ymax - ymin) / H lines.append( f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f} ) out_path.write_text(\n.join(lines)) voc_dir Path(annotations) out_dir Path(labels/train) out_dir.mkdir(parentsTrue, exist_okTrue) for xml_path in voc_dir.glob(*.xml): voc2yolo_one(xml_path, out_dir / (xml_path.stem .txt))这段脚本的逻辑是先解析XML优先使用XML里记录的图片尺寸尺寸缺失时用PIL读同目录图片获得真实宽高遍历每个object把类名映射成索引读取bndbox坐标并做clip裁剪防止坐标越界最后计算归一化中心点坐标和宽高写入TXT。几个参数和细节要特别说明。CLASSES列表的顺序决定类别索引这个顺序必须和训练时data.yaml里的names顺序完全一致否则训练不会报错但类别会整体错位。clip时用W-1而不是W是为了避免归一化后出现恰好等于1.0的坐标YOLO训练时对边界值很敏感。坐标字段用float而不是int因为有些标注工具导出的bndbox是浮点数直接转int会丢失精度在病斑很小的框上误差会被放大。文件名用xml_path.stem来对齐要求XML和JPG同名如果两个文件不同名需要单独建立映射关系不能靠with_suffix。3.3 转换后必做的三项校验文件数量对得上、坐标都在[0,1]、类别号连续转换完不等于能训练先做一轮机器校验别急着启动训练。写一个校验脚本from pathlib import Path labels_dir Path(labels/train) bad [] cls_set set() empty_files 0 for txt in labels_dir.glob(*.txt): lines [line.strip() for line in txt.read_text().splitlines() if line.strip()] if not lines: empty_files 1 for line in lines: parts line.split() cls_set.add(int(parts[0])) vals [float(v) for v in parts[1:]] if len(vals) ! 4: bad.append((txt.name, 字段数错误)) continue if min(vals) 0 or max(vals) 1: bad.append((txt.name, 坐标不在[0,1])) print(空txt文件数:, empty_files) print(覆盖到的类别索引:, sorted(cls_set)) if bad: for name, reason in bad: print(name, reason) else: print(标签校验通过)再配一段图片和标签数量比对image_stems {p.stem for p in Path(images/train).glob(*.jpg)} label_stems {p.stem for p in Path(labels/train).glob(*.txt)} print(无标签的图片数:, len(image_stems - label_stems)) print(无图片的标签数:, len(label_stems - image_stems))三项校验各有意义。文件数量一致性检查的是有没有漏转换的图或者转换出了没有对应图片的孤立标签坐标范围检查的是clip和归一化逻辑有没有写错类别索引连续性检查CLASSES映射是否完整。如果索引不是0到4连续而是跳过了某个数字大概率是类别名拼写不一致导致映射失败脚本静默跳过了某些object。这类静默跳过在日志里看不到只能靠类别集合校验发现。TXT里的空文件可能有两种含义图里确实没有病斑或者标注被漏掉了。如果你确定数据集每一张都有目标但空文件数量很大回源头检查一下XML的object节点是不是因为name拼写不匹配被滤掉了。常见拼写差异是earlyBlight和early_blight这类大小写、下划线不一致这类错误在人工查看时很难发现脚本统计却非常显眼。4. 用这份数据集在YOLO上跑通检测配置、训练命令与评估指标4.1 模型选型与显存预估从YOLO n/s 到 m田间实时性要求8908张、5个类别的数据规模属于中小数据集模型选择不必一上来就上大模型。病害检测的任务特点是背景复杂但目标类别固定病斑大小从几个像素到半个叶片都有输入分辨率往往比模型深度更影响效果。模型规格参数量参考推荐场景显存要求batch16, imgsz640YOLO n最小边缘设备实时推理6G左右可跑YOLO s中等多数单卡训练首选8G以上YOLO m偏大追求更高精度接受慢速推理12G以上我一般从S起步先把整个训练流程跑通确认mAP曲线是正常的再考虑换成m或者加大输入分辨率。N模型在叶片密集、病斑小的图像上容易出现漏检S在精度和速度之间更均衡。显存实在不够不要急着换模型先把batch降到8或者开启混合精度训练大多数情况下能解决。病斑检测有个通用技巧同样一个模型把输入分辨率从640提高到896对小病斑的mAP提升往往比换大模型更明显。代价是显存占用接近翻倍训练时间变长。后面调优时可以优先动imgsz。4.2 写data.yaml路径、类名与train/val目录都要绝对路径YOLO训练需要一份数据配置文件路径写错或者类别顺序对不上训练时会收到各种奇怪的错误。这里给一份参考path: /home/user/dataset train: images/train val: images/val names: 0: early_blight 1: late_blight 2: healthy 3: scab 4: black_scurfpath写数据集的绝对路径train和val是相对path的目录。我建议path写绝对路径而不是相对路径因为YOLO会在当前工作目录下解析相对路径换了运行目录就找不到了。names的顺序是这里最容易翻车的点转换脚本里CLASSES列表的顺序必须和yaml里names字段完全一致两个文件里early_blight的位置要一一对应。顺序错位不会报错训练照常进行但类别之间彻底错乱。对这份数据集val目录建议独立划分不要从训练集里复制图片。有些公开数据集直接把train包含val训练时又没排除结果验证指标虚高换到真实场景立刻现原形。4.3 训练命令预训练权重、epochs、batch、imgsz怎么定数据和配置文件就绪后用一条命令启动训练yolo train \ datadata.yaml \ modelyolo11s.pt \ epochs100 \ batch16 \ imgsz640 \ cacheTrue \ device0 \ projectruns/yolo11s \ namepotato_disease几个参数的含义和选择依据如下。model指定预训练权重路径首次训练务必使用预训练权重而不是从随机初始化开始8908张的数据规模不足以从零训练收敛迁移学习能显著提高收敛速度和小类别的AP。epochs设100是为了先看曲线走势训练到60个epoch后如果mAP还在平稳上升可以续跑。batch按显存调整16是一个比较稳的起点。cacheTrue把数据缓存到内存速度提升明显但显存小于8G时建议改为cacheFalse或cachedisk防止缓存导致显存溢出。project和name决定结果保存目录训练日志、权重、验证结果都会写到这里。训练结束后结果在runs/yolo11s/potato_disease目录。weights/best.pt是验证集上mAP最高的权重训练阶段用它做后续推理weights/last.pt是最后一个epoch的权重续训时使用。results.png能看到每一轮的损失和mAP曲线confusion_matrix.png是类别混淆矩阵这两个文件是最先要看的东西。4.4 评估指标在病害检测里怎么读mAP50 vs mAP50-95漏检还是误判优先训练完成打开results.png先看两条曲线mAP50和mAP50-95。mAP50表示预测框和真实框的IoU超过0.5就算检测正确适合评价大目标、粗略定位的场景mAP50-95把IoU阈值从0.5到0.95逐步提高并取平均对框的位置精确度更敏感。叶片病害中晚疫病早期病斑只有米粒大小框的边界很难卡准mAP50-95数值难看是正常的不要因此怀疑模型坏了。真正要关心的是mAP50有没有到0.85以上以及每个类别单独的AP曲线。precision和recall需要结合使用场景来权衡。田间巡检时漏检一个病斑比误检一块背景更严重因为漏检意味着病害可能在扩散这种情况下recall优先推理时置信度阈值可以降到0.25甚至0.2。如果是做精量施药打错位置浪费药液precision优先阈值上调到0.5左右。混淆矩阵重点看早疫病和晚疫病这两个格子两者在发病初期的病斑外观高度相似如果在混淆矩阵上互相串的框数量很多先检查标注标准是否一致再决定是否用阈值做二次区分。5. 踩坑记录与排查方法病害标注训练中最常见的五个坑5.1 标注框越界训练报错或loss变成nan现象转换后训练到某个batch报错提示标签坐标有负数或大于宽高有时不报错但训练过程中loss突然变成nan。原因XML里的bndbox坐标超出了图片实际尺寸。常见于标注工具批量导出时图片被二次缩放旧XML的size字段还没更新也有的图像经过旋转裁剪标注框没有重新计算。解决转换脚本里加clip逻辑把所有坐标限制在0到W-1、0到H-1范围内。如果坐标完全错乱clip已经救不回来需要回到原始图重新检查标注。另外优先读取图片真实尺寸而不是XML里的size字段很多导出工具的size字段不可靠。5.2 某个类别AP为0但训练loss正常下降现象整体mAP在涨某个类别的AP始终接近0训练过程一切正常。原因类别样本极度不均衡且验证集划分时没有分层。比如健康叶片只有200框随机划分后验证集里一个健康叶片的框都没有AP计算时该类别没有正样本结果记为0。解决用分层划分代替随机划分按类别框数保证训练集和验证集分布一致。更稳妥的做法是按图像ID排序后间隔取样每10张图取2张进验证集分布自然跟随总体。如果某个类别框数实在太少比如不到300框优先做同类别的复制粘贴增强把这类的目标粘贴到背景图上而不是单纯依赖训练策略。5.3 验证集mAP挺高换一块地就掉点现象训练集和验证集都来自同一区域同一批照片mAP能到0.9拿手机在另一块地拍几张叶片一测掉到0.5以下。原因随机划分把同一地块、同一时期、同一光照条件的图同时分进了训练集和验证集模型学到了田块背景的纹理特征而不是病斑本身。农业图像的数据泄漏问题很隐蔽背景相似度比类别外观更容易被模型利用。解决划分数据时按采集批次、地块编号做分组隔离保证同一地块的图只出现在训练集或只出现在验证集。如果数据集没有提供地块信息按文件名中的拍摄时间前缀分组也可以。这一步没有后悔药数据划分一旦固定后续所有比较都建立在这个有缺陷的基础上。5.4 早疫病和晚疫病互相误判混淆矩阵上两格集中现象混淆矩阵里早疫病和晚疫病两个类别互相串其它类别正常。训练集里这两类图像在颜色和形态上确实很接近。原因早期病斑形态相似度高标注人员判定标准不统一有的框把孢子斑边缘一圈都框进去了有的只框中心坏死区框的内容不一致模型学到的特征自然不稳定。解决先抽20张两类图像对比标注框的实际位置和范围统一标准后重新修正标注。如果标准统一后依然分不清考虑把两个类别合并为一个“叶斑病”训练二分类检测更实际。农业场景里区分早疫晚疫对指导用药方式很重要但如果图像质量本身拍不清楚强行区分只会让两类都掉点。5.5 训练到一半中断续训却从零开始现象云服务器断连或者手动停止训练再次启动时发现loss从很高重新下降之前的训练白跑了。原因训练命令没有使用断点续训参数或者在缓存了增强数据后强制停止缓存损坏导致不能恢复。解决用last.pt续训。YOLO训练会在project目录保存last.pt重新启动时加上resumeTrue或者直接指定model为上次的last.pt路径yolo train modelruns/yolo11s/potato_disease/weights/last.pt \ datadata.yaml epochs100 \ resumeTrue这会把优化器状态、学习率调度、当前epoch一起带回。如果loss正常但mAP和中断前对不上检查是不是换过data.yaml或者改过类别顺序续训要求数据配置完全一致。6. 把训练结果用到田间验证批量推理和伪标签扩数据的一点点经验模型训练完先别急着写报告做一轮批量推理更实际。把一批没参与训练的地块图片喂给best.pt统计每张图的框数和置信度分布yolo predict \ modelruns/yolo11s/potato_disease/weights/best.pt \ sourcefield_photos/ \ save_txtTrue \ conf0.25 \ projectruns/predict \ namefield_check跑完后直接看每张图的TXT文件数量框数异常多的图片十有八九是背景误检框数很少但人工确认有病的图则说明存在漏检。把这两类图单独挑出来比只看mAP更能判断模型能不能实际用。田间光照变化大我习惯把conf设成0.25做首轮筛查再对置信度在0.25到0.5之间的框做一次人工复核而不是直接调高阈值。如果还有几百张未标注但确认有病的图可以试试伪标签扩数据用best.pt跑这批图只保留置信度高于0.6的预测框作为伪标签转成YOLO格式和原始训练集合并后再训练一轮。伪标签的好处是能让模型在更多样的背景和光照下见病斑缺点是会把模型的误检模式也放大一遍所以置信度门槛不能低于0.6并且扩完后要重新检查每个类别的数据量比例防止某类被伪标签带偏。我自己的习惯是每次训练完把五个类别的AP和置信度阈值对应关系记在一张表里下一次调参只改一个变量对比才有效。标注问题比模型结构问题更常见这份数据集跑出来的结果好不好五成取决于你怎么做划分和转换三成取决于标注质量本身模型结构反而只占两成。希望帮到你。本文还有配套的精品资源点击获取