ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

土豆病害检测数据集实战:8908张图、5类标注与YOLO训练避坑指南

土豆病害检测数据集实战:8908张图、5类标注与YOLO训练避坑指南

简介:这份资源是面向农业视觉检测研究者与算法开发者的土豆病害检测数据集说明文档,聚焦于利用图像识别技术对土豆在生长、储存环节中的常见病害进行早期识别与干预。数据集共含8908张标注图片,覆盖损伤、缺陷、真菌性病害、正常及萌芽五类土豆状态,累计标注框达30854个,其中真菌性病害与正常土豆样本尤为丰富,可为目标检测模型训练提供充足且多样的样本支撑。压缩包内为1个docx文档,约3.71MB,集中说明数据集的VOC与YOLO双格式组织方式、类别名称与各类框数统计、labelImg标注流程及使用免责声明,并附图片预览与标注示例,便于读者快速评估数据是否符合自身研究或开发需求。目前已有68人学习浏览,适合需要构建病害识别模型、开展农业视觉实验的读者参考使用。

1. 土豆病害检测数据集:8908 张图、5 类、30854 个框,先看清它到底能干什么

拿到一个农业检测数据集,最怕的不是数据少,而是标注口径和你的业务目标对不上。这份土豆病害检测数据集给的是 8908 张 jpg、8908 个 VOC xml、8908 个 YOLO txt,5 个类别合计 30854 个矩形框,标注工具是 labelImg。它解决的不是“识别土豆”这么粗的问题,而是把土豆在分选、储存、出库环节的异常状态拆成了可训练的视觉类别:损伤、缺陷、真菌性病害、正常、萌芽。适合谁?做农产品分选设备视觉模块的、做仓储质检原型的、拿 YOLO 系列跑农业小目标实验的,以及需要一份现成 VOC+YOLO 双格式数据做格式转换和增强验证的从业者。先别急着训练,把类别定义和框数分布读一遍,比直接跑 baseline 更省时间。

2. 五类标注的语义边界:先定口径,再谈模型精度

2.1 类别名背后的业务含义与框数分布

这份数据集的类别名是英文的,直接拿去训练没问题,但落到业务里必须先翻译成可操作的判定规则。五个类别分别是 Damaged potato(损伤)、Defected potato(缺陷)、Diseased-fungal potato(真菌性病害)、Potato(正常)、Sprouted potato(萌芽)。框数分布很不均衡:正常土豆 9868 框,真菌性病害 7674 框,损伤 4850 框,萌芽 4509 框,缺陷 3953 框。最多和最少差了约 2.5 倍,这个比例在单阶段检测器里不算灾难,但足以让模型偏向多数类。

我一般会先做一张类别对照表,把英文名、中文业务叫法、判定要点写清楚,再决定要不要合并或重映射。比如“损伤”和“缺陷”在产线上可能是同一道工序的两个等级,如果业务只关心“是否合格”,完全可以把这两类合并成一个“异常”类,框数立刻变成 8803,和正常类接近,训练会稳很多。反过来,如果业务要区分病害类型做用药建议,那五类就得保留,并且要针对少数类做重采样。

类别英文名业务叫法框数占比
Potato正常986832.0%
Diseased-fungal potato真菌性病害767424.9%
Damaged potato损伤485015.7%
Sprouted potato萌芽450914.6%
Defected potato缺陷395312.8%

这张表不是给你抄的,是给你做决策用的。占比低于 15% 的类别,在 8908 张图的规模下,单类样本量大约 700 到 900 张图(按每图多框估算),够训一个能用的检测头,但不够训一个鲁棒性很强的头。常见做法是对萌芽和缺陷两类做在线增强,或者用 copy-paste 把少数类实例贴到正常图上,但要注意贴的时候别破坏光照一致性,否则模型会学到“贴上去的边界”这种伪特征。

2.2 VOC 与 YOLO 双格式的实际差异

数据集同时给了 Pascal VOC 的 xml 和 YOLO 的 txt,很多人以为两者完全等价,直接拿 txt 训练就完事。实际拆开看,VOC 的 xml 里存的是绝对像素坐标 xmin、ymin、xmax、ymax,还有 filename、size 等元信息;YOLO 的 txt 存的是归一化的 cx、cy、w、h,外加类别索引。两者框的是同一个矩形,但坐标精度和越界处理不一样。VOC 允许框超出图像边界(xmin 为负、xmax 大于宽),YOLO 归一化后如果直接截断到 0 到 1,宽高会变形。

