基于卷积神经网络CNN的图像分类

2023-05-05 10:12:04 浏览数 (3)

基于卷积神经网络CNN的图像分类 基于Tkinter自制GUI界面点击分类

大家好,我是Peter~

本文主要包含两个方向的内容:

  1. 如何使用卷积神经网路对一份数据进行catsdogs的分类:图像数据生成、搭建CNN模型及可视化、模型训练与预测、损失精度可视化
  2. 将构建的CNN网络模型保存后,基于Tkinter制作一个简单的GUI界面,选择图片运行立即显示分类结果

过程详解,代码注释极其详细,源码运行即可出结果,极度适合入门。

导入库

导入建模相关的库:

代码语言:python代码运行次数:0复制
import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
%matplotlib inline
import random
import os

import tensorflow as tf
from keras import layers
from keras import models
from keras.models import Sequential
from keras.layers import Conv2D,MaxPooling2D,Dropout,Flatten,Dense,Activation,BatchNormalization
from keras.preprocessing.image import ImageDataGenerator,load_img
from keras.utils.np_utils import to_categorical
from sklearn.model_selection import train_test_split

数据准备

代码语言:python代码运行次数:0复制
filenames=os.listdir("./train")

categories=[]

for file in filenames:  # 遍历每张图片
    category=file.split('.')[0]  # 根据图片名称的首位进行判断
    
    if category=='dog':  # cat-0  dog-1
        categories.append(1)
    else:
        categories.append(0)
        
df=pd.DataFrame({
    'filename': filenames,
    'category': categories
})
df.head()

总共是2000*2

数据预处理

训练集和验证集

代码语言:python代码运行次数:0复制
df["category"] = df["category"].map({0:"cat",1:"dog"})

将train中的数据分成训练集 验证集:

代码语言:python代码运行次数:0复制
# 训练集和验证集
train_df, validate_df = train_test_split(df,
                                        test_size=0.2,
                                        random_state=42)

实施索引重置功能:

代码语言:python代码运行次数:0复制
# 索引重置
train_df.reset_index(drop=True, inplace=True)

train_df.head()

对验证集实施相同的操作:

代码语言:python代码运行次数:0复制
validate_df.reset_index(drop=True, inplace=True)

validate_df.head()

整体的数据量:

代码语言:python代码运行次数:0复制
total_train = len(train_df)
total_validate = len(validate_df)
total_train,total_validate

(1600, 400)

测试集

测试集中的图片是没有具体的分类结果的:

代码语言:python代码运行次数:0复制
test_filenames = os.listdir("./test1")

test_df = pd.DataFrame({"filename": test_filenames})

test_samples = len(test_df)

test_df

在测试集中是没有分类标签label的

定义图片参数

代码语言:python代码运行次数:0复制
image_Width=128  # 宽
image_Height=128 # 高

image_Size=(image_Width,image_Height)  # 每张图片大小
image_Channels=3   # 通道数

生成图像数据

基于ImageDataGenerator生成训练集和验证集中的图片数据:

代码语言:python代码运行次数:0复制
train_datagen = ImageDataGenerator(rotation_range=15,  # 旋转角度
                                rescale=1./255, # 像素值缩放比例
                                shear_range=0.1,  # 随机错切变换的角度
                                zoom_range=0.2,  # 随机缩放的角度
                                horizontal_flip=True, # 随机将一半图像进行水平翻转
                                width_shift_range=0.1,  # 水平和垂直方向的范围;相对于总宽度和高度的比例
                                height_shift_range=0.1
                                )

对训练集的生成器:

代码语言:python代码运行次数:0复制
# 对训练集的生成器

batch_size = 15

train_generator = train_datagen.flow_from_dataframe(train_df,
                                                   "./train",
                                                   x_col="filename",
                                                   y_col="category",
                                                   target_size=image_Size,
                                                   class_mode="categorical",
                                                   batch_size=batch_size
                                                   )

运行结果:Found 1600 validated image filenames belonging to 2 classes.

验证集实施相同的操作:

代码语言:python代码运行次数:0复制
validation_datagen = ImageDataGenerator(rescale=1./255)

batch_size = 15

validation_generator = validation_datagen.flow_from_dataframe(validate_df,
                                                   "./train",
                                                   x_col="filename",
                                                   y_col="category",
                                                   target_size=image_Size,
                                                   class_mode="categorical",
                                                   batch_size=batch_size
                                                   )

运行结果:Found 400 validated image filenames belonging to 2 classes.

最后对测试集也实施相同的操作:

