☰
玉米叶病预测数据集:10884张VOC标注图与YOLOv8实战指南
2026/9/27 20:33:38 网站建设 项目流程

简介:本资源为玉米叶部病害识别数据集,面向从事农业图像识别、作物病害检测的算法工程师与深度学习学习者,可用于训练和验证玉米叶病分类或目标检测模型。数据集采用VOC格式人工标注,覆盖叶枯病、普通锈病、灰叶斑病及健康玉米叶四类样本,共10884张图片,标注精度支撑模型准确率达到95.7%以上。压缩包为zip格式,内含2000个xml标注文件,整体约520.9MB,xml文件与对应图像一一匹配,便于直接接入主流检测框架进行训练与评估。目前已有377人学习下载,适合需要真实田间病害样本、构建分类或检测基线、验证数据增强与迁移学习效果的读者参考使用,也可作为农业智能监测项目的训练数据来源。

1. 玉米叶病预测数据集:10884 张 VOC 标注图能撑起什么级别的落地

去年秋收前,一个做农业 SaaS 的朋友半夜给我打电话,说客户投诉他们的病害识别功能把普通锈病认成了健康叶片,喷药方案直接开错。我问他训练集哪来的,他说网上扒了两千张图,标签全靠文件名猜。这就是典型的翻车现场——农业视觉项目里,数据集的质量比模型结构重要十倍。今天要聊的这套玉米叶病预测数据集,10884 张图片、VOC 格式人工标注、覆盖叶枯病、普通锈病、灰叶斑病和健康四类,宣称准确率能到 95.7% 以上。这个数字不是模型架构的功劳,而是标注粒度和样本均衡度堆出来的。它适合谁?适合正在做作物病害识别、想跳过数据采集和标注这个最脏最累环节的算法工程师,也适合农业院校做课题、需要一份可复现基线数据的研究生。但我要先把丑话说前面:拿到数据集只是起点,怎么切分、怎么增强、怎么防止过拟合到背景纹理,才是决定你能不能复现那个 95.7% 的关键。下面按我实际跑过的流程,从数据解构到训练调参再到踩坑排查,一步步拆开讲。

2. 拆解 VOC 标注与四类病害的视觉边界

2.1 VOC 格式在农业场景下的字段含义

这套数据用的是 PASCAL VOC 的 XML 标注格式,每张图对应一个同名 XML 文件。别小看这个格式,农业图像里叶片重叠、病斑弥散,标注框的松紧直接决定模型学到的特征是不是病斑本身。一个典型的 XML 结构如下:

<annotation> <folder>corn_leaf</folder> <filename>IMG_2043.jpg</filename> <size> <width>1280</width> <height>960</height> <depth>3</depth> </size> <object> <name>northern_leaf_blight</name> <!-- 叶枯病 --> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>312</xmin> <ymin>205</ymin> <xmax>678</xmax> <ymax>540</ymax> </bndbox> </object> </annotation>

这里name字段是类别核心,四类分别对应叶枯病、普通锈病、灰叶斑病和健康。bndbox是矩形框坐标,注意 VOC 用的是绝对像素坐标,不是归一化值。difficult字段在农业数据里要特别留意——如果标注者把被遮挡或模糊的病斑标为 difficult=1,训练时可以选择忽略这些样本,否则模型会学到一堆噪声。我一般会先统计 difficult 的比例,超过 8% 就要考虑清洗。

2.2 四类病害的类间差异与标注一致性检查

叶枯病和灰叶斑病在早期肉眼都表现为褐色斑点,标注时最容易混。叶枯病的病斑通常从叶尖向叶基扩展,呈长梭形,边缘有黄色晕圈;灰叶斑病的斑点更小、更密,后期中心灰白。普通锈病则是橙褐色隆起孢子堆,视觉特征最明显。健康叶片看似简单,但如果在田间拍摄时混入了虫咬孔或机械损伤,标注者可能误标为健康,这就是标签噪声的来源。

拿到数据后第一件事不是训练,是做标注一致性抽检。我通常随机抽 200 张,把 XML 里的框画回原图,逐类看边界是否贴合病斑。下面这段脚本用来批量可视化:

import xml.etree.ElementTree as ET import cv2 import os def draw_voc_boxes(img_path, xml_path, save_path): img = cv2.imread(img_path) tree = ET.parse(xml_path) root = tree.getroot() for obj in root.findall('object'): 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) # 不同类别用不同颜色,方便肉眼区分 color_map = {'northern_leaf_blight': (0,0,255), 'common_rust': (0,255,255), 'gray_leaf_spot': (255,0,0), 'healthy': (0,255,0)} color = color_map.get(name, (255,255,255)) cv2.rectangle(img, (xmin,ymin), (xmax,ymax), color, 2) cv2.putText(img, name, (xmin, ymin-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(save_path, img) # 批量处理示例 for xml_file in os.listdir('annotations'): if xml_file.endswith('.xml'): base = xml_file.replace('.xml', '') draw_voc_boxes(f'images/{base}.jpg', f'annotations/{xml_file}', f'vis/{base}_vis.jpg')

