摘要:
合集:AI案例-CV-农业
数据集:苹果叶病害图像数据集
数据集价值:实现早期病害诊断,减少农药滥用和产量损失(预估可降低经济损失10%-30%)。
解决方案:PyTorch框架, ResNet18模型
一、赛题描述
赛题:粮食和经济作物病害图像识别挑战赛第一轮:苹果病害图像识别挑战赛
主办方:科大讯飞
主页:https://challenge.xfyun.cn/topic/info?type=apple-diseases&option=ssgy
赛事背景
农作物病害严重制约着农业生产,农作物生长过程中的各种病害会显著降低农产品的数量和质量,为了提高农业生产效率,及时发现和早期预防农作物病害对提高产量至关重要。粮食和经济作物作为农业生产中的重要领域,其中对苹果,小麦等作物的病虫害识别、预测、防治是农业生产研究的重中之重。苹果是世界上果树栽培面积较广的树种之一,也是我国最重要的水果之一。我国的苹果种植总面积达到3800多万亩,苹果总产量达到4200多万吨,无论是种植面积还是产量,我国均占全球的50%以上,所以我国可以说是世界第一苹果大国,然而我国的苹果种植业面临病害导致的减产这一问题。而在粮食作物领域,我国是世界上小麦总产量最高,消费量最大的国家,小麦的种植生产与国家粮食安全息息相关。每年多种疾病可导致小麦减产超过20%,在大农场生产模式下损失严重。而由于田地的复杂性,从农作物图像中识别和诊断病害是一项高度复杂的任务,针对大果园的人工诊断和识别方法费时低效,速度慢、准确度低,易导致农药的误用、滥用,破坏自然环境。图像处理和机器视觉能够适应复杂多变的自然场景,为苹果病害的识别和诊断奠定基础。利用计算机视觉和图像处理策略设计的苹果叶片病害智能识别算法,可以快速、低成本和精确地对农作物病害进行诊断和识别,有助于建立病害预测机制,及时进行防控,具有重大的实用意义。
赛事任务
最为有效的病害识别方法是图片识别,为提高模型训练的实际意义,本次比赛选择了在粮食和经济作物中较为重要的小麦和苹果两种作物作为识别目标。本次大赛初赛提供了具有较为纯净的大量苹果叶片的病害图片集,涉及8类病害及健康样本,参赛选手需基于提供的样本构建模型,实现苹果的病害图像识别。
在第二轮赛中,出于模拟更真实应用场景的考虑,不同于第一阶段较为清晰的样本,第二阶段使用背景较为复杂、模糊、且有一定噪声数据的数据集,共有6种小麦病害以及健康小麦样本。
二、数据集内容
提供了九类自然环境下苹果叶片的病害图像数据:包括图像及其所属病害标签。数据主体为实验室和自然环境条件下的农作物图像,每张图像的主体突出度,背景复杂程度、光照条件,图像清晰度均存在一定差别。
苹果叶片的病害类型:labletoclass.txt
d1 黑斑病(Alternaria leaf spot)
d2 褐斑病(Brown spot)
d3 青枯病(Frogeye leaf spot)
d4 灰斑病(Grey spot)
d5 健康(Health)
d6 花叶病(Mosaic)
d7 白粉病(Powdery mildew)
d8 锈病(Rust)
d9 疮痂病(Scab)
目录:
- train:包括子目录d1-d9,为九类自然环境下苹果叶片的病害图像数据。一共包括10,212张图片。
- test:包括4371张图片。
数据集图片样例:

