opencv+CNN实现人脸识别

机器学习 同时被 2 个专栏收录
23 篇文章 1 订阅
23 篇文章 1 订阅

在知乎上看到一个有趣的专栏,讲的是国外(日本?)一个牛人用OpenCV+CNN实现了一个人脸识别工具,觉得挺好玩的,所以fork下来自己也研究了一下,在这里做一个总结:

项目描述

总的来说,要实现最终的人脸识别功能,就要分别实现以下几个小目标:

  1. 通过笔记本自带的摄像头实现实时的人脸检测,这里用到了python下的openCV;
  2. 为了得到用于识别模型的输入,还需要从已有照片中提取出目标(比如说自己)的人脸,以及其他人的人脸。别忘了给这两类数据分别贴上标签;
  3. 实现对来自照片或摄像头帧的人脸的统一处理(大小、去色等),这样才能使得输入到模型中的输入标准化;
  4. 构造一个CNN 模型,以目标和其他人的人脸为输入,经过训练得到模型参数并保存,这里用到了python下的keras框架;
  5. 回到1,当通过笔记本摄像头实现了人脸检测后,将这个人脸输入到训练好的模型中,如果模型输出0,说明该人脸是属于目标的,即完成了人脸识别;

该项目是在Python下实现的,用到了openCV和keras。这里实在想吐槽一下,不晓得是不是ubuntu16.04的版本太高,导致和openCV不兼容,总之在ubuntu下面费了九牛二虎之力也不能正常使用openCV,摄像头打不开。至于keras和它所依托的theano倒是装得很顺利,

conda install xx
 
  • 1
  • 1

的安装方式还是很方便的。 
至于windows下的话,则是keras始终跑不了。。。前前后后也花了几个星期。。。 
最后是在ubuntu下训练和识别,摄像头帧在windows环境下先采好,算是勉强完成了目标。感慨就是环境的搭建总归是很蛋疼的。 
好在整个小项目还是弄懂了的,一共用到了5个模块,下面分别进行描述:

摄像头的人脸检测

人脸识别的前提是人脸检测,这里其实通过openCV调用笔记本摄像头,并且用其自带的人脸检测工具实现,尽管识别的成功率不是特别高,头转得超过某个角度就识别不出来了,不过比较正的人脸还是能成功识别的。

def camera_detector():
    global frame_num
    cap = cv.VideoCapture(0) #打开笔记本内置的摄像头

    while cap.isOpened():
        #time.sleep(1) #延迟x秒
        ret, frame = cap.read() #读取一帧,前一个返回值是是否成功,后一个返回值是图像本身
#        out.write(frame) #把每帧图片一帧帧地写入video中
        show_image = face_detector(frame, face_cascade) #show_image是返回的已标记出人脸的图片
        cv.imshow("monitor", show_image) #在窗口显示一帧

        key = cv.waitKey(40)
        if key == 27 or key == ord('q'): #如果按ESC或q键,退出
            break
        if key == ord('s'): #如果按s键,保存图片
            cv.imwrite("frame_%s.png" % frame_num,frame)
            frame_num = frame_num+1
#    out.release()
    cap.release()
    cv.destroyAllWindows()
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
# 检测出图片中的人脸,并用方框标记出来
def face_detector(image, cascade):
    global face_num #引用全局变量
    grayImage = cv.cvtColor(image, cv.COLOR_BGR2GRAY) #灰度化图片 
    equalImage = cv.equalizeHist(grayImage) #直方图均衡化
    faces = cascade.detectMultiScale(equalImage, scaleFactor=1.3, minNeighbors=3)

    for (x,y,w,h) in faces:
        #裁剪出人脸,单独保存成图片,注意这里的横坐标与纵坐标不知为啥颠倒了
        #cv.imwrite("face_%s.png" %(face_num), image[y:y+h,x:x+w])
        cv.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)
        face_num = face_num + 1
    return image
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

从照片中提取人脸作为模型输入

这一步其实和上面的差不多,只不过上面是实时地从摄像头的每一帧图像上提取人脸,这里是从事先准备好的照片上提取出人脸来。 
遍历路径path中的所有图片:

def get_my_face(path):
    for root, dirs, files in os.walk(path):
        for name in files:
            if name.endswith("jpg"):
                img = cv.imread(os.path.join(root, name)) #打开该照片
                print('now is %s' % name)
                face_detector(img, face_cascade)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

一样用到了face_detector(image, cascade)函数来检测人脸,不过这里略有不同,是直接把提取的人脸另存为图片。

def face_detector(image, cascade):
    global face_num
    global root_path
    grayImage = cv.cvtColor(image, cv.COLOR_BGR2GRAY) #灰度化图片 
    equalImage = cv.equalizeHist(grayImage) #直方图均衡化
    faces = cascade.detectMultiScale(equalImage, scaleFactor=1.3, minNeighbors=3)

    os.chdir(os.path.join(root_path,'training'))
    for (x,y,w,h) in faces:
        #裁剪出人脸,单独保存成图片,注意这里的横坐标与纵坐标不知为啥颠倒了
        cv.imwrite("face_%s.png"%(face_num), image[y:y+h,x:x+w])
        face_num = face_num + 1
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

