人脸识别流程,主要包括人脸数据建档与识别两大部分,其中人脸数据建档主要实现对采集人员的人脸数据进行收集入库的过程,人脸识别主要实现对待识别人脸的数据采集与判定的过程。
从具体实现过程来划分,人脸识别整个过程主要包括人脸图特征检测,特征描述,特征对齐,数据库的处理(建立索引以及数据入库)等几个模块。
本文主要以facenet算法为基准来实现人脸识别过程,face算法见如下连接。
https://github.com/davidsandberg/facenet
1、人脸检测
采用mtcnn算法实现对图片的人脸进行检测,load_and_align_data 返回图片中最大人脸的坐标(因为最终目的是实现人脸识别,所以在人脸检测时只需要关注最大人脸即可)
def load_and_align_data(image_paths, image_size=160, margin=44, gpu_memory_fraction=1.0):
minsize = 20 # minimum size of face
threshold = [ 0.6, 0.7, 0.7 ]
factor = 0.709
print("Creating networks and loading parameters")
with tf.Graph().as_default():
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=gpu_memory_fraction)
sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options, log_device_placement=False))
with sess.as_default():
pnet, rnet, onet = align.detect_face.create_mtcnn_alone_pb(sess, "./mtcnn.pb")
#for image in tmp_image_paths:
try:
img = misc.imread(image_paths, mode='RGB')
except(OSError, IOError):
print("OSError, Path")
return []
img_size = np.asarray(img.shape)[0:2]
for k in range(4):
img = ndimage.rotate(img, -90*k)
try:
bounding_boxes, _ = align.detect_face.detect_face(img, minsize, pnet, rnet, onet, threshold, factor)
except Exception as e:
print("MemoryError, Path")
return []
if len(bounding_boxes) > 0:
break
size = 0
img_list = []
#获取最大人脸
for i in range(len(bounding_boxes)):
det = np.squeeze(bounding_boxes[i,0:4])
# x1,y1,x2,y2
bb = np.zeros(4, dtype=np.int32)
bb[0] = np.maximum(det[0]-margin/2, 0)
bb[1] = np.maximum(det[1]-margin/2, 0)
bb[2] = np.minimum(det[2]+margin/2, img_size[1])
bb[3] = np.minimum(det[3]+margin/2, img_size[0])
hight = bb[3] - bb[1]
width = bb[2] - bb[0]
if hight * width > size:
size = hight * width
cropped = img[bb[1]:bb[3],bb[0]:bb[2],:]
aligned = misc.imresize(cropped, (image_size, image_size), interp='bilinear')
img_list = facenet.prewhiten(aligned)
#img_list.append(prewhitened)
images = np.stack(img_list)
return images
2、人脸对齐
通过mtcnn算法获取人脸区域后,对该区域进行放射变换处理,依据眼睛与鼻子的点坐标来实现人脸区域的矫正对齐。
img = cv2.imread(opic)
faceKeyPoint = faceKeyPoint[0]
#根据两个鼻子和眼睛进行3点对齐
eye1 = faceKeyPoint[0]
eye2 = faceKeyPoint[1]
noise = faceKeyPoint[2]
source_point = np.array(
[eye1, eye2, noise], dtype=np.float32
)
eye1_noraml= [int(x) for x in face_comm.get_conf('alignment','left_eye').split(',')]
eye2_noraml=[int(x) for x in face_comm.get_conf('alignment','right_eye').split(',')]
noise_normal=[int(x) for x in face_comm.get_conf('alignment','noise').split(',')]
#设置的人脸标准模型
dst_point = np.array(
[eye1_noraml,
eye2_noraml,
noise_normal],
dtype=np.float32)
tranform = cv2.getAffineTransform(source_point, dst_point)
imagesize=tuple([int(x) for x in face_comm.get_conf('alignment','imgsize').split(',')])
img_new = cv2.warpAffine(img, tranform, imagesize)
3、人脸特征描述
将上述步骤获取得到的人脸区域进行特征描述,最终获得128维的特征向量,这128维的特征向量为该人脸的代表
facenet_model = facenet.load_model(model_path)
config = tf.ConfigProto()
# Get input and output tensors
gfacenet_persistent_session = tf.Session(graph=facenet_model, config=config)
images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")
face=load_and_align_data(image)
# Run forward pass to calculate embeddings
feed_dict = {images_placeholder: [face], phase_train_placeholder: False}
return gfacenet_persistent_session.run(embeddings, feed_dict=feed_dict)[0]
上述代码实现中,首先进行模型加载,任何配置相关参数(gpu环境等),最后导入人脸区域内容,执行模型,输入人脸的特征向量。
4、入库处理
将上述获得的人脸特征向量与人物名称一同进行保存。本文中保存的数据库为lmdb
total_number = face_lmdb_operate.lmdb_search_total_number(env)
image_info = {}
image_info['name'] = name
index = total_number + 1
print(image_path)
embedding = face_encoder_emdding.generate_embedding(image_path)
image_info['info'] = embedding.tolist()
face_lmdb_operate.lmdb_insert_path(env,index,image_info)
上述代码实现 人物名称 与 人脸特征向量的配对
def lmdb_insert_path(env, index, info):
txn = env.begin(write=True)
print(index)
print(info)
txn.put(str(index).encode(), str(info).encode())
txn.commit()
上述代码才是实现 lmdb的插入动作。
5、建立索引
人脸识别时需要对待检测人脸的特征向量与数据库中的特征向量进行对比分析。如果一个个进行比较,效率有点低下,本文借鉴annoy算法来对数据库中的人脸 建立索引。
annoy索引算法假设每个人脸特征可以隐射成高维空间的一个点,如果两个人脸很相似,任何超平面都无法将他们分开。也就是说如果代表人脸的两个点很接接近,如果用超平面去做分割处理,相似的点一定会在同一个平面上。
# 遍历
lmdb_file = self.lmdb_file
if os.path.isdir(lmdb_file):
evn = lmdb.open(lmdb_file)
wfp = evn.begin()
annoy = AnnoyIndex(self.f)
for key, value in wfp.cursor():
key = int(key)
value = face_comm.str_to_embed(value)
annoy.add_item(key,value)
annoy.build(self.num_trees)
annoy.save(self.annoy_index_path)
上述代码为通过lmdb数据库实现annoy索引的过程。
6、人脸识别(测试验证)
对待识别人脸进行特征提取,特征描述后,得到待识别人脸的特征向量,然后通过annoy,在索引中查询最近邻的几个点,如果距离小于阈值(0.6)则认为是同一个人,然后通过数据库查询该人员的相关信息即可。
number=int(face_comm.get_conf('annoy','num_nn_nearst'))
return annoy.get_nns_by_vector(face_vector,number,include_distances=True)