如今在人脸识别领域中,开始逐步加强对深度学习技术的运用,这促使人脸识别的相关技术的发展速度得到显著提升,在此背景下,人脸识别技术在未来必然会拥有更为广阔的发展前景。

本文主要以卷积神经网络为基础对人脸识别模型进行设计,使用深度学习框架,通过Keras完成对卷积神经网络模型进行构建,使用OpenCV接口识别人脸并处理人脸数据建立数据集,然后对设立的模型开展训练,在完成训练后,运用该模型来实现人脸识别打卡。系统主要分为人脸采集、人脸图像处理、训练人脸模型训练和人脸识别打卡小程序四个模块,四个模块均能按照预定方式实现相应功能,整体来看,系统所具备的性能能够达到预期水平。

关键词  人脸识别  卷积神经网络  OpenCV  Keras  

5  系统实现

经过前期的需求分析与系统设计,本章将按照人脸检测收集、人脸处理、模型训练和人脸识别GUI打卡小程序模块,分别介绍基于深度学习的人脸识别系统的开发的具体过程及实现代码。

5.1  人脸检测收集

设定获取图片后的保存路径save_path,然后执行get_face.py中的__name__ == '__main__方法,首先使用OpenCV中的VideoCapture函数调用并启用摄像头,然后使用OpenCV中的级联分类器(CascadeClassifier)加载人脸识别分类器haarcascade_frontalface_default.xml,针对所收集每帧图像中包含的人脸相关信息进行检测,且把得到的信息保存至特定位置,同时在视频中标记出人脸框与当前以保存图片张数,实现效果如图5.1所示。

图5.1 人脸检测收集实现

实现代码如下:
def getface(name, cameraid, max_num, path_name):
    cv2.namedWindow(name)
    videocap = cv2.VideoCapture(cameraid)
    data_path = "haarcascade_frontalface_default.xml"
    classfier = cv2.CascadeClassifier(data_path)#加载OpenCV人脸识别器
    color = (0, 255, 0
    num = 0
    while videocap.isOpened):
        ok, frame = videocap.read()

if not ok:
            break
        grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32
        if len(faceRects) > 0: #检测到人脸,对人脸进行加框

for faceRect in faceRects:

x, y, w, h = faceRect
                img_name = '%s/%d.jpg ' %(path_name, num)#保存人脸到目录
                
                if num > max_num:  #如果下载数量完成,退出

   break
                cv2.rectangle(frame,

            if num > max_num:
             break
        cv2.imshow(name, frame)
        c = cv2.waitKey(10
        if c & 0xFF == ord'q'):
            break
        videocap.release()
        cv2.destroyAllWindows()

5.2  人脸处理

首先设置图像大小参数IMAGE_SIZE,图像尺寸进行调整图片初始长宽存在差异先确定哪一个边的长度是最长的,之后对其跟短边的差距进行计算,以明确短边对应的像素宽度需增加的具体数值,给图像增加边界,然后使用OpenCV中的cv2.copyMakeBorder函数进行边缘填充,最后使用OpenCV中的cv2.resize函数调整图像大小并返回。当训练模型时,会调用load_dataseth函数,从指定路径读取训练数据图片进行转换并得到相应的四维数组,

尺寸最后标注数据,chenkepu文件夹下都是需要被识别的脸部图像,全部指定为0,其他文件夹下是其他人脸,全部指定为1,最后返回数据集。实现代码如下:
IMAGE_SIZE = 64 #图像大小参数IMAGE_SIZE
def resize_image(image,value=BLACK)
    return cv2.resize(constant,(height, width)
images = [] #保存图片
labels = []#保存标签
# 读取图片
def read_path(path_name):
    for dir_item in os.listdir(path_name):
        full_path = os.path.abspath(os.path.join(path_name, dir_item)
        if os.path.isdir(full_path): #读取路径
            read_path(full_path)
        else:
            if dir_item.endswith('.jpg'):
                image = cv2.imread(full_path)
                image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)
                images.append(image)
                labels.append(path_name)
    return images, labels

def load_dataset(path_name): #加载数据时调用此函数
    images, labels = read_path(path_name)
    images = np.array(images)
    labels = np.array([0 if label.endswith('chenkepu'else 1 for label in labels])#打上标签
    return images, labels #返回图片与标签

5.3  模型训练

5.3.1 数据集处理

收集人脸数据完成后,开始训练模型,完成对所需数据的全面加载后,基于函数对数据集进行合理划分,以得到分别用于训练、测试不同方面的集,本次运用比例为7:3的,划分前,先会将数据全部都打乱,使得训练集和测试集的数据分布均匀。划分好数据集以后,若Keras中运用的后端引擎为,则具备的属性为,若为Theano,则具备的属性为,为保证程序具有更高的稳健性,需先对具备的实际属性进行判定,且以此为依据来调整维度顺序。最后要开展归一化处理,保证各特征值所对应的尺度能够相对一致。若不开展该处理,所具备尺度不同的特征值对应的梯度会存在显著差异,但对梯度进行更新的过程中,对应的学习率却具备较高统一性,若其对应数值较小,则较小的梯度难以实现快速的更新,若其对应的数值较大,则较大的梯度在方向上面会缺乏稳定性,不易收敛,一般会将学习率对应的数值调低,以实现跟所具备尺度较大的维度实现匹配,进而保证损失函数能够处于较低范围内,因此,通过归一化,把不同维度的特征值范围调整到相近的范围内,就能统一使用较大的学习率加速学习。因为图片像素值的范围都在0~255,图片数据的归一化可以简单地除以255。实现代码如下:

class Dataset:
    def __init__(self, path_name):
        # 训练集
        self.train_images = None
        self.train_labels = None
        # 验证集
        self.valid_images = None
        self.valid_labels = None
        # 测试集
        self.test_images = None
        self.test_labels = None
        # 数据集加载路径
        self.path_name = path_name
        #def load(self, img_rows=IMAGE_SIZE, img_cols=IMAGE_SIZE,
             img_channels=3, nb_classes=2):
        # 加载数据集到内存
        images, labels = load_dataset(self.path_name)
        #训练 验证 训练标签 验证标签
        train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size=0.3,                                                                                random_state=random.randint(0, 100))
        _, test_images, _, test_labels = train_test_split(images, labels, test_size=0.5,
                                                random_state=random.randint(0, 100))
                 if  K.image_data_format() == 'channels_first':
            train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
            valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
            test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
            self.input_shape = (img_channels, img_rows, img_cols)
        else:
            train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
            valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
            test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
            self.input_shape = (img_rows, img_cols, img_channels)
            valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
            test_labels = np_utils.to_categorical(test_labels, nb_classes)
            # 像素数据浮点化以便归一化
            train_images = train_images.astype('float32')
            valid_images = valid_images.astype('float32')
            test_images = test_images.astype('float32')
            # 将其归一化,图像的各像素值归一化到0~1区间
            train_images /= 255
            valid_images /= 255
            test_images /= 255
            self.train_images = train_images #加载训练数据
            self.valid_images = valid_images #加载验证数据
            self.test_images = test_images   #加载测试数据
            self.train_labels = train_labels #加载训练标签
            self.valid_labels = valid_labels #加载验证标签
            self.test_labels = test_labels   #加载测试标签

5.3.2 搭建神经网络

该网络具备的整体结构列示在表5.1中,Layer指各层对应的具体类型,Filters指输出滤波器的具体数量,Size为卷积核体积,为输出数据信息。

表5.1 网络结构概况 

Layer (type) 

Filters

Size

Output Shape  

conv2d (Conv2D)

32

(3,3)

 (22, 22, 32) 

activation (Activation)

 (22, 22, 32) 

conv2d_1 (Conv2D) 

32

(3,3)

(20, 20, 32)

activation_1 (Activation)  

(20, 20, 32)

max_pooling2d (MaxPooling2D)

(2,2)

(10, 10, 32)

dropout (Dropout) 

(10, 10, 32)

conv2d_2 (Conv2D) 

64

(3,3)

(10, 10, 64)

activation_2 (Activation) 

(10, 10, 64)

conv2d_3 (Conv2D) 

64

(3,3)

(8, 8, 64)

activation_3 (Activation) 

(8, 8, 64)

max_pooling2d_1 (MaxPooling2) 

(2,2)

(4, 4, 64)

dropout_1 (Dropout) 

(4, 4, 64)

flatten (Flatten)

(1024) 

dense (Dense)  

(512)

activation_4 (Activation) 

(512)

dropout_2 (Dropout)

(512)

dense_1 (Dense)      

(2) 

activation_5 (Activation)

(2) 

上表描述了搭建神经网络的详细信息,如下图5.2是基于CNN的人脸识别神经网络结构图。包含4个卷积层、池化及全连接层各2各、Flatten及分类层各1个,其中每一层的作用如下:

图5.2 基于CNN的人脸识别神经网络结构图

(1)卷积层(convolution layer):运用keras内包含的函数基于二维层面开展相关的卷积计算。输入图像纯为64*64,对其开展滑窗计算,如下图5.2所示:

图5.3 卷积计算

(2)激活函数层:激活函数将简单的线性分类变成复杂的非线性分类以获得更好的分类效果,该网络主要运用了relu函数,输入的具体数值若小于0,则输出的所有数值均为0,若输入部分超过0,那么最终输出的数值会与之相等。其优点为能够在较短时间内实现收敛,其数学形式如下:

图5.4 池化

(4)Dropout层:随机对一定数量的输入神经元间存在的链接进行断开,避免出现过拟合情况。

(5)Flatten层:完成上面提及的各个过程后,数据仍旧以二维方式呈现,而基于该层能够将其压缩为一维。

(6)全连接层:负责将数据划分成不同类型且开展回归处理。该层会对512个特征进行保留并传给下层。函数为基础完成整个分类过程。基于分类层面,若神经元所输出的具体数值较大,则更可能被归为真实类别。所以通过该函数进行处理后,从上层得到的N个输入会以映射方式形成与之对应的概率分布,且所有概率相加后为1,所对应概率最高的便是预估的具备类别。其函数式如下:

实现代码如下:

# 建立模型
def build_model(self, dataset, nb_classes=2):
   

# 构建一个空的线性堆叠网络模型

self.model = Sequential()
    self.model.add(Convolution2D(32, 3, 3, padding='same',
                                 input_shape=dataset.input_shape))  # 2维卷积层
 self.model.add(Activation('relu'))  #
self.model.

self.model.add(MaxPooling2D(pool_size=(2, 2)))  # 池化层
self.model.add(Dropout(0.25))  #
self.model.add(Dropout(0.5))  # Dropout层
self.model.add(Dense(nb_classes))  # Dense层
self.model.add(Activation('softmax'))  #分类层,输出最终结果
# 输出模型概况
self.model.summary()

5.3.3 模型训练

搭建神经网络后,开始训练模型。 在开始训练前,先对优化器对应的函数进行设置,优化器在训练过程主要对相关参数进行调节,保证处于最优水平,本次基为主要依据来判定是不是要运用相应的动量方法,相对于传统动量法,其效率更高,实现代码如下:

# 训练模型
def train(self, dataset, batch_size = 32, nb_epoch = 5, data_augmentation=True):
    sgd = SGD(lr=0.01, decay=1e-6,
              momentum=0.9, nesterov=True)  # 采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
    self.model.compile(loss='categorical_crossentropy',
                       optimizer=sgd,
                       

 if not data_augmentation:
        self.model.fit(dataset.train_images,
                       dataset.train_labels,
                       batch_size=batch_size,
                       nb_epoch=nb_epoch,
                       validation_data=(dataset.valid_images, dataset.valid_labels),
                       shuffle=True)
     featurewise_std_normalization=False,  # 是否数据标准化(输入数据除以数据集的标准差) 
datagen.fit(dataset.train_images)
        # 利用生成器开始训练模型
        self.model.fit(datagen.flow(dataset.train_images, dataset.train_labels,
        batch_size=batch_size),

steps_per_epoch = int(2100/batch_size),
        epochs=nb_epoch,

validation_data=(dataset.valid_images, dataset.valid_labels),verbose=1)

5.4  人脸识别GUI打卡小程序

人脸识别GUI打卡小程序使用Python GUI库 Tkinter。实现了签到打卡,模式选择,录入新人,查看签到信息等功能。使用Frame初始化页面,组件使用grid布局排列。数据库使用SqlLite3,在页面创建时初始化。

  1. 初始数据库

在启动小程序时,首先使用sqlite3.connect函数创建或打开一个数据库,然后在初始化数据库时,创建打卡记录表、管理员表、人脸标签表表。在需要调用数据库时,使用数据库类中封装的方法。主要实现代码如下:

self.conn.execute('create table if not exists record_table('
                  'id integer primary key autoincrement,'
                  'name varchar(30) ,'
                  'record_time timestamp)')
self.conn.execute('create table if not exists name_table('
                  'id integer primary key autoincrement,'
                  'name varchar(30))')
self.conn.execute('create table if not exists admin_table('
                  'id integer primary key autoincrement,'
                  'username varchar(30),'
                  'password varchar(32))')

  1. 主页页面

启动小程序后,通过Frame类创建一个窗口,使用grid布局。在网格布局上添加Label标签,设置字体font为粗体。在网格布局上添加签到打卡、录入新人按钮,给按钮command属性上绑定点击事件,分别跳转到签到打卡页面与录入新人界面,通过给按钮bg属性添加16进制颜色,改变按钮的颜色。实现效果如图5.1所示。

图5.5 主页页面

  1. 签到打卡

在主页上点击签到打卡后,小程序将调用电脑摄像头,对摄像头外的人脸进行识别。在CNN模式下,加载训练的H5模式模型文件,对获取的人脸进行识别,识别成功将在打卡记录表中插入一条数据。在LBPH模式下,将加载训练后的yml文件,对人脸进行识别打卡。实现代码如下:

def check(names,mode_id):
    cam = cv2.VideoCapture(0)
    if mode_id==1:
        recognizer = cv2.face.LBPHFaceRecognizer_create() 
        recognizer.read('face_trainer/trainer.yml') #加载模型
        cascadePath = "haarcascade_frontalface_default.xml"
        faceCascade = cv2.CascadeClassifier(cascadePath)#加载人脸识别器
        font = cv2.FONT_HERSHEY_SIMPLEX
       (gray[y:y + h, x:x + w])
                if confidence < 100:
                    username = names[idnum-1]
                    confidence = "{0}%".format(round(100 - confidence))
                    cv2.putText(img, str(username
                    cv2.imshow('camera', img)
                    time.sleep(2)
                    db.record().insert_record(username)  # 签到信息插入数
                    cam.release()
                    cv2.destroyAllWindows()
                    return
                else:
                    idnum = "unknown"
                    confidence = "{0}%".format(round(100 - confidence))
                    cv2.putText(img, str(idnum), (x + 5, y - 5), font, 1, (0, 0, 255), 1)
                    cv2.putText(img, str(confidence),

break
    elif mode_id==2:
        face_recognition(names,True) #进行加载H5人脸识别模型
        print(2)
    else:
        print("请选着模式")
    cam.release()
    cv2.destroyAllWindows()

  1. 登录界面

点击菜单栏中登录按钮,创建Frame窗口,并销毁主页页面。用户名和密码使用Entry控件实现获取输入的数据。获取数据后,点击按钮触发点击事件中函数,把输入的用户名与密码跟数据库中保存的数据开展全面对比,若发现彼此能够匹配,跳转到主页,同时将用户状态self.authority的值改为1。

图5.6 登录界面

  1. 注册界面

点击菜单栏中注册按钮,创建Frame窗口,并销毁主页页面。用户名和密码使用Entry控件实现获取输入的数据。获取数据后,点击按钮触发点击事件中函数,将获取到的用户名与密码向数据中插入数据,实现效果如图5.3所示。

图5.7 注册界面

向数据库中插入数据代码如下:

usr_name = self.var_usr_name.get()
usr_pwd = self.var_usr_pwd.get()
if usr_name == "" or usr_pwd == "":
    tkinter.messagebox.askokcancel(title='提示', message='请输入用户名或密码!')
    return
tkinter.messagebox.askokcancel(title='提示', message='注册成功!')
self.mydb.add_admin(usr_name,usr_pwd)

#向数据库插入一条数据

def add_admin(self,username,password):
    sql = "INSERT INTO admin_table VALUES (null,?,?);"
    self.conn.execute(sql,[username,password])
    self.conn.commit()

  1. 增加人脸界面

在主页上点击增加新人按钮,创建Frame窗口,并销毁主页页面。在LBPH模式下,提示输入姓名,输入姓名后,点击确定后调用OpenCV的VideoCapture函数打开电脑的摄像头,获取人脸并训练数据。实现效果如图5.3所示。

图5.8 增加人脸界面

  1. 签到信息

管理员点击签到信息按钮,从数据库读取打卡记录表,将所有数据都绑定在Treeview上,实现效果如图5.5所示。查询数据库实现代码如下:

def query_record(self):
    self.cursor.execute('select * from record_table') #执行一条查找的SQL数据
    results = self.cursor.fetchall() 
    return results

图5.9 签到信息

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