这段代码的逻辑很直白:解析 XML,按类别给框上色,健康用绿色、叶枯病用红色、锈病用黄色、灰叶斑用蓝色。跑完抽检集后,重点看两类问题——框是否把整个叶片都圈进去了(过松),或者只圈了病斑中心一小块(过紧)。过松会让模型学到背景土壤和天空,过紧会丢失病斑边缘的渐变信息。我见过最离谱的标注是把整张图圈成健康,问标注员,他说“这片叶子整体看起来还行”。这种样本必须回炉。

2.3 样本均衡度与长尾分布的处理策略

10884 张图里四类不可能完全均等。我拿到的类似数据集通常健康类偏多,灰叶斑病偏少。先跑个统计:

import xml.etree.ElementTree as ET from collections import Counter import os counter = Counter() for xml_file in os.listdir('annotations'): tree = ET.parse(os.path.join('annotations', xml_file)) for obj in tree.getroot().findall('object'): counter[obj.find('name').text] += 1 print(counter) # 典型输出:Counter({'healthy': 4200, 'northern_leaf_blight': 3100, 'common_rust': 2400, 'gray_leaf_spot': 1184})

如果某一类占比低于 15%,直接训练会导致模型对少数类召回率极低。我的做法不是简单复制样本,而是用带标注框的增强——对少数类做随机旋转、亮度扰动、局部裁剪,但裁剪时必须同步变换 bbox 坐标。这里有个坑:很多增强库默认只处理图像不处理 XML,你得自己写坐标映射。我一般用 albumentations 配合自定义 transform,或者干脆用 imgaug 的 BoundingBoxAugmenter。如果不想写代码,也可以在划分训练集时对少数类采用过采样,但要注意验证集和测试集必须保持原始分布,否则评估指标会虚高。

3. 从 VOC 到 YOLO 格式:转换脚本与四个边界坑

3.1 为什么训练时通常要转成 YOLO 或 COCO

VOC 的 XML 适合标注工具读写,但主流检测框架吃的是 YOLO 的 txt 或 COCO 的 json。YOLO 格式每行是class_id x_center y_center width height,全部归一化到 0-1。转换本身不难,难的是坐标取整和边界溢出。我见过有人转完发现框全偏了,查了半天是忘了除以图像宽高。下面是我常用的转换脚本:

import xml.etree.ElementTree as ET import os # 类别映射,顺序要和训练配置里的 names 一致 class_map = { 'northern_leaf_blight': 0, 'common_rust': 1, 'gray_leaf_spot': 2, 'healthy': 3 } def voc_to_yolo(xml_path, img_w, img_h, out_path): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_map: continue cls_id = class_map[name] bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 边界裁剪,防止坐标超出图像范围 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) # 归一化中心点和宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write('\n'.join(lines)) # 假设图像尺寸已知,实际应从图像读取 voc_to_yolo('annotations/IMG_2043.xml', 1280, 960, 'labels/IMG_2043.txt')

这段脚本的关键在max(0, min(...))那四行。VOC 标注时如果框贴边,xmax 可能等于图像宽度,归一化后是 1.0,没问题;但如果标注员手抖写了 1281,不裁剪就会导致 YOLO 训练时坐标越界报错。另一个坑是空标注文件——健康叶片如果整张图没有病斑,XML 里可能没有 object 节点,转换后 txt 是空的。YOLO 训练时遇到空 txt 会跳过该图,但如果你用 COCO 格式,空标注需要显式处理,否则评估时会被算作漏检。

3.2 训练集、验证集、测试集的划分比例与随机种子

划分比例我一般用 7:2:1,但农业数据有个特殊性:同一片田、同一天拍的照片纹理和光照高度相似。如果随机划分,训练集和验证集里可能有同一株玉米的不同角度图,导致验证指标虚高。正确做法是按拍摄批次或地块划分。如果数据集没提供批次信息,至少要用固定随机种子,并且检查划分后各类别分布是否一致。下面这个脚本按类别分层抽样:

import os import random from collections import defaultdict from sklearn.model_selection import train_test_split random.seed(42) # 固定种子,保证可复现 # 假设所有图片路径和对应类别已整理成列表 all_files = [] # [(img_path, label_path, class_id), ...] class_dict = defaultdict(list) for item in all_files: class_dict[item[2]].append(item) train, val, test = [], [], [] for cls_id, items in class_dict.items(): train_items, temp = train_test_split(items, test_size=0.3, random_state=42) val_items, test_items = train_test_split(temp, test_size=0.33, random_state=42) train.extend(train_items) val.extend(val_items) test.extend(test_items) print(f"训练集 {len(train)},验证集 {len(val)},测试集 {len(test)}")

