人脸识别_人脸识别算法 - CSDN
人脸识别 订阅
人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术。用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部识别的一系列相关技术,通常也叫做人像识别、面部识别。 展开全文
人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术。用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部识别的一系列相关技术,通常也叫做人像识别、面部识别。
信息
处理方法
人脸识别算法
传统技术
可见光图像的人脸识别
别    名
人像识别、面部识别
用    途
身份识别
中文名
人脸识别
工    具
摄像机或摄像头
人脸识别发展历史
人脸识别系统的研究始于20世纪60年代,80年代后随着计算机技术和光学成像技术的发展得到提高,而真正进入初级的应用阶段则在90年后期,并且以美国、德国和日本的技术实现为主;人脸识别系统成功的关键在于是否拥有尖端的核心算法,并使识别结果具有实用化的识别率和识别速度;“人脸识别系统”集成了人工智能、机器识别、机器学习、模型理论、专家系统、视频图像处理等多种专业技术,同时需结合中间值处理的理论与实现,是生物特征识别的最新应用,其核心技术的实现,展现了弱人工智能向强人工智能的转化。 [1] 
收起全文
精华内容
参与话题
  • 手把手教你实现人脸识别支付系统

    千人学习 2020-07-20 10:48:04
    当我们使用人脸识别技术来构建一个支付系统的时候,在准确性、安全性,以及实时性等方面,将会有更高的要求。 所以本课程着眼于刷脸支付系统实现上的特殊性,重点阐述了一些相关的技术,比如:开放域人脸识别、支付...
  • 利用python、tensorflow、opencv实现人脸识别(包会)!

    万次阅读 多人点赞 2019-02-20 21:44:24
    本人是机械专业在读硕士,在完成暑假实践的时候接触到了人脸识别,对这一实现很感兴趣,所以花了大概十天时间做出了自己的人脸识别。这篇文章应该是很详细的了所以帮你实现人脸识别应该没什么问题。 先说本博文的...

     

    一,前言

    本人是机械专业在读硕士,在完成暑假实践的时候接触到了人脸识别,对这一实现很感兴趣,所以花了大概十天时间做出了自己的人脸识别。这篇文章应该是很详细的了所以帮你实现人脸识别应该没什么问题。

    先说本博文的最终要达到的效果:通过一系列操作,在摄像头的视频流中识别特定人的人脸,并且予以标记。

    本人通过网上资料的查询发现这类人脸识别,大多参考了一位日本程序员小哥的文章。

    链接:https://github.com/Hironsan/BossSensor

    关于这个思路的人脸识别网上资料很多,但是有很多细节没有提到,我在实践的过程中菜过了无数的坑,希望我这篇文章能够对你提供更加清晰的思路和操作。先看结果(人丑勿怪)!这个是识别我的脸,别人的脸不会识别到

    其实,这已经涉及到了一些机器学习的内容,对于像入门机器学习的同学来说是一个不错的练手的项目。

    二、前期准备工作

    首先说,我在刚开始接触的时候,主要是在各种数据包的安装上以及环境的配置上花费了巨大的时间,有些数据包升级版本之后与一些功能不兼容,出了很多问题,所以。我在这里说一下我的数据包的版本和python版本。现在可以用anaconda来下载python和各种数据包,但是最新的版本是用python3.6.X,在后面的实践中可能会出现不同的问题,所以为了安全起见python最好选择3.5.X的,不要安装2.X的,与3.X的版本不兼容,会出现很多问题。另外再安装一个tensorflow,pip,keras,sklearn,PIL,numpy,opencv等。其中keras要安装2.0版本的,opencv安装3.3.1版本的。tensorflow有CPU版本的和GPU版本的,你可以看一下你适合哪一种,这里贴出来一些供你参考:

           您必须从以下 TensorFlow 类型中选择其一来进行安装:

           仅支持 CPU 的 TensorFlow。如果您的系统没有 NVIDIA® GPU,则必须安装此版本。请注意,此版本的                                    TensorFlow 通常更容易安装(用时通常在 5 或 10 分钟内),所以即使您拥有 NVIDIA GPU,我们也建议先安                              装此版本。预编译的二进制文件将使用 AVX 指令。

            支持 GPU 的 TensorFlow。TensorFlow 程序在 GPU 上的运行速度通常要比在 CPU 上快得多。因此,如果您                           的系统配有满足以下所示先决条件的 NVIDIA® GPU,并且您需要运行性能至关重要的应用,则最终应安装此                              版本。

    另外我在安装的过程中发现了几篇比较不错的博文供你参考:

    1.https://blog.csdn.net/Eppley/article/details/79297503

    2.https://blog.csdn.net/WJ_MeiMei/article/details/79684627

    3.https://zhuanlan.zhihu.com/p/24055668

    在几篇博文里你也会看到验证安装正确的方法,如果可以的话,说明你安装成功了,这里我就不多说了。后面的话你可能还会遇到别的什么问题,如果还需要安装什么模块的话,在安装也可以。

    在硬件方面,你还需要一个USB摄像头。

     

    总结:

    1. USB摄像头一个;

    2. python  --  3.5.X

    3. tensorflow

    4. opencv  --  3.3.1

    5. keras   --  2.0.X

    6. sklearn  -- 0.19.0

     

    三、正式开始

     

    1,识别人脸

           实现人脸识别简单程序没几行,但是我们要实现的是识别这个是谁的脸。首先我们让系统识别人脸,这是opencv的工作,我们只需要调用其中的API函数就可以了。下面是调用opencv实现对于人脸的识别。咱们在程序下面对程序进行一些解释:

    
    
    import cv2
    import sys
    from PIL import Image
    
    def CatchUsbVideo(window_name, camera_idx):
        cv2.namedWindow(window_name)
        
        #视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
        cap = cv2.VideoCapture(camera_idx)                
        
        #告诉OpenCV使用人脸识别分类器
        classfier = cv2.CascadeClassifier("H:\\OpenCV\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt2.xml")
        
        #识别出人脸后要画的边框的颜色,RGB格式
        color = (0, 255, 0)
            
        while cap.isOpened():
            ok, frame = cap.read() #读取一帧数据
            if not ok:            
                break  
    
            #将当前帧转换成灰度图像
            grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)                 
            
            #人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
            faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
            if len(faceRects) > 0:            #大于0则检测到人脸                                   
                for faceRect in faceRects:  #单独框出每一张人脸
                    x, y, w, h = faceRect        
                    cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
                            
            #显示图像
            cv2.imshow(window_name, frame)        
            c = cv2.waitKey(10)
            if c & 0xFF == ord('q'):
                break        
        
        #释放摄像头并销毁所有窗口
        cap.release()
        cv2.destroyAllWindows() 
        
    if __name__ == '__main__':
        if len(sys.argv) != 1:
            print("Usage:%s camera_id\r\n" % (sys.argv[0]))
        else:
            CatchUsbVideo("识别人脸区域", 0)
    

        首先,第一行import cv2,实际上,”cv2”中的”2”并不表示OpenCV的版本号。我们知道,OpenCV是基于C/C++的,”cv”和”cv2”表示的是底层CAPI和C++API的区别,”cv2”表示使用的是C++API。这主要是一个历史遗留问题,是为了保持向后兼容性。PIL是一个模块,如果运行的过程中提示你缺少模块的时候你就要安装一个模块了,其余同理,就不再说了。另外在函数Catchusbvideo中,第二个参数指的是你电脑的摄像头的编号,例如是0,1,2等,如果0不行的话,试一下1。在下边的人脸识别分类器中是我自己下载的opencv,下载网站是:https://opencv.org/releases.html,如果你是windows选择对应版本就好,还有就是“H:\\OpenCV\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt2.xml”这是我安装的一个路径,你也要找到这个路径并且复制到程序中,这个东西的作用主要是实现对人脸识别的功能,在安装中还有其他的功能,我也一并列在下面:

                   人脸检测器(默认):haarcascade_frontalface_default.xml 
                   人脸检测器(快速Harr):haarcascade_frontalface_alt2.xml 
                   人脸检测器(侧视):haarcascade_profileface.xml 
                   眼部检测器(左眼):haarcascade_lefteye_2splits.xml 
                   眼部检测器(右眼):haarcascade_righteye_2splits.xml 
                   嘴部检测器:haarcascade_mcs_mouth.xml 
                   鼻子检测器:haarcascade_mcs_nose.xml 
                   身体检测器:haarcascade_fullbody.xml 
                   人脸检测器(快速LBP):lbpcascade_frontalface.xml

    另外,如果我们想构建自己的分类器,比如识别火焰、汽车,数,花等,我们依然可以使用OpenCV训练构建。   

        这个函数完成对人脸的识别以及用一个框框给框起来,其中grey是要识别的图像数据,转化为灰度可以减少计算量。scaleFactor:图像缩放比例,可以理解为同一个物体与相机距离不同,其大小亦不同,必须将其缩放到一定大小才方便识别,该参数指定每次缩放的比例。minNeighbors:对特征检测点周边多少有效点同时检测,这样可避免因选取的特征检测点太小而导致遗漏。minSize:特征检测点的最小值。

            对同一个画面有可能出现多张人脸,因此,我们需要用一个for循环将所有检测到的人脸都读取出来,然后逐个用矩形框框出来,这就是接下来的for语句的作用。Opencv会给出每张人脸在图像中的起始坐标(左上角,x、y)以及长、宽(h、w),我们据此就可以截取出人脸。其中,cv2.rectangle()完成画框的工作,在这里外扩了10个像素以框出比人脸稍大一点的区域。cv2.rectangle()函数的最后两个参数一个用于指定矩形边框的颜色,一个用于指定矩形边框线条的粗细程度。

                运行结果:

                                               

    好,看来可以顺利的识别出视频中的脸,搞定!但是我们想做的是识别这个人脸是谁的,这仅仅能识别这是谁的脸,完全不能满足我们的渴望,接下来我们进行下一步!

    2.模型训练

    模型训练的目的是让电脑知道,这个脸的特征是什么,从而可以在视频流中识别。在训练之前必须先准备足够的脸部照片作为机器学习的资料。

         2.1准备机器学习的资料

    所谓机器学习就是给程序投喂足够多的资料,资料越多,准确度和效率也会越高。要想识别出这张人脸属于谁,我们肯定需要大量的自己的脸和别人的脸,这样才能区别开。然后将这些数据输入到Tensorflow中建立我们自己脸的模型。

    1.keras简介

    上面提到的日本小哥利用深度学习库keras来训练自己的人脸识别模型。 我这里找到一篇keras的中文文档可能对你有些帮助。另外关于Keras, Keras是由纯python编写的基于theano/tensorflow的深度学习框架。Keras是一个高层神经网络API,支持快速实验,能够把你的idea迅速转换为结果,如果有如下需求,可以优先选择Keras:

                    a)简易和快速的原型设计(keras具有高度模块化,极简,和可扩充特性)

                    b)支持CNN和RNN,或二者的结合

                    c)无缝CPU和GPU切换

     

    Keras的模块结构:

                   

            

    使用Keras搭建一个神经网络:

     

          

    数据格式(data_format):

            目前主要有两种方式来表示张量:
            a) th模式或channels_first模式,Theano和caffe使用此模式。
            b)tf模式或channels_last模式,TensorFlow使用此模式。

    因为我装的是tensorflow因此我直接使用了keras的Tensorflow版,同时,为了验证其它深度学习库的效率和准确率,我还使用了Theano,利用CNN——卷积神经网络来训练我的人脸识别模型。本节专注把训练数据准备好。

    完整代码如下:

    
    
    import cv2
    import sys
    
    from PIL import Image
    
    def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
        cv2.namedWindow(window_name)
        
        #视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
        cap = cv2.VideoCapture(camera_idx)                
        
        #告诉OpenCV使用人脸识别分类器
        classfier = cv2.CascadeClassifier("H:\\OpenCV\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt2.xml")
        
        #识别出人脸后要画的边框的颜色,RGB格式
        color = (0, 255, 0)
        
        num = 0    
        while cap.isOpened():
            ok, frame = cap.read() #读取一帧数据
            if not ok:            
                break                
        
            grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  #将当前桢图像转换成灰度图像            
            
            #人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
            faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
            if len(faceRects) > 0:          #大于0则检测到人脸                                   
                for faceRect in faceRects:  #单独框出每一张人脸
                    x, y, w, h = faceRect                        
                    
                    #将当前帧保存为图片
                    img_name = '%s/%d.jpg'%(path_name, num)                
                    image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
                    cv2.imwrite(img_name, image)                                
                                    
                    num += 1                
                    if num > (catch_pic_num):   #如果超过指定最大保存数量退出循环
                        break
                    
                    #画出矩形框
                    cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
                    
                    #显示当前捕捉到了多少人脸图片了,这样站在那里被拍摄时心里有个数,不用两眼一抹黑傻等着
                    font = cv2.FONT_HERSHEY_SIMPLEX
                    cv2.putText(frame,'num:%d' % (num),(x + 30, y + 30), font, 1, (255,0,255),4)                
            
            #超过指定最大保存数量结束程序
            if num > (catch_pic_num): break                
                           
            #显示图像
            cv2.imshow(window_name, frame)        
            c = cv2.waitKey(10)
            if c & 0xFF == ord('q'):
                break        
        
        #释放摄像头并销毁所有窗口
        cap.release()
        cv2.destroyAllWindows() 
        
    if __name__ == '__main__':
        if len(sys.argv) != 1:
            print("Usage:%s camera_id face_num_max path_name\r\n" % (sys.argv[0]))
        else:
            CatchPICFromVideo("截取人脸", 0, 1000, 'C:\\Users\\Administrator\\Desktop\\Python\\data\\liziqiang')
    

     这段代码我们只是在前面代码的基础上增加脸部图像存储功能,比较简单。

    def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):

    在函数定义中,几个参数,反别是窗口名字,摄像头系列号,捕捉照片数量,以及存储路径。根据自己需要进行修改,咱们这里为了精度高一点,选择捕捉1000张脸部照片。在你捕捉的时候由于精度的问题,会捕捉许多非脸部的照片,这时候需要你将不是脸部的照片清洗掉,使数据更加准确。另外,我们还需要捕捉另一个人的图片来提高模型的准确度。然后存储到另一个文件夹下,注意,一个人的照片存储到一个文件夹下,不可弄混。截图完成,就像下图这样。

    好了,经过前面的数据搜集,咱们已经差不多准备了2000张照片,准备工作已经做好,下面我们将进行数据模型的训练!

     

    2.模型训练

    训练程序建立了一个包含4个卷积层的神经网络(CNN),程序利用这个网络训练我的人脸识别模型,并将最终训练结果保存到硬盘上。在我们实际动手操练之前我们必须先弄明白一个问题——什么是卷积神经网络(CNN)?

    想知道你可以谷歌,另外有关神经网络我会另外写一篇博客。这里就不多做介绍了。

    首先建立一个python文件,命名load_dataset。代码如下:

    
    import os
    import sys
    import numpy as np
    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
            right = dw - left
        else:
            pass 
        
        #RGB颜色
        BLACK = [0, 0, 0]
        
        #给图像增加边界,是图片长、宽等长,cv2.BORDER_CONSTANT指定边界颜色由value指定
        constant = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, 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)
                    
                    #放开这个代码,可以看到resize_image()函数的实际调用效果
                    #cv2.imwrite('1.jpg', image)
                    
                    images.append(image)                
                    labels.append(path_name)                                
                        
        return images,labels
        
    
    #从指定路径读取训练数据
    def load_dataset(path_name):
        images,labels = read_path(path_name)    
        
        #将输入的所有图片转成四维数组,尺寸为(图片数量*IMAGE_SIZE*IMAGE_SIZE*3)
        #我和闺女两个人共1200张图片,IMAGE_SIZE为64,故对我来说尺寸为1200 * 64 * 64 * 3
        #图片为64 * 64像素,一个像素3个颜色值(RGB)
        images = np.array(images)
        print(images.shape)    
        
        #标注数据,'liziqiang'文件夹下都是我的脸部图像,全部指定为0,另外一个文件夹下是同学的,全部指定为1
        labels = np.array([0 if label.endswith('liziqiang') else 1 for label in labels])    
        
        return images, labels
    
    if __name__ == '__main__':
        if len(sys.argv) != 1:
            print("Usage:%s path_name\r\n" % (sys.argv[0]))    
        else:
            images, labels = load_dataset("C:\\Users\\Administrator\\Desktop\\Python\\data")
    

           

            稍微解释一下resize_image()函数。这个函数的功能是判断图片是不是正方形,如果不是则增加短边的长度使之变成正方形。这样再调用cv2.resize()函数就可以实现等比例缩放了。因为我们指定缩放的比例就是64 x 64,只有缩放之前图像为正方形才能确保图像不失真。例如:

    这样明显不是正方形。经过程序运行之后要达到这样的目的。

    大概是这么个意思。

    将你捕捉到的照片放在俩个不同的文件夹里,我在这里一块放在了data文件夹里。

     

    然后再新建一个python文件,命名为:face_train。添加如下代码。

    
    import random
    
    import numpy as np
    from sklearn.cross_validation 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
    
    from load_data import load_dataset, resize_image, IMAGE_SIZE
    
    
    
    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
            
            #当前库采用的维度顺序
            self.input_shape = None
            
        #加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作
        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))                
            
            #当前的维度顺序如果为'th',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels
            #这部分代码就是根据keras库要求的维度顺序重组训练数据集
            if K.image_dim_ordering() == 'th':
                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)            
                
                #输出训练集、验证集、测试集的数量
                print(train_images.shape[0], 'train samples')
                print(valid_images.shape[0], 'valid samples')
                print(test_images.shape[0], 'test samples')
            
                #我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将
                #类别标签进行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')
                
                #将其归一化,图像的各像素值归一化到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
                
    #CNN网络模型类            
    class Model:
        def __init__(self):
            self.model = None 
            
        #建立模型
        def build_model(self, dataset, nb_classes = 2):
            #构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
            self.model = Sequential() 
            
            #以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
            self.model.add(Convolution2D(32, 3, 3, border_mode='same', 
                                         input_shape = dataset.input_shape))    #1 2维卷积层
            self.model.add(Activation('relu'))                                  #2 激活函数层
            
            self.model.add(Convolution2D(32, 3, 3))                             #3 2维卷积层                             
            self.model.add(Activation('relu'))                                  #4 激活函数层
            
            self.model.add(MaxPooling2D(pool_size=(2, 2)))                      #5 池化层
            self.model.add(Dropout(0.25))                                       #6 Dropout层
    
            self.model.add(Convolution2D(64, 3, 3, border_mode='same'))         #7  2维卷积层
            self.model.add(Activation('relu'))                                  #8  激活函数层
            
            self.model.add(Convolution2D(64, 3, 3))                             #9  2维卷积层
            self.model.add(Activation('relu'))                                  #10 激活函数层
            
            self.model.add(MaxPooling2D(pool_size=(2, 2)))                      #11 池化层
            self.model.add(Dropout(0.25))                                       #12 Dropout层
    
            self.model.add(Flatten())                                           #13 Flatten层
            self.model.add(Dense(512))                                          #14 Dense层,又被称作全连接层
            self.model.add(Activation('relu'))                                  #15 激活函数层   
            self.model.add(Dropout(0.5))                                        #16 Dropout层
            self.model.add(Dense(nb_classes))                                   #17 Dense层
            self.model.add(Activation('softmax'))                               #18 分类层,输出最终结果
            
            #输出模型概况
            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) #采用SGD+momentum的优化器进行训练,首先生成一个优化器对象  
            self.model.compile(loss='categorical_crossentropy',
                               optimizer=sgd,
                               metrics=['accuracy'])   #完成实际的模型配置工作
            
            #不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的
            #训练数据,有意识的提升训练数据规模,增加模型训练量
            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)
            #使用实时数据提升
            else:            
                #定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一
                #次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器
                datagen = ImageDataGenerator(
                    featurewise_center = False,             #是否使输入数据去中心化(均值为0),
                    samplewise_center  = False,             #是否使输入数据的每个样本均值为0
                    featurewise_std_normalization = False,  #是否数据标准化(输入数据除以数据集的标准差)
                    samplewise_std_normalization  = False,  #是否将每个样本数据除以自身的标准差
                    zca_whitening = False,                  #是否对输入数据施以ZCA白化
                    rotation_range = 20,                    #数据提升时图片随机转动的角度(范围为0~180)
                    width_shift_range  = 0.2,               #数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数)
                    height_shift_range = 0.2,               #同上,只不过这里是垂直
                    horizontal_flip = True,                 #是否进行随机水平翻转
                    vertical_flip = False)                  #是否进行随机垂直翻转
    
                #计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理
                datagen.fit(dataset.train_images)                        
    
                #利用生成器开始训练模型
                self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
                                                       batch_size = batch_size),
                                         samples_per_epoch = dataset.train_images.shape[0],
                                         nb_epoch = nb_epoch,
                                         validation_data = (dataset.valid_images, dataset.valid_labels))    
        
        MODEL_PATH = './liziqiang.face.model.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_SIZE x IMAGE_SIZE
                image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE))   #与模型训练不同,这次只是针对1张图片进行预测    
            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
            
            #给出输入属于各个类别的概率,我们是二值类别,则该函数会给出输入图像属于0和1的概率各为多少
            result = self.model.predict_proba(image)
            print('result:', result)
            
            #给出类别预测:0或者1
            result = self.model.predict_classes(image)        
    
            #返回类别预测结果
            return result[0]
    
    
    
    
    
    
    
        
    if __name__ == '__main__':
        dataset = Dataset('./data/')    
        dataset.load()
        
        model = Model()
        model.build_model(dataset)
        
        #先前添加的测试build_model()函数的代码
        model.build_model(dataset)
    
        #测试训练函数的代码
        model.train(dataset)
        
        
    if __name__ == '__main__':
        dataset = Dataset('./data/')    
        dataset.load()
        
        model = Model()
        model.build_model(dataset)
        model.train(dataset)
        model.save_model(file_path = './model/liziqiang.face.model.h5')
        
        
    if __name__ == '__main__':    
        dataset = Dataset('./data/')    
        dataset.load()
    
        
        #评估模型
        model = Model()
        model.load_model(file_path = './model/liziqiang.face.model.h5')
        model.evaluate(dataset)    
        
        
        
        
        

    运行程序结果:

    可以看到,最后我们对数据进行了验证,准确率达到了99.83%。结果较为理想,并且在model下面也得到了我i们的训练数据。

    由此,我们最重要的训练数据也完成了,加下来就是验证我们训练数据的效果的时候了。

     

     

    3、识别人脸

     

    新建python文件,命名:Face_recognition。代码如下:

    #-*- coding: utf-8 -*-
    
    import cv2
    import sys
    import gc
    from face_train import Model
    
    if __name__ == '__main__':
        if len(sys.argv) != 1:
            print("Usage:%s camera_id\r\n" % (sys.argv[0]))
            sys.exit(0)
            
        #加载模型
        model = Model()
        model.load_model(file_path = './model/liziqiang.face.model.h5')    
                  
        #框住人脸的矩形边框颜色       
        color = (0, 255, 0)
        
        #捕获指定摄像头的实时视频流
        cap = cv2.VideoCapture(0)
        
        #人脸识别分类器本地存储路径
        cascade_path = "H:\\opencv\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt2.xml"    
        
        #循环检测识别人脸
        while True:
            ret, frame = cap.read()   #读取一帧视频
            
            if ret is True:
                
                #图像灰化,降低计算复杂度
                frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            else:
                continue
            #使用人脸识别分类器,读入分类器
            cascade = cv2.CascadeClassifier(cascade_path)                
    
            #利用分类器识别出哪个区域为人脸
            faceRects = cascade.detectMultiScale(frame_gray, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))        
            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]
                    faceID = model.face_predict(image)   
                    
                    #如果是“我”
                    if faceID == 0:                                                        
                        cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness = 2)
                        
                        #文字提示是谁
                        cv2.putText(frame,'liziqiang', 
                                    (x + 30, y + 30),                      #坐标
                                    cv2.FONT_HERSHEY_SIMPLEX,              #字体
                                    1,                                     #字号
                                    (255,0,255),                           #颜色
                                    2)                                     #字的线宽
                    else:
                        pass
                                
            cv2.imshow("识别朕", frame)
            
            #等待10毫秒看是否有按键输入
            k = cv2.waitKey(10)
            #如果输入q则退出循环
            if k & 0xFF == ord('q'):
                break
    
        #释放摄像头并销毁所有窗口
        cap.release()
        cv2.destroyAllWindows()

     

    好,最终实现的结果就是文章开头的时候的效果。希望能帮到你!

    有什么问题我会尽量回答!

    展开全文
  • 基于Opencv快速实现人脸识别(完整版)

    万次阅读 多人点赞 2019-12-31 16:22:22
    上篇博客:...这次进一步地研究这一块的知识,来一波真正意义上的人脸识别,查询的资料可能有点过时,但基本思想是没有毛病的,对一些函数也进行了更新,保证了功能的正常实...

    上篇博客:https://blog.csdn.net/beyond9305/article/details/92844258严格来说标题是有误的,只是单纯地对人脸进行了检测,而并非识别,opencv内置了检测分类器和识别器,这二者还是有很大不同的。

    这次进一步地研究这一块的知识,来一波真正意义上的人脸识别,查询的资料可能有点过时,但基本思想是没有毛病的,对一些函数也进行了更新,保证了功能的正常实现。那就开始吧:

    首先看一下本实验需要的数据集,为了简便我们只进行两个人的识别,选取了我的偶像beyond乐队的主唱黄家驹和贝斯手黄家强,这哥俩长得有几分神似,这也是对人脸识别的一个考验:

    两个文件夹,一个为训练数据集,一个为测试数据集,训练数据集中有两个文件夹0和1,之前看一些资料有说这里要遵循“slabel”命名规则,但后面处理起来比较麻烦,因为目前opencv接受的人脸识别标签为整数,那我们就直接用整数命名吧:

    为了简便,我们每个人用20张照片来训练,0代表黄家驹,1代表黄家强:

    接下来就正式开始吧:

    1. 检测人脸。这应该是最基本的,给我们一张图片,我们要先检测出人脸的区域,然后才能进行操作,opencv已经内置了很多分类检测器,我们这次用haar:

    def detect_face(img):
        #将测试图像转换为灰度图像,因为opencv人脸检测器需要灰度图像
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
        #加载OpenCV人脸检测分类器Haar
        face_cascade = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml')
    
        #检测多尺度图像,返回值是一张脸部区域信息的列表(x,y,宽,高)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5)
    
        # 如果未检测到面部,则返回原始图像
        if (len(faces) == 0):
            return None, None
    
        #目前假设只有一张脸,xy为左上角坐标,wh为矩形的宽高
        (x, y, w, h) = faces[0]
    
        #返回图像的正面部分
        return gray[y:y + w, x:x + h], faces[0]

    2. 有了数据集和检测人脸的功能后,我们就可以进行图片预训练处理了,最后返回所有训练图片的人脸检测信息和标签:

    def prepare_training_data(data_folder_path):
        # 获取数据文件夹中的目录(每个主题的一个目录)
        dirs = os.listdir(data_folder_path)
    
        # 两个列表分别保存所有的脸部和标签
        faces = []
        labels = []
    
        # 浏览每个目录并访问其中的图像
        for dir_name in dirs:
            # dir_name(str类型)即标签
            label = int(dir_name)
            # 建立包含当前主题主题图像的目录路径
            subject_dir_path = data_folder_path + "/" + dir_name
            # 获取给定主题目录内的图像名称
            subject_images_names = os.listdir(subject_dir_path)
    
            # 浏览每张图片并检测脸部,然后将脸部信息添加到脸部列表faces[]
            for image_name in subject_images_names:
                # 建立图像路径
                image_path = subject_dir_path + "/" + image_name
                # 读取图像
                image = cv2.imread(image_path)
                # 显示图像0.1s
                cv2.imshow("Training on image...", image)
                cv2.waitKey(100)
    
                # 检测脸部
                face, rect = detect_face(image)
                # 我们忽略未检测到的脸部
                if face is not None:
                    #将脸添加到脸部列表并添加相应的标签
                    faces.append(face)
                    labels.append(label)
    
        cv2.waitKey(1)
        cv2.destroyAllWindows()
        #最终返回值为人脸和标签列表
        return faces, labels

    3. 有了脸部信息和对应标签后,我们就可以使用opencv自带的识别器来进行训练了:

    #调用prepare_training_data()函数
    faces, labels = prepare_training_data("training_data")
    
    #创建LBPH识别器并开始训练,当然也可以选择Eigen或者Fisher识别器
    face_recognizer = cv2.face.LBPHFaceRecognizer_create()
    face_recognizer.train(faces, np.array(labels))

    4.训练完毕后就可以进行预测了,在这之前我们可以设定一下预测的格式,包括用矩形框框出人脸并标出其名字,当然最后别忘了建立标签与真实姓名直接的映射表:

    #根据给定的(x,y)坐标和宽度高度在图像上绘制矩形
    def draw_rectangle(img, rect):
        (x, y, w, h) = rect
        cv2.rectangle(img, (x, y), (x + w, y + h), (128, 128, 0), 2)
    # 根据给定的(x,y)坐标标识出人名
    def draw_text(img, text, x, y):
        cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_COMPLEX, 1, (128, 128, 0), 2)
    
    #建立标签与人名的映射列表(标签只能为整数)
    subjects = ["jiaju", "jiaqiang"]
    

    5.现在就可以定义我们的预测函数了:

    # 此函数识别传递的图像中的人物并在检测到的脸部周围绘制一个矩形及其名称
    def predict(test_img):
        #生成图像的副本,这样就能保留原始图像
        img = test_img.copy()
        #检测人脸
        face, rect = detect_face(img)
        #预测人脸
        label = face_recognizer.predict(face)
        # 获取由人脸识别器返回的相应标签的名称
        label_text = subjects[label[0]]
    
        # 在检测到的脸部周围画一个矩形
        draw_rectangle(img, rect)
        # 标出预测的名字
        draw_text(img, label_text, rect[0], rect[1] - 5)
        #返回预测的图像
        return img
    

    6.最后使用我们test_data中的图片进行预测并显示最终效果:

    #加载测试图像
    test_img1 = cv2.imread("test_data/test1.jpg")
    test_img2 = cv2.imread("test_data/test2.jpg")
    
    #执行预测
    predicted_img1 = predict(test_img1)
    predicted_img2 = predict(test_img2)
    
    #显示两个图像
    cv2.imshow(subjects[0], predicted_img1)
    cv2.imshow(subjects[1], predicted_img2)

    来看看识别结果如何:

    这就是人脸识别最基本的流程,后续还会进一步的研究,下一篇我们将讨论本次实验的一些细节和注意事项,算是对本篇的一次挖掘和总结吧。最后附上完整代码:

    # # -*- coding:utf-8 -*-
    import cv2
    import os
    import numpy as np
    
    # 检测人脸
    def detect_face(img):
        #将测试图像转换为灰度图像,因为opencv人脸检测器需要灰度图像
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
        #加载OpenCV人脸检测分类器Haar
        face_cascade = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml')
    
        #检测多尺度图像,返回值是一张脸部区域信息的列表(x,y,宽,高)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5)
    
        # 如果未检测到面部,则返回原始图像
        if (len(faces) == 0):
            return None, None
    
        #目前假设只有一张脸,xy为左上角坐标,wh为矩形的宽高
        (x, y, w, h) = faces[0]
    
        #返回图像的正面部分
        return gray[y:y + w, x:x + h], faces[0]
    
    
    # 该函数将读取所有的训练图像,从每个图像检测人脸并将返回两个相同大小的列表,分别为脸部信息和标签
    def prepare_training_data(data_folder_path):
        # 获取数据文件夹中的目录(每个主题的一个目录)
        dirs = os.listdir(data_folder_path)
    
        # 两个列表分别保存所有的脸部和标签
        faces = []
        labels = []
    
        # 浏览每个目录并访问其中的图像
        for dir_name in dirs:
            # dir_name(str类型)即标签
            label = int(dir_name)
            # 建立包含当前主题主题图像的目录路径
            subject_dir_path = data_folder_path + "/" + dir_name
            # 获取给定主题目录内的图像名称
            subject_images_names = os.listdir(subject_dir_path)
    
            # 浏览每张图片并检测脸部,然后将脸部信息添加到脸部列表faces[]
            for image_name in subject_images_names:
                # 建立图像路径
                image_path = subject_dir_path + "/" + image_name
                # 读取图像
                image = cv2.imread(image_path)
                # 显示图像0.1s
                cv2.imshow("Training on image...", image)
                cv2.waitKey(100)
    
                # 检测脸部
                face, rect = detect_face(image)
                # 我们忽略未检测到的脸部
                if face is not None:
                    #将脸添加到脸部列表并添加相应的标签
                    faces.append(face)
                    labels.append(label)
    
        cv2.waitKey(1)
        cv2.destroyAllWindows()
        #最终返回值为人脸和标签列表
        return faces, labels
    
    #调用prepare_training_data()函数
    faces, labels = prepare_training_data("training_data")
    
    #创建LBPH识别器并开始训练,当然也可以选择Eigen或者Fisher识别器
    face_recognizer = cv2.face.LBPHFaceRecognizer_create()
    face_recognizer.train(faces, np.array(labels))
    
    #根据给定的(x,y)坐标和宽度高度在图像上绘制矩形
    def draw_rectangle(img, rect):
        (x, y, w, h) = rect
        cv2.rectangle(img, (x, y), (x + w, y + h), (128, 128, 0), 2)
    # 根据给定的(x,y)坐标标识出人名
    def draw_text(img, text, x, y):
        cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_COMPLEX, 1, (128, 128, 0), 2)
    
    #建立标签与人名的映射列表(标签只能为整数)
    subjects = ["jiaju", "jiaqiang"]
    
    # 此函数识别传递的图像中的人物并在检测到的脸部周围绘制一个矩形及其名称
    def predict(test_img):
        #生成图像的副本,这样就能保留原始图像
        img = test_img.copy()
        #检测人脸
        face, rect = detect_face(img)
        #预测人脸
        label = face_recognizer.predict(face)
        # 获取由人脸识别器返回的相应标签的名称
        label_text = subjects[label[0]]
    
        # 在检测到的脸部周围画一个矩形
        draw_rectangle(img, rect)
        # 标出预测的名字
        draw_text(img, label_text, rect[0], rect[1] - 5)
        #返回预测的图像
        return img
    
    #加载测试图像
    test_img1 = cv2.imread("test_data/test1.jpg")
    test_img2 = cv2.imread("test_data/test2.jpg")
    
    #执行预测
    predicted_img1 = predict(test_img1)
    predicted_img2 = predict(test_img2)
    
    #显示两个图像
    cv2.imshow(subjects[0], predicted_img1)
    cv2.imshow(subjects[1], predicted_img2)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    ps:6.30号就是家驹去世二十六周年了,希望成都的小伙伴们能聚一下,让我们一起缅怀。

    如果文章对您有一点点帮助,还请打赏一二,您的鼓励将是我前进的不竭动力

    公众号为“非著名IT表演艺术家”,比较中二,就是灵光一闪,然后这个名字就冒出来了……

    展开全文
  • python基于openCV人脸识别

    千人学习 2019-10-30 14:34:06
    python 基于openCV人脸识别,简单入门级项目,初学者可以看懂。
  • 人脸识别算法原理过程详解

    千次阅读 2019-10-11 15:53:32
    最近,由于工作需要,为了找到一款高效的人脸识别算法,对各种人脸识别算法都研究了一番,以下记录的是各算法的理论基础。 一.MTCNN 本文章主要介绍MTCNN算法的流程,MTCNN主要由三个框架组成,分别是PNet,RNet,...

    本文为转载内容,由于找不到源作者链接,故特此说明。

    人脸识别各算法详解

    最近,由于工作需要,为了找到一款高效的人脸识别算法,对各种人脸识别算法都研究了一番,以下记录的是各算法的理论基础。

    一.MTCNN

    本文章主要介绍MTCNN算法的流程,MTCNN主要由三个框架组成,分别是PNet,RNet,ONet。下面将分别介绍这三个部分。

    理论基础:
    在这里插入图片描述

    PNet

    Proposal Network (P-Net):该网络结构主要获得了人脸区域的候选窗口和边界框的回归向量。并用该边界框做回归,对候选窗口进行校准,然后通过非极大值抑制(NMS)来合并高度重叠的候选框。

    因为实际的图片大小不一,所以PNet是一个全卷积网络,对于输入的图片大小可以是任意值;将图片输入PNet之前,有一个循环,每一次循环会将图片进行缩放,再输入PNet;这样形成一个图片金字塔,图片每次缩放因子是0.80(论文的初始值应该是0.709),当宽高小于12时候这张图片对应的循环结束,12是PNet的最小图片输入尺寸。下图表示的是PNet的结构

    在这里插入图片描述
    由上面这张图可以得到,一张12x12x3的图片最终的输出的结果是1x1x32的特征图,再分成三条支路,用于人脸分类、边框回归、人脸特征点定位。

    这三条支路的损失函数分别是交叉熵(二分类问题常用)、平方和损失函数、5个特征点与标定好的数据的平方和损失。最后的总损失是三个损失乘上各自的权重比之和,在PNet里面三种损失的权重是1:0.5:0.5。将图片输入PNet后,得到了cls_cls_map, reg这两个数组,其中cls_cls_map是(H,W,2)的二维数组,就是非人脸和人脸的概率。PNet直接输出的边界框并不是传统回归中的边界坐标,而是预测人脸位置相对于输入图片的位置差,即为reg。

    将cls_cls_map里面人脸的概率和一个事先设定的阈值相比较,如果大于这个阈值,就将这张图片对应的reg数组里面的预测值提取出来,通过逆运算得到原始像素坐标。对于 x * y 的输入,将产生大小为[(x−12)/2+1]∗[(y−12)2+1]的输出。因为池化层的步长是2,所以上述式子的分母为2。将reg的坐标通过此方法可还原预测边框值在原始图片的像素坐标。最后返回的数组是(x1,y1,x2,y2,score,reg),其中(x1,y1,x2,y2)是bbox在原始图片中的像素坐标。score是cls_cls_map对应的人脸概率。

    完成这一步后,将使用非极大值抑制法(NMS)去掉一些重复框,这个算法的原理是将上一步返回的数组的score值最大的那一行元素提取出来,将剩下的所有的元素的score和一个设定好的阈值相比较,将score值大于阈值(0.5)的元素抛弃,再将剩下的元素重复之前的提取最大值并进行比较的操作。直到最后,这样就初步抛弃了那些重合度较高的人脸框。

    因为(x1,y1,x2,y2)是在原图像中的像素坐标,reg是候选框区域相对于像素坐标的偏差,这样讲将原像素坐标加上偏差值,即可得到候选框的坐标。将初步筛选后的的bbox按照上面的方法refine,到此为止,PNet这一部分就结束了。输出的是候选框的4个坐标加上对应的score值。

    RNet

    Refine Network (R-Net):该网络结构还是通过边界框回归和NMS来去掉那些false-positive区域。只是由于该网络结构和P-Net网络结构有差异,多了一个全连接层,所以会取得更好的抑制false-positive的作用。

    首先将PNet的输出resize成正方形,这主要是基于人脸一般都是正方形的。
    再将PNet生成的bbox里的元素调整一下,生成(dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph)这样的数组;生成的元素的意义如下:

    	1>. dx,dy:bbox的相对本身起点坐标(0,0)。
    	2>. edx,edy:bbox的相对本身终点坐标(tmpw-1, tmph-1)。
    	3>. x,y : 原始图片的bbox起点。
    	4>. ex,ey:原始图片的bbox结束点。
    

    在生成的过程还会有检测,避免bbox的坐标超出原始图片或者为负值;接下来遍历这个数组,将里面的bbox从原始图片里面抠出来,resize成24x24同时进行归一化。

    完成了前面的操作后就是将24x24的图片喂入RNet了,下图表示的是RNet的结构:
    在这里插入图片描述
    可以看出RNet最后是采用的全连接层,这也是为什么喂入的图片统一成24x24大小。

    由上面这张图可以得到,一张24x24x3的图片最终的输出的结果是3x3x64的特征图,再经历全连接层后分成三条支路,用于人脸分类、边框回归、人脸特征点定位。这三条支路的损失函数和PNet的一样,各损失的权重比也为1:0.5:0.5。

    将图片输入RNet后,得到了cls_scores, reg这两个数组,cls_scores表示非人脸和人脸的概率,reg表示bbox的回归信息。同样将cls_scores中人脸的概率与实现设定的阈值比较,将大于阈值的图片对应的bbox提取出来,过滤掉一部分非人脸的bbox。

    接着再次调用NMS,抛弃掉大量的重叠率高的人脸框,经过两次的筛选,剩下的bbox的数量就少了很多。最后进行RNet的最后一步操作,就是回归信息reg来调整bbox的坐标,大致就是将bbox的4个坐标乘上bbox的宽或者高,其中x和宽相乘,y和高相乘。最后就是返回调整后的四个坐标

    ONet

    Output Network (O-Net):该层比R-Net层又多了一层卷基层,所以处理的结果会更加精细。作用和R-Net层作用一样。但是该层对人脸区域进行了更多的监督,同时还会输出5个地标(landmark)。
    首先将RNet的输出resize成正方形,接下来的操作和对应的RNet部分相似,只是再喂入ONet之前图片是resize乘48x48。

    在这里插入图片描述
    将48x48x3的图片喂入ONet后输出的是3x3x128的特征图,经过全连接层后同样是有着三条支路。三条支路的损失函数与PNet、RNet一样,但是三个损失函数的权重比为1:0.5:1。

    这次从ONet的输出接受cls_scores, reg, landmark这三个数组,同样先根据cls_scores的人脸概率是否大于设定的阈值来抛弃一部分非人脸框。接下来就是确定landmark的值,因为前面直接得到的关键点的x、y坐标相关信息并不是x、y的值,而是一个相对于宽高的偏置值,最终的关键点的x、y值可以通过这个偏置值和bbox的宽或者高(x与宽,y与高)相乘再与bbox的坐标相加得到。

    接下来就是回归信息reg来调整bbox的坐标,与RNet输出前的操作一样。完成之后经历两次的NMS操作,但是这次的NMS操作与之前的略有不用,大家可以看详细的代码解释。最后就可以输出bbox和landmark了,至此算法就结束了。

    二.PFLD

    PFLD算法,目前主流数据集上达到最高精度、ARM安卓机140fps,模型大小仅2.1M!

    算法思想:
    在这里插入图片描述

    其中,黄色曲线包围的是主网络,用于预测特征点的位置;
    绿色曲线包围的部分为辅网络,在训练时预测人脸姿态(有文献表明给网络加这个辅助任务可以提高定位精度,具体参考原论文),这部分在测试时不需要。

    对于上述影响精度的挑战,修改loss函数在训练时关注那些稀有样本,而提高计算速度和减小模型size则是使用轻量级模型。

    Loss函数设计

    Loss函数用于神经网络在每次训练时预测的形状和标注形状的误差。
    考虑到样本的不平衡,作者希望能对那些稀有样本赋予更高的权重,这种加权的Loss函数被表达为:
    在这里插入图片描述
    M为样本个数,N为特征点个数,Yn为不同的权重,|| * ||为特征点的距离度量(L1或L2距离)。(以Y代替公式里的希腊字母)

    进一步细化Yn:
    在这里插入图片描述
    其中:
    在这里插入图片描述
    即为最终的样本权重。
    K=3,这一项代表着人脸姿态的三个维度,即yaw, pitch, roll 角度,可见角度越高,权重越大。
    C为不同的人脸类别数,作者将人脸分成多个类别,比如侧脸、正脸、抬头、低头、表情、遮挡等,w为与类别对应的给定权重,如果某类别样本少则给定权重大。

    主网络

    作者使用轻量级的MobileNet,其参数如下:
    在这里插入图片描述

    辅网络

    参数如下:
    在这里插入图片描述

    验证结果如下:
    在这里插入图片描述

    三. 虹软人脸识别算法

    虹软属于人脸检测技术,能够帮助您检测并且定位到影像(图片或者视频)中的人脸。

    本文将以这三个库为基础,从人脸注册开始,到人脸识别结束。全程演示人脸识别的流程。

    人脸信息是保存在AFR_FSDKFace类中的。这的主要结构为

      public static final int FEATURE_SIZE = 22020;
      byte[] mFeatureData;
    

    如果要进行人脸注册,我们需要定义另外一个类来把人脸信息和姓名关联起来。

    class FaceRegist {
            String mName;
            List<AFR_FSDKFace> mFaceList;
    
        public FaceRegist(String name) {
            mName = name;
            mFaceList = new ArrayList<>();
        }
    }
    

    包含特征信息的长度和内容的byte数组。
    我们把这些功能定义在类FaceDB中。FaceDB需要包含引擎定义,初始化,把人脸信息保存在版本库和从版本库中读出人脸信息这些功能

    初始化引擎
    为了程序结构性考虑,我们将人脸识别相关的代码独立出来一个类FaceDB,并定义必要的变量

    	public static String appid = "bCx99etK9Ns4Saou1EbFdC18xHdY9817EKw****";
    	public static String ft_key = "CopwZarSihp1VBu5AyGxfuLQdRMPyoGV2C2opc****";
    	public static String fd_key = "CopwZarSihp1VBu5AyGxfuLXnpccQbWAjd86S8****";
    	public static String fr_key = "CopwZarSihp1VBu5AyGxfuLexDsi8yyELdgsj4****";
    	 
    	 
    	String mDBPath;
    	List<FaceRegist> mRegister;
    	AFR_FSDKEngine mFREngine;
    	AFR_FSDKVersion mFRVersion;
    
    • 定义有参数的构造函数来初始化引擎
    public FaceDB(String path) {
            mDBPath = path;
            mRegister = new ArrayList<>();
            mFRVersion = new AFR_FSDKVersion();
            mUpgrade = false;
            mFREngine = new AFR_FSDKEngine();
            AFR_FSDKError error = mFREngine.AFR_FSDK_InitialEngine(FaceDB.appid, FaceDB.fr_key);
            if (error.getCode() != AFR_FSDKError.MOK) {
                Log.e(TAG, "AFR_FSDK_InitialEngine fail! error code :" + error.getCode());
            } else {
                mFREngine.AFR_FSDK_GetVersion(mFRVersion);
                Log.d(TAG, "AFR_FSDK_GetVersion=" + mFRVersion.toString());
            }
        }
    

    定义析构函数释放引擎占用的系统资源

    public void destroy() {
            if (mFREngine != null) {
                mFREngine.AFR_FSDK_UninitialEngine();
            }
     
        }
    

    实现人脸增加和读取功能
    通常人脸库会存放在数据库中,本次我们使用List来进行简单的模拟,并将其保存在文本文件中,需要时从文本中读取,保存时写入到文件中。

    我们使用addFace方法将待注册的人脸信息添加到人脸库中

    public  void addFace(String name, AFR_FSDKFace face) {
            try {
                //check if already registered.
                boolean add = true;
                for (FaceRegist frface : mRegister) {
                    if (frface.mName.equals(name)) {
                        frface.mFaceList.add(face);
                        add = false;
                        break;
                    }
                }
                if (add) { // not registered.
                    FaceRegist frface = new FaceRegist(name);
                    frface.mFaceList.add(face);
                    mRegister.add(frface);
                }
     
                if (!new File(mDBPath + "/face.txt").exists()) {
                    if (!saveInfo()) {
                        Log.e(TAG, "save fail!");
                    }
                }
     
                //save name
                FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true);
                ExtOutputStream bos = new ExtOutputStream(fs);
                bos.writeString(name);
                bos.close();
                fs.close();
     
                //save feature
                fs = new FileOutputStream(mDBPath + "/" + name + ".data", true);
                bos = new ExtOutputStream(fs);
                bos.writeBytes(face.getFeatureData());
                bos.close();
                fs.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    使用loadFaces从文件中读取人脸

    public boolean loadFaces(){
            if (loadInfo()) {
                try {
                    for (FaceRegist face : mRegister) {
                        Log.d(TAG, "load name:" + face.mName + "'s face feature data.");
                        FileInputStream fs = new FileInputStream(mDBPath + "/" + face.mName + ".data");
                        ExtInputStream bos = new ExtInputStream(fs);
                        AFR_FSDKFace afr = null;
                        do {
                            if (afr != null) {
                                if (mUpgrade) {
                                    //upgrade data.
                                }
                                face.mFaceList.add(afr);
                            }
                            afr = new AFR_FSDKFace();
                        } while (bos.readBytes(afr.getFeatureData()));
                        bos.close();
                        fs.close();
                    }
                    return true;
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                if (!saveInfo()) {
                    Log.e(TAG, "save fail!");
                }
            }
            return false;
        }
    

    实现业务逻辑
    实现人脸注册功能
    人脸识别的前提条件就是人脸信息要先注册到人脸库中,注册人脸库

    第一步当然是获取待注册的照片,我们可以可以使用摄像头,也可以使用照片。我们使用AlertDialog弹出选择框

    new AlertDialog.Builder(this)
                            .setTitle("请选择注册方式")
                            .setIcon(android.R.drawable.ic_dialog_info)
                            .setItems(new String[]{"打开图片", "拍摄照片"}, this)
                            .show();
    

    在对应的事件处理函数中进行处理

    switch (which){
        case 1://摄像头
            Intent getImageByCamera = new Intent("android.media.action.IMAGE_CAPTURE");
            ContentValues values = new ContentValues(1);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            mPath = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            getImageByCamera.putExtra(MediaStore.EXTRA_OUTPUT, mPath);
            startActivityForResult(getImageByCamera, REQUEST_CODE_IMAGE_CAMERA);
            break;
        case 0://图片
            Intent getImageByalbum = new Intent(Intent.ACTION_GET_CONTENT);
            getImageByalbum.addCategory(Intent.CATEGORY_OPENABLE);
            getImageByalbum.setType("image/jpeg");
            startActivityForResult(getImageByalbum, REQUEST_CODE_IMAGE_OP);
            break;
        default:;
    }
    

    获取一张照片后,后续我们就需要实现人脸检测功能。

        if (requestCode == REQUEST_CODE_IMAGE_OP && resultCode == RESULT_OK) {
                mPath = data.getData();
                String file = getPath(mPath);
                //TODO: add image coversion
            }
    

    在上面的代码中,我们获取到了我们需要的图像数据bmp,把图片取出来
    我们在Application类用函数 decodeImage中实现这段代码

    public static Bitmap decodeImage(String path) {
            Bitmap res;
            try {
                ExifInterface exif = new ExifInterface(path);
                int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
     
                BitmapFactory.Options op = new BitmapFactory.Options();
                op.inSampleSize = 1;
                op.inJustDecodeBounds = false;
                //op.inMutable = true;
                res = BitmapFactory.decodeFile(path, op);
                //rotate and scale.
                Matrix matrix = new Matrix();
    
            if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
                matrix.postRotate(90);
            } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
                matrix.postRotate(180);
            } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
                matrix.postRotate(270);
            }
    
            Bitmap temp = Bitmap.createBitmap(res, 0, 0, res.getWidth(), res.getHeight(), matrix, true);
            Log.d("com.arcsoft", "check target Image:" + temp.getWidth() + "X" + temp.getHeight());
    
            if (!temp.equals(res)) {
                res.recycle();
            }
            return temp;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

    调用AFD_FSDK_StillImageFaceDetection返回检测到的人脸信息
    人脸注册 ,首先要先检测出来人脸,对于静态图片,虹软人脸SDK中对应的是FD,提供了一个方法名称,叫AFD_FSDK_StillImageFaceDetection 。
    我们来看一下参数列表

    类型 名称 说明
    byte[] data 输入的图像数据
    int width 图像宽度
    int height 图像高度
    int format 图像格式
    List<AFD_FSDKFace> list 检测到的人脸会放到到该列表里。
    注意AFD_FSDKFace对象引擎内部重复使用,如需保存,请clone一份AFD_FSDKFace对象或另外保存

    AFD_FSDKFace是人脸识别的结果,定义如下

    public class AFD_FSDKFace {
        Rect mRect;
        int mDegree;
        }
    

    mRect定义一个了一个矩形框Rect

    在此之前我们需要注意虹软人脸SDK使用的图像格式是NV21的格式,所以我们需要将获取到的图像转化为对应的格式。在Android_extend.jar中提供了对应的转换函数

    byte[] data = new byte[mBitmap.getWidth() * mBitmap.getHeight() * 3 / 2];
                ImageConverter convert = new ImageConverter();
                convert.initial(mBitmap.getWidth(), mBitmap.getHeight(), ImageConverter.CP_PAF_NV21);
                if (convert.convert(mBitmap, data)) {
                    Log.d(TAG, "convert ok!");
                }
                convert.destroy();
    

    现在我们就可以调用AFD_FSDK_StillImageFaceDetection方法了

    err  = engine.AFD_FSDK_StillImageFaceDetection(data, mBitmap.getWidth(), mBitmap.getHeight(), AFD_FSDKEngine.CP_PAF_NV21, result);
    

    绘出人脸框
    在List<AFD_FSDKFace>中保存了检测到的人脸的位置信息和深度信息。
    我们可以将检测到的人脸位置信息在图片上用一个矩形框绘制出来表示检测到的人脸信息。

    Canvas canvas = mSurfaceHolder.lockCanvas();
       if (canvas != null) {
          Paint mPaint = new Paint();
          boolean fit_horizontal = canvas.getWidth() / (float)src.width() < canvas.getHeight() / (float)src.height() ? true : false;
          float scale = 1.0f;
          if (fit_horizontal) {
             scale = canvas.getWidth() / (float)src.width();
             dst.left = 0;
             dst.top = (canvas.getHeight() - (int)(src.height() * scale)) / 2;
             dst.right = dst.left + canvas.getWidth();
             dst.bottom = dst.top + (int)(src.height() * scale);
          } else {
             scale = canvas.getHeight() / (float)src.height();
             dst.left = (canvas.getWidth() - (int)(src.width() * scale)) / 2;
             dst.top = 0;
             dst.right = dst.left + (int)(src.width() * scale);
             dst.bottom = dst.top + canvas.getHeight();
          }
          canvas.drawBitmap(mBitmap, src, dst, mPaint);
          canvas.save();
          canvas.scale((float) dst.width() / (float) src.width(), (float) dst.height() / (float) src.height());
          canvas.translate(dst.left / scale, dst.top / scale);
          for (AFD_FSDKFace face : result) {
             mPaint.setColor(Color.RED);
             mPaint.setStrokeWidth(10.0f);
             mPaint.setStyle(Paint.Style.STROKE);
             canvas.drawRect(face.getRect(), mPaint);
          }
          canvas.restore();
          mSurfaceHolder.unlockCanvasAndPost(canvas);
          break;
       }
    }
    

    将人脸注册到人脸库
    检测到了人脸,我们可以输入相应的描述信息,加入到人脸库中。

    为了提高识别的准确性,我们可以对一个人多次注册人脸信息。

    public  void addFace(String name, AFR_FSDKFace face) {
       try {
          //check if already registered.
          boolean add = true;
          for (FaceRegist frface : mRegister) {
             if (frface.mName.equals(name)) {
                frface.mFaceList.add(face);
                add = false;
                break;
             }
          }
          if (add) { // not registered.
         FaceRegist frface = new FaceRegist(name);
         frface.mFaceList.add(face);
         mRegister.add(frface);
      }
    
      if (!new File(mDBPath + "/face.txt").exists()) {
         if (!saveInfo()) {
            Log.e(TAG, "save fail!");
         }
      }
      //save name
      FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true);
      ExtOutputStream bos = new ExtOutputStream(fs);
      bos.writeString(name);
      bos.close();
      fs.close();
      //save feature
      fs = new FileOutputStream(mDBPath + "/" + name + ".data", true);
      bos = new ExtOutputStream(fs);
      bos.writeBytes(face.getFeatureData());
      bos.close();
      fs.close();
       } catch (FileNotFoundException e) {
          e.printStackTrace();
       } catch (IOException e) {
          e.printStackTrace();
       }
    }
    

    最后,别忘记了销毁人脸检测引擎哦

    err = engine.AFD_FSDK_UninitialFaceEngine(); 
    Log.d("com.arcsoft", "AFD_FSDK_UninitialFaceEngine =" + err.getCode()); 
    

    实现人脸识别
    上面的代码准备完毕后,就可以开始我们的人脸识别的功能了。我们使用一个第三方的扩展库,ExtGLSurfaceView的扩展 库CameraGLSurfaceView,用ImageView和TextView显示检测到的人脸和相应的描述信息。

    首先是定义layout。

    <?xml version="1.0" encoding="utf-8"?>
    

     

    <com.guo.android_extend.widget.CameraSurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="1dp"
        android:layout_height="1dp"/>
    
    <com.guo.android_extend.widget.CameraGLSurfaceView
        android:id="@+id/glsurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"/>
    
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"/>
    
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/imageView"
        android:layout_alignRight="@+id/imageView"
        android:layout_below="@+id/imageView"
        android:layout_marginTop="10dp"
        android:text="@string/app_name"
        android:textAlignment="center"/>
    
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/imageView"
        android:layout_alignRight="@+id/imageView"
        android:layout_below="@+id/textView"
        android:layout_marginTop="10dp"
        android:text="@string/app_name"
        android:textAlignment="center"/>
    

    因为引擎需要的图像格式是NV21的,所以需要将摄像头中的图像格式预设置为NV21

    public Camera setupCamera() {
       // TODO Auto-generated method stub
       mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
       try {
          Camera.Parameters parameters = mCamera.getParameters();
          parameters.setPreviewSize(mWidth, mHeight);
          parameters.setPreviewFormat(ImageFormat.NV21);
     
          for( Camera.Size size : parameters.getSupportedPreviewSizes()) {
             Log.d(TAG, "SIZE:" + size.width + "x" + size.height);
          }
          for( Integer format : parameters.getSupportedPreviewFormats()) {
         Log.d(TAG, "FORMAT:" + format);
      }
    
      List<int[]> fps = parameters.getSupportedPreviewFpsRange();
      for(int[] count : fps) {
         Log.d(TAG, "T:");
         for (int data : count) {
            Log.d(TAG, "V=" + data);
         }
      }
          mCamera.setParameters(parameters);
       } catch (Exception e) {
          e.printStackTrace();
       }
       if (mCamera != null) {
          mWidth = mCamera.getParameters().getPreviewSize().width;
          mHeight = mCamera.getParameters().getPreviewSize().height;
       }
       return mCamera;
    }
    

    从摄像头识别人脸,需要使用FT库,FT库在人脸跟踪算法上对人脸检测部分进行了优化,是专门为视频处理而优化的库。

    初始化人脸检测引擎(FT)
    和FD一样,我们需要初始化人脸识别FT引擎。

    Log.d(TAG, "AFT_FSDK_InitialFaceEngine =" + err.getCode());
    err = engine.AFT_FSDK_GetVersion(version);
    Log.d(TAG, "AFT_FSDK_GetVersion:" + version.toString() + "," + err.getCode());
    

    在摄像头的预览事件处理函数中,先调用FT的人脸识函数函数,然后再调用FR中的人脸信息特征提取数函数。

    AFT_FSDKError err = engine.AFT_FSDK_FaceFeatureDetect(data, width, height, AFT_FSDKEngine.CP_PAF_NV21, result);
     
    AFR_FSDKError error = engine.AFR_FSDK_ExtractFRFeature(mImageNV21, mWidth, mHeight, AFR_FSDKEngine.CP_PAF_NV21,mAFT_FSDKFace.getRect(), mAFT_FSDKFace.getDegree(), result);
    

    这里面的result中保存了人脸特征信息。我们可以将其保存下来或下来并与系统中的其它信息进行对比。

    AFR_FSDKMatching score = new AFR_FSDKMatching();
    float max = 0.0f;
    String name = null;
    for (FaceDB.FaceRegist fr : mResgist) {
       for (AFR_FSDKFace face : fr.mFaceList) {
          error = engine.AFR_FSDK_FacePairMatching(result, face, score);
          Log.d(TAG,  "Score:" + score.getScore() + ", AFR_FSDK_FacePairMatching=" + error.getCode());
          if (max < score.getScore()) {
             max = score.getScore();
             name = fr.mName;
          }
       }
    }
    

    当score的特征信息大于0.6时,我们就可以认为匹配到了人脸。显示人脸匹配信息。

    上面的循环中,可以看到,是遍历了真个库进行寻找。我们的目的是为了演示,实际情况下,我们可以在找到一个匹配值比较高的人脸后,就跳出循环。

    MTCNN算法demo: https://download.csdn.net/download/weixin_42713739/11081627
    PFLD人脸算法demo: https://download.csdn.net/download/weixin_42713739/11106807
    虹软算法demo:https://download.csdn.net/download/weixin_42713739/11087890

    展开全文
  • 基于深度学习的人脸识别技术综述

    万次阅读 2018-10-30 14:37:37
    原文地址:基于深度学习的人脸识别技术综述 简介:人脸识别是计算机视觉研究领域的一个热点,同时人脸识别的研究领域非常广泛。因此,本技术综述限定于:一,在LFW数据集上(Labeled Faces in the Wild)获得优秀...

    原文地址:基于深度学习的人脸识别技术综述

    简介:人脸识别是计算机视觉研究领域的一个热点,同时人脸识别的研究领域非常广泛。因此,本技术综述限定于:一,在LFW数据集上(Labeled Faces in the Wild)获得优秀结果的方法; 二,是采用深度学习的方法。

    前言


    LFW数据集(Labeled Faces in the Wild)是目前用得最多的人脸图像数据库。该数据库共13,233幅图像,其中5749个人,其中1680人有两幅及以上的图像,4069人只有一幅图像。图像为250*250大小的JPEG格式。绝大多数为彩色图,少数为灰度图。该数据库采集的是自然条件下人脸图片,目的是提高自然条件下人脸识别的精度。该数据集有6中评价标准:

    一,Unsupervised;
    二,Image-restricted with no outside data;
    三,Unrestricted with no outside data;
    四,Image-restricted with label-free outside data;
    五,Unrestricted with label-free outside data;
    六,Unrestricted with labeled outside data。
    目前,人工在该数据集上的准确率在0.9427~0.9920。在该数据集的第六种评价标准下(无限制,可以使用外部标注的数据),许多方法已经赶上(超过)人工识别精度,比如face++,DeepID3,FaceNet等。

     

     

    图一/表一:人类在LFW数据集上的识别精度
     

    表二:第六种标准下,部分模型的识别准确率(详情参见lfw结果


    续上表





    本文综述的人脸识别方法包括以下几个筛选标准:一,在上表中识别精度超过0.95(超过人类的识别准确度);二,公布了方法(部分结果为商业公司提交,方法并未公布,比如Tencent-BestImage);三,使用深度学习方法(本人是深度学习的追随者);三,近两年的结果。本文综述的方法包括:1,face++(0.9950 );2,DeepFace(0.9735 );3,FR+FCN(0.9645 );4,DeepID(0.9745 );5,FaceNet(0.9963 );6, baidu的方法(0.9977 );7,pose+shape+expression augmentation(0.9807);8,CNN-3DMM estimation(0.9235 ,准确率没那么高,但是值得参考)。

    人脸识别方法


    1,face++(0.9950)

    参考文献:Naive-Deep face Recognition: Touching the Limit of LFW Benchmark or Not?

     

    face++从网络上搜集了5million张人脸图片用于训练深度卷积神经网络模型,在LFW数据集上准确率非常高。该篇文章的网路模型很常规(常规深度卷积神经网络模型),但是提出的问题是值得参考的。
    问题一:他们的Megvii Face Recognition System经过训练后,在LFW数据集上达到了0.995的准确率。在真实场景测试中(Chinese ID (CHID)),该系统的假阳性率(FP=10^{-5})非常低。但是,真阳性率仅为0.66,没有达到真实场景应用要求。其中,年龄差异(包括intra-variation:同一个人,不同年龄照片;以及inter-variation:不同人,不同年龄照片)是影响模型准确率原因之一。而在该测试标准(CHID)下,人类表现的准确率大于0.90.

     

     

    图1-1:在CHID中出错的样本


    问题二:数据采集偏差。基于网络采集的人脸数据集存在偏差。这些偏差表现在:1,个体之间照片数量差异很大;2,大部分采集的照片都是:微笑,化妆,年轻,漂亮的图片。这些和真实场景中差异较大。因此,尽管系统在LFW数据集上有高准确率,在现实场景中准确率很低。
    问题三:模型测试加阳性率非常低,但是现实应用中,人们更关注真阳性率。
    问题四:人脸图片的角度,光线,闭合(开口、闭口)和年龄等差异相互的作用,导致人脸识别系统现实应用准确率很低。
    因此,该文章提出未来进一步研究的方向。方向一:从视频中提取训练数据。视频中人脸画面接近于现实应用场景(变化的角度,光照,表情等);方向二:通过人脸合成方法增加训练数据。因为单个个体不同的照片很困难(比如,难以搜集大量的单个个体不同年龄段的照片,可以采用人脸合成的方法(比如3D人脸重建)生成单个个体不同年龄段的照片)。该文章提出的方向在后续方法介绍中均有体现。


    2,DeepFace(0.9735 )
    参考文献:Deepface: Closing the gap to humal-level performance in face verification
    2.1 简介
    常规人脸识别流程是:人脸检测-对齐-表达-分类。本文中,我们通过额外的3d模型改进了人脸对齐的方法。然后,通过基于4million人脸图像(4000个个体)训练的一个9层的人工神经网络来进行人脸特征表达。我们的模型在LFW数据集上取得了0.9735的准确率。该文章的亮点有以下几点:一,基于3d模型的人脸对齐方法;二,大数据训练的人工神经网络。

    2.2 人脸对齐方法
    文中使用的人脸对齐方法包括以下几步:1,通过6个特征点检测人脸;2,剪切;3,建立Delaunay triangulation;4,参考标准3d模型;5,将3d模型比对到图片上;6,进行仿射变形;7,最终生成正面图像。

     

     

    图2-1 人脸对齐的流程


    2.3 深度神经网络

     

     

    图2-2:深度神经网络


    2.4 结果
    该模型在LFW数据集上取得了0.9735准确率,在其它数据集比如Social Face Classification (SFC) dataset和YouTube Faces (YTF) dataset也取得了好结果,详情请参见原文。


    3,FR+FCN(0.9645 )

    参考文献:Recover Canonical-View Faces in the Wild with Deep Neural Networks

    3.1 简介

    自然条件下,因为角度,光线,occlusions(咬合/张口闭口),低分辨率等原因,使人脸图像在个体之间有很大的差异,影响到人脸识别的广泛应用。本文提出了一种新的深度学习模型,可以学习人脸图像看不见的一面。因此,模型可以在保持个体之间的差异的同时,极大的减少单个个体人脸图像(同一人,不同图片)之间的差异。与当前使用2d环境或者3d信息来进行人脸重建的方法不同,该方法直接从人脸图像之中学习到图像中的规则观察体(canonical view,标准正面人脸图像)。作者开发了一种从个体照片中自动选择/合成canonical-view的方法。在应用方面,该人脸恢复方法已经应用于人脸核实。同时,该方法在LFW数据集上获得了当前最好成绩。该文章的亮点在于:一,新的检测/选择canonical-view的方法;二,训练深度神经网络来重建人脸正面标准图片(canonical-view)。

    3.2 canonical view选择方法

    我们设计了基于矩阵排序和对称性的人脸正面图像检测方法。如图3-1所示,我们按照以下三个标准来采集个体人脸图片:一,人脸对称性(左右脸的差异)进行升序排列;二,图像锐度进行降序排列;三,一和二的组合。

     

    图3-1 正面人脸图像检测方法


    矩阵Y_{i} \in R^{64\times 64}为第i个个体的人脸图像矩阵,D_{i}为第i个个体所有人脸图像集合,Y_{i} \in D_{i}。正面人脸检测公式为:M(Y_{i} )=||Y_{i}P-Y_{i}Q||_{F}^{2}-\lambda ||Y_{i}||_{*}

    3.3 人脸重建

    我们通过训练深度神经网络来进行人脸重建。loss函数为:E(\left\{ X_{ik}^{0} \right\}  ;W)=\sum_{i}^{}{} \sum_{k}^{}{} \left| \left| Y_{i}-f(X_{ik}^{0};W ) \right|\right| _{F}^{2}

    i为第i个个体,k为第i个个体的第k张样本。X^{0}和Y为训练图像和目标图像。

    如图3-2所示,深度神经网络包含三层。前两层后接上了max pooling;最后一层接上了全连接层。于传统卷积神经网络不同,我们的filters不共享权重(我们认为人脸的不同区域存在不同类型的特征)。第l层卷积层可以表示为:

    X_{q,uv}^{l+1} =\sigma (\sum_{p=1}^{I}{x_{pq,uv}^{l} }\circ (X_{p}^{l} ) _{uv} +x_{q}^{l} )
     

    图3-2 深度神经网络

    最终,经过训练的深度神经网络生成的canonical view人脸图像如图3-3所示。

     

    图3-3 canonical view人脸图像

     

    4,DeepID(0.9745 )

    参考文献:DeepID3: Face Recognition with Very Deep Neural Networks


    4.1 简介

    深度学习在人脸识别领域的应用提高了人脸识别准确率。本文中,我们使用了两种深度神经网络框架(VGG net 和GoogleLeNet)来进行人脸识别。两种框架ensemble结果在LFW数据集上可以达到0.9745的准确率。文章获得高准确率主要归功于大量的训练数据,文章的亮点仅在于测试了两种深度卷积神经网络框架。

    4.2 深度神经网络框架


    图4-1 两种深度卷积神经网络框架


    5,FaceNet(0.9963)
    参考文献:FaceNet: A Unified Embedding for Face Recognition and Clustering

    5.1 简介


    作者开发了一个新的人脸识别系统:FaceNet,可以直接将人脸图像映射到欧几里得空间,空间的距离代表了人脸图像的相似性。只要该映射空间生成,人脸识别,验证和聚类等任务就可以轻松完成。该方法是基于深度卷积神经网络,在LFW数据集上,准确率为0.9963,在YouTube Faces DB数据集上,准确率为0.9512。FaceNet的核心是百万级的训练数据以及 triplet loss。

    5.2 triplet loss
    triplet loss是文章的核心,模型将图像x embedding入d-维的欧几里得空间f(x)\in R^{d}。我们希望保证某个个体的图像x_{i}^{a} (anchor) 和该个体的其它图像x_{i}^{p} (positive) 距离近,与其它个体的图像x_{i}^{n} (negtive) 距离远。如图5-1所示:

    图5-1 triplet loss示意图
     

     


    triplets 的选择对模型的收敛非常重要。如公式1所示,对于x_{i}^{a},我们我们需要选择不同个体的图片x_{i}^{p},使argmax_{x_{i}^{p} } \left| \left| f(x_{i}^{a} )-f(x_{i}^{p} )\right|  \right| _{2}^{2};同时,还需要选择同一个体不同图片x_{i}^{n},使得argmin_{x_{i}^{n} } \left| \left| f(x_{i}^{a} )-f(x_{i}^{n} )\right|  \right| _{2}^{2}

    5.3 深度卷积神经网络
    采用adagrad优化器,使用随机梯度下降法训练CNN模型。在cpu集群上训练了1000-2000小时。边界值\alpha设定为0.2。总共实验了两类模型,参数如表5-1和表5-2所示。
    表5-1 CNN模型1


    表5-2 CNN模型2



    6,baidu的方法
    参考文献:Targeting Ultimate Accuracy : Face Recognition via Deep Embedding

    6.1 简介
    本文中,作者提出了一种两步学习方法,结合mutil-patch deep CNN和deep metric learning,实现脸部特征提取和识别。通过1.2million(18000个个体)的训练集训练,该方法在LFW数据集上取得了0.9977的成绩。

    6.2 multi-patch deep CNN
    人脸不同区域通过深度卷积神经网络分别进行特征提取。如图6-1所示。


    图6-1 multi-patch示意图
    6.3 deep metric learning
    深度卷积神经网络提取的特征再经过metric learning将维度降低到128维度,如图7-2所示。


    图6-2 metric learning示意图

    7,pose+shape+expression augmentation(0.9807)
    参考文章:Do We Really Need to Collect Millions of Faces for Effective Face Recognition

    7.1 简介
    该文章的主要思路是对数据集进行扩增(data augmentation)。CNN深度学习模型,比如face++,DeepID,FaceNet等需要基于百万级人脸图像的训练才能达到高精度。而搜集百万级人脸数据所耗费的人力,物力,财力是很大的,所以商业公司使用的图像数据库是不公开的。
    本文中,采用了新的人脸数据扩增方法。对现有公共数据库人脸图像,从pose,shape和expression三个方面合成新的人脸图像,极大的扩增数据量。在LFW和IJB-A数据集上取得了和百万级人脸数据训练一样好的结果。该文章的思路很好,很适合普通研究者。

    7.2 pose+shape+expression扩增方法
    一,pose(姿态,文章中为人脸角度,即通过3d人脸模型数据库合成图像看不见的角度,生成新的角度的人脸)。首先,通过人脸特征点检测(facial landmark detector),获取人脸特征点。根据人脸特征点和开放的Basel 3D face set数据库的人脸模板合成3d人脸。如图7-1所示。

    图7-1 pose(角度)生成示意图

    二,shape(脸型)。首先,通过Basel 3D face获取10种高质量3d面部扫描数据。再将图像数据与不同3d脸型数据结合,生成同一个人不同脸型的图像。如图7-2所示:

    图7-2 不同脸型生成示意图

    三,expression(表情,本文中,将图像的张嘴表情替换为闭口表情)。采用中性嘴型将图像中的开口表情换位闭口表情。如图7--3所示。

    图7-3 不同表情(开口/闭口)生成示意图

    7.3 模型及训练方法
    文章模型采用的ILSVRC数据集上预训练的VGG-19模型。训练方法是常规梯度下降训练方法。值得提出的地方是,该文章对测试集也进行了augmentation。

    8, CNN-3DMM estimation(0.9235)
    参考文献:

    1,Regressing Robust and Discriminative 3D Morphable Models with a very Deep Neural Network(很优秀的工作,强烈推荐阅读原文)

    2,中文解析:使用深度卷积神经网络方法进行3D人脸重建

    8.1 简介
    当在真实场景中应用3d模拟来增加人脸识别精度,存在两类问题:要么3d模拟不稳定,导致同一个个体的3d模拟差异较大;要么过于泛化,导致大部分合成的图片都类似。因此,作者研究了一种鲁棒的三维可变人脸模型(3D morphable face models (3DMM))生成方法。他们采用了卷积神经网络(CNN)来根据输入照片来调节三维人脸模型的脸型和纹理参数。该方法可以用来生成大量的标记样本。该方法在MICC数据集上进行了测试,精确度为state of the art 。与3d-3d人脸比对流程相结合,作者在LFW,YTF和IJB-A数据集上与当前最好成绩持平。文章的关键点有两个:一,3D重建模型训练数据获取;二,3D重建模型训练 。

    8.2 训练数据
    作者采用了近期发表的多图像3DMM生成方法(M.Piotraschke 2016)。他们在CASIA WebFace数据集上采用该方法生成3DMM。这些3d人脸模型用于训练CNN的gound truth。多图像3DMM重建包括两步:一,从CASIA数据集选取500K当个图像来估计3DMM参数。二,同一个体不同照片生成的3DMM聚合一起,获取单个个体的3DMM(约10K个体)。

    8.2.1 Single image 3DMM fitting
    采用两种不同的方法来对每一个训练图片配对上3DMM。对于图像I,我们估计\alpha ^{*}\beta  ^{*}来表示与输入图像I类似的图像。采用了目前最好的人脸特征点检测器(CLNF)来检测K=68个人脸特征点P_{k} \in R^{2} ,k\in 1..K和置信值w。其中,脸部特征点用于在3DMM坐标系中初始化输入人脸的角度。角度表达为6个自由度:角度r=\left[ r _{\alpha } ,r_{\beta a},r_{\gamma a}  \right] 和平移t=\left[t_{X} ,t_{Y} ,t_{Z}  \right]。然后再对脸型,纹理,角度,光照和色彩进行处理。

    8.2.2 Multi image 3DMM fitting
    多图像3DMM生成通过pool 单个个体不同图片生成的3DMM的脸型和纹理参数来实现。
    \bar{\gamma } =\sum_{i=1}^{N}{w_{i} \cdot \gamma _{i} }其中\sum_{i=1}^{N}{w_{i}=1 }w_{i}为CLNF脸部特征检测生成的置信值。

    8.3 3D重建模型训练
    对于数据集中每一个个体,有多张图片以及单个pool的3DMM。我们将该数据用于训练模型,使模型可以根据同一个体不同的图片来生成类似的3DMM特征向量。
    如图8-1所示,我们采用了101层的deep ResNet网络来进行人脸识别。神经网络的输出层为198维度的3DMM特征向量\gamma。然后,使用CASIA 图像生成的pooled 3DMM作为目标值对神经网络进行fine-tuned。我们也尝试了使用VGG-16结构,结果比ResNet结构稍微差一点。

    图8-1 3D重建训练示意图

    8.3.1 The asymmetric Euclidean loss
    我们在实验中发现,使用Euclidean loss会导致输出3d人脸缺少细节,如图8-2所示。因此,我们引入了asymmetric Euclidean loss。

    其中,\gamma为目标pooled 3DMM值,\gamma _{p}为输入,\lambda _{1,2}为平衡over和under estimation errors的值。在实际操作中,我们设定\lambda _{1} =1,\lambda _{2} =3,来鼓励模型学习更多的细节。


    图8-2 不同loss函数对结果的影响

    8.4 实验结果
    8.4.1 3D重建结果
    MICC数据集包含53个个体的人脸视频和个体的3D模型作为gound truth。这些视频可以用于单张图片和多张图片的3D重建。实验结果如表8-1所示,该重建方法比当前的方法都要好。
    表8-1 3D重建实验结果

    8.4.2 人脸识别
    我们研究了同一人不同的照片重建的3DMM是否比不同人的照片重建的3DMM差异更小。我们在LFW,YTF和IJB-A数据集上测试了我们的方法。结果如表8-2和图8-3所示。
    表8-2 LFW和YTF测试结果


    图8-3 LFW,YTF和IJB-A测试结果

    8.4.3 定性结果
    图8-4展示了训练模型生成的3DMM结果。

    图8-4 3DMM生成模型结果


    总结
    本文综述了8种基于深度学习的人脸识别方法,包括:1,face++(0.9950 );2,DeepFace(0.9735 );3,FR+FCN(0.9645 );4,DeepID(0.9745 );5,FaceNet(0.9963 );6, baidu的方法(0.9977 );7,pose+shape+expression augmentation(0.9807);8,CNN-3DMM estimation(0.9235 )。上述方法可以分为两大类:
    第一类:face++,DeepFace,DeepID,FaceNet和baidu。他们方法的核心是搜集大数据,通过更多更全的数据集让模型学会去识别人脸的多样性。这类方法适合百度/腾讯/谷歌等大企业,未来可以搜集更多更全的训练数据集。数据集包扩同一个体不同年龄段的照片,不同人种的照片,不同类型(美丑等)。通过更全面的数据,提高模型对现场应用中人脸差异的适应能力。
    第二类:FR+FCN,pose+shape+expression augmentation和CNN-3DMM estimation。这类方法采用的是合成的思路,通过3D模型等合成不同类型的人脸,增加数据集。这类方法操作成本更低,更适合推广。其中,特别是CNN-3DMM estimation,作者做了非常出色的工作,同时提供了源码,可以进一步参考和深度研究。

    上述方法在理想条件下的人脸识别精确度已经达到或者超越人类的表现。但是,由于光线,角度,表情,年龄等多种因素,导致人脸识别技术无法在现实生活中广泛应用。未来研究中,不管哪种思路,均是提高模型对现场复杂环境的适应能力,在复杂环境中,也能达到人类识别的精确度。

    人脸识别技术是计算机视觉和深度学习领域中相对成熟的技术,很期待该技术的广泛应用。

    展开全文
  • 人脸识别各算法详解

    万次阅读 多人点赞 2020-10-12 18:22:54
    人脸识别各算法详解 最近,由于工作需要,为了找到一款高效的认识识别算法,对各种人脸识别算法都研究了一番,以下记录的是各算法的理论基础。 一.MTCNN 本文章主要介绍MTCNN算法的流程,MTCNN主要由三个框架组成...
  • 人脸识别学习总结

    千次阅读 2019-07-07 17:24:36
    人脸识别算法进行了一定程度的学习,从最开始的特征脸到如今的CNN人脸检测,有了较为全面的了解。重点掌握了基于PCA的特征脸检测,LDA线性判别分析(Fisher线性判别),以及基于级联器的Haar特征,LBP特征的人脸...
  • 来源 | 清华大学AMiner平台(公众号ID:SciTouTiao)看点:全面解析人脸识别技术原理、领域人才情况、技术应用领域和发展趋势。自20世纪下半叶,计算机视觉技术逐渐地发展壮大。同时,伴随着数字图像相关的软硬件...
  • 人脸识别之特征脸方法(Eigenface)

    万次阅读 多人点赞 2015-04-25 22:16:27
    人脸识别之特征脸方法(Eigenface)zouxy09@qq.comhttp://blog.csdn.net/zouxy09 因为需要,花了一点时间写了下经典的基于特征脸(EigenFace)的人脸识别方法的Matlab代码。这里仅把该代码分享出来。其实,在较新...
  • 人脸识别经典算法一:特征脸方法(Eigenface)

    万次阅读 多人点赞 2014-04-06 00:07:53
    这篇文章是撸主要介绍人脸识别经典方法的第一篇,后续会有其他方法更新。特征脸方法基本是将人脸识别推向真正可用的第一种方法,了解一下还是很有必要的。
  • opencv(人脸检测和识别

    万次阅读 多人点赞 2018-11-05 13:20:08
    Opencv的人脸检测函数,定义了具体可跟踪对象类型的数据文件。 Haar级联分类器,通过对比分析相邻图像区域来判断给定图像或子图像与已知对象是否匹配。 两个图像的相似程度可以通过它们对应特征的欧式距离来...
  • 人脸识别原理

    千次阅读 2019-09-20 17:10:21
    人脸识别主要分为人脸检测(face detection)、特征提取(feature extraction)和人脸识别(face recognition)三个过程。 人脸识别又可以分为两个大类:一类是确认,这是人脸图像与数据库中已存的该人图像比对的...
  • 海康威视人脸识别

    万次阅读 2018-08-23 16:41:15
    开始用海康威视的人脸识别产品做项目了, 都说海康的SDK内容很多,很乱,无从下手。 看了产品线确实很多,而且要用最新的型号开发,入门难。
  • 分享一个免费的人脸识别API、SDK~~

    万次阅读 2015-05-21 09:52:51
    EYEKEY是北京天诚盛业科技有限公司旗下的新型生物识别云服务平台,旨在...现在EyeKey生物识别云服务平台,已开放人脸识别API、SDK,感兴趣地盆友们,猛戳http://www.eyekey.com/devcenter/api/APIface.html了解详情吧~~
  • 一、开发环境介绍Visual Studio 2015 Qt 5.6.3 (该版本及以上版本都可以) dlib-19.7 opencv-2.4.13.3-vc14 mkl_2018.0.124 (编译dlib时需要) ... Visual studio+Qt的开发环境搭建参考:Visual Stud
  • 人脸识别的十个关键技术组成及原理

    万次阅读 多人点赞 2018-08-02 14:36:43
    人脸识别技术已成为纳入研发参考的、给人们带来高质量生活的又一科技解决途径。日常生活中,人脸识别的应用已经常见,那么你知道它是如何做到如此智能吗?下面,我们就带大家了解人脸识别涉及的十个关键技术。 1、...
  • 人脸识别的整个流程

    万次阅读 2016-01-27 22:07:05
    人脸识别系统的整个流程
  • 人脸识别和人脸检测的区别

    千次阅读 2017-05-02 17:13:44
    网上很多帖子、博客在谈人脸识别、人脸检测,其实很多都弄混了,现在来纠正一下。(观点只代表作者本人) 人脸识别是对已知人脸进行分类的过程。就像我们看到其他人一样能够分辨出谁是自己的朋友谁不是。人脸识别...
  • 人脸识别技术发展现状及未来趋势

    万次阅读 2019-02-20 10:05:40
    人脸识别技术商业化应用领域不断扩张的趋势下,"刷脸"办事正愈发常见。  人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术。用摄像机或摄像头采集含有人脸的图像或视频流,并自动在...
1 2 3 4 5 ... 20
收藏数 89,255
精华内容 35,702
关键字:

人脸识别