代码语言:python代码运行次数:0复制
test_datagen = ImageDataGenerator(rotation_range=15,
                                rescale=1./255,
                                shear_range=0.1,
                                zoom_range=0.2,
                                horizontal_flip=True,
                                width_shift_range=0.1,
                                height_shift_range=0.1
                                )

从dataframe生成数据:

代码语言:python代码运行次数:0复制
batch_size = 15

test_generator = train_datagen.flow_from_dataframe(test_df,  
                                                   # directory=None,
                                                   "./test1",
                                                   x_col="filename",
                                                   y_col=None,
                                                   target_size=image_Size,
                                                   class_mode=None,
                                                   batch_size=batch_size
                                                  ) 

Found 7486 validated image filenames.

构建CNN网络

构建的CNN网络:

代码语言:python代码运行次数:0复制
model=Sequential()

# 卷积层1
model.add(Conv2D(32,(3,3),activation='relu',input_shape=(image_Width,image_Height,image_Channels)))
# 批标准化层
model.add(BatchNormalization()) 
# 池化层
model.add(MaxPooling2D(pool_size=(2,2)))
# Dropout层;防止过拟合
model.add(Dropout(0.25))

# 卷积层2
model.add(Conv2D(64,(3,3),activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))

# 卷积层3
model.add(Conv2D(128,(3,3),activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512,activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5))

# 最后的输出层:分2个类
model.add(Dense(2,activation='softmax'))

模型编译

代码语言:python代码运行次数:0复制
model.compile(loss='categorical_crossentropy', # 多份类-使用交叉损失熵
              optimizer='rmsprop',  # 优化器
              metrics=['accuracy']  # 评价指标-分类精度
             )

模型可视化

提供两种Keras模型可视化的方法:

  • 基于visualkeras(先pip安装)
  • 基于plot_model模块

模型信息汇总

代码语言:python代码运行次数:0复制
model.summary()  # 模型基本信息

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 126, 126, 32)      896       
                                                                 
 batch_normalization (BatchN  (None, 126, 126, 32)     128       
 ormalization)                                                   
                                                                 
 max_pooling2d (MaxPooling2D  (None, 63, 63, 32)       0         
 )                                                               
                                                                 
 dropout (Dropout)           (None, 63, 63, 32)        0         
                                                                 
 conv2d_1 (Conv2D)           (None, 61, 61, 64)        18496     
                                                                 
 batch_normalization_1 (Batc  (None, 61, 61, 64)       256       
 hNormalization)                                                 
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 30, 30, 64)       0         
 2D)                                                             
                                                                 
 dropout_1 (Dropout)         (None, 30, 30, 64)        0         
                                                                 
 conv2d_2 (Conv2D)           (None, 28, 28, 128)       73856     
                                                                 
 batch_normalization_2 (Batc  (None, 28, 28, 128)      512       
 hNormalization)                                                 
                                                                 
 max_pooling2d_2 (MaxPooling  (None, 14, 14, 128)      0         
 2D)                                                             
                                                                 
 dropout_2 (Dropout)         (None, 14, 14, 128)       0         
                                                                 
 flatten (Flatten)           (None, 25088)             0         
                                                                 
 dense (Dense)               (None, 512)               12845568  
                                                                 
 batch_normalization_3 (Batc  (None, 512)              2048      
 hNormalization)                                                 
                                                                 
 dropout_3 (Dropout)         (None, 512)               0         
                                                                 
 dense_1 (Dense)             (None, 2)                 1026      
                                                                 
=================================================================
Total params: 12,942,786
Trainable params: 12,941,314
Non-trainable params: 1,472

可视化1

代码语言:python代码运行次数:0复制
import visualkeras  # 基于Keras搭建的模型可视化

visualkeras.layered_view(model)

可视化2

代码语言:python代码运行次数:0复制
tf.keras.utils.plot_model(model, show_shapes=True)

定义回调函数

代码语言:python代码运行次数:0复制
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
early_stop = EarlyStopping(patience=10)

learning_rate_reduction = ReduceLROnPlateau(monitor="val_acc", 
                                            patience=2,
                                            verbose=1,
                                            factor=0.5,
                                            min_lr=0.00001
                                           )

callbacks = [early_stop, learning_rate_reduction]

模型训练

代码语言:python代码运行次数:0复制
epochs = 10

