【facenet人脸识别】利用LFW数据集进行人脸比对测试

近期,做人脸识别项目,用到了facenet这个开源框架,并使用LFW人脸数据集进行了测试。现将该过程总结如下:

1 facenet简介

GitHub地址:https://github.com/davidsandberg/facenet.git

facenet的原理就是基于同一人脸总是比不同人脸更相似这一先验知识,然后利用传统卷积神经网络特征提取,利用三元损失函数进行训练。最终,将人脸映射到特征空间后,同一身份的人脸距离较近,不同身份的人脸距离较远。模型的输出是一个512维的向量(原来是128维)。

算法详情可参考其论文:https://arxiv.org/pdf/1503.03832.pdf

2 LFW数据集简介

网盘链接: https://pan.baidu.com/s/1qOrFv_8RhIhUJvAmwE8p0g 提取码: kfwh 

LFW数据集是对5000多人在自然场景下采集的共13000多张图像。lfw_funneled文件夹中每个子文件夹代表一个人,其中包含其若干张同一身份不同场景下的照片,有的只有一张,有的有多张。

lfw_funneled中还包含了几个txt文档,这里面记录了这些人脸的不同组合,我们使用其中的pairs.txt中的组合进行人脸比对测试。

pairs.txt里面包含了6000对人脸,3000对同一身份,3000对不同身份。文档第一行的10  300代表正负样本以300的数量依次罗列,重复10次,因此共10*(300对正样本+300对负样本)= 6000对人脸。

3  测试过程

3.1 图像路径提取

首先,我们根据pairs.txt进行图片路径的提取:


  
  
  1. def get_img_pairs_list(pairs_txt_path,img_path):
  2. """ 指定图片组合及其所在文件,返回各图片对的绝对路径
  3. Args:
  4. pairs_txt_path:图片pairs文件,里面是6000对图片名字的组合
  5. img_path:图片所在文件夹
  6. return:
  7. img_pairs_list:深度为2的list,每一个二级list存放的是一对图片的绝对路径
  8. """
  9. file = open(pairs_txt_path)
  10. img_pairs_list,labels = [],[]
  11. while 1:
  12. img_pairs = []
  13. line = file.readline().replace( '\n', '')
  14. if line == '':
  15. break
  16. line_list = line.split( '\t')
  17. if len(line_list) == 3:
  18. # 图片路径示例:
  19. # 'C:\Users\thinkpad1\Desktop\image_set\lfw_funneled\Tina_Fey\Tina_Fey_0001.jpg'
  20. img_pairs.append(img_path+ '\\'+line_list[ 0]+ '\\'+line_list[ 0]+ '_'+( '000'+line_list[ 1])[ -4:]+ '.jpg')
  21. img_pairs.append(img_path+ '\\'+line_list[ 0]+ '\\'+line_list[ 0]+ '_'+( '000'+line_list[ 2])[ -4:]+ '.jpg')
  22. labels.append( 1)
  23. elif len(line_list) == 4:
  24. img_pairs.append(img_path+ '\\'+line_list[ 0]+ '\\'+line_list[ 0]+ '_'+( '000'+line_list[ 1])[ -4:]+ '.jpg')
  25. img_pairs.append(img_path+ '\\'+line_list[ 2]+ '\\'+line_list[ 2]+ '_'+( '000'+line_list[ 3])[ -4:]+ '.jpg')
  26. labels.append( 0)
  27. else:
  28. continue
  29. img_pairs_list.append(img_pairs)
  30. return img_pairs_list,labels

利用上述代码,即可提取所有人类对的绝对路径,返回一个路径list及其标签(1或0)。

3.2 人脸检测、对比

