基于YOLO11cls的水稻叶病虫害图像分类:15000张图数据整理与一键训练实战指南
发布时间:2026/9/30 13:53:22来源:尧图网络
简介面向水稻叶病虫害分类实战场景这份资源提供了一套高可用数据集与训练配套方案适合算法工程师、农业AI研究者及目标检测/分类方向学习者直接使用。数据集包含15000余张真实场景水稻叶片图像覆盖细菌性叶枯病、褐斑病、健康叶片、叶瘟病、叶鞘腐病、窄褐斑病、穗颈瘟、稻飞虱、纹枯病、钨黄病毒病共10个类别并以对应分类文件夹形式整理标注质量较高可直接接入YOLO11cls等图像分类框架训练。资源同时附赠一键训练脚本与博主训练结果日志便于快速复现效果、调整参数。包体为单个PDF文件约2.32MB内含数据集详细介绍、目录结构及百度网盘获取方式PDF仅作为载体核心数据与训练脚本需按说明获取。该资源已有318人浏览学习适合作为水稻叶病害识别项目的数据基础或通用分类数据补充能显著降低数据采集和前期准备成本。1. 水稻叶病虫害分类15000张图配YOLO11cls从数据整理到一键训练一次走通做水稻叶病虫害识别这件事真正卡住人的往往不是模型选型而是数据本身。我见过不少人手里攒了一万多张田间照片文件夹里按病害类别分得也算整齐可一到训练就反复翻车要么YOLO11cls读不了目录结构要么验证集里混着训练集的原图导致指标虚高要么训练到一半被几张损坏的JPEG图打断。这个标题把“15000张图对应分类文件夹整理YOLO11cls一键训练脚本”打包在一起正好对应一条完整落地路径先把数据按分类文件夹的规矩收拾干净再用Ultralytics的YOLO11cls分类模型跑通训练最后拿精度的验证结果反推数据清洗是否到位。适合手里已经有水稻叶图片、想尽快出一个可用分类模型的从业者也适合第一次接触yolo分类任务、需要一份可照抄脚本的新手。2. 先按分类文件夹把数据立起来15000张图的目录、拆分与清脏2.1 用train/val/test三级目录匹配YOLO11cls的读取规则YOLO11cls虽然是Ultralytics体系里的分类模型但它不读VOC或COCO那种标注文件而是直接从一个标准的图像分类目录里按类别文件夹读取图片。目录结构长这样rice_leaf_dataset/ ├── train/ │ ├── rice_blast/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ ├── rice_brown_spot/ │ ├── rice_leaf_healthy/ ├── val/ │ ├── rice_blast/ │ ├── rice_brown_spot/ │ ├── rice_leaf_healthy/ └── test/ ├── rice_blast/ └── ...关键点是train、val、test这三个一级目录下面每一类病害单独建一个文件夹文件夹名就是类别名。YOLO11cls训练时会递归扫描data参数指向的目录自动把train/val/子目录名收集成类别清单。很多人第一坑就踩在这里model.train(datarice_leaf_dataset/train)把训练集目录直接传给data参数。这会让你训练时没有验证集最后YOLO11cls跑完只能拿最后一个batch的loss说事完全没法判断模型好坏。data参数应当指向包含train和val的同级父目录也就是rice_leaf_dataset这一层。类别文件夹的命名我一般用英文小写加下划线。虽然Ultralytics在Linux下能识别中文目录名但在Windows上配合Excel整理数据时经常出现编码不一致导致类别数量对不上。叶子病害的常见类别可以按“稻瘟病(rice_blast)、纹枯病(rice_sheath_blight)、稻曲病(rice_false_smut)、褐斑病(rice_brown_spot)、白叶枯病(rice_bacterial_blight)、健康叶(rice_leaf_healthy)”来分。建议八类以内超过八类之后标注成本和混淆概率会明显上升单靠15000张图去撑十个类别会有压力。2.2 一个整理脚本把散图按类别归位、剔重、抽val很多人的原始数据不是一上来就分好train/val的而是几十个文件夹散落在硬盘里有的按日期命名有的按田间地块命名里面还可能混着重复下载的图片和损坏文件。我一般会先扫描所有图片文件计算文件哈希来去重再用PIL验证图片能否正常打开最后按比例随机抽到train、val、test三个目录。import hashlib import os import random import shutil from pathlib import Path from PIL import Image IMG_EXTS {.jpg, .jpeg, .png, .bmp, .webp} def md5_of_file(path): h hashlib.md5() with open(path, rb) as f: for chunk in iter(lambda: f.read(8192), b): h.update(chunk) return h.hexdigest() def is_valid_image(path): try: with Image.open(path) as im: im.verify() return True except Exception: return False def organize(src_dirs, dest_root, target_counts): seen set() for src_dir in src_dirs: src_dir Path(src_dir) for img_path in src_dir.rglob(*): if img_path.suffix.lower() not in IMG_EXTS: continue if not is_valid_image(img_path): print(f损坏图片跳过: {img_path}) continue md5 md5_of_file(img_path) if md5 in seen: print(f重复图片跳过: {img_path}) continue seen.add(md5) # 假设来源目录名就是类别名按需改写映射 label src_dir.name label_dir dest_root / label label_dir.mkdir(parentsTrue, exist_okTrue) target_path label_dir / f{md5[:12]}{img_path.suffix} shutil.copy(img_path, target_path) # 再按类别做train/val拆分 for label_dir in (dest_root).iterdir(): if not label_dir.is_dir(): continue imgs list(label_dir.glob(*)) random.shuffle(imgs) n len(imgs) train_n int(n * 0.8) val_n int(n * 0.1) for i, img in enumerate(imgs): if i train_n: split train elif i train_n val_n: split val else: split test out dest_root / split / label_dir.name / img.name out.parent.mkdir(parentsTrue, exist_okTrue) shutil.move(str(img), str(out))这段脚本的逻辑分三段第一段做图片完整性校验坏图直接跳过并打印路径这是为了不让一张截断的JPEG在训练中途让整个进程崩掉。第二段用MD5去重因为同一张图在不同文件夹里重复出现很常见尤其从微信群、网盘收集的数据不剔重会导致训练集和验证集数据泄漏val指标虚高。第三段按8:1:1的比例把每个类随机拆成train、val、test目录。参数说明target_counts参数在实际用的时候可以单独传一个字典来约束每类目标数量比如少数类目标定为800张超过就按比例抽到上限random.seed要在脚本开头固定否则每次运行拆分结果都不一样。拆分时用copy而不是move能让你保留一份原始素材给后面人工复核标签留下后悔药。我一般会顺便输出每个类别的数量统计单类少于300张的要么补充图片要么在后续训练里提高该类权重。3. YOLO11cls选型与训练原理分类头怎么看这张叶子图3.1 为什么用yolo11s-cls而不是重新训ResNet/ConvNeXtYOLO11cls是Ultralytics YOLO11系列里专门做图像分类的模型。它和YOLO11检测模型共享主干网络区别在头部检测版本输出边框和类别概率分类版本则是把特征图经过全局池化后直接接全连接层输出对每个类别的置信度。对于水稻叶病虫害这种“一张图里主病害相对明确”的任务分类模型比检测模型更省事因为你不需要画边界框只要图片主体是叶子就能判断类别。有人会问既然分类任务这么简单为什么不用ResNet或ConvNeXt从头训练答案很简单15000张图对ResNet这种从零开始的模型来说远远不够。水稻叶病害的类间差异很细比如稻瘟病和褐斑病早期症状都是小斑点没有预训练权重的情况下模型很容易被叶脉纹理和光照方向带偏。YOLO11cls提供yolo11n-cls、yolo11s-cls、yolo11m-cls等不同体量的预训练权重它们在ImageNet上已经学过通用纹理特征。水稻叶病害属于细粒度图像分类直接继承这些通用特征再在自建数据集上微调收敛速度和最终精度都明显优于从头训练。以yolo11s-cls为例它的模型体积适中单卡RTX 3060上训练15000张图、100个epoch大约需要两到三个小时。如果换成yolo11n-cls会更快但精度通常会低一到两个百分点yolo11m-cls则对显存要求更高小batch下收益不明显。我的选择原则是先跑s级作为基线如果验证集acc达不到90%再考虑m级而不是一上来就上最大模型。3.2 训练前的两组关键参数imgsz与batch的取舍YOLO11cls训练时有几个参数几乎决定成败其中最容易被忽视的是imgsz。分类任务里很多教程直接照搬检测任务的imgsz640但对水稻叶这类目标imgsz224或256通常更合适。原因有两层一是分类模型在全连接层之前会做全局池化过大输入并不会显著增加细节信息反而让batch变小、训练变慢二是YOLO11cls的预训练权重默认输入是224直接用相近的尺寸可以减少预处理差异带来的分布偏移。我一般用imgsz224起步如果发现某些病斑面积特别小、肉眼都要仔细看才分得清再试试256。batch的选择直接看显存。训练时显存占用和imgsz成正比和batch成正比。RTX 3090或A5000可以跑到batch128普通16G显存用batch64比较稳8G显存就降到32。batch太小的直接后果是BN层的统计量抖动大验证集loss会上下乱跳。YOLO11cls默认的优化器是SGD带warmup初始学习率lr0默认是0.01。如果你只有几千张图、训练epoch设在50以内建议把lr0调到0.005因为小数据集上学习率太大容易在几个epoch后就在验证集上过拟合。数据集越大初始学习率可以越接近默认值。数据增强上YOLO11cls默认会做随机翻转、颜色抖动、尺度变换。不建议关掉这些增强但也不要盲目加mosaic。分类任务里的mosaic会把四张不同病害的叶子拼在一起让模型学到“一张图里同时存在多种病斑”的假规律反而干扰单病害分类。保留默认的hsv_h、hsv_s、hsv_v、fliplr、scale这些就可以了重点还是把数据本身洗干净。4. 一键训练脚本拆解数据检查、训练、导出权重全流程4.1 train.py 的完整代码与关键参数说明一键训练脚本的核心不是把yolo classify train命令敲一遍而是把数据校验、类别统计、模型初始化、训练、结果归档串成一条流水线。下面这份train.py是我常用的模板保存到项目根目录后直接python train.py就能从零跑到出权重。import argparse from pathlib import Path from collections import Counter from ultralytics import YOLO def check_dataset(data_dir: str): data_dir Path(data_dir) train_dir data_dir / train val_dir data_dir / val for split_dir in (train_dir, val_dir): if not split_dir.exists(): raise FileNotFoundError(f缺少 {split_dir.name} 目录: {split_dir}) class_counts {} for split in (train, val, test): split_path data_dir / split if not split_path.exists(): continue counts Counter() for label_dir in split_path.iterdir(): if label_dir.is_dir(): counts[label_dir.name] len(list(label_dir.glob(*))) class_counts[split] counts print(f[{split}] 类别数: {len(counts)}, 总图数: {sum(counts.values())}) train_counts class_counts[train] min_train min(train_counts.values()) if min_train 50: print(f注意: 训练集中最少的类别只有 {min_train} 张建议补充数据或用类别权重。) return data_dir def train(args): data_dir check_dataset(args.data) model YOLO(args.weights) model.train( datastr(data_dir), epochsargs.epochs, imgszargs.imgsz, batchargs.batch, lr0args.lr0, optimizerSGD, patience15, seed42, projectargs.project, nameargs.name, pretrainedTrue, verboseTrue, ) def main(): parser argparse.ArgumentParser() parser.add_argument(--data, typestr, defaultrice_leaf_dataset) parser.add_argument(--weights, typestr, defaultyolo11s-cls.pt) parser.add_argument(--epochs, typeint, default100) parser.add_argument(--imgsz, typeint, default224) parser.add_argument(--batch, typeint, default64) parser.add_argument(--lr0, typefloat, default0.01) parser.add_argument(--project, typestr, defaultruns/classify) parser.add_argument(--name, typestr, defaultrice_leaf_exp1) args parser.parse_args() train(args) if __name__ __main__: main()这段脚本有个容易被新手忽略的设计check_dataset()在训练前强制扫描目录结构如果当前路径下没有train和val直接抛出FileNotFoundError。比让Ultralytics自己报“Dataset not found”要友好得多因为后者往往出现在训练跑了半天、日志刷了一大屏之后浪费的可是实打实的时间。类别统计被我放在同一个函数里打印出来的min_train数值能提前警告少数类问题。参数说明patience15表示验证集指标连续15个epoch不提升就提前停止这能避免训练后期白跑几十个epoch。seed42固定随机种子让每次实验可以复现。pretrainedTrue会用指定的.pt权重初始化网络不传这个参数时默认从网络下载对应版本的预训练权重。project和name合起来决定训练结果输出到runs/classify/rice_leaf_exp1/里面存了每轮的权重、loss曲线和验证指标。4.2 在命令行里用yolo classify train直接复现的等价写法有些人不想维护Python脚本只想要一条命令跑起来那可以直接用Ultralytics的命令行入口yolo classify train \ data/path/to/rice_leaf_dataset \ modelyolo11s-cls.pt \ epochs100 \ imgsz224 \ batch64 \ lr00.01 \ optimizerSGD \ patience15 \ seed42 \ projectruns/classify \ namerice_leaf_exp1这条命令和上面Python脚本的train()部分完全等价。差别在于命令行模式不会做训练前的目录检查所以你得自己在shell里确认数据集结构没问题。命令行参数基本涵盖了所有可调项cacheTrue这个参数值得补充它会把图片加载到内存或磁盘缓存里15000张图第一次读取时要几十秒缓存后每轮epoch都能节省大量IO时间尤其是从机械硬盘读图时效果立竿见影。命令行跑完后的输出物和Python脚本一样都在runs/classify/rice_leaf_exp1/下里面有weights/best.pt和weights/last.pt。best.pt是根据验证集指标挑出来的最优权重last.pt是最后一个epoch的权重。模型训练中途意外中断时last.pt就是后悔药用model YOLO(runs/classify/rice_leaf_exp1/weights/last.pt)重新加载后可以接着train(resumeTrue)继续训练。4.3 训练完怎么把best.pt用起来分类模型的推理比检测简单得多用Ultralytics内置的predict接口就能读单张图或整个文件夹from ultralytics import YOLO model YOLO(runs/classify/rice_leaf_exp1/weights/best.pt) results model.predict( sourcetest_images/, imgsz224, saveTrue, projectruns/predict, namerice_leaf_test, ) for r in results: top5 r.probs.top5 top5conf r.probs.top5conf print(图片:, r.path) for idx, conf in zip(top5, top5conf): print(f {model.names[idx]}: {conf:.4f})代码里r.probs.top5返回置信度最高的前五个类别索引top5conf是对应的置信度。分类任务里我一般不看top1而是看top5里有没有“第二个可能性接近真实答案”的类别。水稻叶病害很多症状在早期相互重叠模型给出“稻瘟病0.48、褐斑病0.42”这类结果时说明输入图片本身就存在歧义这类样本应该输出“待人工复核”而不是强行返回最大置信度类别。这个逻辑放进田间巡检系统里比单纯追求acc更能减少漏检带来的实际损失。5. 水稻叶分类训练的5个常见坑从数据泄漏到中文类名翻车5.1 train/val之间存在重复图val准确率虚高现象训练loss正常下降验证集acc第一轮就超过90%后续却几乎不涨最后val acc比人工目测的预期高一大截。原因数据收集阶段从多个渠道下载图片同一张图被不同来源重复保存文件名不同但内容相同。按文件名拆分train/val时重复图同时进训练集和验证集模型等于“看到过答案”。我遇到过一张稻瘟病叶片图以三个文件名出现在三个文件夹里train和val都包含它造成该类的验证指标干干净净但实际换批新图就掉5个点以上。解决在第2章的整理脚本里用MD5去重这一步必须在拆分train/val之前完成。更稳妥的做法是不仅算文件MD5还算图片内容的感知哈希(pHash)因为同一张图转一次格式或压缩后MD5会变pHash在缩放和轻微重压缩下也能识别为重复。15000张图用pHash全量比对Python跑一次大约十几分钟这个代价值得。5.2 个别类图片损坏导致训练随机中断现象训练到某个epoch时突然抛出OSError: image file is truncated或者PIL.UnidentifiedImageError程序整个退出之前的epoch全部白跑。原因田间用手机拍的图经过微信、网盘多次传输文件被截断或头部信息损坏。这类文件用常规图片查看器打开可能只有一张模糊的破图但训练时PIL读取到半截就抛异常。有时候OpenCV能读、PIL不能读或者反过来单靠一种库校验不够。解决在整理阶段就遍历所有图片用Image.open().verify()做完整性校验专门剔除截断的JPEG。如果图片损坏但确实只有这一张可以用OpenCV的cv2.imread()尝试补救读取能读出来就重编码一份完整的JPEG读不出来的直接记入broken.txt清单。训练脚本里我同时把YOLO的fragment_kb参数调高一点比如设成fragment_kb256它允许Ultralytics跳过部分损坏图片而不是崩溃属于保底措施。5.3 少数类样本太少val的recall长期上不去现象总acc做到93%但稻曲病、白叶枯病这类类别val的recall只有60%出头模型把大量患病叶判成健康叶。原因15000张图听着多分到八类以后每类平均不到2000张少数类可能只有几百张。在类别不均衡下SGD优化的模型会偏向样本量大的类别大类的梯度完全淹没小类。recall低比precision低更难处理因为漏掉病斑意味着田间防治机会错过。解决先按类别统计少数类少于500张就优先补充数据。实在补不了在train.py里手动传class_weights给少数类更高的损失权重让模型每看到一个少数类样本都“更认真”一点。还可以在数据增强层面给少数类做额外旋转、亮度扰动来增加样本量。注意不要直接复制少数类图片充数简单复制会加剧过拟合用轻微模糊或裁剪来制造新样本才有价值。5.4 中文目录或中文类名让脚本在Windows上直接报编码错现象Windows下脚本报UnicodeDecodeError或者Ultralytics训练结束后model.names里的中文类别名全部乱码预测时输出一堆\uXXXX。原因YOLO11cls在读取分类目录时会把类名写进训练配置和权重文件中文类名在不同Python版本和系统编码下行为不一致控制台输出和文件保存用的编码可能不同。PyTorch在Windows上还会受文件系统编码影响目录名含中文时会出现路径解析失败。解决类目录名统一用英文小写加下划线类别到中文名的映射单独放在一个labels_map.json里训练时不用只在做结果展示和报表时读取映射。JSON文件统一用utf-8编码写入。数据集的全部路径都不能有中文包括C:\Users\张三\...这种用户名带中文的情况把数据集放到D:\dataset\rice_leaf_dataset这种纯英文路径下可以避开绝大多数Windows编码坑。5.5 imgsz强行拉到640反而掉点现象网络教程都说分辨率越大看得越清于是把imgsz设成640结果训练时间翻倍val acc反而比224低了两个点。原因这是一个典型的玄学问题背后实际是分布失配。yolo11s-cls预训练权重在224下学到了敏感的特征尺度直接跳到640时模型看到的是被放大的叶片局部原来的病斑纹理尺度变了。加上batch被迫减半BN统计量不稳定损失函数的收敛路径完全变了。更大分辨率不一定能带来更多判别信息水稻叶病斑的关键特征往往在边缘轮廓和颜色分布而不是高分辨率细节。解决先走224跑一轮完整训练记得保存混淆矩阵找出真正分不清的类别对。如果混淆集中在稻瘟病和褐斑病这种小斑点和颜色相似的类再单独用256试一个实验控制其他参数不变对比val acc后再决定。不要一上来就追求高分辨率计算成本翻倍收益却要实验验证。YOLO11cls在训练结束后可以通过model.val(imgsz256)直接换分辨率评估已有的best.pt不必重新训练就能看出输入分辨率的影响。6. 训练结束别急着打包三个验证动作判断模型能不能下田6.1 用脚本重算每类precision/recall不看总acc训练日志里的acc是加权平均或总体平均对类别不均衡不敏感。我习惯训练结束后用best.pt单独跑一遍val集把每个类别的precision、recall和F1逐项打出来from ultralytics import YOLO from collections import defaultdict model YOLO(runs/classify/rice_leaf_exp1/weights/best.pt) results model.val() # results.top1等指标是总体下面按类别汇总 counts defaultdict(int) correct defaultdict(int) for r in results: true_label r.path.split(/)[-2] pred model.names[r.probs.top1] counts[true_label] 1 if pred true_label: correct[true_label] 1 for label in sorted(counts): rec correct[label] / counts[label] print(f{label}: recall{rec:.3f} samples{counts[label]})这段代码通过r.path反推验证集图片所在的目录名当作真实标签再对比top1预测结果逐类计算recall。重点观察recall低于85%的类别这类别在田间的漏检率会直接放大因为商业落地场景中病叶漏判比误判危害更大。看到哪类低就先回数据层找原因而不是急着调模型。6.2 拿一小批“时间外”新图做鲁棒性抽查验证集和训练集来自同一批收集时间、同一批拍摄设备这本身就带来乐观偏差。我一般会在训练前把最近一周拍的新鲜叶图单独锁进一个holdout/目录绝不让它参与任何拆分和训练。训练结束后用best.pt去推理这些holdout图看精度是否掉得比val明显。如果val acc 94%holdout acc只有86%说明模型记住了数据收集时的环境特征而非病害本身需要回数据层检查光照条件、拍摄距离、背景复杂度是否太单一。通常的做法是增加不同地块、不同时间、不同手机拍摄的图片来稀释设备偏好。6.3 导出ONNX后的部署习惯模型验证通过后导出成ONNX格式能摆脱对PyTorch和Ultralytics版本的依赖同时方便集成到服务端推理或边缘盒子yolo classify export modelruns/classify/rice_leaf_exp1/weights/best.pt formatonnx imgsz224导出后的best.onnx文件可以直接用ONNX Runtime或OpenVINO加载推理推理单张图耗时通常能比PyTorch原生快一半以上。导出时需要注意固定imgszONNX模型一旦导出输入尺寸就必须一致训练时用224导出也用224不要想在部署端随便改。我的习惯是每次训练实验都同时保留best.pt和best.onnx并且把对应的类名映射文件作为交付物的三件套一起打包。验证集、holdout、ONNX导出都跑通之后这个模型才算真正达到“可以下田”的状态。教训是凡是没做holdout验证就上线过的模型无一例外都在现场返过工。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网