——第一期:关于制作简单动态眨眼
一、探索背景
随着人工智能的发展,ai人脸识别已经成为人工智能应用中不可缺少的一部分,各行各业都开始用这个新兴技术,减少审核成本,增加审核效率,可是人工智能毕竟跟人还是有差别的,因为人工智能的对人脸的判断是通过无数算法跟模型训练,才会越来越精确,所以有很多人为看起来非常假的图,人工智能计算后是可以通过的。
比如一张人脸,假设是否是本人人脸的两个判断条件是:1、人脸跟本人身份证照片相似度达90%。2、检测的人是个活体。满足这两个条件ai的判断就是通过,但是这两个条件其实都是可以伪造的,比如说找本人的图片,通过技术手段让眼睛嘴巴动起来,ai就会判断成活体,并且与本人相似度很高,因此就通过了,这种情况我们称为ai人脸欺诈。
所以本期的探索就是通过opencv让静态人脸上的眼睛动起来,从而模仿照片为本人且是活体的效果,模拟ai人脸欺诈。
二、需要用到的第三方依赖库
OpenCV
OpenCV是一个跨平台的计算机视觉库,是开源的,可以应用在主流操作系统上,例如:Linux、windows、Android和Mac os操作系统上。它由一系列的c函数和少量的c++类构成,同时也提供了Python、Ruby、MATLAB等接口,实现了图像处理和计算机视觉方面的很多通用算法。本次的模拟ai人脸欺诈,主要用的就是这个开源库。
Pillow 与PIL
PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。 由于PIL仅支持到Python 2.7,加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫Pillow,支持最新Python 3.x,又加入了许多新特性,因此,我们可以直接安装使用Pillow。
三、环境的搭建
python版本:2.7
版本依赖库:
pip:9.0.1
$brew install pip
安装pillow:5.0.0
$pip install pillow
安装pillow-PIL 0.1dev
$pip install pillow
numpy 1.14.1
$pip install numpy
这里可能会遇到已经安装numpy1.0.8rc1无法卸载的问题,请参考链接: http://blog.csdn.net/hqzxsc2006/article/details/51602654
OpenCV-python 3.3.0.10
$brew install OpenCV
如果遇到Permissiondenied安装失败,请加上sudo重试。 除了以上的版本,还需要一个python编辑器,推荐用pycharm。
亲测在真实环境下搭建环境过程比较繁琐,而且容易遇到各种问题。建议通过pycharm搭建一个虚拟环境来实现。
四、用python实现静态图变动态图
首先附上静图变动图的源码:
#静态图片变动图代码
import os
import numpy
from PIL import Image, ImageDraw
import cv2
left = cv2.imread("/Users/aa/PycharmProjects/untitled1/left.png")
right = Image.open("/Users/aa/PycharmProjects/untitled1/right.png")
classifier = cv2.CascadeClassifier("/Users/aa/PycharmProjects/untitled1/haarcascade_eye.xml")
count = 0
while count > -1:
img = cv2.imread("/Users/aa/PycharmProjects/untitled1/timg.jpg")
Face = img[55:136, 170:255]
eyeRects = classifier.detectMultiScale(img, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20))
key = cv2.waitKey(1)
if len(eyeRects) > 0:
for faceRect in eyeRects:
x, y, w, h = faceRect
cv2.rectangle(img, (int(x), int(y)), (int(x) + int(w), int(y) + int(h)), (0, 255, 0), 2, 0)
ex_show=cv2.resize(left,(w,h),interpolation=cv2.INTER_CUBIC)
#Y1=y+55
#Y2=(y+h)+55
#X1=x+170
#X2=(x+w)+170
if key == ord('p'):
#img[ Y1:Y2, X1:X2]=ex_show
#Face[y:y+h, x:x+w]=ex_show
img[y:y+h, x:x+w]= ex_show
cv2.imshow('video', img)
cv2.resizeWindow('video',1280,720)
if key == ord('q'):
break
cv2.destroyAllWindows()
以上内容及代码均基于网上搜索和参考别人的代码,结合我的思考和理解,自己写的代码,下面内容会解析代码每一句代表的意义以及作用,并且我也绘制了一些数学模型图,帮助大家对眼睛识别及替换的原理有更深刻的理解。
首先我们先导入依赖库,用它的image跟ImageDraw包来处理图片
import os
import numpy
from PIL import Image, ImageDraw
import cv2
然后先读取一张主图:
img = cv2.imread("/Users/aa/PycharmProjects/untitled1/timg.png")
格式为:img = cv2.imread("文件路径"),默认是彩色的,如果想要灰色可以这样设置:img=cv2.imread("文件路径",0),后面的参数决定图片的色彩。
然后我们再用:
cv2.imshow('video', img)
来查看图片是否显示正常,显示的结果是这样子的:
导入成功,想要眼睛动起来,首先我们得先识别出眼睛所在的区域。这时候可以用一个现成的经过训练的人眼识别xml帮助,文件名字:haarcascade_eye.xml,这个xml在网上也能找到,导入这个xml并建立一个对象。
classifier = cv2.CascadeClassifier("/Users/dingjingjing058/Downloads/haarcascade_eye.xml")
通过对象调用xml的detectMultiScale函数并且赋值给一个变量。
eyeRects = classifier.detectMultiScale(img, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20))
这条代码的意思,就是在整张图片中,识别出人眼的区域。
detectMultiScale这个函数的用法我就不详细说了,粗略的说一下每一个参数的意义:
detectMultiScale函数介绍:
参数1:image--待检测图片,一般为灰度图像加快检测速度
参数2:objects--被检测物体的矩形框向量组;
参数3:scaleFactor--表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%;
参数4:minNeighbors--表示构成检测目标的相邻矩形的最小个数(默认为3个)。 如果组成检测目标的小矩形的个数和小于minneighbors - 1 都会被排除。如果minneighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上;
参数5:flags--flags--要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,因此这些区域通常不会是人脸所在区域;
参数6、7:minSize和maxSize用来限制得到的目标区域的范围。
然后就是把识别出来的人眼区域画一个矩形,画矩形的代码是这样的:
cv2.rectangle(img, ((x1,y1) , (X2,Y2), (0, 255, 0), 2, 0)
其中X1,Y1是矩形的左上角坐标,X2,Y2是矩形的右下角坐标。根据这个坐标来绘制一个矩形。
可是我们怎么根据人眼识别的点来画矩形呢?用一个for循环在xml里遍历一下faceRect这个函数,遍历的代码就这样:
if len(faceRects) > 0:
for faceRect in eyeRects:
x, y, w, h = eyeRect
cv2.rectangle(img, (int(x), int(y)), (int(x) + int(w), int(y) + int(h)), (0, 255, 0), 2, 0)
可能比较抽象,我们就以图画一个坐标,原点为右上角的点,图像的宽为x轴,高为y轴,然后画矩形就是用x跟y轴的坐标。
原理图如下,上述代码就会绘制出来下面的这幅图。
然后我们再来运行后的人眼识别结果,识别成功:
识别成功以后,怎么把这个静态图片变成一个动态的图片呢?这时候我们就需要替换矩形内部的图像,首先先把要换图的区域给定位出来,用这部分代码定位:
img[ y:(y + h), x:(x + w)]
这行代码的意思是把y1=y y2=y+h,x1=x ,X2=x+w,把这四根线重叠的部分给抠出来,如下图黑色阴影部分:
然后再置换成另外一张图片,我们首先导入要替换眼睛部分的图片,并让图片适应框的大小。
ex_show=cv2.resize(left,(w,h),interpolation=cv2.INTER_CUBIC)
然后把刚刚圈出来的那个区域,替换成这张图片:
img[ y:(y + h), x:(x + w)]=ex_show
然后就大功告成,效果如下图所示:
然后怎么让这个图动起来呢,我们就设定按键盘某个按键的时候,这张图片才会替换,这样不断按这个按钮,图片就变成动图啦。
if key == ord('p'):
img[ y:(y + h), x:(x + w)]=ex_show
这样一幅动态识别替换眼睛部分的动图就完成啦,这样就可以用静态图片不断替换眼睛部分以营造是活体的效果,从而瞒过人工智能。
五、识别准确度优化的实现
以上内容是我对源码的思考和理解,以下内容是我在源码的基础上自己写的一些优化代码,让眼部的识别更加精准。
虽然我们成功的完成了人眼识别并替换其他图片,但是我们都发现,上图的识别部分不太准确,有一部分已经识别到腋下去了,那么我们怎么让识别范围更加的准确呢?
小编的想法就是,框定一个区域,比如说人脸,然后让眼睛识别就在这个区域内进行,这样的话准确度会大大的提高。
于是决定先把人脸部分的坐标先抠出来:
Face = img[55:136, 170:255]
然后在抠出来的图里面进行遍历,识别人眼。
if len(faceRects) > 0:
for faceRect in eyeRects:
x, y, w, h = faceRect
cv2.rectangle(Face, (int(x), int(y)), (int(x) + int(w), int(y) + int(h)), (0, 255, 0), 2, 0)
然后再运行一下,我们发现已经把矩形准确的定位到眼睛的部分啦。
但是这时候我们发现运行程序,会发现替换图片的时候跑偏了。为什么会跑偏呢?原来我们在抠出来的图中画矩形的时候,矩形已经不是以整个图片为原点,而是以抠出来的图的左上角坐标为原点,这时候的x,y是相对于坐标(55,170)而设置的,再来看我们替换图片的代码:
img[ y:(y + h), x:(x + w)]=ex_show
但是我们替换图片的时候,仍然是以原图为参考的,这时候的x,y识别到的 是以原图左上角为原点,x,y为矩形左上角点的部分。这时候我们得把抠出来的图的坐标再还原到原图上,从而识别替换部分的坐标:
所以替换图片的坐标应该改成这样:
Y1=y+55
Y2=(y+h)+55
X1=x+170
X2=(x+w)+170
if key == ord('p'):
img[ Y1:Y2, X1:X2]=ex_show
其实还有一种更加简单的方式,没错就是直接把替换区域也改成以抠出来的图为参考:
Face[y:y+h, x:x+w]=ex_show
这样一幅准确的人眼识别动图就做出来啦!
这段代码中抠出来的人脸坐标是写死的只是为了更便于理解它的精确过程,之后可以换成智能人脸识别,先把人脸识别出来,然后把人脸的部分抠出来再进行人眼识别,其实跟上述的原理一样的,只不过把写死的这个坐标img[55:136,170:255]替换成变量坐标而已。
最后,本期只是做一个特别基本的尝试探索,在这个过程中也遇到了许多问题,环境上,遇到了很多环境搭建的问题,比如说安装了以后仍然无法导入image跟imageview的包,最后通过搭建虚拟环境解决了问题,比如人眼识别不太精准总是识别到其他东西。这期做的只是一个非常简单的人眼识别然后替换的实现,后期会尝试实现人眼蒙版过渡得更自然-接近于真实人眼的睁眼闭眼的模拟,然后让识别更加的精确,不会识别到别的地方。后期会尝试加上嘴巴的识别,让静态图片的嘴巴实现动态张闭,以及静态图摇头的功能的实现。