我一般会写一个校验脚本,把每张图的 xml 和 txt 对一遍,确认类别索引映射正确、框的 IoU 接近 1、没有空标注文件。这一步花十分钟,能省掉后面训练 loss 不降时排查半天的痛苦。类别索引的映射尤其要盯:YOLO 的 txt 里类别是 0 到 4 的整数,你得知道 0 对应哪个英文名,不同导出脚本的排序可能不一样。这份数据集没有附带 names 文件,所以映射关系必须自己从 xml 里反推,或者按类别名首字母排序去猜——别猜,写脚本统计每个索引对应的 xml 类别名,统计完再固化下来。

import os import xml.etree.ElementTree as ET from collections import defaultdict # 统计 YOLO txt 中每个类别索引实际对应的 VOC 类别名 xml_dir = "annotations_xml" txt_dir = "labels_yolo" index_to_name = defaultdict(set) for txt_file in os.listdir(txt_dir): if not txt_file.endswith(".txt"): continue stem = os.path.splitext(txt_file)[0] xml_path = os.path.join(xml_dir, stem + ".xml") if not os.path.exists(xml_path): continue # 从 xml 读出该图所有类别名,按出现顺序无法直接对应索引, # 这里用框的坐标做匹配:读 txt 第一列索引,再找 xml 中同位置框的类别 tree = ET.parse(xml_path) objs = tree.findall("object") with open(os.path.join(txt_dir, txt_file)) as f: lines = [l.strip().split() for l in f if l.strip()] for line in lines: cls_idx = int(line[0]) # 简化处理:同一张图内类别数通常不多,记录索引与类别名的共现 for obj in objs: name = obj.find("name").text index_to_name[cls_idx].add(name) for idx in sorted(index_to_name): print(idx, index_to_name[idx])

这段脚本的逻辑是:遍历每个 YOLO txt,找到同名 xml,把 txt 里的类别索引和 xml 里的类别名做共现统计。参数上,xml_dir 和 txt_dir 要换成你解压后的实际目录名;如果一张图里同一索引对应多个类别名,说明映射有冲突,得回去查导出脚本。跑完你会得到一个索引到名称的字典,把它写成 data.yaml 里的 names 列表,顺序不能错。注意这段脚本是共现统计,不是严格的一一匹配,因为没做坐标对齐;如果数据干净,每个索引只会出现一个名称,出现多个就说明标注或导出有问题,这正是我们要提前发现的。

3. 从解压到第一次训练:目录结构、data.yaml 与增强策略

3.1 目录重组与训练集验证集划分

原始数据通常是 images 和 annotations 平铺,或者按 VOC 和 YOLO 分两个文件夹。YOLO 训练要求 images/train、images/val、labels/train、labels/val 的结构,且图片和标签同名不同扩展名。我一般先做一次全量清点,确认 jpg、xml、txt 三者数量都是 8908,再按 8:2 划分。划分时要注意同一张原图增强出来的变体不能跨集,否则验证集精度会虚高。这份数据集正文提到“有增强”,但没说明增强图是否和原图混在一起,所以划分前最好用感知哈希或文件名的相似前缀做一次去重分组,把疑似同源的图分到同一侧。

# 清点三类文件数量,确认一一对应 find images -name "*.jpg" | wc -l find annotations_xml -name "*.xml" | wc -l find labels_yolo -name "*.txt" | wc -l # 建立 YOLO 目录结构 mkdir -p dataset/images/train dataset/images/val mkdir -p dataset/labels/train dataset/labels/val

清点命令看着简单,但血泪经验是:很多人解压后发现有嵌套目录,find 统计出来数量对,实际路径却多一层,训练时找不到图。所以清点完还要抽三张图,用 python 的 PIL 打开确认能读,再确认同名 txt 存在。划分脚本我一般用固定随机种子,把文件名列表打乱后切分,同时输出一个 split.json 记录哪些图进了验证集,方便后面复现。增强图的去重可以用 imagehash 库算 phash,距离小于阈值的归为一组,整组进同一侧。这一步不做,验证集 mAP 可能比真实高 5 到 10 个点,上线就翻车。

3.2 data.yaml 的写法与类别顺序陷阱

YOLO 训练靠 data.yaml 指定路径和类别名。这份数据集没有现成的 yaml,得自己写。关键是 names 的顺序必须和 txt 里的索引严格一致,而索引又来自你第 2 章统计出来的映射。我见过太多人直接按字母序写 names,结果 Damaged 和 Defected 两个 d 开头的类别错位,训练 loss 正常下降,但推理时把损伤判成缺陷,产线上就是误剔。

path: /data/potato_dataset train: images/train val: images/val nc: 5 names: 0: Damaged potato 1: Defected potato 2: Diseased-fungal potato 3: Potato 4: Sprouted potato

