基于卷积神经网络CNN的图像分类 基于Tkinter自制GUI界面点击分类
大家好,我是Peter~
本文主要包含两个方向的内容:
- 如何使用卷积神经网路对一份数据进行
cats
和dogs
的分类:图像数据生成、搭建CNN模型及可视化、模型训练与预测、损失精度可视化 - 将构建的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
生成训练集和验证集中的图片数据:
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流对象
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()