水稻叶病虫害分类数据集与YOLO11cls图像分类实战:从数据体检到训练调优
简介水稻叶病虫害分类数据集资源包以PDF文档形式交付主要面向农业图像分类与YOLO11cls算法训练场景适合需要真实场景数据支撑的开发者或农业AI研究者使用。内容涵盖15000张水稻叶片图像覆盖细菌性叶枯病、褐斑病、健康叶片、叶瘟病、叶鞘腐病、穗颈瘟、稻飞虱、纹枯病等10个类别每个类别均有独立文件夹标注结构规范可直接用于YOLO分类流程训练。由于原始图片体量较大资源包以1个PDF文件交付大小2.32MB文档内附数据集详细介绍、类别说明、目录组织方式及网盘获取链接方便按需下载原始图片。已有318人学习浏览除分类数据外还附赠YOLO11cls一键训练脚本及博主训练结果日志可作为基准结果参考帮助快速上手训练、验证模型效果节省数据整理与环境配置时间。1. 水稻叶病虫害分类数据集 YOLO11cls为什么我把这事押在“图像分类”而不是“检测”上下午接到需求给植保无人机做水稻叶病虫害识别手上只有一批整理好的照片没有逐张画框的标注。如果硬上检测模型光是目标框标注就能拖垮整个排期。水稻叶病虫害分类数据集的正确打开方式是先把“这片叶子有没有病、是什么病”用图像分类解决15000张图按类别文件夹归好再配一套 YOLO11cls 一键训练脚本当天就能出一个可复现的基线模型。这套方案适合三类人做农学毕设的学生、给植保团队做 POC 的算法工程师、以及手里有大量无标注田间照片但不想花钱标框的从业者。分类任务不追求“病斑在第几片叶”只输出整体概率分布对巡田拍照的场景反而是更稳的切入点。2. 水稻叶病虫害数据集先做“体检”再谈训练15000张图不是越多越好拿到一份“整理好”的数据集第一件要做的事不是立刻训练而是把类目结构、数量分布、图片质量全部过一遍。绝大多数翻车现场都发生在跳过这一步的时候训练跑到一半发现某个类全是模糊图或者验证集和训练集有重复图片准确率虚高得离谱。2.1 类目文件夹该怎么排train/val 划分的常见做法数据集既然已经按“对应分类文件夹整理”那我一般不会再去动它的原始目录而是另建一个干净的数据根目录用软链接或复制脚本把原始图片按 8:1:1 拆成训练、验证、测试三份。拆分的单位是“类”不是“图片”否则某类图片会随机漏进验证集导致训练时少学了这类特征。常见的水稻叶病虫害类目不会太细以稻瘟病叶瘟、稻白叶枯病、胡麻叶斑病、细菌性条斑病、稻曲病、健康叶为主。类目太少模型学不到区分度类目太多单类样本不够分15000 张图对应 5 到 10 个类是相对舒服的区间。目录结构我一般这样排data/ricecls/ ├── train/ │ ├── rice_blast/ # 稻瘟病叶瘟 │ ├── bacterial_blight/ # 稻白叶枯病 │ ├── brown_spot/ # 胡麻叶斑病 │ ├── bacterial_stripe/ # 细菌性条斑病 │ ├── rice_kernel_smut/ # 稻曲病 │ └── healthy/ # 健康叶 ├── val/ │ └── ...与 train 同名子目录 └── test/ └── ...与 train 同名子目录这个结构就是 ImageNet 风格YOLO11cls 原生支持不需要额外写 label 文件。很多从 YOLOv5、YOLOv8 迁移过来的同学会习惯性地先写 data.yaml实际上分类任务根本不需要 yaml类名直接从子目录名读取。网上搜“yolov8 训练自己的数据集”时绝大多数教程讲的是检测任务检测才需要 yaml 标注框路径分类链路完全不同这个区别先记住。划分脚本可以这样写按类分层抽样保证每个类在三份子集中的比例一致import random import shutil from pathlib import Path random.seed(42) src_root Path(原始分类文件夹) # 已经按类归好的图 dst_root Path(data/ricecls) ratios {train: 0.8, val: 0.1, test: 0.1} for cls_dir in src_root.iterdir(): if not cls_dir.is_dir(): continue imgs list(cls_dir.glob(*.jpg)) list(cls_dir.glob(*.png)) random.shuffle(imgs) n len(imgs) # 按比例切成三段 n_train int(n * ratios[train]) n_val int(n * ratios[val]) splits imgs[:n_train], imgs[n_train:n_trainn_val], imgs[n_trainn_val:] for split_name, split_imgs in zip(ratios.keys(), splits): out_dir dst_root / split_name / cls_dir.name out_dir.mkdir(parentsTrue, exist_okTrue) for img_path in split_imgs: # 复制而不是移动原始数据始终留一份后悔药 shutil.copy2(img_path, out_dir / img_path.name) print(f{cls_dir.name}: {split_name} - {len(split_imgs)})这段脚本的精髓在shutil.copy2复制而不是移动源数据保留划分错了还有后悔药。random.seed(42)固定随机种子保证每次执行划分结果一致模型对比时不会因为数据划分不同而说不清是谁的功劳。另外我在 glob 时同时接了 jpg 和 png但没处理大写后缀如果原始文件夹里有.JPGglob 会漏掉建议在脚本里加上img_path.suffix.lower()的判断把后缀统一转小写再做匹配。2.2 数据体检脚本查数量、查损坏、查模糊、查重复划分完成之后接下来的体检项目是四件事每个类到底有多少张图、有没有打不开的损坏文件、有没有低分辨率或模糊到无法辨认的图、训练集和验证集之间有没有重复图。最后一项很多人忽略但重复图混进验证集造成的准确率虚高是最典型的“自己骗自己”。下面是一段更直白的体检脚本贴在训练之前跑一遍from PIL import Image from pathlib import Path import numpy as np from collections import defaultdict root Path(data/ricecls) stats defaultdict(lambda: {count: 0, broken: 0, blurry: 0, small: 0}) for split in [train, val, test]: for cls_dir in (root / split).iterdir(): if not cls_dir.is_dir(): continue for img_path in cls_dir.glob(*.*): key f{split}/{cls_dir.name} stats[key][count] 1 try: img Image.open(img_path) img.verify() # 只检查文件完整性不加载全部像素 w, h Image.open(img_path).size except Exception: stats[key][broken] 1 continue if min(w, h) 224: # 低于 YOLO11 默认输入尺寸 stats[key][small] 1 # 用 Laplacian 方差判断模糊度低于阈值视为模糊 gray np.array(Image.open(img_path).convert(L).resize((224, 224))) lap np.abs(np.diff(gray.astype(np.float32), axis0)) \ np.abs(np.diff(gray.astype(np.float32), axis1)) blur_score float(np.mean(lap)) if blur_score 20: stats[key][blurry] 1 for k, v in sorted(stats.items()): print(f{k}: count{v[count]} broken{v[broken]} small{v[small]} blurry{v[blurry]})这里要说明几个细节。img.verify()只读文件头不会把整张图加载进内存体检 15000 张图也就几十秒的事。模糊度的计算没有用 OpenCV 的 Laplacian 算子而是直接对灰度图做差分效果等价但少一次依赖安装阈值 20 是我凭经验拍的值如果原始照片本身偏柔光这个阈值要往下调到 10 附近否则会误杀大量正常图。判断完体检结果后我会把 blurry 和 broken 的文件单独移到一个quarantine/目录不删除等人工确认后再清理。除了这些我还会顺手统计每个类的平均尺寸和宽高比。如果数据集中有大量宽高比超过 2:1 的横幅照片而训练时用的是正方形输入YOLO11cls 默认的 resize 会把长边压扁叶片纹理细节直接丢失。对这种图我一般先做个中心裁剪或按长边补齐而不是直接丢给模型。2.3 类别不均衡怎么处理先别急着上采样体检报告出来后最常见的坏消息是某个病类别只有 60 张图而健康叶有 6000 张。第一反应是过采样复制这能解决“数量”问题但解决不了“多样性”问题——60 张图复制 20 遍模型记住的是同一批叶子。我的顺序是先合并语义相近的细分类比如把“稻瘟病急性型”和“稻瘟病慢性型”合并成“稻瘟病”看类目数是否维持在一个合理范围。如果合并后仍然是长尾分布再做离线数据增强旋转、翻转、亮度扰动可以给少数类补充变化最后才是用 class_weight 或者干脆接受现实把少数类的任务目标从“分类”降级为“异常检测”只判有病没病。公开数据集如 PlantDoc 这类植物病害集可以拿来做预训练或补充样本但要小心域差异PlantDoc 的叶片背景和拍摄角度跟无人机巡田照片差别很大直接混训会拖低真实场景的准确率。用这些外部数据做先验、在自己的 15000 张图上微调是更稳的用法。3. YOLO11cls 选型与数据适配为什么选它目录怎么接3.1 cls 分支和 det 分支的差异分类不是检测的简化版YOLO 系列从 YOLOv8 开始就同时提供检测、分割、姿态、分类四条任务分支YOLO11cls 是延续这个设计的最新分类分支。很多人以为 cls 是 det 去掉了框训练会简单很多这个理解不准确。检测任务的优化目标是“框的位置 框内类别”分类任务的优化目标是“整张图属于哪个类”前者要求模型关注局部区域后者要求模型理解全局纹理特征。水稻叶病虫害分类里稻瘟病和胡麻叶斑病在视觉上都是“叶片上有斑点”区别在于斑点的分布密度、边缘形状和颜色梯度这些特征需要模型在全局范围内做对比跟检测模型在框内做判别根本不是一回事。YOLO11cls 的优势在于它复用了一整套训练工程能力自动混合精度、早停、学习率调度、结果可视化、ONNX/TFLite 导出这些从 YOLOv8 时代就验证过的能力让 15000 张图的训练变得非常省心。模型权重按体积分为 n/s/m/l/x 五档水稻叶这种背景相对单一的图像分类任务n 档往往就够先跑通再考虑换更大的模型。3.2 用脚本把任意图片目录接进 YOLO11clsYOLO11cls 读取数据的逻辑是你把根目录传给它它自动扫描根目录下的子文件夹名称作为类名然后在每个类文件夹里读取图片。也就是说只要你把数据集整理成前文那种 train/val/test 结构就能直接开训不需要任何 label 文件。这一点和 YOLOv8 训练自己的数据集检测时必写 data.yaml 的流程完全不同最容易带偏人。为了让“任意图片目录”都能一键接入我会先跑一段目录自检脚本把不符合规范的子目录在训练前暴露出来from pathlib import Path root Path(data/ricecls) for split in [train, val]: split_path root / split if not split_path.exists(): raise SystemExit(f缺少 {split} 目录) for cls_path in split_path.iterdir(): if not cls_path.is_dir(): # 类目录下混入了文件YOLO 会跳过它导致该类完全没训练 print(f[警告] {split}/{cls_path.name} 不是文件夹) continue img_count sum(1 for p in cls_path.glob(*.*)) if img_count 0: print(f[警告] {split}/{cls_path.name} 是空目录) elif img_count 50: print(f[注意] {split}/{cls_path.name} 只有 {img_count} 张容易欠拟合)这段脚本不做修复只做提醒因为修复逻辑改名、移动、删空目录因数据而异强行自动化反而可能把原始数据弄坏。跑完之后确认类和目录名没有中文或特殊空格我一般统一换成小写英文加下划线。类名会被 YOLO 写进labels.txt作为输出的可读名称中文类名在 Linux 上训练没问题但导出模型后在 Windows 工业机上做推理时控制台编码不统一容易输出乱码到时候排查半天才发现是编码问题。这个坑后面还会细说。3.3 环境准备一条命令装完但版本要对齐YOLO11cls 的训练环境不复杂一条pip install ultralytics就能装齐但有两个前置条件容易忽略PyTorch 版本和 CUDA 版本要匹配且 Ultralytics 包版本不能太老。我的建议是新建一个干净的 conda 环境Python 3.10 或 3.11 都行先安装 PyTorch 再装 Ultralytics顺序别反。conda create -n ricecls python3.11 -y conda activate ricecls pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics python -c from ultralytics import YOLO; print(YOLO(yolo11n-cls.pt))最后一行如果能在屏幕上打印出模型结构说明安装成功。首次运行会联网下载yolo11n-cls.pt预训练权重大概 5MB 左右如果公司内网限制外网提前手动下载放到当前目录就行。CPU 也能训练但 15000 张图 224 分辨率下CPU 一个 epoch 可能要跑十几分钟而一张普通消费级显卡只要 1 到 2 分钟。没有 GPU 的情况下先把 imgsz 降到 160、epochs 减到 30 试通流程比硬等一个完整训练要划算得多。4. YOLO11cls 一键训练脚本从命令行到 5 个必调参数4.1 脚本主干把数据集路径、模型参数和训练配置串起来“一键训练脚本”的“一键”不是指跑一个黑盒命令而是指把数据路径检查、模型选择、训练参数、结果输出串成一条可重复执行的流水线。我写的脚本长这样改动最小、可读性最高# train_rice_cls.py from ultralytics import YOLO from pathlib import Path def main(data_root./data/ricecls, model_sizen, epochs60, imgsz224, batch16): data_root Path(data_root) # 训练前先做存在性检查避免训练到一半才发现路径写错 for split in [train, val]: if not (data_root / split).exists(): raise FileNotFoundError(f{data_root / split} 不存在请先完成第 2 章的数据划分) # 按型号选预训练权重 weight_map {n: yolo11n-cls.pt, s: yolo11s-cls.pt, m: yolo11m-cls.pt} model YOLO(weight_map[model_size]) # 训练参数集中管理便于后面网格搜索 results model.train( datastr(data_root), epochsepochs, imgszimgsz, batchbatch, patience15, # 15 个 epoch 没有改善就早停 lr00.01, # 初始学习率SGD/AdamW 下语义不同 optimizerauto, # 让框架自动选优化器 seed42, workers4, # Windows 下若报错改成 0 projectruns/ricecls, # 所有输出集中到一个项目目录 namefyolo11{model_size}-{imgsz}, exist_okTrue, # 重复跑同名实验时覆盖不额外加后缀 ) if __name__ __main__: import argparse parser argparse.ArgumentParser() parser.add_argument(--data, default./data/ricecls) parser.add_argument(--model, defaultn, choices[n, s, m]) parser.add_argument(--epochs, typeint, default60) parser.add_argument(--imgsz, typeint, default224) parser.add_argument(--batch, typeint, default16) args parser.parse_args() main(args.data, args.model, args.epochs, args.imgsz, args.batch)这里每个参数背后都有取舍。patience15让模型在验证集准确率连续 15 个 epoch 不涨时自动停止避免最后一个 epoch 从头跑到尾的无效等待。exist_okTrue是最容易被忽略的Ultralytics 默认会在重名实验名后面加_2后缀如果你用 crontab 或循环脚本自动化跑实验积累的临时目录会越来越多显存排查时根本分不清哪个是哪个。设为True后重复运行会覆盖同名目录保持输出整洁。4.2 5 个必调参数epochs、imgsz、batch、lr0、patience很多人拿到训练脚本第一反应是把 epochs 拉到 300觉得练得越多越准。这个思路在水稻叶病虫害分类任务上不成立。15000 张图的规模60 到 80 个 epoch 已经足够模型收敛再多就会开始背训练集。我给一组参考值基于个人经验适用于叶片特写占主体的数据参数推荐值范围说明epochs50 - 80配合早停使用看 val 曲线决定是否延长imgsz224 起步可试 320224 是速度和精度的平衡点320 更稳但显存占用翻倍batch16 - 32小显存从 8 起步n 模型 16 大约占用 6G 显存lr00.01auto 优化器下适用改用 AdamW 时降到 0.001patience10 - 20越大越耐心越小越早停建议 15imgsz的坑最隐蔽。很多人以为它只是输入分辨率其实它还决定了训练时的随机裁剪尺度。YOLO11cls 会把输入图随机缩放后裁剪到imgsz如果原始图片短边只有 300 像素而你设了 640 的imgsz模型看到的是被强行放大的模糊图特征反而学得更差。对于 15000 张图的叶片数据224 够用想提精度先加augmentTrue让模型看更多变换而不是一味抬高分辨率。batch的设置不只受显存影响还受类别均衡度影响如果你的数据集有少数类batch 太小会导致每个 batch 里根本没有少数类的样本梯度更新方向完全被多数类主导这时候要把 batch 尽量调大让每个 batch 尽可能覆盖更多类。4.3 训练完看什么结果文件的内容与验证命令训练结束后脚本会在runs/ricecls/yolo11n-224/下生成一系列产物重点看四个东西weights/best.pt是验证集上表现最好的权重last.pt是最后一个 epoch 的权重results.csv记录了每个 epoch 的指标曲线confusion_matrix.png是多分类混淆矩阵图。best.pt和last.pt的区别必须说清如果早停被触发last.pt 停在最后一个 epoch而 best.pt 停在 val acc 峰值处很多人的部署模型用的是 last.pt结果准确率比训练时低一大截就是这个原因。验证命令也由同一个脚本流程串起来yolo classify val modelruns/ricecls/yolo11n-224/weights/best.pt data./data/ricecls imgsz224 batch16跑完会输出 val acc top1 和 top5 两个指标。top1 就是要重点盯的它代表“模型对一张水稻叶图片给出的最高概率类别恰好是真实类别”的比例。如果 top1 在验证集上有 85% 以上这个模型已经具备基本的巡田筛查能力如果只有 60%别急着调参先回头把第 5 章的排查清单过一遍大概率是数据问题而不是模型问题。5. 训练与排查分类准确率上不去的 4 类常见翻车现场这一章写的是我踩过或帮别人排过的最常见的坑。每条按“现象、原因、解决”三件套展开你对照自己的日志就能定位问题。5.1 某一类准确率塌方其他类都正常现象混淆矩阵图上某一个类别的对角线数值特别低被错误地分到另一个视觉相似的类里。比如胡麻叶斑病被大量预测成稻瘟病。原因两类视觉特征重叠且其中一类样本数量少。胡麻叶斑病的斑点小而密稻瘟病叶瘟的斑点呈梭形但在低分辨率或光照不均的照片上二者差异被抹平。解决先看这个类的样本数是否明显低于均值如果是回到第 2.3 节做合并或过采样如果数量正常则问题出在类本身的视觉区分度这时可以查一下这两个类对应的训练图片看看是不是标注本身把接近的图片分错了。我遇到过一次真实情况是“细菌性条斑病”和“稻白叶枯病”在早期症状上几乎无法肉眼区分人工标注的一致性都只有 80%模型再努力也就到瓶颈。这时候最好的解决是合并成一个大类“细菌性叶部病害”把任务重新定义为四分类而不是硬扛五分类。5.2 训练 loss 一直在降但验证集准确率涨到某个点后开始掉现象终端输出的 train loss 曲线非常漂亮持续下降但 val acc 到 85% 附近就停住再往后开始缓慢下跌。把 results.csv 拉出来画曲线能看到明显的 gap 拉大。原因过拟合。模型开始记住训练集里的背景纹理、光照条件甚至拍摄设备的 EXIF 特征在没见过的验证图上自然吃瘪。解决先把 epochs 交给早停处理确认 patience 生效然后加大weight_decay从默认的 0.0005 提到 0.001再检查训练时是否正确开了augmentTrue如果为了追求“训练速度”关掉了增强过拟合提前出现是必然结果。这里要特别注意调参时一次只动一个变量。我见过把 weight_decay、epochs、augment 三个同时改掉结果模型精度反而下降都不知道是哪个改动导致的。5.3 CUDA out of memory 或训练非常慢现象训练在第一个 epoch 跑到一半直接报CUDA out of memory或者全程 GPU 利用率只有 40% 但显存已被占满。原因batch 和 imgsz 乘积超过显存容量workers 设得太高导致 CPU 预处理跟不上GPU 频繁等待。解决按显存减半调整。batch 从 16 减到 8imgsz从 224 减到 160二者对显存的影响是乘法关系改一个不够就两个一起改。再不行换yolo11n-cls.pt是最小的分类权重。workers4在 Windows 上经常因为多进程数据加载报错改成workers0可以绕过去代价是每个 epoch 的数据加载时间变长但至少不报错。另外检查一下是不是同时开着其他占显存的进程nvidia-smi看一眼我至少两次发现是隔壁同事的训练任务占着显存。5.4 训练集上指标很好一到无人机实拍照片就崩现象验证集准确率 88%把模型部署到无人机拍摄的画面里对画面中心裁剪出的叶片图预测准确率跌到 60% 以下。原因域差异。训练照片是近景特写叶片大、背景干净无人机照片是俯拍叶片小、背景有泥土和水面反光模型的注意力被背景干扰。如果数据集里有大量近景特写YOLO11cls 学到的可能不完全是叶片纹理而是“背景健康的深色斑点有病的”这种捷径。解决训练前对图片做随机背景替换增强把叶片从原图中抠出来贴到泥土、水面、水泥地等不同背景上或者在推理端先做 patch 裁剪把无人机大图切成 224 的块再用模型逐块分类。推理端的 patch 滑动步长设为宽度的 50%可以做重叠覆盖减少漏检。这一步是 POC 走向落地的必经之路越早发现越省钱。5.5 训练中断了想续上结果但不记得参数了现象训练到第 37 个 epoch 时断电或手动 CtrlC重跑完整脚本则前 37 个 epoch 白费。原因没有使用断点恢复机制Ultralytics 的resume参数没有启用。解决在训练脚本里预留resumeTrue的入口让它自动从runs/ricecls/yolo11n-224/weights/last.pt续训。注意 resume 只认last.pt不认best.pt这是框架的行为不要改动。续训前先检查last.pt文件的大小如果只有几百字节说明在 epoch 中途中断且没有来得及写入 checkpoint这种情况没有后悔药只能重新跑所以我的习惯是给训练命令加nohup或screen兜底防止终端关闭导致进程被杀。6. 从跑通到能用混淆矩阵、误判图与分类阈值调优模型跑通只是起点。我每次训练完的第一步不是看 acc而是打开confusion_matrix.png找出对角线之外哪两块颜色最深——它们才是真正决定这个模型能否上田间的关键。误判最多的一对类决定了你的类目设计是否合理。比如胡麻叶斑和稻瘟病怎么都分不开合并类目比疯狂调参更实用。第二步是导出误判图。用 best.pt 对验证集做一次批量预测把预测错误的图片按“真实类别-预测类别”分组存到本地目录人眼快速扫一遍。这一步能发现很多指标看不到的问题某类误判图全是模糊照片说明数据质量拖了后腿某类误判图是清晨背光拍摄的暗图说明训练集里缺少低光照样本需要在数据层面补。第三步是调整分类阈值。YOLO11cls 输出的probs.top1conf不是校准过的概率直接把最大值作为置信度容易给低质量图一个虚高的分数。我的做法是在推理脚本里设置一个 0.7 的阈值低于这个值统一归类为“待人工复核”宁可让无人机多拍几张也不要让错误判断混进病害统计报表。阈值定多少取决于你对漏报和误报的容忍度病害筛查场景下我倾向保守。部署到边缘设备时用model.export(formatonnx)转成 ONNX再按目标平台量化成 int8 TFLite推理延迟能从几十毫秒降到个位数毫秒。我习惯在交付前把整条链路做成一个 test 脚本输入 50 张训练中没见过的实拍图输出每张图的类名、置信度和标注判断人工核对一遍再决定是否工程化。这个习惯帮我挡掉过至少三次“验证集 90% 但现场零可用”的尴尬。希望帮到你。本文还有配套的精品资源点击获取