摘要:
合集:AI案例-CV-林业
数据集:PlantVillage2015v3彩色植物病害图像数据集
数据集价值:实现早期病害诊断,减少农药滥用和产量损失(预估可降低经济损失10%-30%)。
解决方案:Tensorflow框架, CNN模型
一、问题描述
人类社会需要在2050年前将粮食产量增加约70%,以养活预计将超过90亿的人口。目前,传染病平均降低了40%的潜在产量,许多发展中国家的农民经历了高达100%的产量损失。
智能手机在全球农作物种植者中的广泛分布,预计到2020年将达到50亿部,这提供了将智能手机转变为种植粮食的不同社区宝贵工具的潜力。一个潜在的应用是通过机器学习和众包开发移动疾病诊断。通过现有的在线平台PlantVillage发布超过50,000张精心策划的健康和感染作物叶片的图像。我们描述了数据和平台。这些数据是一个正在进行的众包努力的开始,旨在使计算机视觉方法能够帮助解决由于传染病导致的作物植物产量损失的问题。
二、数据集内容
PlantVillage-v3是一个植物病害图像数据库,常作为基础数据集用于农作物病害及植物病害的相关研究。 该数据库的图像都是在实验室中拍摄的, 目前数据集中有 54,305 张植物病害叶片图像,其中包含 13 种植物共 26 类病害叶片。 该数据集中 有38 个类别的样本图像。Plant Village过去在网站上公开过本数据集中的图片,但是目前不开放下载。
数据结构
PlantVillage-v3数据集目录plantvillage dataset包含的三个子目录 color
, grayscale
, 和 segmented
分别代表了图像的不同处理和预处理版本。具体来说:
- color(彩色图像):
- 这个目录包含的是原始的彩色图像,通常是 RGB 彩色图像。这些图像在拍摄时通常会保留植物的颜色信息,包含了植物的不同部位(叶子、果实、花朵等)以及病害的表现。
- 每个图像都是三通道的(红、绿、蓝),能够提供丰富的颜色信息,有助于区分植物种类和病害的不同特征。
- grayscale(灰度图像):
- 这个目录包含的是将彩色图像转换为灰度图像后的版本。灰度图像只包含亮度信息,去除了颜色的维度,通常通过取 RGB 值的平均值或使用其他转换方法生成。
- 在某些情况下,灰度图像能帮助减少计算的复杂度,特别是当颜色信息不太重要时,或者当你只关心形状、纹理等视觉特征时,灰度图像可能更加有效。
- segmented(分割图像):
- 这个目录包含的是经过图像分割处理的图像,通常是将感兴趣的目标(例如植物叶子或病变区域)从背景中提取出来,生成了一个更清晰的图像,其中目标区域与背景区分开来。
- 分割图像常用于图像处理和计算机视觉任务,如物体检测和语义分割。在
segmented
目录中,图像的前景(植物部分或病变部分)会与背景(如土壤、天空等)分开,可能会采用掩码(mask)的形式表现出来。
这些不同的图像类型为不同的研究和任务提供了不同的视角和数据处理方式。选择哪个数据集取决于你的具体任务需求和模型的目标。本案例只包括彩色图像数据集。
该数据集中 有38 个类别的样本图像,分别存储在38个文件目录中。以下表格为:文件目录名和对应的图片个数:
38个类别 | 图片数 |
---|---|
Orange__Haunglongbing(Citrus_greening) | 5,507 |
Tomato___Tomato_Yellow_Leaf_Curl_Virus | 5,357 |
Soybean___healthy | 5,090 |
Peach___Bacterial_spot | 2,297 |
Tomato___Bacterial_spot | 2,127 |
Tomato___Late_blight | 1,909 |
Squash___Powdery_mildew | 1,835 |
Tomato___Septoria_leaf_spot | 1,771 |
Tomato___Spider_mites Two-spotted_spider_mite | 1,676 |
Apple___healthy | 1,645 |
Tomato___healthy | 1,591 |
Blueberry___healthy | 1,502 |
Pepper,_bell___healthy | 1,478 |
Tomato___Target_Spot | 1,404 |
Grape__Esca(Black_Measles) | 1,383 |
Corn(maize)___Common_rust | 1,192 |
Grape___Black_rot | 1,180 |
Corn_(maize)___healthy | 1,162 |
Strawberry___Leaf_scorch | 1,109 |
Grape__Leaf_blight(Isariopsis_Leaf_Spot) | 1,076 |
Cherry_(including_sour)___Powdery_mildew | 1,052 |
Potato___Late_blight | 1,000 |
Tomato___Early_blight | 1,000 |
Potato___Early_blight | 1,000 |
Pepper,_bell___Bacterial_spot | 997 |
Corn_(maize)___Northern_Leaf_Blight | 985 |
Tomato___Leaf_Mold | 952 |
Cherry_(including_sour)___healthy | 854 |
Apple___Apple_scab | 630 |
Apple___Black_rot | 621 |
Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot | 513 |
Strawberry___healthy | 456 |
Grape___healthy | 423 |
Tomato___Tomato_mosaic_virus | 373 |
Raspberry___healthy | 371 |
Peach___healthy | 360 |
Apple___Cedar_apple_rust | 275 |
Potato___healthy | 152 |
38 个类别的样本,总共图片数为54,305个。为了区分健康和有害的叶子,通常通过文件目录的命名和类别标签来实现。目录名称中通常包含关键词“Healthy”表示健康的叶子,其他名称则表示不同的疾病。每个目录名称通常包含以下信息:
- 植物种类:如番茄、马铃薯、苹果等。
- 疾病名称 或 “healthy” 表示健康。
例如:
Tomato_Healthy
: 表示健康的番茄叶子。Tomato_Late_blight
: 表示患有晚疫病的番茄叶子。Apple_Black_rot
: 表示患有黑腐病的苹果叶子。
数据集图片样例:

数据集版权许可协议
Deed – Attribution-NonCommercial-ShareAlike 4.0 International – Creative Commons
数据集引用要求
@article{DBLP:journals/corr/HughesS15,
author = {David P. Hughes and
Marcel Salath{\'{e} } },
title = {An open access repository of images on plant health to enable the
development of mobile disease diagnostics through machine
learning and crowdsourcing},
journal = {CoRR},
volume = {abs/1511.08060},
year = {2015},
url = {http://arxiv.org/abs/1511.08060},
archivePrefix = {arXiv},
eprint = {1511.08060},
timestamp = {Mon, 13 Aug 2018 16:48:21 +0200},
biburl = {https://dblp.org/rec/bib/journals/corr/HughesS15},
bibsource = {dblp computer science bibliography, https://dblp.org}
}
三、识别样例
源码:plant-village-disease-classification-using-nn.ipynb
安装
参考文章《安装深度学习框架TensorFlow》基于Python3.9安装TensorFlow2.6.0框架。
查看已安装的tensorflow版本信息:
conda list tensor
# packages in environment at D:\App-Data\conda3\envs\tensorflow-gpu-2-6-p3-9:
#
# Name Version Build Channel
tensorboard 2.6.0 py_1 anaconda
tensorboard-data-server 0.6.1 py39haa95532_0 anaconda
tensorboard-plugin-wit 1.8.1 py39haa95532_0 anaconda
tensorflow 2.6.0 gpu_py39he88c5ba_0 anaconda
tensorflow-base 2.6.0 gpu_py39hb3da07e_0 anaconda
tensorflow-estimator 2.6.0 pyh7b7c402_0 anaconda
tensorflow-gpu 2.6.0 h17022bd_0 anaconda
安装其他库:
conda install -c conda-forge opencv
conda install pandas tqdm seaborn scikit-learn
conda install -c conda-forge opencv
导入库
在TensorFlow 2.x版本中,Keras已经被集成到TensorFlow中,因此你不再需要单独安装Keras库。你可以通过tensorflow.keras来访问Keras的功能。
import os
import cv2
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import tensorflow as tf
# import keras
from tensorflow.keras import layers, models
from tensorflow.keras import metrics
# from keras.callbacks import EarlyStopping,ModelCheckpoint
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from tqdm import tqdm
读取数据
data()函数遍历指定目录下的子文件夹(每个子文件夹代表一个类别),收集所有图像路径和对应的标签。
def data(dataset_path):
images = []
labels = []
for subfolder in tqdm(os.listdir(dataset_path)):
subfolder_path = os.path.join(dataset_path, subfolder)
for image_filename in os.listdir(subfolder_path):
image_path = os.path.join(subfolder_path, image_filename)
images.append(image_path)
labels.append(subfolder)
df = pd.DataFrame({'image': images, 'label': labels})
return df
#train
train = data('./plantvillage dataset/color')
train.head()
数据集可视化
随机选择50张图像展示,每张图像显示其对应的类别标签。
train.label.value_counts().to_frame()
plt.style.use('dark_background')
plt.figure(figsize=(50,80))
for n,i in enumerate(np.random.randint(0,len(train),50)):
plt.subplot(10,5,n+1)
img = cv2.imread(train.image[i])
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.axis('off')
text = f'{train.label[i]}\n'
plt.title(text,fontsize=25)
数据分割
使用train_test_split
将数据分为训练集(80%)、验证集(10%)和测试集(10%),并保持类别分布(stratify)。
X_train, X_test1, y_train, y_test1 = train_test_split(train['image'], train['label'], test_size=0.2, random_state=42,shuffle=True,stratify=train['label'])
X_val, X_test, y_val, y_test = train_test_split(X_test1,y_test1, test_size=0.5, random_state=42,shuffle=True,stratify=y_test1)
df_train = pd.DataFrame({'image': X_train, 'label': y_train})
df_test = pd.DataFrame({'image': X_test, 'label': y_test})
df_val = pd.DataFrame({'image': X_val, 'label': y_val})
数据预处理
使用Keras的ImageDataGenerator
进行数据增强和归一化:
- 训练集:进行水平翻转增强,并将像素值归一化到[0,1]范围
- 验证集和测试集:仅进行像素值归一化
- 图像统一调整为224×224大小,批量大小为32
image_size = (224, 224)
batch_size = 32
datagen = ImageDataGenerator(
rescale=1./255
)
train_datagen = ImageDataGenerator(
rescale=1./255,
#rotation_range=20,
#width_shift_range=0.2,
#height_shift_range=0.2,
#shear_range=0.2,
#zoom_range=0.2,
horizontal_flip=True,
#fill_mode='nearest'
)
train_generator = train_datagen.flow_from_dataframe(
df_train,
x_col='image',
y_col='label',
target_size=image_size,
batch_size=batch_size,
shuffle=True
)
test_generator = datagen.flow_from_dataframe(
df_test,
x_col='image',
y_col='label',
target_size=image_size,
batch_size=batch_size,
shuffle=False
)
val_generator = datagen.flow_from_dataframe(
df_val,
x_col='image',
y_col='label',
target_size=image_size,
batch_size=batch_size,
shuffle=False
)
模型架构
在 TensorFlow 中,tf.keras.models.Sequential 是一个用于构建顺序堆叠的神经网络模型的类。它允许你按顺序逐层添加神经网络层,适用于简单的线性结构(每层只有一个输入张量和一个输出张量)。
1层. 输入层
输入层为224×224×3的RGB图像:
layers.Conv2D(32, kernel_size=(3, 3), activation=’relu’, input_shape=(224, 224, 3)), layers.MaxPooling2D(2, 2),
- 作用:第一层卷积,提取低级特征(边缘、纹理)。
- 参数:
- 32个3×3的卷积核,输入通道为3(RGB)。
- 参数数量 =
(3*3*3)*32 + 32(bias) = 896
。
- 输出形状:
(None, 222, 222, 32)
其中(224-3+1=222
,因无填充且步幅默认为1)
2层. 池化层
MaxPooling2D层(2×2池化)
- 作用:下采样,减少计算量并增强平移不变性。
- 输出形状:
(None, 111, 111, 32)
其中(222/2=111
,向下取整)
3-12层. 重复的卷积+池化块
包括1层和2层共6组12层 Conv2D(64, kernel_size=(3, 3), activation='relu') → MaxPooling2D(2, 2)
:
- 每层卷积核数量从32逐步增加到64。
- 每层参数计算示例(以第二层为例):
Conv2D(64, (3,3))
参数 =(3*3*32)*64 + 64 = 18,496
。
- 输出形状逐步缩小:
- 经过6次池化后,最终输出为
(None, 3, 3, 64)
(224 → 111 → 55 → 27 → 13 → 6 → 3
)
- 经过6次池化后,最终输出为
13层. 展平层
Flatten()
- 作用:将3D特征图展平为1D向量,供全连接层使用。
- 输出形状:
(None, 576)
其中(3*3*64=576
)
14-15层. 全连接层
全连接层(64个神经元,ReLU激活):Dense(64, activation=’relu’)。参数 = 576*64 + 64 = 36,928
。
输出层(38个神经元,softmax激活) – 对应38个类别:Dense(38, activation='softmax')
。参数 = 64*38 + 38 = 2,470
(用于38分类任务)。
model = models.Sequential([
layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(224, 224, 3)),
layers.MaxPooling2D(2, 2),
layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
layers.MaxPooling2D(2, 2),
layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
layers.MaxPooling2D(2, 2),
layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
layers.MaxPooling2D(2, 2),
layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
layers.MaxPooling2D(2, 2),
layers.Conv2D(64, kernel_size=(3, 3), activation='relu'),
layers.MaxPooling2D(2, 2),
layers.Flatten(),
layers.Dense(64, activation='relu'),
layers.Dense(38, activation='softmax'),
])
model.summary()
输出:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 222, 222, 32) 896
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 111, 111, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 109, 109, 64) 18496
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 54, 54, 64) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 52, 52, 64) 36928
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 26, 26, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 24, 24, 64) 36928
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 12, 12, 64) 0
_________________________________________________________________
conv2d_4 (Conv2D) (None, 10, 10, 64) 36928
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 5, 5, 64) 0
_________________________________________________________________
conv2d_5 (Conv2D) (None, 3, 3, 64) 36928
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 1, 1, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 64) 0
_________________________________________________________________
dense (Dense) (None, 64) 4160
_________________________________________________________________
dense_1 (Dense) (None, 38) 2470
=================================================================
Total params: 173,734
Trainable params: 173,734
Non-trainable params: 0
_________________________________________________________________
模型训练
- 使用Adam优化器
- 损失函数:分类交叉熵
- 评估指标:准确率和AUC
- 回调函数:
- ModelCheckpoint:保存最佳模型
- EarlyStopping:当验证损失在5个epoch内没有改善时停止训练
- 训练20个epoch
checkpoint_cb =ModelCheckpoint("my_keras_model.keras", save_best_only=True)
early_stopping_cb =EarlyStopping(patience=5, restore_best_weights=True)
model.compile(optimizer ='adam', loss='categorical_crossentropy', metrics=['accuracy',metrics.AUC()])
hist=model.fit(train_generator,epochs=20,validation_data=val_generator,callbacks=[checkpoint_cb,early_stopping_cb])
运行结果
输出:my_keras_model.keras
Epoch 1/20
1358/1358 [==============================] - 1785s 1s/step - loss: 2.0448 - accuracy: 0.4302 - auc: 0.9128 - val_loss: 1.1932 - val_accuracy: 0.6317 - val_auc: 0.9733
Epoch 2/20
1358/1358 [==============================] - 470s 346ms/step - loss: 0.8811 - accuracy: 0.7293 - auc: 0.9836 - val_loss: 0.8068 - val_accuracy: 0.7517 - val_auc: 0.9846
Epoch 3/20
1358/1358 [==============================] - 448s 330ms/step - loss: 0.5869 - accuracy: 0.8159 - auc: 0.9910 - val_loss: 0.5293 - val_accuracy: 0.8344 - val_auc: 0.9905
Epoch 4/20
1358/1358 [==============================] - 385s 284ms/step - loss: 0.4654 - accuracy: 0.8496 - auc: 0.9938 - val_loss: 0.5165 - val_accuracy: 0.8348 - val_auc: 0.9924
Epoch 5/20
316/1358 [=====>........................] - ETA: 5:12 - loss: 0.4049 - accuracy: 0.8687 - auc: 0.9950
评估模型
- 绘制训练过程中的损失、准确率和AUC曲线
- 在测试集上评估最终模型的性能(损失、准确率、AUC)
CM = confusion_matrix(y_test,y_pred)
plt.figure(figsize=(15,25))
sns.heatmap(CM,fmt='g',center = True,cbar=False,annot=True,cmap='Set2',xticklabels=class_,yticklabels=class_)
CM
输出:
array([[ 57, 1, 0, ..., 0, 0, 0],
[ 0, 58, 0, ..., 0, 0, 0],
[ 0, 0, 23, ..., 0, 0, 0],
...,
[ 0, 0, 0, ..., 533, 0, 0],
[ 0, 0, 0, ..., 0, 32, 0],
[ 0, 0, 0, ..., 0, 0, 156]])
输出模型文件:my_keras_model.keras
源码开源协议
Apache2.0