获取到人脸对的图片路径及标签之后,在使用facenet将其转化为512维的向量之前,需要先对图像进行人脸提取,即截取其中的人脸区域。这里用到了MTCNN模型,用于检测出人脸并将人脸区域单独提出来,然后就可以利用facenet进行人脸特征向量的转化了。得到这对人脸的特征向量之后,求其欧氏距离,即可根据该距离判断其是否为同一身份了。提取及比对过程如下(其中模型model是MTCNN的参数,在facenet的GitHub项目的“facenet/src/models/”路径下已有;model_facenet模型因为比较大,需要单独下载,点击下载:链接: https://pan.baidu.com/s/1ty7NfBYIretHhnZwwl2dTg 提取码: g3jy):


  
  
  1. def face_verification(img_pairs_list):
  2. model = './model/'
  3. model_facenet = r'XXX\XXX\20180402-114759.pb' # 模型在你电脑中的路径
  4. # mtcnn相关参数
  5. minsize= 40
  6. threshold=[ 0.4, 0.5, 0.6] # pnet、rnet、onet三个网络输出人脸的阈值,大于阈值则保留,小于阈值则丢弃
  7. factor = 0.709 # scale factor
  8. # 创建mtcnn网络
  9. with tf.Graph().as_default():
  10. sess=tf.Session()
  11. with sess.as_default():
  12. pnet,rnet,onet=detect_face.create_mtcnn(sess, model)
  13. margin = 44
  14. image_size = 160
  15. with tf.Graph().as_default():
  16. with tf.Session() as sess:
  17. # 根据模型文件载入模型
  18. facenet.load_model(model_facenet)
  19. # 得到输入、输出等张量
  20. images_placeholder = tf.get_default_graph().get_tensor_by_name( "input:0")
  21. embeddings = tf.get_default_graph().get_tensor_by_name( "embeddings:0")
  22. phase_train_placeholder = tf.get_default_graph().get_tensor_by_name( "phase_train:0")
  23. # 设置可视化进度条相关参数
  24. jd = '\r %2d%%\t [%s%s]'
  25. bar_num_total = 50
  26. total_num = len(img_pairs_list)
  27. result, dist = [],[]
  28. for i in range(len(img_pairs_list)):
  29. # 画进度条
  30. if i%round(total_num/bar_num_total) == 0 or i == total_num -1:
  31. bar_num_alright = round(bar_num_total*i/total_num)
  32. alright = '#'*bar_num_alright
  33. not_alright = '□'*(bar_num_total-bar_num_alright)
  34. percent = (bar_num_alright/bar_num_total)* 100
  35. print(jd % (percent,alright,not_alright),end= '')
  36. # 读取一对人脸图像
  37. img_pairs = img_pairs_list[i]
  38. img_list = []
  39. img1 = cv2.imread(img_pairs[ 0])
  40. img2 = cv2.imread(img_pairs[ 1])
  41. img_size1 = np.asarray(img1.shape)[ 0: 2]
  42. img_size2 = np.asarray(img2.shape)[ 0: 2]
  43. # 检测该对图像中的人脸
  44. bounding_box1,_1=detect_face.detect_face(img1,minsize,pnet,rnet,onet,threshold,factor)
  45. bounding_box2,_2=detect_face.detect_face(img2,minsize,pnet,rnet,onet,threshold,factor)
  46. # 未检测到人脸,则将结果标为-1,后续计算准确率时排除
  47. if len(bounding_box1)< 1 or len(bounding_box2)< 1:
  48. result.append( -1)
  49. dist.append( -1)
  50. continue
  51. # 将图片1加入img_list
  52. det = np.squeeze(bounding_box1[ 0, 0: 4])
  53. bb = np.zeros( 4, dtype=np.int32)
  54. bb[ 0] = np.maximum(det[ 0]-margin/ 2, 0)
  55. bb[ 1] = np.maximum(det[ 1]-margin/ 2, 0)
  56. bb[ 2] = np.minimum(det[ 2]+margin/ 2, img_size1[ 1])
  57. bb[ 3] = np.minimum(det[ 3]+margin/ 2, img_size1[ 0])
  58. cropped = img1[bb[ 1]:bb[ 3],bb[ 0]:bb[ 2],:]
  59. aligned = cv2.resize(cropped, (image_size, image_size))
  60. prewhitened = facenet.prewhiten(aligned)
  61. img_list.append(prewhitened)
  62. # 将图片2加入img_list
  63. det = np.squeeze(bounding_box2[ 0, 0: 4])
  64. bb = np.zeros( 4, dtype=np.int32)
  65. bb[ 0] = np.maximum(det[ 0]-margin/ 2, 0)
  66. bb[ 1] = np.maximum(det[ 1]-margin/ 2, 0)
  67. bb[ 2] = np.minimum(det[ 2]+margin/ 2, img_size2[ 1])
  68. bb[ 3] = np.minimum(det[ 3]+margin/ 2, img_size2[ 0])
  69. cropped = img2[bb[ 1]:bb[ 3],bb[ 0]:bb[ 2],:]
  70. aligned = cv2.resize(cropped, (image_size, image_size))
  71. prewhitened = facenet.prewhiten(aligned)
  72. img_list.append(prewhitened)
  73. images = np.stack(img_list)
  74. # 将两个人脸转化为512维的向量
  75. feed_dict = { images_placeholder: images, phase_train_placeholder: False }
  76. emb = sess.run(embeddings, feed_dict=feed_dict)
  77. # 计算两个人脸向量的距离
  78. ed = np.sqrt( np.sum( np.square( np.subtract(emb[ 0], emb[ 1]) ) ) )
  79. dist.append(ed)
  80. # 根据得出的人脸间的距离,判断是否属于同一个人
  81. if ed<= 1.1:
  82. result.append( 1)
  83. else:
  84. result.append( 0)
  85. return result,dist

上述代码可以实现在某一指定阈值下,进行人脸比对,得出对比结果存于result中,用于后续计算准确率;同时,为了画出ROC曲线,这里还返回了,所有人脸对的欧氏距离,存于dist中。

实际上,上述result是dist在某一个阈值下的截面数据,通过设置不同阈值,即可根据dist得出不同的result,下面正是利用这个原理画出的ROC曲线。

3.3 ROC曲线

根据3.2得出的每对人脸的欧氏距离,还有3.1得出的各对人脸样本的标签,即可画出计算出ROC曲线所需指标:TPR、FPR。

代码如下:


  
  
  1. def roc(dist,labels):
  2. TP_list,TN_list,FP_list,FN_list,TPR,FPR = [],[],[],[],[],[]
  3. for t in range( 180):
  4. threh = 0.1+t* 0.01
  5. TP,TN,FP,FN = 0, 0, 0, 0
  6. for i in range(len(dist)):
  7. if labels[i]== 1 and dist[i]!= -1:
  8. if dist[i]<threh:
  9. TP += 1
  10. else:
  11. FN += 1
  12. elif labels[i]== 0 and dist[i]!= -1:
  13. if dist[i]>=threh:
  14. TN += 1
  15. else:
  16. FP += 1
  17. TP_list.append(TP)
  18. TN_list.append(TN)
  19. FP_list.append(FP)
  20. FN_list.append(FN)
  21. TPR.append(TP/(TP+FN))
  22. FPR.append(FP/(FP+TN))
  23. return TP_list,TN_list,FP_list,FN_list,TPR,FPR

 4 完整代码


  
  
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Fri Mar 22 09:59:41 2019
  4. @author: Leon
  5. 内容:
  6. 人脸验证准确率测试
  7. 样本:LFW人脸集,共6000对人脸,中3000对同一身份、3000对不同身份。
  8. """
  9. import numpy as np
  10. import cv2
  11. import tensorflow as tf
  12. import matplotlib.pyplot as plt
  13. # facenet 和 detect_face 均在facenet项目文件中,这里是直接将其放到测试脚本同一路径下了,也可以安装facenet,然后调用之
  14. import facenet
  15. import align.detect_face as detect_face
  16. def face_verification(img_pairs_list):
  17. model = './model/'
  18. model_facenet = r'XXX\XXX\20180402-114759.pb'
  19. # mtcnn相关参数
  20. minsize= 40
  21. threshold=[ 0.4, 0.5, 0.6] # pnet、rnet、onet三个网络输出人脸的阈值,大于阈值则保留,小于阈值则丢弃
  22. factor = 0.709 # scale factor
  23. # 创建mtcnn网络
  24. with tf.Graph().as_default():
  25. sess=tf.Session()
  26. with sess.as_default():
  27. pnet,rnet,onet=detect_face.create_mtcnn(sess, model)
  28. margin = 44
  29. image_size = 160
  30. with tf.Graph().as_default():
  31. with tf.Session() as sess:
  32. # 根据模型文件载入模型
  33. facenet.load_model(model_facenet)
  34. # 得到输入、输出等张量
  35. images_placeholder = tf.get_default_graph().get_tensor_by_name( "input:0")
  36. embeddings = tf.get_default_graph().get_tensor_by_name( "embeddings:0")
  37. phase_train_placeholder = tf.get_default_graph().get_tensor_by_name( "phase_train:0")
  38. # 设置可视化进度条相关参数
  39. jd = '\r %2d%%\t [%s%s]'
  40. bar_num_total = 50
  41. total_num = len(img_pairs_list)
  42. result, dist = [],[]
  43. for i in range(len(img_pairs_list)):
  44. # 画进度条
  45. if i%round(total_num/bar_num_total) == 0 or i == total_num -1:
  46. bar_num_alright = round(bar_num_total*i/total_num)
  47. alright = '#'*bar_num_alright
  48. not_alright = '□'*(bar_num_total-bar_num_alright)
  49. percent = (bar_num_alright/bar_num_total)* 100
  50. print(jd % (percent,alright,not_alright),end= '')
  51. # 读取一对人脸图像
  52. img_pairs = img_pairs_list[i]
  53. img_list = []
  54. img1 = cv2.imread(img_pairs[ 0])
  55. img2 = cv2.imread(img_pairs[ 1])
  56. img_size1 = np.asarray(img1.shape)[ 0: 2]
  57. img_size2 = np.asarray(img2.shape)[ 0: 2]
  58. # 检测该对图像中的人脸
  59. bounding_box1,_1=detect_face.detect_face(img1,minsize,pnet,rnet,onet,threshold,factor)
  60. bounding_box2,_2=detect_face.detect_face(img2,minsize,pnet,rnet,onet,threshold,factor)
  61. # 未检测到人脸,则将结果标为-1,后续计算准确率时排除
  62. if len(bounding_box1)< 1 or len(bounding_box2)< 1:
  63. result.append( -1)
  64. dist.append( -1)
  65. continue
  66. # 将图片1加入img_list
  67. det = np.squeeze(bounding_box1[ 0, 0: 4])
  68. bb = np.zeros( 4, dtype=np.int32)
  69. bb[ 0] = np.maximum(det[ 0]-margin/ 2, 0)
  70. bb[ 1] = np.maximum(det[ 1]-margin/ 2, 0)
  71. bb[ 2] = np.minimum(det[ 2]+margin/ 2, img_size1[ 1])
  72. bb[ 3] = np.minimum(det[ 3]+margin/ 2, img_size1[ 0])
  73. cropped = img1[bb[ 1]:bb[ 3],bb[ 0]:bb[ 2],:]
  74. aligned = cv2.resize(cropped, (image_size, image_size))
  75. prewhitened = facenet.prewhiten(aligned)
  76. img_list.append(prewhitened)
  77. # 将图片2加入img_list
  78. det = np.squeeze(bounding_box2[ 0, 0: 4])
  79. bb = np.zeros( 4, dtype=np.int32)
  80. bb[ 0] = np.maximum(det[ 0]-margin/ 2, 0)
  81. bb[ 1] = np.maximum(det[ 1]-margin/ 2, 0)
  82. bb[ 2] = np.minimum(det[ 2]+margin/ 2, img_size2[ 1])
  83. bb[ 3] = np.minimum(det[ 3]+margin/ 2, img_size2[ 0])
  84. cropped = img2[bb[ 1]:bb[ 3],bb[ 0]:bb[ 2],:]
  85. aligned = cv2.resize(cropped, (image_size, image_size))
  86. prewhitened = facenet.prewhiten(aligned)
  87. img_list.append(prewhitened)
  88. images = np.stack(img_list)
  89. # 将两个人脸转化为512维的向量
  90. feed_dict = { images_placeholder: images, phase_train_placeholder: False }
  91. emb = sess.run(embeddings, feed_dict=feed_dict)
  92. # 计算两个人脸向量的距离
  93. ed = np.sqrt( np.sum( np.square( np.subtract(emb[ 0], emb[ 1]) ) ) )
  94. dist.append(ed)
  95. # 根据得出的人脸间的距离,判断是否属于同一个人
  96. if ed<= 1.1:
  97. result.append( 1)
  98. else:
  99. result.append( 0)
  100. return result,dist
  101. def get_img_pairs_list(pairs_txt_path,img_path):
  102. """ 指定图片组合及其所在文件,返回各图片对的绝对路径
  103. Args:
  104. pairs_txt_path:图片pairs文件,里面是6000对图片名字的组合
  105. img_path:图片所在文件夹
  106. return:
  107. img_pairs_list:深度为2的list,每一个二级list存放的是一对图片的绝对路径
  108. """
  109. file = open(pairs_txt_path)
  110. img_pairs_list,labels = [],[]
  111. while 1:
  112. img_pairs = []
  113. line = file.readline().replace( '\n', '')
  114. if line == '':
  115. break
  116. line_list = line.split( '\t')
  117. if len(line_list) == 3:
  118. # 图片路径示例:
  119. # 'C:\Users\thinkpad1\Desktop\image_set\lfw_funneled\Tina_Fey\Tina_Fey_0001.jpg'
  120. img_pairs.append(img_path+ '\\'+line_list[ 0]+ '\\'+line_list[ 0]+ '_'+( '000'+line_list[ 1])[ -4:]+ '.jpg')
  121. img_pairs.append(img_path+ '\\'+line_list[ 0]+ '\\'+line_list[ 0]+ '_'+( '000'+line_list[ 2])[ -4:]+ '.jpg')
  122. labels.append( 1)
  123. elif len(line_list) == 4:
  124. img_pairs.append(img_path+ '\\'+line_list[ 0]+ '\\'+line_list[ 0]+ '_'+( '000'+line_list[ 1])[ -4:]+ '.jpg')
  125. img_pairs.append(img_path+ '\\'+line_list[ 2]+ '\\'+line_list[ 2]+ '_'+( '000'+line_list[ 3])[ -4:]+ '.jpg')
  126. labels.append( 0)
  127. else:
  128. continue
  129. img_pairs_list.append(img_pairs)
  130. return img_pairs_list,labels
  131. def roc(dist,labels):
  132. TP_list,TN_list,FP_list,FN_list,TPR,FPR = [],[],[],[],[],[]
  133. for t in range( 180):
  134. threh = 0.1+t* 0.01
  135. TP,TN,FP,FN = 0, 0, 0, 0
  136. for i in range(len(dist)):
  137. if labels[i]== 1 and dist[i]!= -1:
  138. if dist[i]<threh:
  139. TP += 1
  140. else:
  141. FN += 1
  142. elif labels[i]== 0 and dist[i]!= -1:
  143. if dist[i]>=threh:
  144. TN += 1
  145. else:
  146. FP += 1
  147. TP_list.append(TP)
  148. TN_list.append(TN)
  149. FP_list.append(FP)
  150. FN_list.append(FN)
  151. TPR.append(TP/(TP+FN))
  152. FPR.append(FP/(FP+TN))
  153. return TP_list,TN_list,FP_list,FN_list,TPR,FPR
  154. if __name__ == '__main__':
  155. pairs_txt_path = 'C:/Users/thinkpad1/Desktop/image_set/lfw_funneled/pairs.txt'
  156. img_path = 'C:/Users/thinkpad1/Desktop/image_set/lfw_funneled'
  157. img_pairs_list,labels = get_img_pairs_list(pairs_txt_path,img_path)
  158. result,dist = face_verification(img_pairs_list)
  159. num_right, num_total = 0, 0
  160. num_total = len([r for r in result if r != -1])
  161. num_right = len([result[i] for i in range(len(result)) if result[i] == labels[i]])
  162. print( "人脸验证测试完毕")
  163. print( "阈值为1.1,共%d对人脸,准确率%2.4f%%"%(num_total, round( 100*num_right/num_total, 4)))
  164. TP_list,TN_list,FP_list,FN_list,TPR,FPR = roc(dist,labels)
  165. plt.plot(FPR,TPR,label= 'Roc')
  166. plt.plot([ 0, 1], [ 0, 1], '--', color=( 0.6, 0.6, 0.6), label= 'Luck')
  167. plt.xlabel( 'FPR')
  168. plt.ylabel( 'TPR')
  169. plt.legend()
  170. plt.plot(np.linspace( 0.1, 1.89, 180),TP_list,label= 'TP')
  171. plt.plot(np.linspace( 0.1, 1.89, 180),TN_list,label= 'TN')
  172. plt.plot(np.linspace( 0.1, 1.89, 180),FP_list,label= 'FP')
  173. plt.plot(np.linspace( 0.1, 1.89, 180),FN_list,label= 'FN')
  174. plt.legend()

5 测试结果

在阈值1.1下测试准确率为93.48%,这里没有达到其宣称的99%+的准确率。

利用每对人脸距离,通过设置不同距离阈值,画出ROC曲线,如下图(左),将TP,TN,FP,FN的曲线也画出来,可以佐证阈值在1.1时,达到最好的分类效果(TP、TN最大,FP、FN最小)。

 

  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值