【关键步骤】【含代码】基于卷积神经网络的人脸识别

软件:
Python 3.5

系统使用工具
集成开发环境:
IDLE(是安装好python之后,自动安装好的一个python自带的集成开发环境)
插件:
opencv3.4.3、numpy1.14.6、keras2.2.4、tensorflow cpu1.11.0和sklearn0.20.0。
这些插件的具体安装步骤,网上都可以找到具体的教程,这里便不再赘述。

系统功能需求
通过电脑本地的摄像头,拍摄实时人脸照片,与训练好的卷积神经网络模型中存放的人脸信息进行比对,同时在桌面上显示识别出的人脸标签值。
 

关键步骤
1. 人脸数据的获取
利用opencv来调用摄像头,获取实时视频流,通过opencv自带的人脸分类器haar来识别并标注出人脸区域,将当前帧保存为图片存到指定的文件夹下面。
代码如下:

#catchpicture.py
import cv2  
cap = cv2.VideoCapture(0)
num = 0    
while cap.isOpened():
    ret, frame = cap.read() #读取一帧数据
    gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)#将图片转化成灰度
    face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
    face_cascade.load('F:\python35\haarcascade_frontalface_alt2.xml')#一定要告诉编译器文件所在的具体位置
    '''此文件是opencv的haar人脸特征分类器'''
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)
    if len(faces) > 0:
        for (x,y,w,h) in faces:
         #将当前帧保存为图片
         img_name = '%s/%d.jpg'%("F:\data\me", num)                
         image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
         cv2.imwrite(img_name, image)                                                     
         num += 1                
         if num > 1000:   #如果超过指定最大保存数量退出循环
           break
         cv2.rectangle(frame,(x,y),(x+w,y+h),(0,0,255),2)
         #显示当前捕捉到了多少人脸图片
         font = cv2.FONT_HERSHEY_SIMPLEX
         cv2.putText(frame,'num:%d'%(num),(x + 30,y + 30),font,1,(255,0,255),4)
    #超过指定最大保存数量结束程序
    if num > 1000 :break             
    #显示图像并等待10毫秒按键输入,输入‘q’退出程序
    cv2.imshow("capture", frame)
    if cv2.waitKey(10) & 0xFF == ord('q'):
      break   
    #释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows() 

2. 图片预处理
第一步获取到的人脸图片集中的每一张图片大小都不一样,为了后续操作的方便需要将,捕获到的人脸照片压缩为像素值为6464的并灰度化处理。所以图片预处理一共分为两部先是压缩成比例6464的,第一一步定义了一个resize_image()函数作用是先将图片补成正方形之后压缩成像素值为64*64,第二步利用opencv自带的cvtColor()函数将图片灰度化。
代码如下:

#picturepraction.py
import os
import cv2
IMAGE_SIZE = 64
def resize_image(image, height=IMAGE_SIZE, width=IMAGE_SIZE):
    top, bottom, left, right = (0, 0, 0, 0)
    h, w, _ = image.shape
    longest_edge = max(h, w)
    if h < longest_edge:
        dh = longest_edge - h
        top = dh // 2
        bottom = dh - top
    elif w < longest_edge:
        dw = longest_edge - w
        left = dw // 2
        righ = dw - left
    else:
        pass
    BLACK = [0, 0, 0]
    constant = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=BLACK)
    return cv2.resize(constant, (height, width))
if __name__ == '__main__':
    path_name = "F:\data\me"
    i = 0
    for dir_item in os.listdir(path_name):
        full_path = os.path.abspath(os.path.join(path_name, dir_item))
        i += 1
        image = cv2.imread(full_path)       #读取出照片
        image = resize_image(image)         #将图片大小转为64*64
        image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)     #将图片转为灰度图
        cv2.imwrite(full_path,image)

3.  图片加载到内存
将图片预处理之后的图片集,以多维数组的形式,加载到内存,并且要为每一类样本数据标注标签值。

代码如下:

#loaddata.py
import os
import sys
import numpy as np
import cv2
#读取图片数据并与标签绑定
def read_path(images, labels, path_name, label):
    for dir_item in os.listdir(path_name):

        full_path = os.path.abspath(os.path.join(path_name, dir_item))
        image = cv2.imread(full_path)
        images.append(image)
        labels.append(label)
def loaddata(parent_dir):
    images = []
    labels = []
    read_path(images, labels, parent_dir+"me", 0)
    read_path(images, labels, parent_dir+"chen", 1)
    read_path(images, labels, parent_dir+"jia", 2)
    read_path(images, labels, parent_dir+"other", 3)
    images = np.array(images)
    labels = np.array(labels)
    return images,labels
if __name__ == '__main__':
        images, labels = loaddata("F:/example/")

4.搭建卷积神经网络

搭建卷积神经网络前,需要先完成六个步骤:

第一步需要先把数据加载到内存即将图片预处理之后的图片集,以多维数组的形式,加载到内存,并且要为每一类样本数据标注标签值;

第二步划分数据集即按照交叉验证的原则划分数据集、验证集、训练集。交叉验证是机器学习中的一种常用来精度测试的方法,要先拿出大部分数据用来模型训练,少部分数据用来模型验证,验证结果与真实值计算出差平方和,以上工作重复进行,直至差平方和为0,模型训练完毕,可以交付使用。而在我自己的模型中,导入了sklearn库的交叉验证模块,利用函数train_test_split()函数来划分训练集、验证集和测试集。train_test_split()函数中的test_size参数用来指定划分的比例,另一个参数random_state是用来指定一个随机数种子,从全部数据中随机选取数据建立自己的数据集、验证集和训练集;

第三步要改变图片的维度即我们小组用到了keras库,这个库是建立在tensorflow或者theano基础上的,所以keras库的后端系统可以是tensorflow也可以是theano。但是tensorflow和theano定义的图像数据输入到CNN网络的维度顺序是不一样的,tensorflow的维度顺序为行数(rows)、列数(cols)、通道数(颜色通道,channels);theano则是通道数、行数、列数。所以需要调用函数image_dim_ordering()来确定后端系统的类型(我们用‘th’来代表theano用‘tf’来代表tensorflow),最后用numpy库提供的reshape()函数来调整维度;

第四步采用one-hot编码即因为我的卷积神经网络采用了categorical_crossentropy作为我们的损失函数,而这个函数要求标签集必须采用one-hot编码。所谓的one-hot编码,我理解就是状态位编码,one-hot采用状态寄存器编码,每一个状态值对应一个寄存器,且任意时刻,只有一位是有效的。假设,我们类别有两种分别为0和1,0代表我,1代表others,如果标签为0,编码为[1 0]表示的是第一位有效,如果标签为1,编码为[0 1]表示的是第二位有效。这样做的原因是为了方便CNN操作;

第五步归一化图像数据即数据集先让它浮点化之后又归一化的目的是提升网络收敛速度,减少模型的训练实践,同时适应值域在(0,1)之间的激活函数,增大区分度。归一化有一个特别重要的原因是确保特征值权重一致;

第六步确定优化器即最开始使用的是SGD优化器,SGD优化器随机梯度下降算法参数更新针对每一个样本集x(i) 和y(i) 。批量梯度下降算法在大数据量时会产生大量的冗余计算,比如:每次针对相似样本都会重新计算。这种情况时,SGD算法每次则只更新一次。因此SGD算法通过更快,并且适合online。但是SGD以高方差进行快速更新,这会导致目标函数出现严重抖动的情况。一方面,正是因为计算的抖动可以让梯度计算跳出局部最优,最终到达一个更好的最优点;另一方面,SGD算法也会因此产生过调。之后改进使用了Adam,Adam算法是另一种自适应参数更新算法。和Adadelta、RMSProp算法一样,对历史平方梯度v(t)乘上一个衰减因子,adam算法还存储了一个历史梯度m(t)。mt和vt分别是梯度一阶矩(均值)和二阶矩(方差)。当mt和vt初始化为0向量时,adam的作者发现他们都偏向于0,尤其是在初始化的时候和衰减率很小的时候(例如,beta1和beta2趋近于1时)。通过计算偏差校正的一阶矩和二阶矩估计来抵消偏差。

之后构建了一个卷积神经网络,这个卷积神经网络一共16层:3层卷积层、2层池化层、3层Dropout层、1层flatten层、2层全连接层和1层分类层。结构如下图:

 

#face_CNN_keras.py
import random

import numpy as np
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K
#ADAM优化器
from keras.optimizers import Adam

from loaddata import loaddata
from picturepraction import resize_image, IMAGE_SIZE

class Dataset:
    def __int__(self):

        # 训练数据
        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.input_shape = None

    # 加载数据并预处理
    def load(self, img_rows=IMAGE_SIZE, img_cols=IMAGE_SIZE, img_channels=3, nb_classes=4):
        images, labels = loaddata("F:/example/")

        # 随机划分训练集、验证集(利用交叉验证原则)
        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_dim_ordering() == 'th'):#如果后端系统是theano,维度顺序为通道数、行、列
            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:                              #如果后端系统是tensorflow,维度顺序为行、列、通道数
            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)

        # 输出训练集、验证集、测试集的数量
        print(train_images.shape[0], 'train samples')
        print(valid_images.shape[0], 'valid_samples')
        print(test_images.shape[0], 'test_samples')

        #模型使用categorical_crossentropy作为损失函数
        #因此需要根据图像集数量将图像集标签进行one-hot编码使其向量化
        train_labels = np_utils.to_categorical(train_labels, nb_classes)
        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')
        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




class Model:
    def __init__(self):
        self.model = None

    def build_model(self, dataset, nb_classes=4):
        self.model = Sequential()

        #第一层卷积
        #保留边界像素
        self.model.add(Convolution2D(32, 3, 3, border_mode='same', input_shape=dataset.input_shape, activation='relu'))#卷积层和激活函数
        ##输出(32, 64, 64)

        self.model.add(MaxPooling2D(pool_size=(2, 2)))                                                       #池化层
        #输出(32, 32, 32)

        self.model.add(Dropout(0.5))

        #第二层卷积
        #保留边界像素
        self.model.add(Convolution2D(32, 3, 3, border_mode='same', activation='relu'))#卷积层和激活函数
        ##输出(32, 32, 32)

      
        self.model.add(Dropout(0.5))

        
        #第三层卷积
        self.model.add(Convolution2D(64, 3, 3, border_mode='same', activation='relu'))
        #输出(64, 32, 32)

        self.model.add(MaxPooling2D(pool_size=(2, 2)))
        #输出(64, 16, 16)

        self.model.add(Dropout(0.5))

        self.model.add(Flatten())   #数据从二维转为一维
        #输出64*16*16 =  16384

        #二层全连接神经网络 512*人的个数
        self.model.add(Dense(512))
        self.model.add(Activation('relu'))
        self.model.add(Dropout(0.5))
        self.model.add(Dense(nb_classes))

        self.model.add(Activation('softmax'))
        self.model.summary()

    def train(self, dataset, batch_size=20, nb_epoch=10, data_augmentation=True):
        #sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
        
        #self.model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
        #Adam优化器
        adam = Adam(lr=0.001,beta_1=0.9,beta_2=0.999,epsilon=1e-8)
        self.model.compile(loss='categorical_crossentropy',optimizer=adam,metrics=['accuracy'])
        
        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)

    MODEL_PATH = 'F:/example/number3.h5'


    def save_model(self, file_path=MODEL_PATH):
        self.model.save(file_path)

    def load_model(self, file_path=MODEL_PATH):
        self.model = load_model(file_path)

    def evaluate(self, dataset):
        score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose=1)
        print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))

    def face_predict(self, image):
        if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
            image = resize_image(image)
            image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE))
        elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
            image = resize_image(image)
            image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))

        image = image.astype('float32')
        image /= 255

        result = self.model.predict_proba(image)

        result = self.model.predict_classes(image)

        return result[0]

if __name__ == '__main__':
    dataset = Dataset()
    dataset.load()
    model = Model()
    model.build_model(dataset)
    model.train(dataset)
    model.save_model(file_path="F:/example/number3.h5")

5.  识别人脸
利用opencv获取实时人脸数据,调用训练好的卷积神经网络模型,来识别人脸。
代码如下:

#faceclassify.py
import cv2
import sys
import gc
from face_CNN_keras import Model
import tensorflow as tf
if __name__ == '__main__':
    model = Model()#加载模型
    model.load_model(file_path = 'F:/example/number1.h5')             
    color = (0, 255, 255)#框住人脸的矩形边框颜色 
    cap = cv2.VideoCapture(0)#捕获指定摄像头的实时视频流
    cascade_path = "F:\python35\haarcascade_frontalface_alt2.xml"#人脸识别分类器本地存储路径    
    #循环检测识别人脸
    while cap.isOpened():
        ret, frame = cap.read()   #读取一帧视频
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)#图像灰化,降低计算复杂度
        cascade = cv2.CascadeClassifier(cascade_path)#使用人脸识别分类器,读入分类器                
        faceRects = cascade.detectMultiScale(gray, scaleFactor = 1.2, minNeighbors = 3, minSize = (16, 16))#利用分类器识别出哪个区域为人脸        
        if len(faceRects) > 0:                 
            for faceRect in faceRects: 
                x, y, w, h = faceRect
                #截取脸部图像提交给模型识别这是谁
                image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
                cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness = 2)
                faceID = model.face_predict(image)   
                #如果是“我”
                if faceID == 0:                                                        
                    cv2.putText(frame, "zhuang", (x+30, y+30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 2)#在显示界面输出
                    print("zhuang")#在控制台输出
                elif faceID == 1:
                    cv2.putText(frame, "chen", (x+30, y+30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 2)#在显示界面输出
                    print("chen")#在控制台输出
                elif faceID == 2:
                    cv2.putText(frame, "jia", (x+30, y+30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 2)#在显示界面输出
                    print("jia")#在控制台输出
                else:
                    cv2.putText(frame, "unknown",(x+30, y+30),cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 2)#在显示界面输出
                    print("unknown")#在控制台输出                           
        cv2.imshow("classify me", frame)
        k = cv2.waitKey(10)#等待10毫秒看是否有按键输入
        if k & 0xFF == ord('q'):#按q退出
            break
    #释放摄像头并销毁所有窗口
    cap.release()
    cv2.destroyAllWindows()

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值