上面这份 yaml 的 names 顺序只是示例,实际必须用你统计脚本跑出来的映射替换。path 写绝对路径最稳,train 和 val 写相对 path 的子路径。nc 是类别数,必须等于 names 长度。注意类别名里有空格和连字符,yaml 里用引号包起来更安全,虽然不加引号多数解析器也能读,但遇到冒号或特殊字符就会翻车。写完后用 ultralytics 的 check 功能或直接加载一次数据集,确认能找到图和标签,再开始训练。参数上,如果显存够,imgsz 从 640 起步,batch 按显存调,这份数据 8908 张、5 类、框偏中小,640 是比较稳的起点。

3.3 针对类别不均衡的增强参数怎么设

正文说数据集有增强,但没给增强策略细节,所以训练时的在线增强得自己配。针对第 2 章说的不均衡,我一般会开 mosaic、mixup 和 copy_paste 中的一到两个,但农业图像有个坑:mosaic 四图拼接会引入不自然的边界,土豆这种圆形目标被切一半再拼,模型可能学到拼接缝。我的做法是 mosaic 概率设 0.5 而不是默认的 1.0,close_mosaic 设最后 10 个 epoch 关闭,让模型在接近真实分布的图上收尾。hsv_h 和 hsv_s 可以适当放大,因为仓储和田间光照差异大;degrees 旋转别开太大,土豆的萌芽方向有语义,转 180 度可能把“芽朝上”变成“芽朝下”,但检测任务只框位置,影响有限,控制在 10 度以内比较保守。

from ultralytics import YOLO model = YOLO("yolov8n.pt") # 从轻量模型起步,先验证数据管线 model.train( data="potato_dataset/data.yaml", epochs=100, imgsz=640, batch=16, mosaic=0.5, # 降低拼接概率,减少不自然边界 close_mosaic=10, # 最后 10 轮关闭 mosaic hsv_h=0.015, hsv_s=0.7, degrees=10.0, fliplr=0.5, patience=20, # 20 轮无提升则早停 project="runs_potato", name="baseline_v8n", )

这段训练脚本的参数说明:mosaic 设 0.5 是折中,既保留小目标多样性又不过度引入拼接伪影;close_mosaic 让收尾阶段贴近真实分布;patience 设 20 是因为农业数据增强后波动大,早停太早会错过后期收敛。先拿 yolov8n 跑通管线,确认 loss 正常下降、验证集能出框,再换更大的模型。如果第一轮就出现 loss 为 nan,优先查标签越界和空 txt,而不是调学习率。训练完看混淆矩阵,重点看 Damaged 和 Defected 之间的互混,这两类语义最接近,混得多说明标注口径本身模糊,得回去看预览图确认边界案例。

4. 避坑与排查:这份数据集最容易翻车的五个地方

4.1 现象:训练 loss 正常但验证 mAP 极低。原因:增强图跨集泄漏。解决:划分前做 phash 去重分组,同源图整组进同一侧,并固定随机种子记录 split 清单。

这是最常见的翻车点。数据集正文明确写了“有增强”,但没给增强前后的对应关系。如果随机按文件名切分,同一张原图的旋转、亮度变体可能一半在训练集一半在验证集,模型在验证集上等于见过原图,mAP 虚高;反过来如果增强图全挤在验证集,mAP 又异常低。现象就是训练 loss 一路降,验证指标却像玄学一样跳。解决办法不是调参,是回到数据划分:用 imagehash 算 phash,汉明距离小于 8 的归为一组,按组划分,保证同源图不跨集。做完这一步再训,指标才可信。

4.2 现象:推理时框偏移或宽高异常。原因:VOC 越界框转 YOLO 时被截断。解决:转换时保留原始宽高做归一化,对越界框做裁剪并记录,训练前过滤掉宽或高小于 2 像素的退化框。

VOC 的 xml 允许 xmin 为负或 xmax 超宽,labelImg 画框时手一抖就可能越界。转 YOLO 时如果先截断再归一化,框会变形;如果直接归一化不截断,cx 可能大于 1,训练时被静默丢弃或报错。我一般会在转换脚本里做两件事:一是用图像实际宽高归一化,二是对越界框裁剪到图像内并重新算宽高,裁剪后宽或高小于 2 像素的直接丢弃并记日志。这份数据 30854 个框,跑一遍统计能知道有多少越界,数量多说明标注质量要打问号,得抽图人工看。

4.3 现象:某个类别始终学不出来。原因:类别索引映射错位或该类别样本被增强稀释。解决:用第 2 章的共现统计脚本核对索引,再统计每个类别的图级样本数,对少于 800 图的类别做重采样或提高 copy_paste 概率。