history = model.fit(train_generator,  # 生成数据
                    epochs=epochs,  # 迭代次数
                    validation_data=validation_generator,  # 验证集数据
                    validation_steps=total_validate // batch_size,   #  验证集步长
                    steps_per_epoch=total_train // batch_size,  # 每次迭代步长
                    callbacks=callbacks  # 回调函数
                   )

损失和精度可视化

训练过程中损失Loss和精度Accuray的可视化,包含训练集和验证集:

代码语言:python代码运行次数:0复制
# 损失绘图
import matplotlib.pyplot as plt

history_dict = history.history
history_dict

{'loss': [1.3272511959075928,
  0.9481741786003113,
  0.7862767577171326,
  0.7371966242790222,
  0.6606544852256775,
  0.620651125907898,
  0.596297562122345,
  0.6052179932594299,
  0.5645563006401062,
  0.5669357776641846],
 'accuracy': [0.5526813864707947,
  0.5684542655944824,
  0.620820164680481,
  0.6359621286392212,
  0.6656151413917542,
  0.6776025295257568,
  0.6883280873298645,
  0.6965299844741821,
  0.7160883545875549,
  0.7167192697525024],
 'val_loss': [1.1246546506881714,
  1.6301778554916382,
  0.8391894102096558,
  1.2209837436676025,
  0.6906123161315918,
  0.5846400260925293,
  0.5789883136749268,
  0.584494411945343,
  0.5889277458190918,
  0.6922145485877991],
 'val_accuracy': [0.5,
  0.5,
  0.5512820482254028,
  0.5076923370361328,
  0.6358974575996399,
  0.699999988079071,
  0.6846153736114502,
  0.7051281929016113,
  0.6974359154701233,
  0.6435897350311279],
 'lr': [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001]}

损失可视化

代码语言:python代码运行次数:0复制
loss = history_dict["loss"]
val_loss = history_dict["val_loss"]

epochs = range(1,len(loss)   1)

plt.figure(figsize=(12,8))

plt.plot(epochs,  # 循环轮数
         loss,  # loss取值
         "r",
         label="loss"
        )

plt.plot(epochs,
         val_loss,  # val_loss
         "b",
         label="val_loss"
        )

plt.title("Loss and Val_Loss")
plt.xlabel("Epochs")
plt.legend()

plt.show()

精度可视化

代码语言:python代码运行次数:0复制
accuracy = history_dict["accuracy"]
val_accuracy = history_dict["val_accuracy"]

epochs = range(1,len(accuracy)   1)

plt.figure(figsize=(12,8))

plt.plot(epochs,  # 循环轮数
         accuracy,  # loss取值
         "r",
         label="accuracy"
        )

plt.plot(epochs,
         val_accuracy,  # val_loss
         "b",
         label="val_accuracy"
        )

plt.title("Accuracy and Val_Accuracy")
plt.xlabel("Epochs")
plt.legend()

plt.show()

模型保存

一行代码将前面建立的CNN模型进行保存;后面搭建GUI时会使用。

代码语言:python代码运行次数:0复制
model.save("model_cats_dogs_10category.h5")

模型预测

对测试集中的图像进行预测

代码语言:python代码运行次数:0复制
predict = model.predict(test_generator,
                        steps=np.ceil(test_samples / batch_size))

将预测的结果转成具体的分类:

代码语言:python代码运行次数:0复制
test_df["category"] = np.argmax(predict, axis=-1)

test_df.head()

预测结果可视化

在测试集中选择部分数据进行可视化

代码语言:python代码运行次数:0复制
sample_test = test_df.head(9)

plt.figure(figsize=(12,24))

for index, row in sample_test.iterrows():
    filename = row["filename"]
    category = row["category"]
    img = load_img("./test1/"   filename,target_size=image_Size)
    plt.subplot(3,3,index 1)
    plt.imshow(img)
    plt.xlabel(filename   "("   "{}".format(category)   ")")
    
plt.tight_layout()
plt.show()

下面是第二部分:将整个过程基于tkinter制成一个简单的GUI界面,通过点击实现图像分类。

导入库

主要是图像处理相关的库

代码语言:python代码运行次数:0复制
import tkinter as tk
from tkinter import filedialog
from tkinter import *
from PIL import ImageTk, Image
import numpy as np

import tensorflow as tf
from keras.models import load_model

tkinter库需要先进行安装。

导入模型

导入搭建好的CNN模型的h5文件:

代码语言:python代码运行次数:0复制
# 导入训练好的模型

model = load_model("model_cats_dogs_10category.h5")

图像窗口初始化

GUI界面的窗口参数初始化

