AI时代的到来,手机上的APP开始应用人脸识别去完成事情,如iphoneX的人脸解锁,百度自动贩卖机的人脸识别系统进行支付,支付宝的人脸识别登录等,提高了使用软件的易用性,但也因为其便利性,在某些市面上的应用已经发生了非本人(活人)通过图片可通过人脸识别,想想这样自己在应用上存储的信息和钱财不能得到保障,是一件很可怕的事情。因为这样,更应该去了解人脸识别的理论及实现,从而更好的去防范这种行为的发生。话不多说,把实现方法和在实现过程中遇到的问题和大家讨论讨论~

  一、环境准备

    MacOs+PyCharm+Python+OpenCV

    1.Mac下PyCharm下载、安装、激活

      参考:http://blog.csdn.net/qq_35246620/article/details/78254527?utm_source=gold_browser_extension

    2.Python安装

      Mac系统下本身已自带python2.7,所以这一步可以省去。要查看版本的话,终端使用如下命令查看:    

which python
python

      

    3.OpenCV安装

      在实现人脸识别的代码中,我们需要用到的依赖库有:

        Pillow 5.0.0
        Pillow-PIL 0.1dev
        numpy 1.14.1
        opencv-python 3.3.0.10

      在本机环境安装并配置以上库步骤偏复杂,所以直接在PyCharm中该工程中设置虚拟环境,然后再在虚拟环境中下载安装。虚拟环境与本机进行隔离,在不同的项目中使用到的第三方库版本可切换。下面是虚拟环境的创建和安装依赖库步骤:

      因为OpenCV是安装到虚拟环境中,而我们后面需要的xml文件是在OpenCV/data/haarcascades/下,所以需要到OpenCV官网下载对应安装包