萌芽类 4509 框、缺陷类 3953 框,按每图平均 3.5 框算,图级样本大约 1100 到 1300 张,不算太少,但如果增强图里这两类占比更低,实际有效样本会更少。现象是训练到后期,这两类的 recall 明显低于正常类。先排除索引错位——这是黑匣子,索引错了 loss 照样降,只是学的是别的类。确认索引无误后,再看图级分布,必要时对少数类图做 oversampling,或者在 dataloader 里给少数类更高的采样权重。别一上来就换大模型,数据层面的问题换模型解决不了。

4.4 现象:验证集图片能读但训练报“no labels found”。原因:images 和 labels 路径层级不匹配。解决:确认 labels 目录与 images 目录同级且文件名主干一致,用脚本逐张检查同名 txt 是否存在。

YOLO 找标签的规则是把 images 路径里的 /images/ 替换成 /labels/,扩展名换成 .txt。如果你的目录是 dataset/images/train 和 dataset/labels_yolo/train,替换后找不到,就报 no labels。现象很迷惑:图能读、txt 也在,就是匹配不上。解决方法是严格按 images/train 对 labels/train 的结构放,或者用绝对路径的 txt 清单。我一般会写个十行脚本,遍历 images 下所有 jpg,拼出对应 txt 路径,统计缺失数量,缺失超过 0 就先修目录再训练。

4.5 现象:训练几轮后显存暴涨或 dataloader 卡死。原因:个别图片尺寸异常大或损坏。解决:训练前统一做尺寸统计,过滤超长边超过 4000 像素的图,并用 PIL 的 verify 做完整性检查。

农业数据集常混入手机直出的大图,8908 张里只要有几十张 6000 像素宽的,dataloader 的 batch 内 padding 就会把显存吃满。现象是前几轮正常,某个 batch 突然 OOM。解决办法是训练前跑一遍尺寸统计,输出宽高分布,把超长边超过 4000 的图缩放到 2000 以内再存一份,或者直接在 dataloader 里限制。同时用 PIL verify 检查损坏文件,损坏的图会让某个 worker 卡死,表现为训练停住不动、GPU 利用率掉到 0。这两项检查加起来不到五分钟,能省掉一晚上的排查。

5. 进阶验证:用分层评估和可视化把数据集吃透

训练跑通只是开始,真正决定这份数据能不能用的是分层评估。我习惯在验证集上按类别、按框大小、按图像来源(原图还是增强图)三个维度拆开看指标。按类别看,重点盯 Damaged 和 Defected 的互混率;按框大小看,小框(面积小于 32×32)的 recall 往往比大框低一截,如果低太多,说明 imgsz 不够或者数据里小目标标注偏松;按来源看,原图和增强图分开算 mAP,差距大就说明增强策略和真实分布偏离,得回调 mosaic 和 hsv 参数。这三个维度用 ultralytics 的 val 接口加自定义脚本就能出,不需要额外工具。

可视化验证我一般做两件事:一是把验证集预测结果按置信度排序,抽最低的 50 张看,这些图往往是标注边界模糊或漏标的;二是把每个类别的真值框和预测框叠在一张图上,看系统性偏移。土豆是近圆形目标,如果预测框普遍偏大,可能是标注时习惯把整个土豆连阴影一起框进去,而模型学到了阴影边缘。这种偏差在 mAP 上看不出来,但落到分选设备的抓取坐标上就是几毫米的误差。发现后要么回去修标注口径,要么在训练时加一点 scale 抖动让模型对边界不敏感。

评估维度看什么指标异常信号对应动作
按类别各类 recall、互混率两类互混超过 20%复查标注口径或合并类别
按框大小小框 recall比大框低 15 个点以上提高 imgsz 或过滤退化框
按来源原图与增强图 mAP 差差距超过 8 个点回调 mosaic、hsv 参数
置信度低样本最低 50 张预测大量漏标或边界模糊人工复核并修标注

最后说一个我自己的习惯:每次拿到新数据集,不管正文写得多清楚,我都会先抽 30 张图把 xml 和 txt 的框画出来叠在原图上,肉眼过一遍。这份数据 8908 张、30854 个框,抽 30 张只要几分钟,但能发现类别名拼写不一致、框整体偏移、某类框全是同一个尺寸这类脚本查不出的问题。从那以后我每次开新数据集都强制走一遍抽图叠框,再跑统计脚本,最后才进训练。希望这套流程帮到你,少在数据管线上翻车,把时间留给模型和业务。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进