Keras是一个由Python编写的开源人工神经网络库,可以作为Tensorflow、Microsoft-CNTK和Theano的高阶应用程序接口,进行深度学习模型的设计、调试、评估、应用和可视化 。简而言之,Keras可以理解为前端,TensorFlow为后端。
相比于tensorflow,keras简单易用,搭建网络容易,对新手十分友好。
1、项目文件
dataset文件夹是数据集,里面存放在10种动物的图片数据;
icons文件夹里面存放在图形界面系统需要的按钮图片;
log文件夹里面存放在训练过程中的损失曲线图和准确率曲线图;
model文件夹里面存放在训练好后的模型文件;
main.py是主函数,数据集的读取、卷积神经网络的搭建和模型训练、测试等都在里面;
app.py、graphics.py和Image.py均为图形界面需要的支撑文件;
gui.py是图形界面主程序;
classification_utilities.py是混淆矩阵用到的支撑文件。
2、main.py详解
导入TensorFlow、Keras、numpy等支撑库
import os #用于读取文件路径
import numpy as np #用于数值操作
import cv2 # 用于图像处理
from tensorflow.python.keras.utils.np_utils import to_categorical #将labels转成二进制形式
from tensorflow.python.keras.models import load_model # 加载模型
from tensorflow.python.keras.applications.densenet import DenseNet201
from tensorflow.python.keras.applications.resnet import ResNet152
from tensorflow.python.keras.applications.vgg19 import VGG19
from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.models import Model as keras_Model
from tensorflow.python.keras.callbacks import TensorBoard
from classification_utilities import display_cm #用于给混淆矩阵加标签
整个代码结构一共两大类:一是数据读取类;一是模型类
首先看数据类:
定义一个读取文件夹中所有文件的函数,根据输入的文件夹绝对路径,将该文件夹下的所有指定后缀的文件读取存入一个list,该list的第一个元素是该文件夹的名字
# 输入一个文件路径,对其下的每个文件夹下的图片读取,并对每个文件夹给一个不同的Label
# 返回一个img的list,返回一个对应label的list,返回一下有几个文件夹(有几种label)
def read_file(self, path):
img_list = []
label_list = []
dir_counter = 0
n = 0
# 对路径下的所有子文件夹中的所有jpg文件进行读取并存入到一个list中
for child_dir in os.listdir(path):
child_path = os.path.join(path, child_dir)
for dir_image in os.listdir(child_path):
if endwith(dir_image, 'jpg'):
img = cv2.imread(os.path.join(child_path, dir_image))
img = cv2.resize(img, (self.size, self.size))
img_list.append(img)
label_list.append(dir_counter)
n = n + 1
dir_counter += 1
# 返回的img_list转成了 np.array的格式
img_list = np.array(img_list)
return img_list, label_list, dir_counter
在这里多说一下endwith函数,这是根据python自带函数endswiths函数增加的内容,endswith函数用于判断字符串是否以指定后缀结尾,如果以指定后缀结尾返回True,否则返回False。可选参数"start"与"end"为检索字符串的开始与结束位置。而此处的endwith函数用于根据输入的字符串和标签,对这个字符串的后缀和标签进行匹配,这样就可以避免读取到不是图片后缀的文件。
def endwith(s,*endstring):
resultArray = map(s.endswith,endstring)
if True in resultArray:
return True
else:
return False
读取文件夹中每个子文件中后缀为jpg的文件,
def read_file(path):
img_list = []
label_list = []
dir_counter = 0
n = 0
# 对路径下的所有子文件夹中的所有jpg文件进行读取并存入到一个list中
for child_dir in os.listdir(path):
child_path = os.path.join(path, child_dir)
for dir_image in os.listdir(child_path):
if endwith(dir_image, 'jpg'):
img = cv2.imread(os.path.join(child_path, dir_image))
img_list.append(img)
label_list.append(dir_counter)
n = n + 1
dir_counter += 1
# 返回的img_list转成了 np.array的格式
img_list = np.array(img_list)
return img_list, label_list, dir_counter
以上读取文件的准备工作就做好了,下面就可以定义一个读取训练和测试数据的函数了
class DataSet(object):
def __init__(self, path1, path2):
self.num_classes = None
self.X_train = None
self.X_test = None
self.Y_train = None
self.Y_test = None
self.extract_data(path1, path2)
# 在这个类初始化的过程中读取path下的训练数据
def extract_data(self, path1, path2):
# 根据指定路径读取出图片、标签和类别数
X_train, y_train, counter1 = read_file(path1)
X_test, y_test, counter2 = read_file(path2)
# 将训练集、测试集格式化城制定形状[数据量,图片长,图片宽,图片通道数]
X_train = X_train.reshape(X_train.shape[0], 64,64,3)
X_test = X_test.reshape(X_test.shape[0], 64,64,3)
# 这里讲一下图片的性质
# 归一化操作归一化,0-255不太方便神经网络进行计算,因此将范围缩小到0—1
X_train = X_train.astype('float32') / 255
X_test = X_test.astype('float32') / 255
# 将labels转成矩阵形式
Y_train = to_categorical(y_train, num_classes=counter1)
Y_test = to_categorical(y_test, num_classes=counter2)
# 将格式化后的数据赋值给类的属性上
self.X_train = X_train
self.X_test = X_test
self.Y_train = Y_train
self.Y_test = Y_test
self.num_classes = counter1
# 查看训练集和测试集的大小
def check(self):
print('text shape:', self.X_test.shape)
print('train shape:', self.X_train.shape)
数据做好之后,下面就可以开始模型的搭建和训练了
下面定义个类函数,包括模型设计、模型训练、模型验证等三大主功能
class Model(object):
# 指定训练好的模型保存地址
FILE_PATH = r"./model/model.h5"
def __init__(self):
self.model = None
# 读取实例化后的DataSet类作为进行训练的数据源
def read_trainData(self, dataset):
self.dataset = dataset
# 建立模型
def build_model(self):
# self.base_model = InceptionV3(include_top=False)
# self.base_model = VGG19(weights='imagenet')
self.base_model = ResNet152(weights='imagenet')
x = self.base_model.output
self.predictions = Dense(10, activation='softmax')(x)
self.model = keras_Model(inputs=self.base_model.input, outputs=self.predictions)
# model.summary()输出模型各层的参数状况
self.model.summary()
print('模型共有网络层数:' + str(len(self.model.layers)))
# 进行模型训练的函数,具体的optimizer、loss可以进行不同选择
def train_model(self):
self.model.compile(optimizer='adadelta',
loss='categorical_crossentropy',
metrics=['accuracy']
)
self.model.fit(self.dataset.X_train,
self.dataset.Y_train,
epochs=60,
batch_size=20,
callbacks=[TensorBoard(log_dir='./log')]
)
def evaluate_model(self):
print('\nTesting---------------')
import sklearn.metrics as metrics
facies_labels = ['cane', 'cavallo', 'elefante', 'farfalla', 'gallina','gatto','mucca',"pecora","ragno","scoiattolo"]
y_pred_ohe = self.model.predict(self.dataset.X_test) # shape=(n_samples, 12)
y_pred_labels = np.argmax(y_pred_ohe, axis=1) # only necessary if output has one-hot-encoding, shape=(n_samples)
confusion_matrix = metrics.confusion_matrix(y_true=np.argmax(self.dataset.Y_test, axis=1),
y_pred=y_pred_labels) # shape=(12, 12)
loss, accuracy = self.model.evaluate(self.dataset.X_test, self.dataset.Y_test)
print('分类准确率 = %.4f' % accuracy)
display_cm(confusion_matrix, facies_labels, hide_zeros=False)
def save(self, file_path=FILE_PATH):
self.model.save(file_path)
print('Model Saved.')
def load(self, file_path=FILE_PATH):
print('Model Loaded.')
self.model = load_model(file_path)
一切设计就绪,剩下的就可以开始正式开始炼丹之旅了
datasets = DataSet(r"./dataset/train",r"./dataset/test")
datasets.check()
model = Model()
model.read_trainData(datasets)
model.build_model()
model.train_model()
model.save()
model.load()
model.evaluate_model()
对于算法部分的工作到这里就可以算是结束了,下面是系统设计部分,系统设计部分简单而言就是开发一个图形界面,使得用户可以通过鼠标操作来完成某张图片的识别演示。整体的系统界面如下图所示:
从上图可以看到系统由识别结果输出框、图片显示框、三个功能按钮组成,话不多说,直接上代码。
首先定义主界面的大小,并将其置于屏幕中央
# 设置主屏幕
MainWindow.setObjectName("MainWindow")
MainWindow.setFixedSize(825, 570)
# 获取屏幕的尺寸信息,也可以理解为屏幕的分辨率信息。
# 获取到的屏幕信息有两个属性,一个是width对应屏幕的长度,一个是height对应屏幕的宽度
center = QDesktopWidget().screenGeometry()
# 将主窗口置于屏幕中间
MainWindow.move((center.width() - 825) / 2, (center.height() - 570) / 2)
其次定义右边三个按钮的垂直布局
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
# setGeometry()设置窗口的位置
self.verticalLayoutWidget.setGeometry(QtCore.QRect(620, 190, 151, 341))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget_2")
# QVBoxLayout 垂直布局管理
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
# setContentsMargins外边距左 上 右 下
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.pushButton_1 = QtWidgets.QPushButton(self.verticalLayoutWidget)
self.pushButton_1.setObjectName("pushButton_1")
self.verticalLayout_2.addWidget(self.pushButton_1)
self.pushButton_2 = QtWidgets.QPushButton(self.verticalLayoutWidget)
self.pushButton_2.setObjectName("pushButton_2")
self.verticalLayout_2.addWidget(self.pushButton_2)
self.pushButton_3 = QtWidgets.QPushButton(self.verticalLayoutWidget)
self.pushButton_3.setObjectName("pushButton_3")
self.verticalLayout_2.addWidget(self.pushButton_3)
然后定义图片显示窗口
self.graphicsView = GraphicsView(self.centralwidget)
self.graphicsView.setEnabled(True)
self.graphicsView.setGeometry(QtCore.QRect(30, 180, 552, 352))
self.graphicsView.setObjectName("graphicsView")
最后定义上方的结果显示框
self.label = QtWidgets.QTextEdit(self.centralwidget)
self.label.setGeometry(QtCore.QRect(31, 100, 550, 52))
self.label.setObjectName("label")
self.label.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) #设置垂直滚动条
MainWindow.setCentralWidget(self.centralwidget)
以上界面的整体布局就设置完了,运行程序可以得到下面的界面布局
然后使用信息槽,将定义出的三大块布局填充上对应的内容
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
retranslateUi定义如下
设置窗口名称和主界面的背景色为青蓝色#E9F2FF
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
MainWindow.setStyleSheet("QMainWindow{background:#E9F2FF}")
设置图片显示窗口背景颜色
self.graphicsView.setStyleSheet("QGraphicsView{background:#E9F2FF}")
设置三个按钮的信息,包括按钮的名字,按钮的图案,按钮大小等,以及三个按钮对应链接的三个功能函数
self.pushButton_1.setText(_translate("MainWindow", "打开图片"))
self.pushButton_1.setIcon(QtGui.QIcon("icons/open.svg"))
self.pushButton_1.setIconSize(QtCore.QSize(40, 40))
self.pushButton_1.setStyleSheet("QPushButton{background:#16A085;border:none;color:#000000;font-size:15px;}"
"QPushButton:hover{background-color:#008080;}")
self.pushButton_2.setText(_translate("MainWindow", "图像识别"))
self.pushButton_2.setIcon(QtGui.QIcon("icons/bank_card.svg"))
self.pushButton_2.setIconSize(QtCore.QSize(40, 40))
self.pushButton_2.setStyleSheet("QPushButton{background:#9F35FF;border:none;color:#000000;font-size:15px}"
"QPushButton:hover{background-color:#9932CC;}")
self.pushButton_3.setText(_translate("MainWindow", "退出系统"))
self.pushButton_3.setIcon(QtGui.QIcon("icons/close_.svg"))
self.pushButton_3.setIconSize(QtCore.QSize(40, 40))
self.pushButton_3.setStyleSheet("QPushButton{background:#CE0000;border:none;color:#000000;font-size:15px;}"
"QPushButton:hover{background-color:#8B0000;}")
self.pushButton_1.clicked.connect(self.clickOpen)
self.pushButton_2.clicked.connect(self.recognition)
self.pushButton_3.clicked.connect(self.close)
设置结果显示框的信息
self.label.setText("识别结果")
self.label.setFrameShape(QtWidgets.QFrame.Box)
self.label.setMidLineWidth(5)
font = QtGui.QFont()
font.setBold(True)
font.setPointSize(13)
font.setWeight(75)
self.label.setFont(font)
self.label.setAlignment(Qt.AlignCenter)