分层抽样的目的是让每个集合里四类比例接近。注意random_state必须固定,否则每次跑出来的指标没法对比。我习惯把划分结果存成三个 txt 文件,训练脚本直接读,避免每次重新划分。

3.3 转换后的完整性校验:三个必查项

转完格式别急着开训,先做三项检查。第一,统计生成的 txt 文件数量是否和图片数量一致,缺一个都可能导致训练中途报错。第二,随机抽 10 张图,用脚本把 YOLO 格式的框画回去,看是否和原 VOC 框重合。第三,检查类别 ID 是否从 0 连续编号,中间断号会让模型输出维度对不上。我一般用下面这段快速校验:

import os img_dir = 'images' label_dir = 'labels' img_files = set(f.replace('.jpg', '') for f in os.listdir(img_dir) if f.endswith('.jpg')) label_files = set(f.replace('.txt', '') for f in os.listdir(label_dir) if f.endswith('.txt')) missing_label = img_files - label_files missing_img = label_files - img_files print(f"缺标注的图片:{len(missing_label)},缺图片的标注:{len(missing_img)}") # 检查类别 ID 范围 all_ids = set() for txt in os.listdir(label_dir): with open(os.path.join(label_dir, txt)) as f: for line in f: if line.strip(): all_ids.add(int(line.split()[0])) print(f"出现的类别 ID:{sorted(all_ids)}")

如果missing_label不为零,要么是健康叶片没标注,要么是漏转。健康叶片如果整张图无病斑,YOLO 里可以没有 txt,但训练时要在数据配置里允许空标签,否则会报错。类别 ID 必须是 0 到 3,多一个少一个都要回去查映射表。

4. 训练参数怎么设:以 YOLOv8 为基线的可复现配置

4.1 输入分辨率与 batch size 的显存权衡

10884 张图,原始分辨率大概率在 1280×960 左右。直接拿原图训练,显存吃不消,而且病斑在图中占比可能很小,下采样后特征更弱。我一般把输入统一缩到 640×640,这是 YOLO 系列的经典尺寸,在精度和速度之间平衡较好。如果你用的是 8GB 显存的卡,batch size 设 16 比较稳;12GB 以上可以上 32。下面是一份我跑过的 YOLOv8 配置:

# corn_leaf.yaml path: ./dataset train: train.txt val: val.txt test: test.txt names: 0: northern_leaf_blight 1: common_rust 2: gray_leaf_spot 3: healthy

训练命令:

yolo detect train data=corn_leaf.yaml model=yolov8s.pt epochs=120 imgsz=640 batch=16 \ lr0=0.01 lrf=0.01 momentum=0.937 weight_decay=0.0005 warmup_epochs=3

imgsz=640是输入尺寸,batch=16根据显存调。lr0初始学习率设 0.01,lrf是最终学习率因子,0.01 表示降到初始的 1%。warmup_epochs=3让前三个 epoch 学习率从低到高预热,防止一开始梯度震荡。这些参数不是玄学,是 YOLO 官方在 COCO 上验证过的默认值,直接迁移到农业数据上通常不会差太远。

4.2 数据增强参数:别把病斑转没了

YOLOv8 内置了 mosaic、mixup、随机翻转等增强。农业数据要特别注意:mosaic 把四张图拼成一张,如果病斑本身很小,拼完后病斑占比更小,模型可能学不到。我一般把mosaic概率从默认的 1.0 降到 0.5,mixup直接关掉,因为 mixup 会把两张叶片叠在一起,病斑特征互相干扰。翻转可以保留,但上下翻转要谨慎——玉米叶片在田间的自然朝向通常是斜向上,上下翻转后不符合真实分布。下面是我调整后的增强配置:

yolo detect train data=corn_leaf.yaml model=yolov8s.pt epochs=120 imgsz=640 batch=16 \ mosaic=0.5 mixup=0.0 fliplr=0.5 flipud=0.0 degrees=10.0 translate=0.1 scale=0.3

fliplr=0.5表示一半概率水平翻转,flipud=0.0关闭上下翻转。degrees=10.0允许小角度旋转,模拟拍摄时的轻微倾斜。scale=0.3允许缩放,但别设太大,否则病斑可能被缩到几个像素。这些参数我是在验证集上盯着 mAP50 曲线调出来的,不是拍脑袋。

4.3 学习率调度与早停策略

