【OpenCV-Python:实现人脸、人眼、嘴巴识别】实战

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

image.png
3.OpenCV安装
在实现人脸识别的代码中,我们需要用到的依赖库有:
        Pillow 5.0.0
        Pillow-PIL 0.1dev
        numpy 1.14.1
        opencv-python 3.3.0.10
在本机环境安装并配置以上库步骤偏复杂,所以直接在PyCharm中该工程中设置虚拟环境,然后再在虚拟环境中下载安装。虚拟环境与本机进行隔离,在不同的项目中使用到的第三方库版本可切换。下面是虚拟环境的创建和安装依赖库步骤:
image.png
image.png
image.png
因为OpenCV是安装到虚拟环境中,而我们后面需要的xml文件是在OpenCV/data/haarcascades/下,所以需要到OpenCV官网下载对应安装包
https://opencv.org/opencv-3-3.html ,解压缩后,我们就看到了xml文件。
image.png
二、代码实现

# !/usr/bin/env python
# coding=utf-8
import os
import numpy
from PIL import Image, ImageDraw
import cv2
#created by chenfenyu 2018.3.20


cap = cv2.VideoCapture(0)
#获取外接摄像头
eye = cv2.imread("/Users/funny/Downloads/img/eye.png")
#读取眼睛区域替换的图片
mouth = cv2.imread("/Users/funny/Downloads/img/mouth.png")
#读取嘴巴区域替换的图片
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) + 0.5), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) + 0.5))
#获取摄像头返回的宽和高
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
#确定保存视频的格式
video = cv2.VideoWriter("/Users/funny/Downloads/a.avi", fourcc, 5, size)
''' cv.VideoWriter参数(视频存放路径,视频存放格式,fps帧率,视频宽高)
    注意点1:OpenCV只支持avi的格式,而且生成的视频文件不能大于2GB,而且不能添加音频
    注意点2:若填写的文件名已存在,则该视频不会录制成功,但可正常使用
'''
print cap.isOpened()
#检测是否摄像头正常打开:成功打开时,isOpened返回ture
classifier_face = cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_frontalface_alt.xml")
#定义分类器(人脸识别)
classifier_eye = cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_eye.xml")
#定义分类器(人眼识别)
classifier_mouth=cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_mcs_mouth.xml")
#定义分类器(嘴巴识别)
while (True):
    #取得cap.isOpened()返回状态为True,即检测到人脸
    #img = cv2.imread("/Users/funny/Downloads/img/pp.png")
    ret, img = cap.read()
    '''第一个参数ret的值为True或False,代表有没有读到图片
       第二个参数是frame,是当前截取一帧的图片
    '''
    faceRects_face = classifier_face.detectMultiScale(img, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20))
    #检测器:detectMultiScale参数(图像,每次缩小图像的比例,匹配成功所需要的周围矩形框的数目,检测的类型,匹配物体的大小范围)
    key = cv2.waitKey(1)
    #键盘等待
    if len(faceRects_face) > 0:
        #检测到人脸
        for faceRect_face in faceRects_face:
            x, y, w, h = faceRect_face
            #获取图像x起点,y起点,宽,高
            h1=int(float(h/1.5))
            #截取人脸区域高度的一半位置,以精确识别眼睛的位置
            intx=int(x)
            inty=int(y)
            intw=int(w)
            inth=int(h)
            #转换类型为int,方便之后图像截取
            my = int(float(y + 0.7 * h))
            #截取人脸区域下半部分左上角的y起点,以精确识别嘴巴的位置
            mh = int(0.4 * h)
            #截取人脸区域下半部分高度,以精确识别嘴巴的位置
            img_facehalf = img[inty:(inty+h1), intx:intx+intw]
            img_facehalf_bottom = img[my:(my + mh), intx:intx + intw]
            '''img获取坐标为,【y,y+h之间(竖):x,x+w之间(横)范围内的数组】
               img_facehalf是截取人脸识别到区域上半部分
               img_facehalf_bottom是截取人脸识别到区域下半部分
            '''
            cv2.rectangle(img, (int(x), my), (int(x) + int(w), my + mh), (0, 255, 0), 2, 0)
            '''矩形画出区域 rectangle参数(图像,左顶点坐标(x,y),右下顶点坐标(x+w,y+h),线条颜色,线条粗细)
                画出人脸识别下部分区域,方便定位
            '''
            faceRects_mouth = classifier_mouth.detectMultiScale(img_facehalf_bottom, 1.1, 1, cv2.CASCADE_SCALE_IMAGE, (5, 20))
            #嘴巴检测器
            if len(faceRects_mouth) > 0:
                for faceRect_mouth in faceRects_mouth:
                    xm1, ym1, wm1, hm2 = faceRect_mouth
                    cv2.rectangle(img_facehalf_bottom, (int(xm1), int(ym1)), (int(xm1) + int(wm1), int(ym1) + int(hm2)), (0,0, 255), 2, 0)
                    img_mx = cv2.resize(mouth, (wm1, hm2), interpolation=cv2.INTER_CUBIC)
                    #调整覆盖图片大小 resize参数(图像,检测到的(宽,高),缩放类型)
                    if key == ord('z'):
                    #检测当键盘输入z时,开始替换图片
                        img[my+ym1:(my+ym1+hm2), intx+xm1:(intx + xm1+wm1)] = img_mx
                        #将调整大小后的图片赋值给img
            cv2.rectangle(img, (int(x), int(y)), (int(x) + int(w), int(y) + int(h1)), (0, 255, 0), 2, 0)
            # 画出人脸识别上部分区域,方便定位
            faceRects_eye = classifier_eye.detectMultiScale(img_facehalf, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20))
            #检测器识别眼睛
            if len(faceRects_eye) > 0:
                #检测到眼睛后循环
                eye_tag = []
                #定义一个列表存放两只眼睛坐标
                for faceRect_eye in faceRects_eye:
                    x1, y1, w1, h2 = faceRect_eye
                    cv2.rectangle(img_facehalf, (int(x1), int(y1)), (int(x1) + int(w1), int(y1) + int(h2)), (0, 255, 0), 2, 0)
                    #画出眼睛区域
                    a = ((inty+y1),(inty+y1 + h2), (intx+x1),(intx+x1 + w1))
                    #定义a变量获取眼睛坐标,现在img顶点位置已经改变,需要加上intx和inty的值才可以
                    eye_tag.append(a)
                    #通过append存入数组a中
                n_eyetag = numpy.array(eye_tag)
                #存放为ndarray数组类型,输入内容为[[x1 y1 x1+w y1+h][x1 y1 x1+w y1+h]...],后面会获取多维数组的下标来替换数值

                if len(faceRects_eye)==2:
                    #眼睛识别到两个时,同时替换图片
                    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)
                    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)
                    if key == ord('p'):
                    #检测到键盘输入p时,进行替换
                        img[n_eyetag[0,0]:n_eyetag[0,1],n_eyetag[0,2]:n_eyetag[0,3]]=img_ex
                        img[n_eyetag[1, 0]:n_eyetag[1, 1], n_eyetag[1, 2]:n_eyetag[1, 3]] = img_ex1
                if len(faceRects_eye)==1:
                    # 眼睛识别到一个时,替换图片
                    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)
                    if key == ord('p'):
                        img[n_eyetag[0, 0]:n_eyetag[0, 1], n_eyetag[0, 2]:n_eyetag[0, 3]] = img_ex

            video.write(img)
    cv2.imshow('video', img)
    #显示图片,标题名字为video
    cv2.resizeWindow('video',1280,720)
    #调整窗口大小video为1280*720
    if key == ord('q'):
    #检测到键盘输入q,退出循环
        break