https://opencv.org/opencv-3-3.html 解压缩后,我们就看到了xml文件。

 

  二、代码实现   

  1 # !/usr/bin/env python
  2 # coding=utf-8
  3 import os
  4 import numpy
  5 from PIL import Image, ImageDraw
  6 import cv2
  7 #created by chenfenyu 2018.3.20
  8 
  9 
 10 cap = cv2.VideoCapture(0)
 11 #获取外接摄像头
 12 eye = cv2.imread("/Users/funny/Downloads/img/eye.png")
 13 #读取眼睛区域替换的图片
 14 mouth = cv2.imread("/Users/funny/Downloads/img/mouth.png")
 15 #读取嘴巴区域替换的图片
 16 size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) + 0.5), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) + 0.5))
 17 #获取摄像头返回的宽和高
 18 fourcc = cv2.VideoWriter_fourcc(*'mp4v')
 19 #确定保存视频的格式
 20 video = cv2.VideoWriter("/Users/funny/Downloads/a.avi", fourcc, 5, size)
 21 ''' cv.VideoWriter参数(视频存放路径,视频存放格式,fps帧率,视频宽高)
 22     注意点1:OpenCV只支持avi的格式,而且生成的视频文件不能大于2GB,而且不能添加音频
 23     注意点2:若填写的文件名已存在,则该视频不会录制成功,但可正常使用
 24 '''
 25 print cap.isOpened()
 26 #检测是否摄像头正常打开:成功打开时,isOpened返回ture
 27 classifier_face = cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_frontalface_alt.xml")
 28 #定义分类器(人脸识别)
 29 classifier_eye = cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_eye.xml")
 30 #定义分类器(人眼识别)
 31 classifier_mouth=cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_mcs_mouth.xml")
 32 #定义分类器(嘴巴识别)
 33 while (True):
 34     #取得cap.isOpened()返回状态为True,即检测到人脸
 35     #img = cv2.imread("/Users/funny/Downloads/img/pp.png")
 36     ret, img = cap.read()
 37     '''第一个参数ret的值为True或False,代表有没有读到图片
 38        第二个参数是frame,是当前截取一帧的图片
 39     '''
 40     faceRects_face = classifier_face.detectMultiScale(img, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20))
 41     #检测器:detectMultiScale参数(图像,每次缩小图像的比例,匹配成功所需要的周围矩形框的数目,检测的类型,匹配物体的大小范围)
 42     key = cv2.waitKey(1)
 43     #键盘等待
 44     if len(faceRects_face) > 0:
 45         #检测到人脸
 46         for faceRect_face in faceRects_face:
 47             x, y, w, h = faceRect_face
 48             #获取图像x起点,y起点,宽,高
 49             h1=int(float(h/1.5))
 50             #截取人脸区域高度的一半位置,以精确识别眼睛的位置
 51             intx=int(x)
 52             inty=int(y)
 53             intw=int(w)
 54             inth=int(h)
 55             #转换类型为int,方便之后图像截取
 56             my = int(float(y + 0.7 * h))
 57             #截取人脸区域下半部分左上角的y起点,以精确识别嘴巴的位置
 58             mh = int(0.4 * h)
 59             #截取人脸区域下半部分高度,以精确识别嘴巴的位置
 60             img_facehalf = img[inty:(inty+h1), intx:intx+intw]
 61             img_facehalf_bottom = img[my:(my + mh), intx:intx + intw]
 62             '''img获取坐标为,【y,y+h之间(竖):x,x+w之间(横)范围内的数组】
 63                img_facehalf是截取人脸识别到区域上半部分
 64                img_facehalf_bottom是截取人脸识别到区域下半部分
 65             '''
 66             cv2.rectangle(img, (int(x), my), (int(x) + int(w), my + mh), (0, 255, 0), 2, 0)
 67             '''矩形画出区域 rectangle参数(图像,左顶点坐标(x,y),右下顶点坐标(x+w,y+h),线条颜色,线条粗细)
 68                 画出人脸识别下部分区域,方便定位
 69             '''
 70             faceRects_mouth = classifier_mouth.detectMultiScale(img_facehalf_bottom, 1.1, 1, cv2.CASCADE_SCALE_IMAGE, (5, 20))
 71             #嘴巴检测器
 72             if len(faceRects_mouth) > 0:
 73                 for faceRect_mouth in faceRects_mouth:
 74                     xm1, ym1, wm1, hm2 = faceRect_mouth
 75                     cv2.rectangle(img_facehalf_bottom, (int(xm1), int(ym1)), (int(xm1) + int(wm1), int(ym1) + int(hm2)), (0,0, 255), 2, 0)
 76                     img_mx = cv2.resize(mouth, (wm1, hm2), interpolation=cv2.INTER_CUBIC)
 77                     #调整覆盖图片大小 resize参数(图像,检测到的(宽,高),缩放类型)
 78                     if key == ord('z'):
 79                     #检测当键盘输入z时,开始替换图片
 80                         img[my+ym1:(my+ym1+hm2), intx+xm1:(intx + xm1+wm1)] = img_mx
 81                         #将调整大小后的图片赋值给img
 82             cv2.rectangle(img, (int(x), int(y)), (int(x) + int(w), int(y) + int(h1)), (0, 255, 0), 2, 0)
 83             # 画出人脸识别上部分区域,方便定位
 84             faceRects_eye = classifier_eye.detectMultiScale(img_facehalf, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20))
 85             #检测器识别眼睛
 86             if len(faceRects_eye) > 0:
 87                 #检测到眼睛后循环
 88                 eye_tag = []
 89                 #定义一个列表存放两只眼睛坐标
 90                 for faceRect_eye in faceRects_eye:
 91                     x1, y1, w1, h2 = faceRect_eye
 92                     cv2.rectangle(img_facehalf, (int(x1), int(y1)), (int(x1) + int(w1), int(y1) + int(h2)), (0, 255, 0), 2, 0)
 93                     #画出眼睛区域
 94                     a = ((inty+y1),(inty+y1 + h2), (intx+x1),(intx+x1 + w1))
 95                     #定义a变量获取眼睛坐标,现在img顶点位置已经改变,需要加上intx和inty的值才可以
 96                     eye_tag.append(a)
 97                     #通过append存入数组a中
 98                 n_eyetag = numpy.array(eye_tag)
 99                 #存放为ndarray数组类型,输入内容为[[x1 y1 x1+w y1+h][x1 y1 x1+w y1+h]...],后面会获取多维数组的下标来替换数值