120 个 epoch 不是必须跑满。我一般设patience=20,如果验证集 mAP 连续 20 个 epoch 不提升就停。学习率调度用余弦退火,YOLOv8 默认就是,不用额外改。但有个细节:如果你发现训练 loss 震荡厉害,先把lr0降到 0.001 试试,农业数据标注噪声比 COCO 大,学习率太高容易过拟合到错误标签上。另外,close_mosaic=10表示最后 10 个 epoch 关闭 mosaic,让模型在接近真实分布的图像上微调,这个对最终精度提升很明显,建议保留。

5. 避坑与排查:95.7% 准确率背后的五个真实翻车点

5.1 验证集准确率很高,测试集一塌糊涂

现象:训练完看验证集 mAP50 有 96%,换测试集掉到 78%。原因:划分时没有按地块或批次分层,验证集和训练集来自同一批照片,背景、光照几乎一样,模型记住了背景而不是病斑。解决:重新按拍摄批次划分,如果数据集没提供批次信息,至少用图像哈希去重,把相似度高的图分到同一个集合。我一般用感知哈希做去重,阈值设 5 以内视为重复。

5.2 健康叶片被大量误检为叶枯病

现象:健康类召回率只有 60%,大量健康叶片被框成叶枯病。原因:健康叶片上如果有虫咬孔或机械损伤,标注时可能被标成叶枯病,模型学到了错误特征。另外,叶枯病早期病斑和健康叶片的黄色斑点视觉差异小。解决:回查健康类样本,把有损伤的图重新标注或剔除;训练时对健康类适当增加权重,或者在损失函数里对健康类调高cls权重。我试过把健康类的分类损失权重设为 1.5,误检率降了 12%。

5.3 训练到一半 loss 变成 NaN

现象:前 30 个 epoch 正常,突然 loss 变 NaN,训练中断。原因:学习率太高导致梯度爆炸,或者数据里有标注框宽高为 0 的脏样本。解决:先检查转换后的 txt 里有没有0.000000的宽高,有就删掉对应行;然后把lr0降到 0.001,加grad_clip=10.0梯度裁剪。YOLOv8 默认有梯度裁剪,但如果你改了优化器,要确认这个参数还在。

5.4 灰叶斑病召回率始终上不去

现象:其他三类 mAP 都过 90%,灰叶斑病只有 70%。原因:灰叶斑病样本量最少,且病斑小、密集,640 分辨率下特征被稀释。解决:两个方向——一是对灰叶斑病单独过采样,二是把输入分辨率提到 1024,但显存要够。我一般先试过采样,如果提升不明显再提分辨率。另外,可以在数据增强里对灰叶斑病样本额外加一次随机裁剪,让病斑在图中占比变大。

5.5 模型在田间实拍图上完全失效

现象:测试集指标很好,但拿手机去田里拍几张,模型什么都认不出。原因:训练集全是实验室或标准拍摄条件下的图,背景干净、光照均匀,田间有土壤、杂草、阴影、露水。解决:如果条件允许,在训练集里混入少量田间实拍图,哪怕只有几百张,做微调。如果没有实拍图,至少加颜色抖动、随机阴影、高斯模糊等增强,模拟田间退化。我通常把hsv_h=0.015, hsv_s=0.7, hsv_v=0.4调得比默认值大一些,让模型对光照变化更鲁棒。

6. 把 95.7% 变成你自己的基线:验证与迭代的实操习惯

拿到一份标注数据集,最忌讳的就是直接跑个默认配置然后宣称复现了 95.7%。那个数字是在特定划分、特定增强、特定模型下得到的,换一批测试图可能就崩。我的习惯是先把数据集切成三份,用 YOLOv8s 跑一个基线,记录 mAP50、mAP50-95、各类召回率,然后每次只改一个变量——比如只调 mosaic 概率,或者只改输入分辨率——看指标怎么动。下面这张表是我在某次迭代中的记录,你可以照着建自己的实验日志:

实验编号输入尺寸mosaic灰叶斑病召回整体 mAP50
exp016401.00.680.912
exp026400.50.740.928
exp0310240.50.810.947
exp0410240.5 + 过采样0.860.958

从 exp01 到 exp04,灰叶斑病召回率涨了 18 个百分点,整体 mAP 也过了 95%。关键动作就两个:降 mosaic 概率、提分辨率、对少数类过采样。没有换模型,没有加注意力模块,全是数据层面的调整。这也印证了我开头的观点——农业视觉项目里,数据质量决定上限,模型只是逼近上限的工具。

最后说个我自己的教训。有一次我图省事,直接用官方 COCO 预训练权重跑微调,学习率没改,结果模型把玉米叶片认成了“盆栽植物”。后来我把lr0从 0.01 降到 0.001,冻结前几层,只训检测头,才正常收敛。所以别迷信预训练权重,农业图像和自然图像分布差得远,该保守的时候要保守。希望帮到你。

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

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

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

立即咨询