简介:面向农作物病虫害检测与图像分类项目的数据集资源,汇集腰果、木薯、玉米、番茄四大类作物的二十二个常见病虫害与健康状态类别,共一千张真实场景高质量图像。类别覆盖腰果炭疽病、木薯细菌性枯萎病、玉米草地贪夜蛾、番茄叶斑病等典型病虫害,文件夹按类别组织,区分清晰,免去自行整理标注的麻烦。资源采用文件夹区分目标类别,可直接用于 YOLO11cls 等图像分类算法训练;随包附带 YOLO11cls 一键训练脚本及博主训练结果日志,便于快速跑通流程、对照调参,同时类别定义规范,便于模型评估与迁移学习。资源为单个 PDF 文件,大小约 5.63MB,实际图像数据通过内附说明从百度网盘获取。目前已有63位学习者下载使用,适合需要病虫害识别数据集或希望快速上手 YOLO11cls 分类训练的开发者参考。
1. 目标分类的农作物病虫害数据集实战:1000张图整理出目录,YOLO11cls 一键训练能跑到什么程度
做农作物病虫害识别,绝大多数团队卡在第一步:手上有一批田间照片,也知道该用深度学习做目标分类,但不敢动手,因为总觉得“数据太少”“模型太复杂”。标题里的目标分类,指的是图像分类任务——输入一张叶片或果实的照片,模型输出“稻瘟病”“白叶枯”“健康”这一类别的概率,而不是画边界框的目标检测。1000张图听着不多,但在预训练权重加持下,足够训出一个能跑的初版模型;前提是数据按“每个类别一个文件夹”整理好,再用 YOLO11cls 配合一键训练脚本把流程固定下来。这篇笔记就按这套路径讲:目录怎么建、训练命令怎么写、参数怎么调、坑在哪,适合农业信息化开发者和做毕业论文的农学方向学生直接照着走。
2. 先把 1000 张图按分类文件夹整理好:YOLO11cls 不认标注文件,只认目录结构
很多人拿到数据集压缩包后第一件事是解压看图片,然后就开始找训练教程,结果训练时报错“数据集路径错误”。原因很统一:对 YOLO 系列的分类训练器来说,它不读任何.txt标注文件,也不读classes.txt,它只认目录结构。你文件夹怎么摆,模型就认为什么是类别。
2.1 标准目录结构:train 和 val 下面,每个类别一个文件夹
YOLO11cls 期望的数据集根目录长这样:
dataset_split/ ├── train/ │ ├── leaf_blast/ # 稻瘟病 │ ├── leaf_blight/ # 胡麻叶斑病 │ └── healthy/ # 健康 └── val/ ├── leaf_blast/ ├── leaf_blight/ └── healthy/根目录下只需要train和val两个文件夹,train和val内部再按类别各建一个文件夹。类别文件夹的名字,就是模型最后输出的类别名,会直接出现在训练日志、混淆矩阵和预测结果里。所以命名这件事不能随意:全部用小写英文字母或拼音加下划线,比如rice_blast、corn_rust,不要用中文、空格、括号,也不要出现class_1这种读完不知道是什么的代号。真到了部署阶段,你看着终端输出一个leaf_blast和一个A12,前者能立刻对应到病害,后者还得翻备注。
无论你是自己拍的图,还是从公开渠道下载整理好的水稻、玉米、烟草病虫害数据集,目录规范都是这一套。格式上建议统一成.jpg,训练前全部转一遍,省得混着.png、.bmp时有些带透明通道的图在缩放环节出怪问题。另外图片成像尺寸最好接近,别一半是 200 像素的手机缩略图,一半是 4000 像素的相机原图,虽然训练时会统一 resize,但分辨率差异过大会让模型学到“清晰度”这个和病害无关的特征。
2.2 训练前先写个统计脚本:每类多少张图,一眼看清
数据集整理成标准结构后,我一般不会直接开训,而是先跑一段统计脚本,确认每个类别在训练集和验证集里各有多少张。这个习惯救过我很多次,因为网上整理好的数据集,类别分布经常和 Readme 里写的不一致。
import os from collections import Counter from pathlib import Path root = Path('dataset_split/train') counter = Counter() valid_ext = {'.jpg', '.jpeg', '.png', '.bmp'} for cls_dir in root.iterdir(): if not cls_dir.is_dir(): continue # 跳过 classes.txt 这类文件 count = sum( 1 for img in cls_dir.iterdir() if img.suffix.lower() in valid_ext and not img.name.startswith('.') ) counter[cls_dir.name] = count for cls_name, num in sorted(counter.items()): print(f'{cls_name}: {num} 张')这段逻辑很简单:遍历train目录下的每个子目录,只统计常见图片后缀,忽略隐藏文件和非图片文件。脚本本身不复杂,但它能一次性暴露三个问题:类别文件夹名是不是规范、某个类别是不是一张图都没有、是不是混入了 Excel 表或.txt文件。跑完train再跑一遍val,两边都要看,因为训练集很丰富但验证集只有 20 张图,同样会让训练过程抖动得很厉害。参数上唯一要记得改的是root路径,换成dataset_split对应的实际路径即可。
2.3 自动划分训练集和验证集:按类别独立随机,别全局乱混
如果你的原始照片本来就是按类别分好文件夹的,但还没有划验证集,那就需要写一个划分脚本。核心原则是:每个类别单独随机切分,而不是把几百张图混在一起再随机抽。全局随机会导致类别比例在训练集和验证集里不一致,少数类可能在验证集里只剩一两张。
import random import shutil from pathlib import Path random.seed(42) # 固定随机种子,复现划分结果 src_dir = Path('raw_dataset') # 原始数据:类别文件夹/图片 train_dir = Path('dataset_split/train') val_dir = Path('dataset_split/val') split_ratio = 0.8 for cls_dir in src_dir.iterdir(): if not cls_dir.is_dir(): continue images = sorted( list(cls_dir.glob('*.jpg')) + list(cls_dir.glob('*.jpeg')) ) random.shuffle(images) cut = int(len(images) * split_ratio) for phase, parts in [('train', images[:cut]), ('val', images[cut:])]: out_dir = Path('dataset_split') / phase / cls_dir.name out_dir.mkdir(parents=True, exist_ok=True) for img in parts: shutil.copy2(img, out_dir / img.name) print(f'{cls_dir.name}: 训练 {cut} 张 / 验证 {len(images) - cut} 张')这里用shutil.copy2而不是shutil.move,原始数据保留一份完整备份,划分翻车时还有后悔药。split_ratio = 0.8是常见做法,1000 张图规模下训练集有 800 张,可以接受。注意我只统计了.jpg和.jpeg,如果你的数据里有 PNG 就补上glob('*.png')。另外random.seed(42)的一行不能省,不固定种子的话每次跑出来的验证集都不一样,模型对比就失去了意义。
2.4 下载的数据集必须复查三件事
从网上找数据集时,解压完直接训练是踩坑率最高的路径。第一件事是看目录层级:很多包解压后是dataset.zip/dataset/类别/图片的嵌套结构,多套了一层目录,data参数直接指进去会报“找不到 train”。第二件事是看类别名:有些整理者直接用中文名建目录,训练能跑,但后续的混淆矩阵、导出 ONNX 都会遇到编码问题。第三件事是抽查图片能否正常读取,用 Python 的PIL.Image.open逐张过一遍,遇到损坏的 JPEG 直接删除或记录路径,否则训练中途会偶发报错,且报错信息往往指向不明确。
3. YOLO11cls 选型与最小训练命令:先跑通,再谈优化
数据集目录就位之后,下一个问题是用什么模型。标题点名了 YOLO11cls,这个选择在 1000 张图的小规模农作物病虫害分类场景下是合理的,但你需要知道它到底是什么、边界在哪,否则会把分类任务做成检测任务的错觉。
3.1 cls 后缀的意义:YOLO11 家族里的分类分支
这里说的 YOLO11cls,是 Ultralytics YOLO11 系列里带-cls后缀的模型文件,比如yolo11n-cls.pt、yolo11s-cls.pt。它和做目标检测的yolo11n.pt不是同一个模型,网络结构最后的输出头不同:检测头输出边界框坐标和类别,分类头只输出一个类别概率分布。输入是一张叶片照片,输出是一个长度等于类别数的概率向量,取最大值对应的就是预测病害。
对我来说,在下面的场景里我会选它:单张图里只有一种主要病害,或者只需要判断“有病/没病、属于哪一大类”。因为分类模型只能回答“这张图整体上是什么”,你不能指望它告诉你“病斑在叶片的哪个位置”。如果你的实际需求是要同时标出多个病斑的位置和大小,那就该去做目标检测任务,用yolo11n.pt配合目标检测数据集格式。
如果你是按 YOLOv8 训练自己数据集的流程走过来的,YOLO11cls 的上手成本几乎为零,命令结构、数据集约定、输出目录都保持了一致的风格。YOLO11 相比 YOLOv8 主要在骨干网络和模块组合上有调整,对小模型来说推理速度更快一点,但对分类任务本身没有颠覆性变化,把它当作一个更现代的 YOLOv8cls 用即可。
3.2 最小训练命令:三条命令跑通第一个模型
先安装运行时环境,建议新建虚拟环境,避免把系统 Python 弄乱:
pip install ultralytics如果机器有 NVIDIA GPU,装完后可以先跑python -c "import torch; print(torch.cuda.is_available())"确认输出是True。安装 ultralytics 时会自动装 torch,但默认装的往往是 CPU 版或与当前 CUDA 不匹配的版本,这一步不检查,训练速度会慢到让你怀疑人生。
然后直接在项目根目录执行训练命令:
yolo classify train \ data=dataset_split \ model=yolo11s-cls.pt \ epochs=80 \ imgsz=224 \ batch=16这里逐一说明参数:data指向数据集根目录,也就是包含train/和val/的那一层,不是类别文件夹;model给一个.pt文件就是加载预训练权重做迁移学习,给 YAML 配置则是从头训练,1000 张图根本没有资格从头训,必须用预训练;epochs=80是训练轮数,小数据集最好给到 80 以上;imgsz=224是输入图片分辨率,分类任务默认 224 是速度与精度的平衡点;batch=16是批大小,取决于显存,显存不足就降到 8 或用yolo11n-cls.pt。首次运行会自动下载预训练权重到缓存目录,如果部署环境离线,需要提前在有网的机器上下载好权重文件拷过去。
3.3 训练产物在哪里,看什么文件判断有没有翻车
训练结束后,终端会打印每个 epoch 的损失和精度,同时在工作目录生成runs/classify/train/文件夹。里面要重点看四个东西:weights/best.pt是验证集精度最高的模型,最终部署用这个;weights/last.pt是最后一个 epoch 的模型,断点续训用这个;results.csv是每一轮的完整指标记录;confusion_matrix.png是混淆矩阵图,能直观看到哪两类病害互相认错。
results.csv里最值得关注的是val_loss、top1_acc、top5_acc。分类任务的 top1 就是预测概率最高的类别对不对;top5 是概率排名前五里有没有正确类别,类别数多的时候 top1 和 top5 差距大说明模型虽然没答对,但已经知道正确答案“接近”。如果你的类别只有三四个,top1 和 top5 差距不会太大,重点盯 top1 就行。判断翻车的标准不是训练集精度,训练集 99% 很正常;要看验证集 top1 是否在稳步上升、val_loss 是否先降后升——先降后升就是典型的过拟合信号。
4. 一键训练脚本:把数据集自检、参数解析、训练调用串成一个 Python 文件
很多所谓“一键训练”就是照着教程把 yolo 命令抄进.bat或.sh脚本,双击运行。这确实算一键,但它没解决新手最容易犯的错:路径写错、数据集结构不对、每个 epoch 之间没有反馈。我更习惯把自检逻辑写进脚本里,让脚本在训练前主动报错,而不是让训练器抛一个莫名其妙的 Traceback。
4.1 脚本职责划分:自检、训练、产出三件事各管各的
一个靠谱的一键训练脚本,至少要做到三件事:启动时检查数据集目录是否存在、train/val 是否齐全、每类图片数量是否大于某个阈值;解析训练参数,让 epochs、imgsz、batch 可以通过命令行覆盖;调用model.train()并把输出目录固定下来,避免每次训练默认生成train2、train3这样的混乱目录。下面是按这个思路写的最小完整脚本,适用于固定数据集目录、反复调参的日常实验。
import argparse from pathlib import Path from collections import Counter from ultralytics import YOLO VALID_EXT = {'.jpg', '.jpeg', '.png', '.bmp'} def count_images(cls_dir: Path) -> int: return sum( 1 for img in cls_dir.iterdir() if img.suffix.lower() in VALID_EXT and not img.name.startswith('.') ) def check_dataset(data_dir: Path) -> None: for split in ['train', 'val']: split_dir = data_dir / split if not split_dir.exists(): raise ValueError(f'缺少 {split} 目录: {split_dir}') class_dirs = [p for p in split_dir.iterdir() if p.is_dir()] if len(class_dirs) < 2: raise ValueError(f'{split} 下至少需要两个类别目录') for cls_dir in class_dirs: num = count_images(cls_dir) print(f'[{split}] {cls_dir.name}: {num} 张') if num == 0: raise ValueError(f'类别 {cls_dir.name} 中没有图片') def main(): parser = argparse.ArgumentParser(description='农作物病虫害分类一键训练') parser.add_argument('--data', type=str, default='dataset_split') parser.add_argument('--model', type=str, default='yolo11s-cls.pt') parser.add_argument('--epochs', type=int, default=80) parser.add_argument('--imgsz', type=int, default=224) parser.add_argument('--batch', type=int, default=16) parser.add_argument('--device', type=str, default='0') args = parser.parse_args() data_dir = Path(args.data) check_dataset(data_dir) model = YOLO(args.model) model.train( data=str(data_dir), epochs=args.epochs, imgsz=args.imgsz, batch=args.batch, device=args.device, patience=15, project='runs/classify', name='crop_pest', ) if __name__ == '__main__': main()这个脚本的重点在于check_dataset:它在调用训练器之前把数据集巡检完,哪一类没图、哪个目录缺失,直接以清晰的 Python 错误信息告诉你,而不是等你花十分钟启动训练后才在中途报错。model.train()里的patience=15表示验证集指标连续 15 个 epoch 不提升就早停,小数据集很容易过拟合,没有早停的话最后十几个 epoch 可能都在空转。project和name固定输出位置,下次再跑时会覆盖同名目录而不是生成crop_pest2,实验多了之后就知道这个习惯有多重要。
运行命令形如:
python train.py --data dataset_split --model yolo11s-cls.pt --epochs 100 --imgsz 224 --batch 16命令行传参直接覆盖脚本里的默认值,显存小的把--batch 8,想快一点就--model yolo11n-cls.pt。这样即使新手接手,也不会因为改错代码里的默认值导致整套流程出问题。
4.2 1000 张图规模下的参数参考
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 224 | 病斑特征非常小再上 320,但显存占用和时间成本都会涨 |
| epochs | 80~120 | 小数据要训够,配合早停防止过拟合 |
| batch | 16 或 32 | 以训练时不爆显存为准,不要用 batch=1 |
| patience | 10~20 | 验证集指标连续不升就停,节省时间 |
| label_smoothing | 0.05~0.1 | 标注有噪声时有效,能避免模型对错误标签过于自信 |
imgsz是我在农作物病害分类里最常调的一个参数。水稻稻瘟病的病斑很小,224 下可能只有十几个像素,信息丢失严重,这种情况下 320 会明显涨点;但如果你拍的是整株作物而不是单片叶子,224 足够。label_smoothing是给那些标注不够严谨的数据集准备的——比如某张图同时有初期的稻瘟病和生理性黄化,标注者只选了其中一类,平滑一下可以让模型不那么死磕单个标签。
4.3 脚本运行时的实时反馈
训练启动后终端会持续滚动输出每个 epoch 的train loss、val loss、top1_acc、top5_acc。前五个 epoch 指标波动大是正常的,预训练权重加载后模型在迁移特征,不用急着中断;真正要警惕的是训练到第 20 轮时训练集 top1 已经 95% 而验证集 top1 还在 60% 徘徊,这是过拟合的早期信号,优先去检查数据泄漏和类别平衡,而不是盲目加训练轮数。
5. 避坑记录:农作物病虫害分类最容易翻车的 5 个环节
这个章节的每一条都是对方便到不行的流程里踩过的真实问题。1000 张图规模下,模型结构很少是瓶颈,数据侧的脏问题才是让精度上不去的真正原因。
5.1 数据泄漏:同一株作物的多张照片被拆进了训练集和验证集
现象:训练时 top1 一路涨到 98%,验证集也有 95%,你高兴地换了十几张新拍的照片去测,准确率直接掉到 60%。原因:采集病害照片时,同一株作物往往被连拍多张,背景、角度、光线高度相似,随机划分时这些同源图片被拆到两边,模型其实在认背景而不是认病斑。解决:划分时按“样本来源”分组而不是按单张图片随机,最简单的做法是给同一个植株的照片加上统一前缀,按前缀分桶后再划分。
5.2 类别严重不平衡:某个病害类别只有三四十张图
现象:整体 top1 有 85%,但看混淆矩阵发现那个只有 30 张图的类别几乎全被分到了其他类。原因:交叉熵损失在样本多的类别上充分拟合,少数类几乎没有话语权。解决:优先补拍扩充到每类至少 80 张;补拍不方便就对该类单独做增强,水平翻转、随机裁剪、轻微色偏都可以;评估时不要只看总准确率,而是逐类看召回率。
5.3 背景过拟合:模型学会分“土”和“叶子”,没学会分病斑
现象:在实验台、大棚地膜、黑色营养钵背景下拍的照片训练出来模型,拿到田间自然光下测就崩。原因:分类模型极其擅长找捷径,背景颜色和纹理比病斑特征容易学得多。解决:整理数据集时尽量让每类的背景多样化,不要全是一个拍摄场景;训练脚本里打开 Ultralytics 自带的增强参数;如果条件允许,用 Grad-CAM 看模型的注意力区域,落在叶片边缘或土块上就是背景过拟合实锤。
5.4 训练中途断电或被杀进程,没有后悔药
现象:训练到第 40 轮时机器断电或 SSH 断开,重启后发现best.pt还是训练到第 20 轮的版本,白跑了一半时间。原因:很多人只盯着best.pt,忘了last.pt才是断点续训的钥匙。解决:中断后用last.pt接续训练:
yolo classify train resume model=runs/classify/crop_pest/weights/last.pt注意resume时要保持原训练的数据集路径和参数一致,换目录或改数据会导致指标对不上。
5.5 一张图多种病害,分类模型天生答不全
现象:验证集里有一批图是稻瘟病和胡麻叶斑病同时发生,标注者只标了主要的那种,训练时模型反复在这批图上震荡,loss 降不下去。原因:分类任务的输出天然是单选,一张图有多个病害时这个任务本身就不该用分类模型硬做。解决:如果实际场景中混合发病比例不高,就只把混合样本归入“复合病害”类;如果混合发病是常态,趁早转向目标检测,用带病斑框的标注数据训检测模型,输出才是真的可落地。
6. 验证与落地:用混淆矩阵挑模型毛病,用一条命令跑推理
训练完不是终点,你得知道模型在哪些类别上会互相认错,再把模型接到实际图片上验证一遍。
6.1 混淆矩阵是排查“长得像”类别的第一工具
训练输出目录里的confusion_matrix.png是按真实类别和预测类别交叉统计的。对角线越亮说明这类分得越准;矩阵里最亮的非对角线元素,就是模型最容易混乱的类别对。比如稻瘟病和褐变穗,叶片颜色和纹理接近,混淆率高是正常的。看到这类现象不要急着加数据,先确认这两类在形态上是否真的可区分,如果专家都经常认错,就要考虑合并类别或补充专门视角的采集。这一步做好了,再回去挑图和补拍才有方向。
6.2 单张图片的推理命令与结果解析
训练完成后,最常用的落地点是一条预测命令:
yolo classify predict \ model=runs/classify/crop_pest/weights/best.pt \ source=test_leaf.jpg输出会打印每个类别的概率,同时在runs/classify/predict下保存带预测标签的图片。如果要在自己的程序里调用模型,写法比命令行更灵活:
from ultralytics import YOLO model = YOLO('runs/classify/crop_pest/weights/best.pt') result = model.predict('test_leaf.jpg')[0] print(f'预测类别: {result.names[result.probs.top1]}') print(f'置信度: {float(result.probs.top1conf):.4f}')result.probs.top1是概率最高的类别序号,要在result.names这个字典里查回类别名;result.probs.top1conf是置信度,逻辑上可以把它当作产品里的一个“模糊阈值”,低于某个值就返回“不确定,请人工复核”。这个习惯能显著提升实际使用体验,因为田间的未知图像是常态,模型硬答还不如承认自己不确定。
我现在的习惯是,任何小数据分类项目开工前先把统计脚本跑一遍,确认各类别数量和目录结构没问题,再谈训练和调参。这个习惯帮我挡掉过好几次方向性的返工,希望对你也有用。
本文还有配套的精品资源,点击获取