video.release()
#不再录制视频
cap.release()
#释放摄像头
cv2.destroyAllWindows()
#关闭所有窗口显示

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

image.png

1.打开摄像头的时候,开始检测人脸的区域。
2.将获取到的区域上、下半部分图像存储下来。
3.检测上半部分图像区域中双眼的位置。
4.检测下半部分图像区域中嘴巴的位置。
5.模拟人脸识别所进行的操作,如眨眼,摇头,慢慢张开嘴巴,这里我们直接使用键盘‘p’来替换眼睛的位置,‘z’替换嘴巴的位置。
6.关闭摄像头。

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

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

1)cv2.rectangle,我们从上面的注释中知道,获取的是左上角和右下角的顶点坐标,用画图的方式表达如下:
image.png
2)IMG区域取值范围则和1)不同,IMG[左上顶点y:左下顶点y,左上顶点x:右上顶点x],用图来表示S◇acef区域如下:
image.png
所以,之后截取后记得以最原始的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文件,运行工程时提示

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

image

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

image

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

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

四、程序已知的问题
1、暂时定位嘴巴和眼睛位置是以人脸分开两个区域来缩小搜索检测范围,所以抬起头或多人时,仍会检测到别的地方,这是由于OpenCV内的Haar特征分类器训练模型还是不够,所以检测不够精准。程序实现的是实时检测摄像头内图像,换成识别静态图片则准确多了。
2、只要检测到人脸,就会一直循环检测,所以当人物晃动的时候,能检测到多个眼睛和嘴巴,而且检测框不断变换大小。
3、程序暂时只实现了单眼、双眼同时替换,嘴巴替换。当摄像头检测到多个眼睛,只能同时替换至多两个眼睛。
4、替换的图片在肉眼看来很假,瞳孔颜色,眼睛大小都还不够精准,只是用替换图替换。
五、参考资料
    1、OpenCV教程
    2、Python入门教程
    3、人工智能书籍

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容