代码语言:python代码运行次数:0复制
classes = {
    0: "It`s a cat",
    1: "It`s a dog"
}

# 初始化tkinter对象 并设置参数
代码语言:python代码运行次数:0复制
top = tk.Tk()
top.geometry("800x600")  # 设置图形窗口的宽和高
top.title("Cats and Dogs Classification Bases on Keras-CNN")  # 标题设置

top.configure(background="#CDCDCD")  # 背景色
# Label控件:指定的窗口top中显示的文本和图像
label = Label(top, background="#CDCDCD", font=("arial", 15, "bold"))

sign_image = Label(top)  

搭建预测分类函数

代码语言:python代码运行次数:0复制
def classify(file_path):
    global label_packed
    
    image = Image.open(file_path)  # 导入图片调整大小
    image = image.resize((128,128))
    # expand_dims改变图片shape axis=0在首位增加1  比如(3,4) ---> (1,3,4)
    image = np.expand_dims(image, axis=0)  
    image = np.array(image)
    image = image/255
    
    pred_ = model.predict([image])  # 每个类别的概率
    pred = np.argmax(pred_)  # 具体所属类别:确定所在索引
    
    sign = classes[pred]   # 根据自定义的字典获取分类结果
    
    label.configure(foreground="#011638",text=sign)

实现点击功能

代码语言:python代码运行次数:0复制
def show_classify_button(file_path):
    """
    功能:实现点击按钮
    """
    # 实例化按钮对象
    classify_b = Button(top,  # 位置
                     text="Classify Image",  # 按钮名称
                     command=lambda: classify(file_path),  # 执行回调函数
                     padx=10,  # 指定水平和垂直方向上按钮内容和边框的间距
                     pady=5
                    )
    # 对象属性
    classify_b.configure(background='#364156', # 背景色
                         foreground='white', # 前景色
                         font=('arial',10,'bold'))  # 调整字体
    # place: relx, rely代表窗口大小所对应的x, y坐标比例
    classify_b.place(relx=0.8,rely=0.5)

图片加载功能

如何使用tkinter加载本地图像?

  • tkinter.filedialog.asksaveasfilename():选择以什么文件名保存,返回文件名
  • tkinter.filedialog.asksaveasfile():选择以什么文件保存,创建文件并返回文件流对象
  • tkinter.filedialog.askopenfilename():选择打开的文件,返回文件名
  • tkinter.filedialog.askopenfile():选择打开的文件,返回IO流对象
  • tkinter.filedialog.askdirectory():选择目录,返回目录名
  • tkinter.filedialog.askopenfilenames():选择打开多个文件,以元组形式返回多个文件名
  • tkinter.filedialog.askopenfiles():选择打开多个文件,以列表形式返回多个IO流对象
代码语言:python代码运行次数:0复制
def upload_image():
    """
    图片加载功能
    """
    try:
        file_path = filedialog.askopenfilename()  # 打开文件返回文件名;本地目录也是整体的路径
        uploaded = Image.open(file_path)  # 打开图片,PIL类型;pytorch的顺序:(batch,c,h,w)  tf和numpy是(batch,h,w,c)
        uploaded.thumbnail(((top.winfo_width()/2.25),  # 对图片实施裁剪
                            (top.winfo_height()/2.25)))
        
        im=ImageTk.PhotoImage(uploaded)  # tk.PhotoImage(file=path_to_image)
        sign_image.configure(image=im)  # sign_image = Label(top)  Label实例对象中配置im图片
        sign_image.image=im
        label.configure(text='')
        show_classify_button(file_path)  # 调用按钮点击功能
    except:
        pass

功能调用

代码语言:python代码运行次数:0复制
# 调用按钮功能
buttom=Button(top,  
              text="Upload an image",
              command=upload_image,  # 回调函数
              padx=10,
              pady=5)

# 按钮属性设置
buttom.configure(background='#364156', 
                 foreground='white',
                 font=('arial',10,'bold')
                )

# pack:按钮、照片图像、标签布局设置
buttom.pack(side=BOTTOM,
            pady=50)

sign_image.pack(side=BOTTOM,expand=True)

label.pack(side=BOTTOM,expand=True)

# 标题(上面)
title = Label(top,   # 位置
                text="Cats & Dogs Classification", # 标题内容
                pady=20, # 和y轴边距
                font=('arial',30,'bold'))  # 字体设置

title.configure(background='#CDCDCD',foreground='#364156')  # 背景色和前景色
title.pack()  # 布局
top.mainloop()  # 运行图片对象 top = tk.Tk()

1 人点赞