机器学习——植物叶片病害识别
一、选题背景
随着现代科技的发展,人们对于人工智能领域的研究越发的深入。机器学习作为人工智能和识别领域研究的重要课题非常值得我们学习和研究。
本次机器学习设计为植物叶片病害识别,随着社会发展,在农业、林业方面机械化、智能化程度不断的提高。在林业管理上的要求追求便捷,有效,打药除害等都可以通过无人机实现。基于机器学习的植物叶片病害识别,可以通过识别无人机拍下来的树木叶片图片,快速得出该树的健康状态,是正常的,还是患病的以及患的是哪种病。通过机器学习,对正常和患病叶片的训练学习来完成识别以及提高准确度。
通过无人机定位巡航拍照,快速收集林区的树木叶片情况,在识别完图片输出结果。通过结果可以很明确的知道哪个区的那棵树患病。在这过程中节省了大量的人力,物力,以及聘请相关植物病理学家的费用,降低成本,对于林业的发展具有重要的意义。
二、设计方案
本次机器学习设计具体方案,通过网上收集数据集,对数据集进行处理,数据集符合我们所使用的格式。在数据集中的文件打上标签,在对数据进行预处理,之后采用keras框架搭建、使用卷积神经网络构建以及训练模型、通过训练和验证准确性以及训练和验证损失图进行分析、最后导入测试图片进行测试并保存测试图片名称以及识别结果为一个csv数据文件。
本次涉及的技术难点,如何将数据集中大量的数据处理成我们所需要的格式,如何提高图片的识别准确度也是一大难点。对于处理数据,可以采用编写程序,通过数据集自带的csv数据文件进行数据处理。而图片识别准确度可以通过图片的大小,添加卷积层数、对数据进行二次筛选、增加训练次数来提升图片识别的精度。
数据集来源:kaggle:https://www.kaggle.com/competitions/plant-pathology-2021-fgvc8/datakaggle:https://www.kaggle.com/competitions/plant-pathology-2020-fgvc7/data
参考案例:kaggle讨论区,猫狗识别案例,手写字体识别等。
三、实现步骤
植物叶片病害识别的具体实现步骤如下。
1 获取数据集
从kaggle上下载数据集,解压打开。
2 数据集的分析处理
刚获取的数据集主要由一个图片文件夹以及三个csv文件组成,通过编写程序,使用train.csv文件对images文件夹中的文件进行处理,使数据格式符合本次设计使用。
导入相应的库,读取数据文件,将数据文件用pandas导入,转换为DataFrame格式,打印数据查看。
import pandas as pd
from PIL import Image
import os
import shutil
import tqdm
import matplotlib.pyplot as plt
# 传入数据路径
data_csv_path = './train.csv' #分类的数据
images_path = './images' # 原图片路径
create_image_path= './data_new' #文件夹的相对路径文件夹会在同级目录下创建
val_num=0.2 # 从train数据集中分出x%的数据到val中
train_data = pd.read_csv(data_csv_path) # 将train数据用pandas导入
train_data = pd.DataFrame(train_data) # 将数据转换成DataFrame格式
# 查看数据train.csv 1821行*5列
print(train_data)
根据数据,创建数据文件夹,将整理好的图片分为3个类别,分别是训练集train、测试集test、验证集val与其对应的标签保存起来。
# 创建数据文件夹 后面要把整理好的图片分成train test val 与其对应的标签保存起来
fill_if=os.path.exists(create_image_path + '/train_images')
#print("检查是否存在文件夹")
if fill_if ==False: # 判断文件夹是否存在,存在则跳过不存在则创建
os.mkdir(create_image_path + '/train_images') # 创建一个名为data_images的文件夹
print('创建成功')# 判断是否创建成功
# 遍历图片的名称并保存为列表格式
list_image_id = [] # 创建image_name的列表
for i in train_data['image_id']: # 遍历image_id
list_image_id.append(i) # 将image_id保存到列表里
print(list_image_id)
遍历所有标签于其对应的数据,并建立name与label对应的字典,并将数据保存到对应的文件夹。
for s in train_data.columns[1:]: # 遍历标签: s 是对应的标签
# print(s)
# 创建标签对应的文件夹
fill_if_2 = os.path.exists(create_image_path + '/train_images' + '/' + s) # 判断文件夹是否存在
if fill_if_2 == False: # 判断如果文件夹存在则跳过 不存在则创建
mak_file=os.mkdir(create_image_path + '/train_images' + '/' + s) # 创建标签文件夹
list_image_label = [] # 创建label的空列表
for i in train_data[s]: # 遍历标每一个标签里的对应数据
list_image_label.append(i) # 将数据添加到列表里
# print(list_image_label)
dic_name_health=dict(zip(list_image_id,list_image_label)) # 创建image对应label的字典 格式为{'name':label,}
# print(dic_name_health)
for i,j in dic_name_health.items(): # 遍历字典并返回 i=name , j = 标签的值
# print(i,j)
if j == 1: # 判断数据的标签为 1 i 为标签对应的name
# print(i,j)
img_name=i+'.jpg'
# print(img_name)
# 将数据保存到对应的文件夹
img = Image.open(images_path+'/'+img_name)
img.save(create_image_path + '/train_images' + '/' + s + '/' + img_name)
对测试集数据提取,提取过程判断文件夹存在则跳过,不存在则创建,再保存起来。
# 测试集数据提取
fill_if_test = os.path.exists(create_image_path+'/test_images') # 判断文件夹是否存在
if fill_if_test == False: # 判断如果文件夹存在则跳过 不存在则创建
print(fill_if_test)
mak_file=os.mkdir(create_image_path+'/test_images') # 创建标签文件夹
for i in os.listdir(images_path): # 遍历images文件夹下的数据
if i[:4] == 'Test': # 判断数据前4个字符是否为Test,将test的数据保存起来
# 将数据保存到对应的文件夹
img = Image.open(images_path+'/'+i)
img.save(create_image_path+'/test_images'+'/'+i)
将数据中的部分数据划分到验证集中。
import os
import shutil
import tqdm
val_num=0.2 # 从train数据集中分出x%的数据到val中
create_val_image_path='./data_new'
img='./data_new/train_images/'
fill_if_2 = os.path.exists(create_val_image_path + '/' + 'val_images') # 判断文件夹是否存在
if fill_if_2 == False: # 判断如果文件夹存在则跳过 不存在则创建
mak_file = os.mkdir(create_val_image_path + '/' + 'val_images') # 创建标签文件夹
img_label_list=os.listdir(img)
for i in img_label_list:
fill_if_2 = os.path.exists(create_val_image_path + '/' + 'val_images'+'/'+i) # 判断文件夹是否存在
if fill_if_2 == False: # 判断如果文件夹存在则跳过 不存在则创建
mak_file = os.mkdir(create_val_image_path + '/' + 'val_images'+'/'+i) # 创建标签文件夹
img_path=img+i
img_list=os.listdir(img_path)
# print(img_list)
val_=int(len(img_list)*val_num)
for j in tqdm.tqdm(range(val_)):
shutil.move(img+i+'/'+img_list[0],create_val_image_path+ '/' + 'val_images'+'/'+i)
img_list.remove(img_list[0])
3 构建神经模型
导入需要用到的库。
import os
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from keras_preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
对数据路径进行定义,查看数据类别。
base_dir = './data_new' # 存放所有数据的位置
train_dir = os.path.join(base_dir, 'train_images') # 指定训练数据的位置
print(os.listdir(train_dir)) # 查看数据类别
validation_dir = os.path.join(base_dir, 'val_images') # 指定验证数据的位置
神经模型参数设置,并查看本次数据大小。
model_name = 'model_224_150.h5' # 给模型命名以 h5为后缀
class_num = 4 # 类别的个数
epoch = 150 # 训练的轮数
batch = 20 # 批次大小
resize_img = (224, 224) # 图片送入网络的大小
class_mode = 'sparse' # 返回的格式:categorical是返回2D的one-hot编码标签,binary是返回1D的二值标签,sparse返回1D的整数标签,None不反回任何标签仅仅生成数据
activation = 'softmax' # 激活函数设置
loss = 'sparse_categorical_crossentropy'
train_epoch_batch = 100 # 从train迭代器中拿出来数据测次数 这个需要计算的
val_epoch_batch = 50 # 从val迭代器中拿出来数据测次数 这个需要计算的
# # 训练集 得到数据存放的文件夹
train_healthy_dir =os.path.join(train_dir,'healthy')
train_multiple_diseases_dir = os.path.join(train_dir,'multiple_diseases')
train_rust_dir =os.path.join(train_dir,'rust')
train_scab_fir = os.path.join(train_dir,'scab')
# # 判断数据集的大小
print('train_healthy_dir:',len(os.listdir(train_healthy_dir)))
print('train_multiple_diseases_dir:',len(os.listdir(train_multiple_diseases_dir)))
print('train_rust_dir:',len(os.listdir(train_rust_dir)))
print('train_scab_fir:',len(os.listdir(train_scab_fir)))
# # 验证集
validation_healthy_dir =os.path.join(validation_dir,'healthy')
validation_multiple_diseases_dir = os.path.join(validation_dir,'multiple_diseases')
validation_rust_dir =os.path.join(validation_dir,'rust')
validation_scab_dir = os.path.join(validation_dir,'scab')
# # 判断数据集的大小
print('validation_healthy_dir:',len(os.listdir(validation_healthy_dir)))
print('validation_multiple_diseases_dir:',len(os.listdir(validation_multiple_diseases_dir)))
print('validation_rust_dir:',len(os.listdir(validation_rust_dir)))
print('validation_scab_dir:',len(os.listdir(validation_scab_dir)))
对数据进行预处理。
# 数据预处理对数据进行归一化到【0-1】之间进行数据增强
train_datagen = ImageDataGenerator(rescale=1. / 255, # 归一化
rotation_range=40, # 旋转图片
width_shift_range=0.2, # 改变图片的宽
height_shift_range=0.2, # 改变图片的高
shear_range=0.2, # 裁剪图片
zoom_range=0.2, # 缩放图片大小
horizontal_flip=True, # 平移图片
fill_mode='nearest'
)
# 测试集处理
test_datagen = ImageDataGenerator(rescale=1. / 255)
# 得到迭代器
train_generator = train_datagen.flow_from_directory(
train_dir, # 文件夹路径
target_size=resize_img, # 指定resize的大小,需要和神经网络指定的图片大小相同
batch_size=batch, # 批次的大小每一次拿出20个数据送入到网络训练
class_mode=class_mode # 返回的格式:categorical是返回2D的one-hot编码标签,binary是返回1D的二值标签,sparse返回1D的整数标签,None不反回任何标签仅仅生成数据
)
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=resize_img,
batch_size=batch,
class_mode=class_mode
)
根据CNN模型,开始构建卷积神经模型。
#Output shape计算公式:(输入尺寸-卷积核尺寸/步长+1
#对CNN模型,Param的计算方法如下:
#卷积核长度*卷积核宽度*通道数+1)*卷积核个数
#输出图片尺寸:224-3+1=222
model = tf.keras.models.Sequential([
# 32个3*3的卷积核 relu激活函数 输入图像的大小
#32*3*3*3+32=896
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)),
tf.keras.layers.MaxPooling2D(2, 2), # 最大池化层 2*2把 h和 w 编程原来的1/2
#64*3*3*32+64=18496
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D(2, 2),
#128*3*3*64+128=73856
tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D(2, 2),
#128*3*3*128+128=147584
tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D(2, 2),
# 把数据拉平输入全连接层
tf.keras.layers.Flatten(),
# 全连接层
#(18432+1)*512=9437696
tf.keras.layers.Dense(512, activation='relu'), # 512 是输出特征大小
tf.keras.layers.Dropout(0.4),
#(512+1)*512=262656
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dropout(0.4),
# 二分类用sigmoid,1代表得到一个值
#(512+1)*4=2052
tf.keras.layers.Dense(class_num, activation=activation)
])
#看一下特征图的维度如何随着每层变化
model.summary()
读取测试集中的一条数据,查看样本。
from keras.preprocessing import image
img_path = "./data_new/train_images/rust/Train_1624.jpg"
import numpy as np
img = image.load_img(img_path, target_size=(224,224))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
img_tensor /= 255.
#显示样本
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()
激活特征图。
from keras import models
layer_outputs = [layer.output for layer in model.layers[:8]]
activation_model = tf.keras.models.Model(inputs=model.input, outputs=layer_outputs)
#获得改样本的特征图
activations = activation_model.predict(img_tensor)
#第一层激活输出特的第一个滤波器的特征图
import matplotlib.pyplot as plt
first_layer_activation = activations[0]
输出特征图,每行显示16个特征图。
#存储层的名称
layer_names = []
for layer in model.layers[:4]:
layer_names.append(layer.name)
# 每行显示16个特征图
images_pre_row = 16 #每行显示的特征图数
# 循环8次显示8层的全部特征图
for layer_name, layer_activation in zip(layer_names, activations):
n_features = layer_activation.shape[-1] #保存当前层的特征图个数
size = layer_activation.shape[1] #保存当前层特征图的宽高
n_col = n_features // images_pre_row #计算当前层显示多少行
#生成显示图像的矩阵
display_grid = np.zeros((size*n_col, images_pre_row*size))
#遍历将每个特张图的数据写入到显示图像的矩阵中
for col in range(n_col):
for row in range(images_pre_row):
#保存该张特征图的矩阵(size,size,1)
channel_image = layer_activation[0,:,:,col*images_pre_row+row]
#为使图像显示更鲜明,作一些特征处理
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
#把该特征图矩阵中不在0-255的元素值修改至0-255
channel_image = np.clip(channel_image, 0, 255).astype("uint8")
#该特征图矩阵填充至显示图像的矩阵中
display_grid[col*size:(col+1)*size, row*size:(row+1)*size] = channel_image
scale = 1./size
#设置该层显示图像的宽高
plt.figure(figsize=(scale*display_grid.shape[1],scale*display_grid.shape[0]))
plt.title(layer_name)
plt.grid(False)
#显示图像
plt.imshow(display_grid, aspect="auto", cmap="viridis")
plt.show()
配置训练器。
# 配置训练器
model.compile(loss=loss,
optimizer=Adam(lr=0.001),
metrics=['acc'])
对模型进行训练并保存,为了较高的精准度,本次训练次数为150次。上文参数设置可见。
# 因为fit直接训练不能把所有的数据全部放入到内存钟,所以使用fit_generator相当于一个生成器,动态的把所有的数据以batch的形式放入内存
history = model.fit_generator(
train_generator,
steps_per_epoch=train_epoch_batch, # 这个地方需要计算
epochs=epoch, # 训练轮数
validation_data=validation_generator,
validation_steps=val_epoch_batch, # 这个地方需要计算
# verbose=2
)
model.save('./models/' + model_name) # 保存模型
根据训练的结果,绘制训练和验证准确性图,训练和验证损失图。
# # 对准确率与损失进行画图
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'o', label='Training accuracy')
plt.plot(epochs, val_acc, 'r', label='Validation accuracy')
plt.title("Training and Validation accuracy")
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'o', label='Training accuracy')
plt.plot(epochs, val_loss, 'r', label='Validation accuracy')
plt.title("Training and Validation loss")
plt.legend()
plt.show()
4 测试模型,输出结果
载入训练完的模型,取出测试文件夹中的图片,进行测试,可选择测试文件夹中所有的图片,也可以固定测试几张图片。输出图片的测试结果,每种类别的概率,并将测试完的结果保存成csv文件。
import tensorflow as tf
import pandas as pd
import os
import matplotlib.pyplot as plt
import numpy as np
from keras.preprocessing import image
import csv
frame = pd.DataFrame()
# 定义图片路径
img_path = 'data_new/test_images/' # 图片存放文件夹的路径最后要以“/"结尾
model_name='models/model_224_150.h5' # 传入模型
img_list = os.listdir(img_path) # 图片的名字的列表
all_img=len(img_list)
# 数据提取器 这里输入对应的数字可以拿只推理对应数量的图片
img_num = all_img #这里输入 all_img 可以把说有的图片都送入网络中
class_list = ['healthy', 'multiple_diseases', 'rust', 'scab'] # 数据对应的类别标签
# 加载模型
model = tf.keras.models.load_model(model_name) # 加载模型到预测文件
# model.summary() # As a reminder.
# 创建空csv文件
frame.to_csv('predict_result.csv', index=False, sep=',')
with open('predict_result.csv', 'w') as csvfile:
# 先写入columns_name
writer = csv.writer(csvfile)
writer.writerow(['image_id', 'healthy', 'multiple_diseases', 'rust', 'scab'])# 这里出入csv文件的列名
writer = csv.writer(csvfile)
for i in img_list[:img_num]: # 遍历文件夹里的图片
img = img_path + i # 得到图片的相对路径
img_resize = image.load_img(img, target_size=(224, 224)) # 加载图片并resize成(224*224)的格式
img_array = image.img_to_array(img_resize) # 转换成数组
img_tensor = np.expand_dims(img_array, axis=0)
img_input = img_tensor / 255 # 归一化到0-1之间
outputs = model.predict(img_input) # 获取图片信息
outputs = outputs[0] # 获取预测得到概率列表
max_class = max(outputs) # 获得预测的最大的概率值
# print(max_class)
class_dict = dict([i for i in zip(class_list, outputs)]) # 得到标签和预测的概率字典
print("测试结果每个概率为:",class_dict) #输出全部标签
# print(outputs, outputs[0], i[:-4])
print("测试结果概率最高的为:",max_class)
# 往 csv文件里写入数据
writer.writerow([f'{i[:-4]}', outputs[0], outputs[1], outputs[2], outputs[3]])
"""--------展示图片--------"""
# 获取预测概率最大的数据的标签
for k,v in class_dict.items(): #遍历标签与概率的字典返回对应的k,v
if v == max_class: # 判断最大的概率并得到标签
print("测试叶片结果为:",k) # 打印最大的概率
plt.imshow(img_input[0])
plt.title(f'{k}')
plt.show()
csvfile.close() # 关闭csv文件
以下为测试结果,测试出的结果大多接近0.99,输出符合构建模型的预期。输出了每个类别的概率,最高概率为多少,最终模型输出结果是什么病害。
查看识别结果保存的csv文件。
四、总结
结论:从最终结果来看,本次设计的植物叶片病害识别,基本达到了设计初期的预期结果。刚开始,模型因为图片设置为64×64大小的,卷积层不够,识别不准确。经过不断的调整训练模型,最终将64×64调整到224×224大小,卷积层添加了2层,通过长时间训练,准确度得到了良好的提升,并达到预期。为了后面大量的测试,最后添加了将测试结果保存的步骤,这样有利于之后结果的查看以及分析。
收获:经过这次课程设计,深入了解了机器学习的实现步骤。在这过程中,遇到了许许多多的问题,配置环境导致开发软件崩溃,导入的库部分无法使用,版本不匹配,设计的模型训练精准度不够,数据集进行二次添加等等。不过好在通过百度查找相关资料,询问周边的同学朋友这些问题的到了解决。在设计模型之初,看了很多机器学习的案例,比如猫狗大战、森林火灾等等,这些案例为我提供了不少的思路,到外网上看别人对于模型各个方面的讨论,为之后有效提升模型精准度提供了帮助。发现,经过这次设计遇到的大量问题,对于机器学习的理解程度大大加深,通过在不断的错误中改正并获得经验,使知识得到了巩固加强。本次设计的模型还可以进一步扩展,比如多种病害的识别,对于识别图片进行固定规则的重命名等等。这是一段宝贵的程序设计经历,为以后设计相关程序奠定了基础,为课程画上一个完美的句号。
五、设计全部代码
1 import pandas as pd
2 from PIL import Image
3 import os
4 import shutil
5 import tqdm
6 import matplotlib.pyplot as plt
7 # 传入数据路径
8 data_csv_path = './train.csv' #分类的数据
9 images_path = './images' # 原图片路径
10 create_image_path= './data_new' #文件夹的相对路径文件夹会在同级目录下创建
11 val_num=0.2 # 从train数据集中分出x%的数据到val中
12
13 train_data = pd.read_csv(data_csv_path) # 将train数据用pandas导入
14 train_data = pd.DataFrame(train_data) # 将数据转换成DataFrame格式
15 # 查看数据train.csv 1821行*5列
16 # print(train_data)
17 # 创建数据文件夹 后面要把整理好的图片分成train test val 与其对应的标签保存起来
18 fill_if=os.path.exists(create_image_path + '/train_images')
19 print("检查是否存在文件夹")
20 if fill_if ==False: # 判断文件夹是否存在,存在则跳过不存在则创建
21 os.mkdir(create_image_path + '/train_images') # 创建一个名为data_images的文件夹
22 print('创建成功')# 判断是否创建成功
23
24 # 遍历图片的名称并保存为列表格式
25 list_image_id = [] # 创建image_name的列表
26 for i in train_data['image_id']: # 遍历image_id
27 list_image_id.append(i) # 将image_id保存到列表里
28
29 # print(list_image_id)
30 # 遍历所有标签与其对应的数据并创建name与label对应的字典
31 for s in train_data.columns[1:]: # 遍历标签: s 是对应的标签
32 # print(s)
33 # 创建标签对应的文件夹
34 fill_if_2 = os.path.exists(create_image_path + '/train_images' + '/' + s) # 判断文件夹是否存在
35 if fill_if_2 == False: # 判断如果文件夹存在则跳过 不存在则创建
36 mak_file=os.mkdir(create_image_path + '/train_images' + '/' + s) # 创建标签文件夹
37 list_image_label = [] # 创建label的空列表
38
39 for i in train_data[s]: # 遍历标每一个标签里的对应数据
40 list_image_label.append(i) # 将数据添加到列表里
41 # print(list_image_label)
42 dic_name_health=dict(zip(list_image_id,list_image_label)) # 创建image对应label的字典 格式为{'name':label,}
43 # print(dic_name_health)
44 for i,j in dic_name_health.items(): # 遍历字典并返回 i=name , j = 标签的值
45 # print(i,j)
46 if j == 1: # 判断数据的标签为 1 i 为标签对应的name
47 # print(i,j)
48 img_name=i+'.jpg'
49 # print(img_name)
50 # 将数据保存到对应的文件夹
51 img = Image.open(images_path+'/'+img_name)
52 img.save(create_image_path + '/train_images' + '/' + s + '/' + img_name)
53 # 测试集数据提取
54 fill_if_test = os.path.exists(create_image_path+'/test_images') # 判断文件夹是否存在
55 if fill_if_test == False: # 判断如果文件夹存在则跳过 不存在则创建
56 print(fill_if_test)
57 mak_file=os.mkdir(create_image_path+'/test_images') # 创建标签文件夹
58 for i in os.listdir(images_path): # 遍历images文件夹下的数据
59 if i[:4] == 'Test': # 判断数据前4个字符是否为Test,将test的数据保存起来
60 # 将数据保存到对应的文件夹
61 img = Image.open(images_path+'/'+i)
62 img.save(create_image_path+'/test_images'+'/'+i)
63 import os
64 import shutil
65 import tqdm
66 val_num=0.2 # 从train数据集中分出x%的数据到val中
67 create_val_image_path='./data_new'
68 img='./data_new/train_images/'
69
70 fill_if_2 = os.path.exists(create_val_image_path + '/' + 'val_images') # 判断文件夹是否存在
71 if fill_if_2 == False: # 判断如果文件夹存在则跳过 不存在则创建
72 mak_file = os.mkdir(create_val_image_path + '/' + 'val_images') # 创建标签文件夹
73
74 img_label_list=os.listdir(img)
75 for i in img_label_list:
76 fill_if_2 = os.path.exists(create_val_image_path + '/' + 'val_images'+'/'+i) # 判断文件夹是否存在
77 if fill_if_2 == False: # 判断如果文件夹存在则跳过 不存在则创建
78 mak_file = os.mkdir(create_val_image_path + '/' + 'val_images'+'/'+i) # 创建标签文件夹
79 img_path=img+i
80 img_list=os.listdir(img_path)
81 # print(img_list)
82 val_=int(len(img_list)*val_num)
83 for j in tqdm.tqdm(range(val_)):
84 shutil.move(img+i+'/'+img_list[0],create_val_image_path+ '/' + 'val_images'+'/'+i)
85 img_list.remove(img_list[0])
86
87 """---------------------开始构建-------------------"""
88 # 导入需要用到的库
89 import os
90 import tensorflow as tf
91 from tensorflow.keras.optimizers import Adam
92 from keras_preprocessing.image import ImageDataGenerator
93 import matplotlib.pyplot as plt
94
95 """--------------------指定数据路径-------------------"""
96 # 指定好数据集
97 base_dir = './data_new' # 存放所有数据的位置
98 train_dir = os.path.join(base_dir, 'train_images') # 指定训练数据的位置
99 # print(os.listdir(train_dir)) # 查看数据类别
100 validation_dir = os.path.join(base_dir, 'val_images') # 指定验证数据的位置
101
102
103 """------------------参数设置-------------------"""
104 model_name = 'model_224_150_1.h5' # 给模型命名以 h5为后缀
105 class_num = 4 # 类别的个数
106 epoch = 150 # 训练的轮数
107 batch = 20 # 批次大小
108 resize_img = (224, 224) # 图片送入网络的大小
109 class_mode = 'sparse' # 返回的格式:categorical是返回2D的one-hot编码标签,binary是返回1D的二值标签,sparse返回1D的整数标签,None不反回任何标签仅仅生成数据
110 activation = 'softmax' # 激活函数设置
111 loss = 'sparse_categorical_crossentropy'
112 train_epoch_batch = 100 # 从train迭代器中拿出来数据测次数 这个需要计算的
113 val_epoch_batch = 50 # 从val迭代器中拿出来数据测次数 这个需要计算的
114
115 # # 训练集 得到数据存放的文件夹
116 # train_healthy_dir =os.path.join(train_dir,'healthy')
117 # train_multiple_diseases_dir = os.path.join(train_dir,'multiple_diseases')
118 # train_rust_dir =os.path.join(train_dir,'rust')
119 # train_scab_fir = os.path.join(train_dir,'scab')
120 # # 判断数据集的大小
121 # print('train_healthy_dir:',len(os.listdir(train_healthy_dir)))
122 # print('train_multiple_diseases_dir:',len(os.listdir(train_multiple_diseases_dir)))
123 # print('train_rust_dir:',len(os.listdir(train_rust_dir)))
124 # print('train_scab_fir:',len(os.listdir(train_scab_fir)))
125 # # 验证集
126 # validation_healthy_dir =os.path.join(validation_dir,'healthy')
127 # validation_multiple_diseases_dir = os.path.join(validation_dir,'multiple_diseases')
128 # validation_rust_dir =os.path.join(validation_dir,'rust')
129 # validation_scab_dir = os.path.join(validation_dir,'scab')
130 # # 判断数据集的大小
131 # print('validation_healthy_dir:',len(os.listdir(validation_healthy_dir)))
132 # print('validation_multiple_diseases_dir:',len(os.listdir(validation_multiple_diseases_dir)))
133 # print('validation_rust_dir:',len(os.listdir(validation_rust_dir)))
134 # print('validation_scab_dir:',len(os.listdir(validation_scab_dir)))
135
136
137 # 数据预处理对数据进行归一化到【0-1】之间进行数据增强
138 """--------------------数据处理-------------------"""
139
140 train_datagen = ImageDataGenerator(rescale=1. / 255, # 归一化
141 rotation_range=40, # 旋转图片
142 width_shift_range=0.2, # 改变图片的宽
143 height_shift_range=0.2, # 改变图片的高
144 shear_range=0.2, # 裁剪图片
145 zoom_range=0.2, # 缩放图片大小
146 horizontal_flip=True, # 平移图片
147 fill_mode='nearest'
148 )
149 # 测试集处理
150 test_datagen = ImageDataGenerator(rescale=1. / 255)
151
152 # 得到迭代器
153 train_generator = train_datagen.flow_from_directory(
154 train_dir, # 文件夹路径
155 target_size=resize_img, # 指定resize的大小,需要和神经网络指定的图片大小相同
156 batch_size=batch, # 批次的大小每一次拿出20个数据送入到网络训练
157 class_mode=class_mode # 返回的格式:categorical是返回2D的one-hot编码标签,binary是返回1D的二值标签,sparse返回1D的整数标签,None不反回任何标签仅仅生成数据
158 )
159 validation_generator = test_datagen.flow_from_directory(
160 validation_dir,
161 target_size=resize_img,
162 batch_size=batch,
163 class_mode=class_mode
164 )
165 """-----------------构建卷积神经模型----------------"""
166 #Output shape计算公式:(输入尺寸-卷积核尺寸/步长+1
167 #对CNN模型,Param的计算方法如下:
168 #卷积核长度*卷积核宽度*通道数+1)*卷积核个数
169 #输出图片尺寸:224-3+1=222
170 model = tf.keras.models.Sequential([
171 # 32个3*3的卷积核 relu激活函数 输入图像的大小
172 #32*3*3*3+32=896
173 tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)),
174 tf.keras.layers.MaxPooling2D(2, 2), # 最大池化层 2*2把 h和 w 编程原来的1/2
175
176 #64*3*3*32+64=18496
177 tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
178 tf.keras.layers.MaxPooling2D(2, 2),
179
180 #128*3*3*64+128=73856
181 tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
182 tf.keras.layers.MaxPooling2D(2, 2),
183
184 #128*3*3*128+128=147584
185 tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
186 tf.keras.layers.MaxPooling2D(2, 2),
187
188 # 把数据拉平输入全连接层
189 tf.keras.layers.Flatten(),
190 # 全连接层
191 #(18432+1)*512=9437696
192 tf.keras.layers.Dense(512, activation='relu'), # 512 是输出特征大小
193 tf.keras.layers.Dropout(0.4),
194 #(512+1)*512=262656
195 tf.keras.layers.Dense(512, activation='relu'),
196 tf.keras.layers.Dropout(0.4),
197 # 二分类用sigmoid,1代表得到一个值
198 #(512+1)*4=2052
199 tf.keras.layers.Dense(class_num, activation=activation)
200 ])
201 #特征图的维度变化
202 # model.summary()
203
204 """---------------------提取样本,查看特征-------------------"""
205 #从测试集中读取一条样本
206
207 from keras.preprocessing import image
208 img_path = "./data_new/train_images/rust/Train_1624.jpg"
209 import numpy as np
210 img = image.load_img(img_path, target_size=(224,224))
211 img_tensor = image.img_to_array(img)
212 img_tensor = np.expand_dims(img_tensor, axis=0)
213 img_tensor /= 255.
214 #显示样本
215 import matplotlib.pyplot as plt
216 plt.imshow(img_tensor[0])
217 plt.show()
218 #激活特征
219 from keras import models
220 layer_outputs = [layer.output for layer in model.layers[:8]]
221 activation_model = tf.keras.models.Model(inputs=model.input, outputs=layer_outputs)
222 #获得改样本的特征图
223 activations = activation_model.predict(img_tensor)
224 #第一层激活输出特的第一个滤波器的特征图
225 import matplotlib.pyplot as plt
226 first_layer_activation = activations[0]
227 #存储层的名称
228 layer_names = []
229 for layer in model.layers[:4]:
230 layer_names.append(layer.name)
231 # 每行显示16个特征图
232 images_pre_row = 16 #每行显示的特征图数
233 # 循环8次显示8层的全部特征图
234 for layer_name, layer_activation in zip(layer_names, activations):
235 n_features = layer_activation.shape[-1] #保存当前层的特征图个数
236 size = layer_activation.shape[1] #保存当前层特征图的宽高
237 n_col = n_features // images_pre_row #计算当前层显示多少行
238 #生成显示图像的矩阵
239 display_grid = np.zeros((size*n_col, images_pre_row*size))
240 #遍历将每个特张图的数据写入到显示图像的矩阵中
241 for col in range(n_col):
242 for row in range(images_pre_row):
243 #保存该张特征图的矩阵(size,size,1)
244 channel_image = layer_activation[0,:,:,col*images_pre_row+row]
245 #为使图像显示更鲜明,作一些特征处理
246 channel_image -= channel_image.mean()
247 channel_image /= channel_image.std()
248 channel_image *= 64
249 channel_image += 128
250 #把该特征图矩阵中不在0-255的元素值修改至0-255
251 channel_image = np.clip(channel_image, 0, 255).astype("uint8")
252 #该特征图矩阵填充至显示图像的矩阵中
253 display_grid[col*size:(col+1)*size, row*size:(row+1)*size] = channel_image
254 scale = 1./size
255 #设置该层显示图像的宽高
256 plt.figure(figsize=(scale*display_grid.shape[1],scale*display_grid.shape[0]))
257 plt.title(layer_name)
258 plt.grid(False)
259 #显示图像
260 plt.imshow(display_grid, aspect="auto", cmap="viridis")
261 plt.show()
262
263 # 配置训练器
264 model.compile(loss=loss,
265 optimizer=Adam(lr=0.001),
266 metrics=['acc'])
267
268 """---------------------训练模型并保存-------------------"""
269 # 因为fit直接训练不能把所有的数据全部放入到内存钟,所以使用fit_generator相当于一个生成器,动态的把所有的数据以batch的形式放入内存
270 history = model.fit_generator(
271 train_generator,
272 steps_per_epoch=train_epoch_batch, # 这个地方需要计算
273 epochs=epoch, # 训练轮数
274 validation_data=validation_generator,
275 validation_steps=val_epoch_batch, # 这个地方需要计算
276 # verbose=2
277 )
278 model.save('./models/' + model_name) # 保存模型
279
280 # # 对准确率与损失进行画图
281 acc = history.history['acc']
282 val_acc = history.history['val_acc']
283 loss = history.history['loss']
284 val_loss = history.history['val_loss']
285
286 epochs = range(len(acc))
287
288 plt.plot(epochs, acc, 'o', label='Training accuracy')
289 plt.plot(epochs, val_acc, 'r', label='Validation accuracy')
290 plt.title("Training and Validation accuracy")
291 plt.legend()
292
293 plt.figure()
294
295 plt.plot(epochs, loss, 'o', label='Training accuracy')
296 plt.plot(epochs, val_loss, 'r', label='Validation accuracy')
297 plt.title("Training and Validation loss")
298 plt.legend()
299 plt.show()
300
301 """---------------------测试模型并保存结果-------------------"""
302 import tensorflow as tf
303 import pandas as pd
304 import os
305 import matplotlib.pyplot as plt
306 import numpy as np
307 from keras.preprocessing import image
308 import csv
309 frame = pd.DataFrame()
310 """这里修改参数"""
311 # 定义图片路径
312 img_path = 'data_new/test_images/' # 图片存放文件夹的路径最后要以“/"结尾
313 model_name='models/model_224_150.h5' # 传入模型
314 img_list = os.listdir(img_path) # 图片的名字的列表
315 all_img=len(img_list)
316 # 数据提取器 这里输入对应的数字可以拿只推理对应数量的图片
317 img_num = all_img #这里输入 all_img 可以把说有的图片都送入网络中
318 class_list = ['healthy', 'multiple_diseases', 'rust', 'scab'] # 数据对应的类别标签
319 """这里修改参数"""
320 # 加载模型
321 model = tf.keras.models.load_model(model_name) # 加载模型到预测文件
322 # model.summary() # As a reminder.
323 # 创建空csv文件
324 frame.to_csv('predict_result.csv', index=False, sep=',')
325 with open('predict_result.csv', 'w') as csvfile:
326 # 先写入columns_name
327 writer = csv.writer(csvfile)
328 writer.writerow(['image_id', 'healthy', 'multiple_diseases', 'rust', 'scab'])# 这里出入csv文件的列名
329 writer = csv.writer(csvfile)
330
331 for i in img_list[:img_num]: # 遍历文件夹里的图片
332 img = img_path + i # 得到图片的相对路径
333 img_resize = image.load_img(img, target_size=(224, 224)) # 加载图片并resize成(224*224)的格式
334 img_array = image.img_to_array(img_resize) # 转换成数组
335 img_tensor = np.expand_dims(img_array, axis=0)
336 img_input = img_tensor / 255 # 归一化到0-1之间
337 outputs = model.predict(img_input) # 获取图片信息
338 outputs = outputs[0] # 获取预测得到概率列表
339 max_class = max(outputs) # 获得预测的最大的概率值
340 # print(max_class)
341 class_dict = dict([i for i in zip(class_list, outputs)]) # 得到标签和预测的概率字典
342
343 print("测试结果每个概率为:",class_dict) #输出全部标签
344 # print(outputs, outputs[0], i[:-4])
345 print("测试结果概率最高的为:",max_class)
346 # 往 csv文件里写入数据
347 writer.writerow([f'{i[:-4]}', outputs[0], outputs[1], outputs[2], outputs[3]])
348
349 """--------展示图片--------"""
350 # 获取预测概率最大的数据的标签
351 for k,v in class_dict.items(): #遍历标签与概率的字典返回对应的k,v
352 if v == max_class: # 判断最大的概率并得到标签
353 print("测试叶片结果为:",k) # 打印最大的概率
354 plt.imshow(img_input[0])
355 plt.title(f'{k}')
356 plt.show()
357
358 csvfile.close() # 关闭csv文件