机器学习——植物叶片病害识别
一、选题背景
随着现代科技的发展,人们对于人工智能领域的研究越发的深入。机器学习作为人工智能和识别领域研究的重要课题非常值得我们学习和研究。本次机器学习设计为植物叶片病害识别,随着社会发展,在农业、林业方面机械化、智能化程度不断的提高。在林业管理上的要求追求便捷,有效,打药除害等都可以通过无人机实现。基于机器学习的植物叶片病害识别,可以通过识别无人机拍下来的树木叶片图片,快速得出该树的健康状态,是正常的,还是患病的以及患的是哪种病。通过机器学习,对正常和患病叶片的训练学习来完成识别以及提高准确度。
通过无人机定位巡航拍照,快速收集林区的树木叶片情况,在识别完图片输出结果。通过结果可以很明确的知道哪个区的那棵树患病。在这过程中节省了大量的人力,物力,以及聘请相关植物病理学家的费用,降低成本,对于林业的发展具有重要的意义。
二、设计方案
本次机器学习设计具体方案,通过网上收集数据集,对数据集进行处理,数据集符合我们所使用的格式。在数据集中的文件打上标签,在对数据进行预处理,之后采用keras框架搭建、使用卷积神经网络构建以及训练模型、通过训练和验证准确性以及训练和验证损失图进行分析、最后导入测试图片进行测试并保存测试图片名称以及识别结果为一个csv数据文件。本次涉及的技术难点,如何将数据集中大量的数据处理成我们所需要的格式,如何提高图片的识别准确度也是一大难点。对于处理数据,可以采用编写程序,通过数据集自带的csv数据文件进行数据处理。而图片识别准确度可以通过图片的大小,添加卷积层数、对数据进行二次筛选、增加训练次数来提升图片识别的精度。
数据集来源:kaggle:https://www.kaggle.com/competitions/plant-pathology-2021-fgvc8/data
kaggle:https://www.kaggle.com/competitions/plant-pathology-2020-fgvc7/data
参考案例:kaggle讨论区,猫狗识别案例,手写字体识别等。
三、实现步骤
植物叶片病害识别的具体实现步骤如下。1 获取数据集
从kaggle上下载数据集,解压打开。![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113707158-447822382.png)
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)
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113732850-1347161895.png)
根据数据,创建数据文件夹,将整理好的图片分为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)
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113754780-1476423697.png)
遍历所有标签于其对应的数据,并建立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])
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113821994-607834668.png)
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') # 指定验证数据的位置
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113856997-11051551.png)
读取测试集中的一条数据,查看样本。
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()
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113911835-1068522754.png)
输出特征图,每行显示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()
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221113925593-932410492.png)
神经模型参数设置,并查看本次数据大小。
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)))
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114005702-718149444.png)
对数据进行预处理。
# 数据预处理对数据进行归一化到【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 )
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114027029-758293104.png)
根据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()
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114042951-358747872.png)
配置训练器。
# 配置训练器 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) # 保存模型
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114055039-296510459.png)
根据训练的结果,绘制训练和验证准确性图,训练和验证损失图。
# # 对准确率与损失进行画图 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()
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114117999-688767250.png)
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114123896-915361161.png)
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,输出符合构建模型的预期。输出了每个类别的概率,最高概率为多少,最终模型输出结果是什么病害。
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114141074-275994876.png)
查看识别结果保存的csv文件。
![](/i/l/?n=23&i=blog/3049831/202212/3049831-20221221114151846-749310885.png)
四、总结
结论:从最终结果来看,本次设计的植物叶片病害识别,基本达到了设计初期的预期结果。刚开始,模型因为图片设置为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 90 from keras.preprocessing import image 91 import numpy as np 92 img_path = "./data_new/train_images/rust/Train_1624.jpg" 93 img = image.load_img(img_path, target_size=(224,224)) 94 img_tensor = image.img_to_array(img) 95 img_tensor = np.expand_dims(img_tensor, axis=0) 96 img_tensor /= 255. 97 #显示样本 98 import matplotlib.pyplot as plt 99 plt.imshow(img_tensor[0]) 100 plt.show() 101 #存储层的名称 102 layer_names = [] 103 for layer in model.layers[:4]: 104 layer_names.append(layer.name) 105 # 每行显示16个特征图 106 images_pre_row = 16 #每行显示的特征图数 107 # 循环8次显示8层的全部特征图 108 for layer_name, layer_activation in zip(layer_names, activations): 109 n_features = layer_activation.shape[-1] #保存当前层的特征图个数 110 size = layer_activation.shape[1] #保存当前层特征图的宽高 111 n_col = n_features // images_pre_row #计算当前层显示多少行 112 #生成显示图像的矩阵 113 display_grid = np.zeros((size*n_col, images_pre_row*size)) 114 #遍历将每个特张图的数据写入到显示图像的矩阵中 115 for col in range(n_col): 116 for row in range(images_pre_row): 117 #保存该张特征图的矩阵(size,size,1) 118 channel_image = layer_activation[0,:,:,col*images_pre_row+row] 119 #为使图像显示更鲜明,作一些特征处理 120 channel_image -= channel_image.mean() 121 channel_image /= channel_image.std() 122 channel_image *= 64 123 channel_image += 128 124 #把该特征图矩阵中不在0-255的元素值修改至0-255 125 channel_image = np.clip(channel_image, 0, 255).astype("uint8") 126 #该特征图矩阵填充至显示图像的矩阵中 127 display_grid[col*size:(col+1)*size, row*size:(row+1)*size] = channel_image 128 129 130 scale = 1./size 131 #设置该层显示图像的宽高 132 plt.figure(figsize=(scale*display_grid.shape[1],scale*display_grid.shape[0])) 133 plt.title(layer_name) 134 plt.grid(False) 135 #显示图像 136 plt.imshow(display_grid, aspect="auto", cmap="viridis") 137 plt.show() 138 139 """---------------------开始构建-------------------""" 140 # 导入需要用到的库 141 import os 142 import tensorflow as tf 143 from tensorflow.keras.optimizers import Adam 144 from keras_preprocessing.image import ImageDataGenerator 145 import matplotlib.pyplot as plt 146 147 """--------------------指定数据路径-------------------""" 148 # 指定好数据集 149 base_dir = './data_new' # 存放所有数据的位置 150 train_dir = os.path.join(base_dir, 'train_images') # 指定训练数据的位置 151 # print(os.listdir(train_dir)) # 查看数据类别 152 validation_dir = os.path.join(base_dir, 'val_images') # 指定验证数据的位置 153 154 155 """------------------参数设置-------------------""" 156 model_name = 'model_224_150_1.h5' # 给模型命名以 h5为后缀 157 class_num = 4 # 类别的个数 158 epoch = 150 # 训练的轮数 159 batch = 20 # 批次大小 160 resize_img = (224, 224) # 图片送入网络的大小 161 class_mode = 'sparse' # 返回的格式:categorical是返回2D的one-hot编码标签,binary是返回1D的二值标签,sparse返回1D的整数标签,None不反回任何标签仅仅生成数据 162 activation = 'softmax' # 激活函数设置 163 loss = 'sparse_categorical_crossentropy' 164 train_epoch_batch = 100 # 从train迭代器中拿出来数据测次数 这个需要计算的 165 val_epoch_batch = 50 # 从val迭代器中拿出来数据测次数 这个需要计算的 166 167 # # 训练集 得到数据存放的文件夹 168 # train_healthy_dir =os.path.join(train_dir,'healthy') 169 # train_multiple_diseases_dir = os.path.join(train_dir,'multiple_diseases') 170 # train_rust_dir =os.path.join(train_dir,'rust') 171 # train_scab_fir = os.path.join(train_dir,'scab') 172 # # 判断数据集的大小 173 # print('train_healthy_dir:',len(os.listdir(train_healthy_dir))) 174 # print('train_multiple_diseases_dir:',len(os.listdir(train_multiple_diseases_dir))) 175 # print('train_rust_dir:',len(os.listdir(train_rust_dir))) 176 # print('train_scab_fir:',len(os.listdir(train_scab_fir))) 177 # # 验证集 178 # validation_healthy_dir =os.path.join(validation_dir,'healthy') 179 # validation_multiple_diseases_dir = os.path.join(validation_dir,'multiple_diseases') 180 # validation_rust_dir =os.path.join(validation_dir,'rust') 181 # validation_scab_dir = os.path.join(validation_dir,'scab') 182 # # 判断数据集的大小 183 # print('validation_healthy_dir:',len(os.listdir(validation_healthy_dir))) 184 # print('validation_multiple_diseases_dir:',len(os.listdir(validation_multiple_diseases_dir))) 185 # print('validation_rust_dir:',len(os.listdir(validation_rust_dir))) 186 # print('validation_scab_dir:',len(os.listdir(validation_scab_dir))) 187 188 189 # 数据预处理对数据进行归一化到【0-1】之间进行数据增强 190 """--------------------数据处理-------------------""" 191 192 train_datagen = ImageDataGenerator(rescale=1. / 255, # 归一化 193 rotation_range=40, # 旋转图片 194 width_shift_range=0.2, # 改变图片的宽 195 height_shift_range=0.2, # 改变图片的高 196 shear_range=0.2, # 裁剪图片 197 zoom_range=0.2, # 缩放图片大小 198 horizontal_flip=True, # 平移图片 199 fill_mode='nearest' 200 ) 201 # 测试集处理 202 test_datagen = ImageDataGenerator(rescale=1. / 255) 203 204 # 得到迭代器 205 train_generator = train_datagen.flow_from_directory( 206 train_dir, # 文件夹路径 207 target_size=resize_img, # 指定resize的大小,需要和神经网络指定的图片大小相同 208 batch_size=batch, # 批次的大小每一次拿出20个数据送入到网络训练 209 class_mode=class_mode # 返回的格式:categorical是返回2D的one-hot编码标签,binary是返回1D的二值标签,sparse返回1D的整数标签,None不反回任何标签仅仅生成数据 210 ) 211 validation_generator = test_datagen.flow_from_directory( 212 validation_dir, 213 target_size=resize_img, 214 batch_size=batch, 215 class_mode=class_mode 216 ) 217 """-----------------构建卷积神经模型----------------""" 218 #Output shape计算公式:(输入尺寸-卷积核尺寸/步长+1 219 #对CNN模型,Param的计算方法如下: 220 #卷积核长度*卷积核宽度*通道数+1)*卷积核个数 221 #输出图片尺寸:224-3+1=222 222 model = tf.keras.models.Sequential([ 223 # 32个3*3的卷积核 relu激活函数 输入图像的大小 224 #32*3*3*3+32=896 225 tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)), 226 tf.keras.layers.MaxPooling2D(2, 2), # 最大池化层 2*2把 h和 w 编程原来的1/2 227 228 #64*3*3*32+64=18496 229 tf.keras.layers.Conv2D(64, (3, 3), activation='relu'), 230 tf.keras.layers.MaxPooling2D(2, 2), 231 232 #128*3*3*64+128=73856 233 tf.keras.layers.Conv2D(128, (3, 3), activation='relu'), 234 tf.keras.layers.MaxPooling2D(2, 2), 235 236 #128*3*3*128+128=147584 237 tf.keras.layers.Conv2D(128, (3, 3), activation='relu'), 238 tf.keras.layers.MaxPooling2D(2, 2), 239 240 # 把数据拉平输入全连接层 241 tf.keras.layers.Flatten(), 242 # 全连接层 243 #(18432+1)*512=9437696 244 tf.keras.layers.Dense(512, activation='relu'), # 512 是输出特征大小 245 tf.keras.layers.Dropout(0.4), 246 #(512+1)*512=262656 247 tf.keras.layers.Dense(512, activation='relu'), 248 tf.keras.layers.Dropout(0.4), 249 # 二分类用sigmoid,1代表得到一个值 250 #(512+1)*4=2052 251 tf.keras.layers.Dense(class_num, activation=activation) 252 ]) 253 #特征图的维度变化 254 # model.summary() 255 # 配置训练器 256 model.compile(loss=loss, 257 optimizer=Adam(lr=0.001), 258 metrics=['acc']) 259 260 """---------------------训练模型并保存-------------------""" 261 # 因为fit直接训练不能把所有的数据全部放入到内存钟,所以使用fit_generator相当于一个生成器,动态的把所有的数据以batch的形式放入内存 262 history = model.fit_generator( 263 train_generator, 264 steps_per_epoch=train_epoch_batch, # 这个地方需要计算 265 epochs=epoch, # 训练轮数 266 validation_data=validation_generator, 267 validation_steps=val_epoch_batch, # 这个地方需要计算 268 # verbose=2 269 ) 270 model.save('./models/' + model_name) # 保存模型 271 272 # # 对准确率与损失进行画图 273 acc = history.history['acc'] 274 val_acc = history.history['val_acc'] 275 loss = history.history['loss'] 276 val_loss = history.history['val_loss'] 277 278 epochs = range(len(acc)) 279 280 plt.plot(epochs, acc, 'o', label='Training accuracy') 281 plt.plot(epochs, val_acc, 'r', label='Validation accuracy') 282 plt.title("Training and Validation accuracy") 283 plt.legend() 284 285 plt.figure() 286 287 plt.plot(epochs, loss, 'o', label='Training accuracy') 288 plt.plot(epochs, val_loss, 'r', label='Validation accuracy') 289 plt.title("Training and Validation loss") 290 plt.legend() 291 plt.show() 292 293 """---------------------测试模型并保存结果-------------------""" 294 import tensorflow as tf 295 import pandas as pd 296 import os 297 import matplotlib.pyplot as plt 298 import numpy as np 299 from keras.preprocessing import image 300 import csv 301 frame = pd.DataFrame() 302 """这里修改参数""" 303 # 定义图片路径 304 img_path = 'data_new/test_images/' # 图片存放文件夹的路径最后要以“/"结尾 305 model_name='models/model_224_150.h5' # 传入模型 306 img_list = os.listdir(img_path) # 图片的名字的列表 307 all_img=len(img_list) 308 # 数据提取器 这里输入对应的数字可以拿只推理对应数量的图片 309 img_num = all_img #这里输入 all_img 可以把说有的图片都送入网络中 310 class_list = ['healthy', 'multiple_diseases', 'rust', 'scab'] # 数据对应的类别标签 311 """这里修改参数""" 312 # 加载模型 313 model = tf.keras.models.load_model(model_name) # 加载模型到预测文件 314 # model.summary() # As a reminder. 315 # 创建空csv文件 316 frame.to_csv('predict_result.csv', index=False, sep=',') 317 with open('predict_result.csv', 'w') as csvfile: 318 # 先写入columns_name 319 writer = csv.writer(csvfile) 320 writer.writerow(['image_id', 'healthy', 'multiple_diseases', 'rust', 'scab'])# 这里出入csv文件的列名 321 writer = csv.writer(csvfile) 322 323 for i in img_list[:img_num]: # 遍历文件夹里的图片 324 img = img_path + i # 得到图片的相对路径 325 img_resize = image.load_img(img, target_size=(224, 224)) # 加载图片并resize成(224*224)的格式 326 img_array = image.img_to_array(img_resize) # 转换成数组 327 img_tensor = np.expand_dims(img_array, axis=0) 328 img_input = img_tensor / 255 # 归一化到0-1之间 329 outputs = model.predict(img_input) # 获取图片信息 330 outputs = outputs[0] # 获取预测得到概率列表 331 max_class = max(outputs) # 获得预测的最大的概率值 332 # print(max_class) 333 class_dict = dict([i for i in zip(class_list, outputs)]) # 得到标签和预测的概率字典 334 335 print("测试结果每个概率为:",class_dict) #输出全部标签 336 # print(outputs, outputs[0], i[:-4]) 337 print("测试结果概率最高的为:",max_class) 338 # 往 csv文件里写入数据 339 writer.writerow([f'{i[:-4]}', outputs[0], outputs[1], outputs[2], outputs[3]]) 340 341 """--------展示图片--------""" 342 # 获取预测概率最大的数据的标签 343 for k,v in class_dict.items(): #遍历标签与概率的字典返回对应的k,v 344 if v == max_class: # 判断最大的概率并得到标签 345 print("测试叶片结果为:",k) # 打印最大的概率 346 plt.imshow(img_input[0]) 347 plt.title(f'{k}') 348 plt.show() 349 350 csvfile.close() # 关闭csv文件
标签:img,叶片,image,train,dir,path,识别,os,病害 From: https://www.cnblogs.com/MuBenYou/p/16996088.html