您的位置:首页 > 运维架构

opencv+CNN实现人脸识别

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


项目描述

总的来说,要实现最终的人脸识别功能,就要分别实现以下几个小目标:
通过笔记本自带的摄像头实现实时的人脸检测,这里用到了python下的openCV;
为了得到用于识别模型的输入,还需要从已有照片中提取出目标(比如说自己)的人脸,以及其他人的人脸。别忘了给这两类数据分别贴上标签;
实现对来自照片或摄像头帧的人脸的统一处理(大小、去色等),这样才能使得输入到模型中的输入标准化;
构造一个CNN 模型,以目标和其他人的人脸为输入,经过训练得到模型参数并保存,这里用到了python下的keras框架;
回到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'): #如果按
14433
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上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: