使用Keras搭建一个深度学习图片识别系统

149 阅读7分钟

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)