简介苹果叶片病害图像识别数据集面向计算机视觉与农业智能化方向的学习者和研究者用于训练和评估叶片病害分类模型帮助果农快速识别与防治苹果树病害。数据集已专业标注共约1700张图片划分为健康、生锈、痂三个类别并区分训练集与验证集便于模型学习与泛化能力评估。压缩包共1733个文件以1730张jpg图像为主体另含1个py可视化脚本、1个png与1个json标注说明文件整体约354.45MB目录按类别与训练/验证划分结构清晰。运行show脚本可直观查看样本分布与图像特征便于发现数据问题并优化模型。资源同时适配CNN分类网络与YOLOv5分类方案配套项目链接可辅助搭建识别系统。目前已有340人学习下载适合入门图像分类、开展课程设计或农业AI应用实践。1. 苹果叶片病害识别数据集1700 张已标注图像能跑出什么结果果园里最怕的不是虫是叶片上那层说不清是锈还是痂的斑。等肉眼分辨出来往往已经错过最佳防治窗口。这份苹果叶片病害图像识别数据集就是冲着这个场景来的约 1700 张实拍叶片图已经按“健康”“生锈”“痂”三类标注好训练集和验证集分开存放还附带一个 show 脚本用来可视化。它适合两类人——一类是想拿真实农业图像练 CNN 分类的算法工程师另一类是做农业技术落地、需要快速验证识别可行性的开发者。数据集本身不复杂但“已标注”三个字背后有不少细节决定你能不能一次跑通比如类别是否均衡、验证集怎么划、图像尺寸是否统一。下面按“先看清数据、再跑通训练、最后避开标注坑”的顺序拆一遍。2. 先看清数据再动手目录结构、类别分布与 show 脚本拿到一个标注数据集最忌讳直接往模型里灌。苹果叶片这三类里“健康”和“生锈”在颜色上差异明显但“生锈”和“痂”在早期症状上容易混淆如果验证集里这两类样本比例失衡准确率会虚高。所以第一步不是写网络是把数据摸清楚。2.1 目录结构与文件命名规律从项目正文给出的文件名看训练集图片采用Train_数字.jpg的命名方式例如Train_1616.jpg、Train_948.jpg、Train_254.jpg。这种命名说明数据在整理时做过统一重命名好处是避免中文路径和特殊字符导致的读取失败坏处是文件名本身不携带类别信息。类别信息一定藏在配套的 json 文件或按类别分好的子目录里。常见做法是目录长这样dataset/ ├── train/ │ ├── healthy/ │ ├── rust/ │ └── scab/ ├── val/ │ ├── healthy/ │ ├── rust/ │ └── scab/ └── classes.json如果你拿到的不是按类别分目录而是所有图混在一起加一个 json 标注文件那就需要先解析 json 再拆分。先确认这一点再决定后面用ImageFolder还是自定义Dataset。2.2 用 show 脚本做可视化与类别核对资源里带了 show 脚本这是最省事的入口。我一般会先跑它把每个类别随机抽 9 张拼成一张大图看一遍。原因很直接标注错误在数字上看不出来但眼睛扫一遍就能发现“这张明明是健康却被标成生锈”的问题。如果 show 脚本是 Python 写的典型逻辑是读类别目录、随机采样、用 matplotlib 拼图。你可以按下面方式改造它加上类别计数import os import random import matplotlib.pyplot as plt from PIL import Image root dataset/train classes sorted(os.listdir(root)) fig, axes plt.subplots(len(classes), 6, figsize(15, 3 * len(classes))) for i, cls in enumerate(classes): folder os.path.join(root, cls) imgs os.listdir(folder) print(f{cls}: {len(imgs)} 张) # 类别数量直接打印判断是否均衡 samples random.sample(imgs, min(6, len(imgs))) for j, name in enumerate(samples): img Image.open(os.path.join(folder, name)) axes[i][j].imshow(img) axes[i][j].set_title(f{cls} {img.size}) # 标题带尺寸顺便看分辨率是否统一 axes[i][j].axis(off) plt.tight_layout() plt.savefig(check_grid.png, dpi120)这段代码的关键在print那行和set_title里的img.size。类别计数让你知道有没有哪类样本特别少尺寸显示让你知道要不要统一 resize。如果三类数量差距超过 3 倍训练时就得考虑加权采样否则模型会偏向多数类。2.3 训练集与验证集的划分逻辑摘要里明确说“划分了训练集、验证集”但没有给具体比例。农业图像数据集常见做法是 8:2 或 7:3。你需要确认验证集是不是从同一批图里随机抽的——如果是按时间或地块抽的泛化评估会更真实如果是纯随机验证准确率通常会偏高。一个快速判断方法看训练集和验证集里同一类别的图片有没有高度相似的比如同一片叶子的不同角度。如果有说明划分时没做去重验证集分数会虚高。我一般会算一下训练集和验证集的感知哈希重复度超过阈值就重新划。提示如果验证集准确率一开始就 95% 以上先别高兴大概率是训练集和验证集有近重复图。3. 用 CNN 跑通三分类从 DataLoader 到训练循环数据看清之后下一步是把它喂给一个能跑通的分类网络。摘要里给了 CNN 分类网络项目和 YOLOv5 分类两条参考路线。对于 1700 张图、3 个类别的任务直接上 ResNet18 或 MobileNetV3 就够了没必要一上来就上大模型。这一章给一套能直接抄的 PyTorch 训练流程。3.1 数据增强与 DataLoader 配置苹果叶片图像的特点是背景相对单一、叶片占画面主体所以增强策略要克制。水平翻转、轻微旋转、颜色抖动够用垂直翻转要慎用因为叶片正反面纹理不同翻了可能改变类别语义。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader train_tf transforms.Compose([ transforms.Resize((224, 224)), # 统一到 224适配 ResNet 输入 transforms.RandomHorizontalFlip(p0.5), # 水平翻转模拟不同拍摄方向 transforms.RandomRotation(15), # 小角度旋转避免叶片出画 transforms.ColorJitter(brightness0.2, contrast0.2), # 应对不同光照 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # ImageNet 统计量 ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(dataset/train, transformtrain_tf) val_ds datasets.ImageFolder(dataset/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) print(train_ds.class_to_idx) # 确认类别索引映射后面看混淆矩阵要用参数说明batch_size32在 1700 张规模下比较稳显存不够就降到 16num_workers4在 Windows 上如果报错就改成 0Normalize用的是 ImageNet 统计量因为后面用预训练权重这一步不能省。class_to_idx打印出来是为了后面分析混淆矩阵时知道 0、1、2 分别对应哪一类。3.2 迁移学习与训练循环1700 张图从头训 CNN 容易过拟合常见做法是加载 ImageNet 预训练权重只微调最后几层。下面这套循环里我加了早停和验证准确率记录避免跑完才发现过拟合。import torch.nn as nn import torch.optim as optim from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, 3) # 改成 3 分类 model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-4) # 微调用小学习率 scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) best_acc 0.0 for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) acc correct / total scheduler.step() print(fepoch {epoch1}, val_acc {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_apple.pth) # 只存最好的逻辑说明lr1e-4是微调常用值比从头训练的 1e-3 小一个量级StepLR每 7 个 epoch 降一次学习率帮助后期收敛保存best_apple.pth而不是最后一个 epoch是因为验证准确率往往在中途达到峰值。如果训练 5 个 epoch 后验证准确率还在 33% 附近说明数据加载或标签映射出了问题先回去查class_to_idx。3.3 混淆矩阵看“生锈”和“痂”有没有混三分类任务里整体准确率会掩盖类别间的混淆。跑完训练后一定要在验证集上算混淆矩阵重点看“生锈”被预测成“痂”的比例。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) preds model(imgs).argmax(1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annotTrue, fmtd, xticklabelstrain_ds.classes, yticklabelstrain_ds.classes) plt.xlabel(预测) plt.ylabel(真实) plt.savefig(confusion.png, dpi120)如果“生锈”和“痂”之间的非对角线数字明显大于其他错误说明这两类在特征空间里太近。解决办法有两个一是对这两类做更强的颜色增强二是考虑先做二分类健康 vs 病害再在病害里细分。不要盲目加网络深度1700 张图撑不起太深的模型。4. 避坑与排查标注数据集的五个血泪经验标注数据集用起来翻车往往不是模型问题是数据问题。下面五条是我在类似农业图像项目里踩过的按“现象 → 原因 → 解决”写清楚。4.1 验证准确率异常高但实际预测一塌糊涂现象训练时验证集准确率 98%拿几张新拍的叶片图进去预测结果随机跳。 原因训练集和验证集里有近重复图模型记住了纹理而不是学到了病害特征。 解决对训练集和验证集做感知哈希去重重复的只保留在训练集或者按拍摄批次划分验证集确保验证集图片和训练集不是同一批。4.2 某一类召回率极低混淆矩阵里整行错现象健康类识别很准但“痂”几乎全被预测成“生锈”。 原因两类样本数量差距大或者“痂”的标注里混入了早期“生锈”的图。 解决先统计三类数量差距大就用WeightedRandomSampler做加权采样再随机抽“痂”类图片人工看一遍把标错的挑出来重标。4.3 训练 loss 不下降准确率卡在 33%现象loss 一直在 1.1 附近验证准确率等于随机猜。 原因ImageFolder读到的类别目录为空或者标签全是同一个值。 解决打印train_ds.class_to_idx和len(train_ds)确认三个类别都被读到再检查 json 标注文件里的类别名和目录名是否一致大小写和空格都算差异。4.4 图像尺寸差异大导致训练报错现象RuntimeError: stack expects each tensor to be equal size。 原因数据集里图片分辨率不统一Resize写在了ToTensor后面或者漏了Resize。 解决确保transforms.Resize((224, 224))在ToTensor()之前如果要做多尺度训练用RandomResizedCrop替代固定Resize。4.5 show 脚本跑出来全是黑图或报路径错误现象可视化脚本打开图片是黑色或者提示FileNotFoundError。 原因中文路径、空格路径或者图片实际是 RGBA 四通道而 matplotlib 按 RGB 读。 解决把数据集路径改成全英文无空格读图时统一Image.open(path).convert(RGB)避免四通道和灰度图干扰。注意标注数据集的价值在标注质量不在图片数量。1700 张里如果有 100 张标错对三分类任务的影响可能比少 300 张图还大。5. 进阶技巧用 YOLOv5 分类和 TensorBoard 验证训练是否真的在学摘要里提到数据集也支持基于 YOLOv5 的分类任务。YOLOv5 的分类分支和 CNN 分类在数据组织上略有不同它要求按类别分目录但不需要 json 标注文件。如果你已经按train/healthy、train/rust、train/scab整理好直接指向--data参数即可。python classify/train.py \ --data dataset \ --model yolov5s-cls.pt \ --epochs 30 \ --batch-size 32 \ --img 224 \ --project runs/train-cls \ --name apple_exp参数说明--data指向包含 train 和 val 的根目录--model用预训练分类权重--img 224和 CNN 保持一致方便对比--name指定实验名方便在runs/train-cls/apple_exp里找结果。YOLOv5 分类默认会输出混淆矩阵和 top-1/top-5 准确率省去自己写评估代码。但工具换了不代表问题消失。我一般会再加一步 TensorBoard 看训练曲线确认模型是在学特征而不是在背数据。from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/apple_cnn) # 在训练循环里加 writer.add_scalar(loss/train, loss.item(), epoch) writer.add_scalar(acc/val, acc, epoch) # 训练结束后 writer.close()跑tensorboard --logdir runs之后重点看两条线训练 loss 是否稳定下降验证准确率是否在某个 epoch 后不再涨。如果训练 loss 降但验证准确率平就是过拟合回去加增强或减模型容量如果两条都平就是学习率太小或数据有问题。从那以后我每次拿到新的标注数据集都强制先跑一遍 show 脚本、打印类别计数、算一次训练验证重复度再动模型。这三步花不了十分钟但能省掉后面几小时的无效训练。希望帮到你。本文还有配套的精品资源点击获取