
简介面向玉米叶病害识别与目标检测研究的COCO格式图像数据集覆盖叶枯病、普通锈病、灰叶斑病及健康叶片四类共10884张人工标注图片标注准确率可达95.7%以上。数据适用于深度学习研究者、农业智能化开发人员及高校相关专业学生可作为YOLO、MMDetection等检测框架的训练与评估基准。压缩包共含2000个文件其中1995张JPG图像为原始样本3个JSON文件保存COCO标注与类别信息2个TXT文件用于划分说明或辅助整理整体大小约515.62MB解压后目录结构清晰便于直接取用。目前已有404人学习下载。借助这份数据集读者可直接开展模型训练、验证集评测、数据增强与迁移学习实验省去自行采集与标注玉米叶图像的重复劳动也可用于教学演示帮助理解目标检测中数据标注、格式转换、训练配置与结果可视化等关键环节。1. 玉米叶病预测数据集拿到手先别急着训练先看清这 10884 张图的分布再动手农业视觉项目最缺的从来不是模型而是干净、能直接喂给训练管线的数据。我自己经历过拿着两三千张手机拍的叶片图花一整周标完框训练出来的模型在田里一测就翻车的窘境。这套玉米叶病预测数据集是 COCO 格式、纯人工标注的完整图像集一共 10884 张覆盖叶枯病、普通锈病、灰叶斑病和健康叶四类目标标注准确率宣称能做到 95.7% 以上。它的价值在于省掉拍摄和标注两个最耗时的环节让你直接进入模型训练、参数调优和效果评估的正题。适合三类人——刚接触目标检测、想拿真实农业数据跑通 YOLO 系列流程的开发者做玉米病害识别系统落地、需要预训练基线的工程师以及研究数据增强、小目标检测、类别不均衡问题的算法同学。2. 玉米叶病数据集结构拆解COCO 标注链路与四类标签分布2.1 COCO 格式的数据组织从 images 到 annotations 的完整链路这套数据集的标注文件遵循标准 COCO 结构和你在 COCO2017 数据集里看到的组织方式一致。顶层 JSON 包含 info、licenses、images、annotations、categories 五个字段其中最关键的是后三者字段类型作用images数组每张图的 id、width、height、file_nameannotations数组每个标注目标的 id、image_id、category_id、bbox、area、segmentation、iscrowdcategories数组类别 id、名称、超类拿到数据集后的第一步不是急着打开图片看标注框画得准不准而是先写脚本统计 JSON 里的分布。我一般会先做两件事确认 images 数量和 annotations 数量是否匹配预期检查是否存在某张图有标注但图片缺失、或者有图片但没有任何标注的情况。import json from collections import Counter with open(annotations/instances_train.json) as f: coco json.load(f) img_info {img[id]: img for img in coco[images]} cat_info {cat[id]: cat[name] for cat in coco[categories]} per_img Counter() size_ratio [] for ann in coco[annotations]: per_img[ann[image_id]] 1 img img_info[ann[image_id]] x, y, w, h ann[bbox] size_ratio.append((w / img[width], h / img[height])) print(图片数:, len(img_info), 标注数:, len(coco[annotations])) print(类别:, cat_info) print(每图平均目标数:, sum(per_img.values()) / len(per_img)) print(标注宽高比均值:, sum(r[0] for r in size_ratio) / len(size_ratio), sum(r[1] for r in size_ratio) / len(size_ratio))这段代码先把 image_id 映射成图片信息再遍历所有 annotation 计算每张图的目标数量同时算出标注框相对图片的宽高比例。宽高比均值能快速暴露一个问题如果平均 bbox 宽度只有图片宽度的 5%说明这是个以小目标为主的数据集后续数据增强策略要专门做小目标适配。2.2 四类标签的样本分布与图像质量预检四类标签的分布并不均匀这是农业病害数据的常态。从实际使用体验看健康叶和叶枯病的样本量通常较大普通锈病和灰叶斑病相对偏少尤其是灰叶斑病早期病斑面积小、颜色浅标注难度高样本量往往只有健康叶的一半甚至更少。建议先把每个类别的实例数拉出来看一下如果最少的类别占比低于 10%训练时就要考虑类别权重或者采样策略否则模型会倾向于把不确定的样本全部预测成样本量大的类别。图像质量预检同样不能跳过。人工标注数据集难免混入少量模糊图、过曝图或者重复图。我习惯用下面这段代码快速筛一遍分辨率异常和文件损坏的图片import os from PIL import Image valid_ext (.jpg, .jpeg, .png) issues [] for root, _, files in os.walk(images): for name in files: if not name.lower().endswith(valid_ext): continue path os.path.join(root, name) try: with Image.open(path) as im: w, h im.size if w 320 or h 320: issues.append((path, too_small, (w, h))) except Exception as e: issues.append((path, corrupt, str(e))) print(问题图片数:, len(issues)) for item in issues[:10]: print(item)注意这里用 PIL 的Image.open做无损校验它只会读文件头不会把整张图加载进内存所以哪怕是上万张图的目录跑起来也很快。分辨率过滤阈值设 320 是一个经验值低于这个尺寸的图病斑特征可能只有几个像素即使强行训练模型学到的也是噪声而非纹理特征。2.3 训练/验证/测试集的划分策略农业图像数据有一个容易被忽略的问题同一株玉米的连续拍摄帧之间高度相似。如果随机划分数据集相似的帧可能同时出现在训练集和验证集里导致验证指标虚高。等你把模型拿到新地块去测性能立刻打回原形。常见做法是按拍摄批次或田间区域来划分。如果数据集的 image_id 里带批次前缀比如field01_0001.jpg这种命名可以按前缀分组后整组划分如果没有明显分组标识至少要做一次感知哈希去重把相似度超过阈值的图片归到同一组。import random image_ids list(img_info.keys()) random.seed(42) random.shuffle(image_ids) n_train int(len(image_ids) * 0.8) n_val int(len(image_ids) * 0.1) train_ids set(image_ids[:n_train]) val_ids set(image_ids[n_train:n_train n_val]) test_ids set(image_ids[n_train n_val:]) print(ftrain: {len(train_ids)}, val: {len(val_ids)}, test: {len(test_ids)})这里我用 8:1:1 的比例切分固定随机种子保证结果可复现。实际训练时我通常不看测试集只拿验证集做早停和调参最后才用测试集做一次最终评估。这样做是为了避免在调参过程中不小心对测试集产生隐式过拟合。3. 从 COCO 转 YOLO 格式转换脚本与归一化边界框的四个细节3.1 为什么目标检测框架只认各自的标签格式COCO JSON 可以直接喂给 MMDetection 这类框架但 YOLO 生态里用的是每张图一个 txt 的格式每行代表一个目标class_id x_center y_center width height坐标全部归一化到 0~1。很多人处理数据集用于 YOLOv8 训练时第一个卡点就在这里。动手写转换脚本之前先确认一个核心边界COCO 的 bbox 是[x, y, width, height]表示框的左上角坐标和宽高而 YOLO 需要的是框中心点的归一化坐标。两者之间只是加减乘除的关系但不少人在写转换脚本时会把x w / 2写成x w一旦边界框靠右归一化坐标直接越界训练时程序报错甚至静默丢框。3.2 转换脚本从 COCO JSON 到每张图一个 txtimport json, os def coco_to_yolo(coco_path, label_dir): with open(coco_path) as f: coco json.load(f) os.makedirs(label_dir, exist_okTrue) img_id_to_info {img[id]: img for img in coco[images]} cat_id_to_idx {cat[id]: i for i, cat in enumerate(coco[categories])} anns_by_img {} for ann in coco[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_id, anns in anns_by_img.items(): info img_id_to_info[img_id] w, h info[width], info[height] lines [] for ann in anns: cat_idx cat_id_to_idx[ann[category_id]] x, y, bw, bh ann[bbox] cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h lines.append(f{cat_idx} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) txt_name os.path.splitext(info[file_name])[0] .txt with open(os.path.join(label_dir, txt_name), w) as f: f.write(\n.join(lines)) coco_to_yolo(annotations/instances_train.json, labels/train)脚本的逻辑不复杂先把 image_id 和 category_id 映射成字典再把标注按图分组遍历每张图的所有标注把左上角坐标换算成中心点坐标最后除以图片宽高完成归一化。这里有两个容易踩的细节一是cat_id_to_idx要按 categories 数组的遍历顺序来建立而不是直接用 category_id 当输出类别号因为 COCO 的类别 id 不一定是连续整数二是输出文件名必须和图片文件名一致YOLO 训练时会通过去掉扩展名来匹配图片和标签名字对不上就意味着这张图没有标签默认当负样本处理。3.3 转换结果的校验越界检测与空标签排查转换完不能直接开跑先写一个校验脚本扫描所有生成的 txt检查坐标是否在合法的 0~1 区间内、是否存在 NaN、是否每张图都有对应的标签文件。import os, math label_dir labels/train bad_files [] for name in os.listdir(label_dir): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path) as f: lines f.read().strip().splitlines() if not lines: bad_files.append((name, empty)) continue for line in lines: parts line.split() if len(parts) ! 5: bad_files.append((name, bad_format)) break vals list(map(float, parts[1:])) if any(v 0 or v 1 for v in vals) or any(math.isnan(v) for v in vals): bad_files.append((name, out_of_range)) break print(异常标签文件数:, len(bad_files)) for item in bad_files[:20]: print(item)空标签文件尤其需要警惕它不是格式错误而是 COCO 标注里那张图确实没有任何目标。这种情况通常来自数据集制作时保留了部分健康叶子的背景图。YOLO 训练时这类空 txt 会告诉模型这张图没有目标对分类任务来说反而是有用的负样本但如果你预期每张图都有标注就要回去核对 JSON 源文件。越界坐标的处理我建议直接 clip而不是删除整条标注。人工标注时偶尔会出现框稍微超出图像边缘的情况clip 到[0, 1]区间内保留这条标注比直接丢弃更符合实际数据分布。4. 训练参数组合从 95.7% 准确率倒推可复现的超参数4.1 用 YOLOv8 跑通玉米叶病检测的最小配置数据集转换完成后接下来的标准动作是用 YOLOv8 训练自己的数据集。这个数据集声称准确率可达 95.7% 以上但要复现这个数字关键不全在模型结构而在数据配置和超参数组合。先建data.yamlpath: /path/to/corn_leaf # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 test: images/test # 测试图片目录可选 nc: 4 names: 0: northern_leaf_blight 1: common_rust 2: gray_leaf_spot 3: healthy注意names的顺序必须和转换脚本里cat_id_to_idx的映射顺序一致。YOLO 的类别索引是从 0 开始的编号如果这里对不上模型会在错误的语义下学习训练过程不会报错但评估时混淆矩阵会乱成一团。训练命令我一般用yolov8 train \ datadata.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ patience20 \ projectruns/corn_leaf \ nameexp1imgsz640是精度和速度的平衡点。玉米叶病斑往往细长且面积小如果 imgsz 降到 416小目标特征会严重丢失但升到 1280 对显存的消耗会翻好几倍训练时间也成倍拉长。我测试下来在 10884 张图的规模下640 是最稳妥的选择。patience20表示验证指标连续 20 个 epoch 不提升就提前结束既省时间又能防止后期过拟合。4.2 针对叶病小目标的增强策略mosaic、尺度与类别权重叶枯病的病斑是细长的条状普通锈病是密集的小圆点这两种目标在 640 分辨率下只占几十到几百个像素属于典型的小目标检测场景。Ultralytics 默认开启的马赛克增强对这类数据影响很大mosaic 把四张图拼成一张相当于变相缩小了目标尺寸对小目标检测能力反而是负优化。我习惯把mosaic从默认的 1.0 调低到 0.3同时把mixup设为 0。农作物的叶片纹理和背景相对固定mixup 混合出来的图像会让模型学到不真实的叠加纹理在真实田间场景下没有对应分布。在hyp.yaml里重点关注这几个参数hsv_h: 0.02 hsv_s: 0.5 hsv_v: 0.4 degrees: 5.0 translate: 0.1 scale: 0.5 fliplr: 0.5 mosaic: 0.3 mixup: 0.0degrees只给到 5 度而不是常见的 30 度。玉米叶本身的朝向是固定的标注框基本是竖长条旋转过多会让模型学到不存在的姿态分布。hsv_h色调扰动控制在 0.02 以内因为叶枯病和灰叶斑病的区别部分体现在颜色上色调扰动太大会把这两个类别混淆得更严重。处理类别不均衡可以给数据少的类别适当提高损失权重。YOLOv8 里可以通过给少数类增加采样概率来实现或者训练时用类别权重做加权损失。更省事的做法是在数据加载阶段对样本量少的类别做复制采样但注意不要直接复制同一张图的标注容易过拟合到具体样本上。4.3 准确率指标的解读mAP 与真实场景的偏差标题里的 95.7% 准确率常见口径是验证集上的 mAP0.5。这个指标代表 IoU 阈值 0.5 时的平均精度农业场景下模型只要把病害区域大概框出来就算对所以 0.5 的 IoU 要求并不苛刻。真正能说明问题的是 mAP0.5:0.95。它把 IoU 从 0.5 到 0.95 每隔 0.05 采样一次计算每个阈值下的 AP 再取平均对边界框的定位精度要求更严。如果这个值和 mAP0.5 差距超过 15 个百分点说明模型的框虽然能命中目标但边缘贴合度差。对病害检测来说框偏大一点影响不大但框得偏了太多后续做病斑面积估算时误差会被放大。训练结束后用集成工具验证一次yolov8 val \ modelruns/corn_leaf/exp1/weights/best.pt \ datadata.yaml \ imgsz640 \ conf0.25 \ iou0.45我建议把conf从默认的 0.25 往下调到 0.1 做一次评估看看低置信度区间里召回率的变化曲线。农业检测场景下漏检一个病斑比误检一个健康区域的代价更高尤其在病害早期识别场景中。如果下调conf后召回率明显提升而精度下降不多说明模型还有不少真实检测被 0.25 的阈值挡掉了部署时可以考虑用更低的置信度阈值搭配人工复核。5. 避坑指南人工标注叶病数据集的五个高频翻车点5.1 现象训练到一半 loss 突然升高loss 曲线在第 40 个 epoch 左右突然跳高之后又慢慢降下来。第一反应是学习率策略出问题但查了一圈发现是数据集里混入了少量损坏图片。原因MOSAIC 或随机裁剪时恰好裁到了损坏的图片区域加载进来的像素值是异常的反向传播时梯度产生尖峰。解决在训练前做一次完整的图片完整性校验不光检查文件头还要尝试用 OpenCV 或 PIL 实际解码。我在数据处理管线里加了一步cv2.imread后检查返回值是否为None的校验解码失败的图片直接移出训练集留下清单备查。5.2 现象验证集 mAP 高但实际拍摄效果差模型在验证集上跑到 0.94 的 mAP拿手机去田里拍一张识别效果惨不忍睹。原因训练集和验证集来自同一批拍摄数据光照条件、拍摄角度、品种背景都高度相似模型学到的是场景分布而非病害本质。解决划分数据集时按拍摄批次分组而不是随机划分。如果数据集的元信息里没有批次字段就把连续编号的图片视为同批次按区间切分。还有一种血泪经验验证集里至少留出一部分真正来自不同时间段或不同地块的图片哪怕只有几十张也能提前暴露泛化问题。5.3 现象COCO 转 YOLO 后部分图片没有 txt 文件转换脚本跑完发现 labels 目录下文件数比图片数少。原因COCO JSON 里某些图片确实没有标注。这个数据集混合了健康和病害的图片健康叶子的图片里可能只标注了叶子区域也可能完全没标。另一个原因是转换脚本里按anns_by_img遍历那些没有出现在 annotations 里的 image_id 直接被跳过了。解决核对 JSON 里images数组和annotations数组的 image_id 差集。对于完全没有标注的健康叶图可以保留空的 txt 文件作为负样本但要确认这些图和训练图片的分布一致不要出现整块不同的场景背景。5.4 现象叶枯病和灰叶斑病频繁互相误检混淆矩阵里叶枯病和灰叶斑病的互相误检率明显高于其他类别。原因两种病害在中后期的视觉特征确实相似都是叶片上出现不规则斑块。如果标注时没有统一标准同一病害可能在部分图上框了整片病区在另一部分图上只框了单个病斑模型学到的特征不一致。解决回看数据集的类别定义文档如果原始标注标准里对两者的边界有说明严格按照标准处理。训练层面适当降低hsv_h增强强度减少颜色扰动带来的误导。部署层面把这两类的置信度阈值单独调高宁可多输出一个不确定标记也不要武断给出错误确诊。5.5 现象小病斑完全检不出来锈病早期的小圆点在测试图上一个都没检出召回率接近 0。原因这些病斑只有 10×10 像素左右在 YOLO 的特征金字塔里对应的是最深层的小目标检测头特征信息弱加上训练时这些小目标在 loss 中的贡献占比低。解决分两条路走。第一条训练时把imgsz提到 960 或 1280增加小目标在特征图上的像素占比第二条做切片推理把大图切成 640×640 的块分别检测再拼回结果。后者在部署阶段通用性更强可以做成独立推理模块。6. 三个验证技巧把模型从「跑通」推到「可信」6.1 用五折交叉验证替代单次划分单次划分的结果有运气成分。我建议在这个数据集上做五折交叉验证。把 10884 张图按批次分成五份轮流取一份做验证集其余四份训练。训练五轮后统计每折的 mAP 和标准差。如果标准差超过 1.5 个百分点说明模型对划分方式敏感此时要检查批次之间是否有明显分布差异比如某一批全是清晨拍摄的图片露水多背景暗。五折里每一折的模型结果还可以用来做模型集成推理时把五个模型的预测结果做投票能进一步压住单模型的波动。6.2 按病害严重程度分级评估只报总体准确率是不够的。实际业务里早期病害和晚期病害的检测难度完全不同。把验证集里标注的 bbox 按面积占比分成三档小目标占比 2%、中目标2%~10%、大目标10%分别统计 mAP。for size_group, subset in groups.items(): metrics model.eval_subset(subset) print(f{size_group}: mAP0.5 {metrics.map50:.4f})我在实际项目中经常看到总体 mAP 0.92 的模型切到小目标那一档掉到 0.7 以下。这类分析能直观告诉你模型到底有没有真正学会识别早期病斑还是只对肉眼可见的重度病变有效。6.3 用切片推理提升小目标召回最后一个技巧针对锈病早期检测。整图推理时模型需要在 640×640 的视野里找到 10×10 像素的小点难度确实大。策略是把原图按 50% 重叠切成 512×512 的块每个块独立送入模型推理再根据块在图中的偏移量把预测框映射回原图坐标。重叠区域产生的重复框用 NMS 合并。这个方法能把锈病小目标召回率提升 8 到 12 个百分点代价是推理时间变成原来的 4 倍左右。农业场景的检测多数不是实时任务切片推理的耗时完全可接受。从那以后我每次拿到新的目标检测数据集都强制自己先跑一遍分布统计和完整性问题筛查再进训练流程。很多翻车问题根源都在数据准备阶段而不是模型结构。这套玉米叶病数据集在标注质量和规模上属于上乘值得按完整流程跑一遍希望帮到你。本文还有配套的精品资源点击获取