三、baseline方案
源码:baseline-xml.ipynb
这段代码实现了一个基于ResNet18的图像分类系统,用于将图像分类到9个类别(d1-d9)。
安装
参见《安装深度学习框架PyTorch》。
选择合适的CUDA版本进行安装,例如pytorch==2.4.1:
conda create -n pytorch241-gpu python=3.10
conda activate pytorch241-gpu
conda install pytorch==2.4.1 torchvision==0.19.1 torchaudio==2.4.1 pytorch-cuda=12.1 -c pytorch -c nvidia
# 其他开发包
conda install -c conda-forge opencv
# 安装 albumentations 及其核心依赖
conda install -c conda-forge albumentations
加载开发库
import os, sys, glob, argparse
import pandas as pd
import numpy as np
from tqdm import tqdm
%pylab inline
import cv2
from PIL import Image
from sklearn.model_selection import train_test_split, StratifiedKFold, KFold
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
数据准备部分
数据加载
- 从
./data/train/*/*
加载训练图像(按子目录分类) - 从
./data/test/*
加载测试图像 - 随机打乱训练和测试数据顺序
train_path = glob.glob('./data/train/*/*')
test_path = glob.glob('./data/test/*')
np.random.shuffle(train_path)
np.random.shuffle(test_path)
数据可视化
- 显示9张训练图像样本及其类别标签(从路径中提取)
plt.figure(figsize=(7, 7))
for idx in range(9):
plt.subplot(3, 3, idx+1)
plt.imshow(Image.open(train_path[idx]))
plt.xticks([]);
plt.yticks([]);
plt.title(train_path[idx].split('/')[-2])
自定义数据集类(XunFeiDataset)
- 实现图像缓存机制(DATA_CACHE)提高读取效率
- 从图像路径中提取类别标签(d1-d9对应0-8,其他为-1)
- 支持数据增强变换
- 图像通道顺序转换为C×H×W格式
DATA_CACHE = {}
class XunFeiDataset(Dataset):
def __init__(self, img_path, transform=None):
self.img_path = img_path
if transform is not None:
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):
if self.img_path[index] in DATA_CACHE:
img = DATA_CACHE[self.img_path[index]]
else:
img = cv2.imread(self.img_path[index])
DATA_CACHE[self.img_path[index]] = img
if self.transform is not None:
img = self.transform(image = img)['image']
if self.img_path[index].split('/')[-2] in ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9']:
label = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9'].index(self.img_path[index].split('/')[-2])
else:
label = -1
img = img.transpose([2,0,1])
return img, torch.from_numpy(np.array(label))
def __len__(self):
return len(self.img_path)
模型架构
XunFeiNet类
以下这段代码定义了一个自定义的神经网络 XunFeiNet
,继承自 torch.nn.Module
,并使用了预训练的 ResNet-18
模型作为其主干网络。这个网络类 XunFeiNet 基于 ResNet-18,并且修改了其全连接层以适应一个9类分类任务。avgpool 层被替换成了自适应池化,使得模型能处理不同大小的输入图像。最终的全连接层将 512 维的输出映射到 9 维,用于分类任务。
class XunFeiNet(nn.Module):
def __init__(self):
super(XunFeiNet, self).__init__()
model = models.resnet18(True)
model.avgpool = nn.AdaptiveAvgPool2d(1)
model.fc = nn.Linear(512, 9)
self.resnet = model
def forward(self, img):
out = self.resnet(img)
return out
训练配置
数据增强(使用Albumentations)
- 训练集增强:
- 随机旋转90度
- 调整大小(256×256)
- 随机裁剪(224×224)
- 水平翻转(概率0.5)
- 随机亮度和对比度调整
- 标准化(ImageNet均值和标准差)
- 验证集和测试集:
- 仅进行必要的调整大小、裁剪和标准化
数据划分
- 最后1000个样本作为验证集
- 其余作为训练集
训练参数
- 批量大小: 30(训练/验证), 2(测试)
- 损失函数: 交叉熵损失
- 优化器: AdamW,学习率0.001
- 使用GPU加速训练
训练和验证流程
训练函数(train)
- 前向传播计算输出
- 计算交叉熵损失
- 反向传播更新权重
- 每20个批次打印损失值
def train(train_loader, model, criterion, optimizer):
model.train()
train_loss = 0.0
for i, (input, target) in enumerate(train_loader):
input = input.cuda(non_blocking=True)
target = target.cuda(non_blocking=True)
# compute output
output = model(input)
loss = criterion(output, target)
# compute gradient and do SGD step
optimizer.zero_grad()
loss.backward()
optimizer.step()
if i % 20 == 0:
print('Train loss', loss.item())
train_loss += loss.item()
return train_loss/len(train_loader)
验证函数(validate)
- 评估模型在验证集上的准确率
- 计算预测正确的样本比例
def validate(val_loader, model, criterion):
model.eval()
val_acc = 0.0
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(val_loader):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
loss = criterion(output, target)
val_acc += (output.argmax(1) == target).sum().item()
return val_acc / len(val_loader.dataset)
预测函数(predict)
- 生成测试集的预测结果
- 进行10次预测并累加结果(集成预测)
def predict(test_loader, model, criterion):
model.eval()
val_acc = 0.0
test_pred = []
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(test_loader):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
test_pred.append(output.data.cpu().numpy())
return np.vstack(test_pred)
执行流程
- 初始化数据加载器
- 创建模型并移至GPU
- 进行3个epoch的训练
- 每个epoch计算训练损失、训练集准确率和验证集准确率
- 对测试集进行10次预测并累加结果
初始化数据加载器:
import albumentations as A
train_loader = torch.utils.data.DataLoader(
XunFeiDataset(train_path[:-1000],
A.Compose([
A.RandomRotate90(),
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.HorizontalFlip(p=0.5),
# A.RandomContrast(p=0.5),
A.RandomBrightnessContrast(p=0.5),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=30, shuffle=True, num_workers=1, pin_memory=False
)
val_loader = torch.utils.data.DataLoader(
XunFeiDataset(train_path[-1000:],
A.Compose([
A.Resize(256, 256),
A.RandomCrop(224, 224),
# A.HorizontalFlip(p=0.5),
# A.RandomContrast(p=0.5),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=30, shuffle=False, num_workers=1, pin_memory=False
)
test_loader = torch.utils.data.DataLoader(
XunFeiDataset(test_path,
A.Compose([
A.Resize(256, 256),
A.RandomCrop(224, 224),
A.HorizontalFlip(p=0.5),
# A.RandomContrast(p=0.5),
A.RandomBrightnessContrast(p=0.5),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
])
), batch_size=2, shuffle=False, num_workers=1, pin_memory=False
)
for data, lable in train_loader:
break
执行流程:
model = XunFeiNet()
model = model.to('cuda')
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.AdamW(model.parameters(), 0.001)
for _ in range(3):
train_loss = train(train_loader, model, criterion, optimizer)
val_acc = validate(val_loader, model, criterion)
train_acc = validate(train_loader, model, criterion)
print(train_loss, train_acc, val_acc)
pred = None
for _ in range(10):
if pred is None:
pred = predict(test_loader, model, criterion)
else:
pred += predict(test_loader, model, criterion)
训练运行结果
Train loss 2.2545018196105957
Train loss 1.2741355895996094
Train loss 0.8521319627761841
Train loss 0.861651599407196
Train loss 0.49613693356513977
Train loss 0.29787498712539673
Train loss 1.0657925605773926
Train loss 0.325284868478775
Train loss 0.6994998455047607
Train loss 0.4378790557384491
Train loss 0.2327258586883545
Train loss 0.7312375903129578
Train loss 0.39839833974838257
Train loss 0.15284697711467743
Train loss 0.366984486579895
Train loss 0.30065733194351196
0.6105081921370773 0.8452936706112257 0.859
Train loss 0.3844166696071625
Train loss 0.5361185073852539
Train loss 0.2942914366722107
Train loss 0.21940727531909943
Train loss 0.32017746567726135
Train loss 0.5987720489501953
Train loss 0.7167405486106873
Train loss 0.24388320744037628
...
Train loss 0.30459490418434143
Train loss 0.10312030464410782
Train loss 0.17960184812545776
0.2834148531440984 0.9115188361741396 0.936
源码开源协议
四、获取案例套装
文件包大小:1.2 GB