标准化输入

无论是用来训练模型的照片人脸,还是用来最终识别的摄像头人脸,被提取出来后都要进行统一的处理:

import os
import numpy as np
import cv2

IMAGE_SIZE = 64
images = []
labels = []

#==============================================================================
#将image统一补全并缩放成相同的大小
def resize_with_pad(image, height=IMAGE_SIZE, width=IMAGE_SIZE):

    #计算后续填充图片时的上、下、左、右的像素点
    def get_padding_size(image):
        h, w, _ = image.shape
        longest_edge = max(h, w)
        top, bottom, left, right = (0, 0, 0, 0)
        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
        return top, bottom, left, right

    top, bottom, left, right = get_padding_size(image)
    BLACK = [0, 0, 0]
    #填充图片
    constant = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value=BLACK)
    #缩放图片以统一输入
    resized_image = cv2.resize(constant, (height, width))
    return resized_image
#==============================================================================

#==============================================================================
#读取图片,并返回大小统一的图片
def read_image(file_path):
    image = cv2.imread(file_path)
    grayImage = cv.cvtColor(image, cv.COLOR_BGR2GRAY) #灰度化图片 
    image = cv.equalizeHist(grayImage) #直方图均衡化
    image = resize_with_pad(image, IMAGE_SIZE, IMAGE_SIZE)

    return image
#==============================================================================

#==============================================================================
#遍历path,返回它下面的所有PNG图片及其所在路径
def traverse_dir(path):
    for file_or_dir in os.listdir(path):
        abs_path = os.path.abspath(os.path.join(path, file_or_dir))
        #print(abs_path)
        if os.path.isdir(abs_path):  # 遍历子目录
            traverse_dir(abs_path)
        else:                        # file
            if file_or_dir.endswith('.png'):
                image = read_image(abs_path)
                images.append(image)
                labels.append(path)

    return images, labels
#==============================================================================

#==============================================================================
#给获得的图片贴上标签
def extract_data(path):
    images, labels = traverse_dir(path)
    images = np.array(images)
    labels = np.array([0 if label.endswith('me') else 1 for label in labels])

    return images, labels

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

注意到最后的extract_data(path)函数通过训练数据所在的路径给输入贴上不同的标签。目标的所有人脸放在文件夹1里,其他人的人脸放在文件夹2里。目标人脸赋予标签0,其他人的人脸赋予标签1。

训练模型得到参数

这一步是用keras构建CNN模型,并且训练它,得到用于后续人脸识别的模型参数。keras是一个python下很流行的深度学习框架,它封装得很好,所以只是使用一下的话,就会感到很方便。 
CNN 即是针对图像输入的一种卷积神经网络,它的实现原理可以看一下这里,还是挺神奇的,虽然和其他绝大多数机器/深度学习的方法一样,很难通过数学方法证明其有效性。不过我们这里只是用一下。。。

最终,得到并保存模型参数为model.h5

最终实现人脸识别

其实这一步又是在最初人脸检测的基础上实现的,将人脸检测的结果:人脸进行标准化处理,输入到模型中,输出一个预测的标签。如果标签为0,那么就是成功识别了,反之就没有识别到目标物体。

import cv2 as cv
from CNN_train import Model
#from image_show import show_image
face_cascade = cv.CascadeClassifier('./data/haarcascade_frontalface_alt.xml')

if __name__ == '__main__':
    cap = cv.VideoCapture(0)
    model = Model()
    model.load()
    while cap.isOpened():
        _, image = cap.read()
        grayImage = cv.cvtColor(image, cv.COLOR_BGR2GRAY) #灰度化图片 
        equalImage = cv.equalizeHist(grayImage) #直方图均衡化
        faces = face_cascade.detectMultiScale(equalImage, scaleFactor=1.3, minNeighbors=3)
        if len(faces) > 0:
            print('face detected')
            color = (255, 255, 255)  # 白
            for (x,y,w,h) in faces:
                #裁剪出人脸,单独保存成图片
                head = image[y-10:y+h,x:x+w]

                result = model.predict(head)
                if result == 0:  # boss
                    print('Successfully!')
                    #show_image()
                else:
                    print('......')

        key = cv.waitKey(40)
        if key == 27 or key == ord('q'): #如果按ESC或q键,退出
            break

    cap.release()
    cv.destroyAllWindows()

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

总结

复现这个人脸识别器的原因很简单,就是觉得挺好玩的。而且之前总是觉得人脸识别很神秘,现在基本弄懂了这个实现方法之后(虽然keras内部实现完全没懂),就对深度学习和它在生活中的应用有了更深的理解了,总之还是很不错的。 
最后,虽然基本没有改动,但是我把我做了注释的版本放在了GitHub上。

  • 5
    点赞
  • 14
    评论
  • 37
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值