100 
101                 if len(faceRects_eye)==2:
102                     #眼睛识别到两个时,同时替换图片
103                     img_ex=cv2.resize(eye,(n_eyetag[0,1]-n_eyetag[0,0], n_eyetag[0,3]-n_eyetag[0,2]),interpolation=cv2.INTER_CUBIC)
104                     img_ex1 = cv2.resize(eye, (n_eyetag[1, 1] - n_eyetag[1, 0], n_eyetag[1, 3] - n_eyetag[1, 2]), interpolation=cv2.INTER_CUBIC)
105                     if key == ord('p'):
106                     #检测到键盘输入p时,进行替换
107                         img[n_eyetag[0,0]:n_eyetag[0,1],n_eyetag[0,2]:n_eyetag[0,3]]=img_ex
108                         img[n_eyetag[1, 0]:n_eyetag[1, 1], n_eyetag[1, 2]:n_eyetag[1, 3]] = img_ex1
109                 if len(faceRects_eye)==1:
110                     # 眼睛识别到一个时,替换图片
111                     img_ex = cv2.resize(eye, (n_eyetag[0, 1] - n_eyetag[0, 0], n_eyetag[0, 3] - n_eyetag[0, 2]), interpolation=cv2.INTER_CUBIC)
112                     if key == ord('p'):
113                         img[n_eyetag[0, 0]:n_eyetag[0, 1], n_eyetag[0, 2]:n_eyetag[0, 3]] = img_ex
114 
115             video.write(img)
116     cv2.imshow('video', img)
117     #显示图片,标题名字为video
118     cv2.resizeWindow('video',1280,720)
119     #调整窗口大小video为1280*720
120     if key == ord('q'):
121     #检测到键盘输入q,退出循环
122         break
123 
124 video.release()
125 #不再录制视频
126 cap.release()
127 #释放摄像头
128 cv2.destroyAllWindows()
129 #关闭所有窗口显示

    代码中都有注释,基本看懂第一个循环输出人脸区域检测的内容就足够了,后面只是在原来的基础上进行多个区域的循环和判断。接下来我来讲一下这个代码的实现思路,画成图解释一下:

      

   

 

    1.打开摄像头的时候,开始检测人脸的区域。

    2.将获取到的区域上、下半部分图像存储下来。

    3.检测上半部分图像区域中双眼的位置。

    4.检测下半部分图像区域中嘴巴的位置。

    5.模拟人脸识别所进行的操作,如眨眼,摇头,慢慢张开嘴巴,这里我们直接使用键盘‘p’来替换眼睛的位置,‘z’替换嘴巴的位置。

    6.关闭摄像头。

    说到这里,大家可能有点疑问为什么要划出上下部分区域来识别眼睛和鼻子,原因是识别器的准确度不是很高,若检测眼睛,鼻子区域也会识别成眼睛,所以才需要划分范围,请看下图:

     下面我来跟大家讲一下IMG取某区域内图像的坐标以及cv2.rectangle的定位坐标。

      1)cv2.rectangle,我们从上面的注释中知道,获取的是左上角和右下角的顶点坐标,用画图的方式表达如下:

      2)IMG区域取值范围则和1)不同,IMG[左上顶点y:左下顶点y,左上顶点x:右上顶点x],用图来表示S◇acef区域如下:

         所以,之后截取后记得以最原始的IMG坐标为顶点,再来表示要截取的区域,不然就会报错。

  三、Q&A

    1、Q:2018-03-20 11:17:10.309 python[79368:108479406] mMovieWriter.status: 3. Error: Cannot Save

       A:存放信息目录下已有同名文件,只需要删除文件即可,不影响程序正常使用

    2、Q:网络上下载的opencv 识别器xml文件,运行工程时提示

OpenCV Error: Unknown error code -49 (Input file is empty) in cvOpenFileStorage, file /Users/travis/build/skvark/opencv-python/opencv/
modules/core/src/persistence.cpp, line 4484 Traceback (most recent call last): File "/Users/funny/PycharmProjects/untitled1/open-cv.py", line 19, in <module> classifier_mouth=cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_mcs_mouth1.xml") cv2.error: /Users/travis/build/skvark/opencv-python/opencv/modules/core/src/persistence.cpp:4484: error: (-49) Input file is empty in
function cvOpenFileStorage

      A:一般xml文件不会超过1m我下载的这个文件超过了6m,如何检测是否是可用文件?在存放xml文件的目录下,单击后在预览区能显示内容则是正确的,请看下图:

  

        不能显示内容则是错误不可使用的文件:

      

     3、Q:报错信息:无法覆盖图像

  File "/Users/funny/PycharmProjects/untitled1/open-cv.py", line 52, in <module>
    img[ym1:(my+ym1+hm2), intx+xm1:(intx + xm1+wm1)] = img_mx
ValueError: could not broadcast input array from shape (86,143,3) into shape (662,143,3)

      A:计算图像的位置不正确,已经超出了范围,建议参考二代码中讲解图像IMG显示的区域坐标。 

  四、程序已知的问题

    1、暂时定位嘴巴和眼睛位置是以人脸分开两个区域来缩小搜索检测范围,所以抬起头或多人时,仍会检测到别的地方,这是由于OpenCV内的Haar特征分类器训练模型还是不够,所以检测不够精准。程序实现的是实时检测摄像头内图像,换成识别静态图片则准确多了。

    2、只要检测到人脸,就会一直循环检测,所以当人物晃动的时候,能检测到多个眼睛和嘴巴,而且检测框不断变换大小。

    3、程序暂时只实现了单眼、双眼同时替换,嘴巴替换。当摄像头检测到多个眼睛,只能同时替换至多两个眼睛。

    4、替换的图片在肉眼看来很假,瞳孔颜色,眼睛大小都还不够精准,只是用替换图替换。

  五、参考资料

    1、OpenCV教程

    2、Python入门教程

    3、人工智能书籍