代码视界

Hanpeng Chen的个人博客

带你一起用人脸识别技术判断参加美国大选的是不是吴恩达

本文于 992 天之前发表,文中内容可能已经过时。

What!吴恩达去参加美国大选了?最近几周,想必很多人都有看到这新闻,是不是在想吴恩达老师是不是有个双胞胎兄弟去参加美国大选了???

答案都不是,参选的是杨安泽,两人不仅长得像,英文名都叫Andrew,Andrew Yang(杨安泽)、Andrew Ng(吴恩达),下面是两个人的照片:

是不是分不清啊?没关系,接下来带你一起,用人脸识别技术判断是不是同一个人。

在上一篇文章中,我们介绍了实现人脸检测的两种方法,接下来要介绍的是人脸识别,通过对比两张人脸,计算其特征相似度,来判断是否是同一个人。

人脸识别模型

使用基于NN4改造的CNN模型训练和提取特征

nn4.small2.v1是FaceNet论文中描述的NN4模型的变体,在OpenFace的模型列表中有nn4.small2详细介绍,具体内容点击下方链接查看
https://cmusatyalab.github.io/openface/models-and-accuracies/#model-definitions

模型列表

Model Number of Parameters
nn4.small2 3733968
nn4.small1 5579520
nn4 6959088
nn2 7472144

本文使用Keras版本中的一种实现,模型定义在model.py
Keras版本的github地址:https://github.com/krasserm/face-recognition

Retrain人脸识别模型工作流程

1、加载训练数据集
2、人脸检测、对齐和提取(使用OpenFace的人脸对齐工具AlignDlib)
3、人脸特征向量学习(使用预训练的nn4.small1.v1模型)
4、人脸分类(使用KNN或SVM)

加载训练数据集

训练集组织形式

images目录中有3张图像,两张吴恩达(Andrew Ng)的照片,一张杨安泽(Andrew Yang)照片,放一起看两个人还是很像的。

最好是1:1比例,如果原图不是1:1比例,提取后的人脸会进行拉伸变换,仅支持.jpg和.jpeg两种格式。

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
# 加载训练数据集
import numpy as np
import cv2
import os.path

class IdentityMetadata():
def __init__(self, base, file):
self.base = base # 数据集根目录
# self.name = name # 目录名
self.file = file # 图像文件名

def __repr__(self):
return self.image_path()

def image_path(self):
return os.path.join(self.base, self.file)

def load_metadata(path):
metadata = []
for f in os.listdir(path):
# 检查文件名后缀,仅支持 jpg 和 jpeg 两种文件格式
ext = os.path.splitext(f)[1]
if ext == '.jpg' or ext == '.jpeg':
metadata.append(IdentityMetadata(path, f))
return np.array(metadata)

def load_image(path):
img = cv2.imread(path, 1)
# OpenCV 默认使用 BGR 通道加载图像,转换为 RGB 图像
return img[...,::-1]

metadata = load_metadata('images')

人脸检测、对齐和提取

从原图提取96*96RGB人脸图像。

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
# 人脸检测、对齐和提取
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from align import AlignDlib

# 初始化 OpenFace 人脸对齐工具,使用 Dlib 提供的 68 个关键点
alignment = AlignDlib('landmarks.dat')


# 加载一张训练图像
img = load_image(metadata[0].image_path())
# 检测人脸并返回边框
bb = alignment.getLargestFaceBoundingBox(img)
# 使用指定的人脸关键点转换图像并截取 96x96 的人脸图像
aligned_img = alignment.align(96, img, bb, landmarkIndices=AlignDlib.OUTER_EYES_AND_NOSE)
# 绘制原图
plt.figure(1)
plt.subplot(131)
plt.imshow(img)
plt.xticks([])
plt.yticks([])
# 绘制带人脸边框的原图
plt.subplot(132)
plt.imshow(img)
plt.gca().add_patch(patches.Rectangle((bb.left(), bb.top()), bb.width(), bb.height(), fill=False, color='red'))
plt.xticks([])
plt.yticks([])
# 绘制对齐后截取的 96x96 人脸图像
plt.subplot(133)
plt.imshow(aligned_img)
plt.xticks([])
plt.yticks([])
plt.show()

人脸检测、对齐和提取的结果如下图所示

加载预训练模型nn4.small2.v1

我们从 OpenFace 提供的 预训练模型 中选择 nn4.small2.v1。

这些模型使用公开数据集 FaceScrub 和 CASIA-WebFace进行训练。Keras-OpenFace 项目将这些模型文件转换为 csv 文件,然后我们将其转换为 Keras h5 模型文件 nn4.small2.v1.h5。

预训练模型

Model alignment landmarkIndices
nn4.v1 openface.AlignDlib.INNER_EYES_AND_BOTTOM_LIP
nn4.v2 openface.AlignDlib.OUTER_EYES_AND_NOSE
nn4.small1.v1 openface.AlignDlib.OUTER_EYES_AND_NOSE
nn4.small2.v1 openface.AlignDlib.OUTER_EYES_AND_NOSE

nn4.small2.v1.h5、model.py、align.py这些文件可以从下面github仓库获取:
https://github.com/krasserm/face-recognition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 加载预训练模型nn4.small2.v1
from model import create_model

nn4_small2_pretrained = create_model()
nn4_small2_pretrained.load_weights('models/nn4.small2.v1.h5')


def align_image(img):
return alignment.align(96, img, alignment.getLargestFaceBoundingBox(img),
landmarkIndices=AlignDlib.OUTER_EYES_AND_NOSE)

embedded = np.zeros((metadata.shape[0], 128))

for i, m in enumerate(metadata):
img = load_image(m.image_path())
img = align_image(img)
# 数据规范化
img = (img / 255.).astype(np.float32)
# 人脸特征向量
embedded[i] = nn4_small2_pretrained.predict(np.expand_dims(img, axis=0))[0]

计算人脸特征的欧式距离,计算相似度

Squared L2 Distance(欧式距离),计算两个向量之间的距离。

在上一步,我们获得所有测试人脸的特征向量,我们可以通过计算其之间的距离,来判断人脸的相似度,距离越小,相似度越高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Squared L2 Distance
def distance(emb1, emb2):
return np.sum(np.square(emb1 - emb2))

count = 0
def show_pair(idx1, idx2):
global count
count += 1
plt.figure(num=count, figsize=(8,3))
plt.suptitle(f'Distance = {distance(embedded[idx1], embedded[idx2]):.2f}')
plt.subplot(121)
plt.imshow(load_image(metadata[idx1].image_path()))
plt.xticks([])
plt.yticks([])
plt.subplot(122)
plt.imshow(load_image(metadata[idx2].image_path()))
plt.xticks([])
plt.yticks([])


show_pair(0, 1)
show_pair(0, 2)
show_pair(1, 2)
plt.show()

从上面三张结果图我们可以看到,两个吴恩达的照片人脸特征的欧式距离为0.09,吴恩达跟杨安泽的距离为0.33。

如何利用获得的距离来判断两个张照片上的人是否是同一个人?如果距离值低于某一阈值,则认为是同一个人。

如何确定阈值,一般通过大量已标记的测试值,阈值从小到大取值,计算获得识别的准确率,取准确率比较高阈值。

我们这里阈值取0.3,则可以判断测试的人脸1、2为同一个人,3为另一个人,但两个人长得很像。

完整代码:
https://github.com/Hanpeng-Chen/tensorflow-learning

欢迎关注微信公众号: 『前端极客技术』『代码视界』
支付宝打赏 微信打赏

赞赏是不耍流氓的鼓励