https://www.infoq.cn/article/4wT4mNvKlVvEQZR-JXmp
Keras 是一个用 Python 编写的高级神经网络 API,能够以 TensorFlow、CNTK 或 Theano 作为后端运行。FaceNet 是 Google 工程师 Florian Schroff、Dmitry Kalenichenko、James Philbin 等人于 2015 年开发的人脸识别系统,由于算法原理容易理解、应用方便,成了目前最为流行的人脸识别技术。今天,AI 前线带领大家跟随 Jason Brownlee 去学习如何在 Keras 中使用 FaceNet 开发人脸识别系统。
人脸识别是一项计算机视觉任务,根据人脸的照片来识别和验证某个人。
FaceNet 是 Google 研究人员于 2015 年开发的人脸识别系统,这个系统在一系列人脸识别基准数据集上取得了当时最好的成绩。由于该模型的多个第三方开源实现和预训练模型的可用性,FaceNet 系统得到了广泛的使用。
FaceNet 系统可从人脸中提取高质量的特征,称为人脸嵌入(face embeddings),可用于训练人脸识别系统。
在本教程中,你将了解如何使用 FaceNet 和 SVM 分类器开发人脸检测系统,来从照片中识别出身份。
完成本教程之后,你将了解:
关于 Google 开发的 FaceNet 人脸识别系统,以及开源实现和预训练模型。
如何准备人脸检测数据集,包括首先通过人脸检测系统提取人脸,然后通过人脸嵌入提取人脸特征。
如何拟合、评估和演示 SVM 模型从人脸嵌入的身份。
让我们开始吧。
教程概述
本教程分为五个部分,分别是:
-
人脸识别
-
FaceNet 模型
-
如何在 Keras 中加载 FaceNet 模型
-
如何检测人脸进行人脸识别
-
如何开发人脸分类系统
人脸识别
人脸识别是从人脸照片中识别和验证人脸的一般任务。
2011 年出版的一本书 “Handbook of Face Recognition”(《人脸识别手册》)描述了人脸识别的两种主要模式:
人脸识别系统有望自动识别图像和视频中的人脸。它可以在两种模式中的一种或两种模式下进行操作:
(1)人脸验证(或身份验证);
(2)人脸识别(或身份识别)。
——第一页,Handbook of Face Recognition,2011 年。
在本教程中,我们将重点介绍人脸识别任务。
FaceNet 模型
FaceNet 是 Florian Schroff 等人在 Google 于 2015 年发表的论文 “FaceNet: A Unified Embedding for Face Recognition and Clustering”(《FaceNet:人脸识别与聚类的统一嵌入》)中描述的一种人脸识别系统。
这是一种系统,给定一张人脸的图片,系统就将从人脸中提取出高质量的特征,并预测 128 个元素向量表示这些特征,称为人脸嵌入。
FaceNet,它可以直接学习从人脸图像到紧凑的欧几里得空间的映射,其中距离直接对应于人脸相似性的度量。
——FaceNet: A Unified Embedding for Face Recognition and Clustering,2015 年。
该模型是通过三重损失函数训练的深度卷积神经网络,鼓励相同身份的向量变得更相似(距离更小),而不同身份的向量预期变得更不相似(距离更大)。将重点放在训练模型来直接创建嵌入(而不是从模型的中间层提取嵌入),是这项工作中的一个重要创新。
我们的方法使用经过训练的深度卷积网络直接优化嵌入本身,而不是像以前的深度学习方法那样使用中间的瓶颈层。
— FaceNet: A Unified Embedding for Face Recognition and Clustering,2015 年。
然后,将这些人脸嵌入作为在标准人脸识别基准数据集上训练分类器系统的基础,从而得到当时最先进的结果。
与已公布的最好结果相比,我们的系统将错误率降低了 30%……
— FaceNet: A Unified Embedding for Face Recognition and Clustering,2015 年。
这篇论文还探讨了嵌入的其他用途,如基于提取的特征聚类对相似人脸进行分组。
这是一种健壮而有效的人脸识别系统,提取的人脸嵌入的一般性质使得这种方法具有广泛的应用前景。
如何在 Keras 中加载 FaceNet 模型
有许多项目提供了用于训练基于 FaceNet 的模型和利用预训练模型的工具。
也许最著名的叫 OpenFace ,它提供了使用 PyTorch 深度学习框架构建和训练的 FaceNet 模型。有一个到 Keras 的 OpenFace 端口,叫做 Keras OpenFace,但在撰写本文时,这个模型似乎需要 Python 2,这就极大限制了它的应用。
另一个著名的项目是 David Sandberg 的 FaceNet,它提供了利用 TensorFlow 构建和训练的 FaceNet 模型。虽然在撰写本文时还没有提供基于库的安装,也没有提供干净的 API,但这个项目看起来已经成熟。有用的是,David 的项目提供了许多高性能的预训练 FaceNet 模型,并且有许多项目可以移植或转换这些模型,以便能够在 Keras 中使用。
一个值得注意的例子是 Hiroki Taniai 的 Keras FaceNet,他的项目提供了一个脚本,用于将 Inception ResNet v1 模型从 TensorFlow 转换为 Keras。他还提供了预训练 Keras 模型,可供随时使用。
在本教程中,我们将使用 Hiroki Taniai 提供的预训练 Keras FaceNet 模型,它是在 MS-Celeb-1M 数据集上训练的,并要求输入图像是彩色的,其像素值要进行白噪化(在所有三个通道中进行标准化),并且具有 160x160 像素的正方形。
Keras FaceNet 预训练模型 (88 MB) 下载网址如下:
https://drive.google.com/open?id=1pwQ3H4aJ8a6yyJHZkTwtjcL4wYWQb7bn
下载模型文件并将其放在当前工作目录中,文件名为 “facenet_keras.h5”。
我们可以使用 load_model() 函数直接在 Keras 中加载模型,示例代码如下:
复制代码
|
# example of loading the keras facenet model |
|
from keras.models import load_model |
|
# load the model |
|
model = load_model('facenet_keras.h5') |
|
# summarize input and output shape |
|
print(model.inputs) |
|
print(model.outputs) |
运行该示例代码将加载模型并打印输入和输出张量的形状。
我们可以看到,这个模型确实期望将正方形彩色图像作为具有形状为 160x160 的输入,并将输出一个包含 128 个元素向量的人脸嵌入。
复制代码
现在我们有了 FaceNet 模型,我们可以探索如何使用这个模型。
人脸识别中如何检测人脸
在进行人脸识别之前,需要对人脸进行检测。
人脸检测是自动定位照片中的人脸并通过在其范围内绘制边界框来定位这些人脸的过程。
在本教程中,我们还将使用多任务级联卷积神经网络(Multi-Task Cascaded Convolutional Neural Network,MTCNN)进行人脸检测,例如,从照片中查找并提取人脸。这是一种最先进的人脸检测深度学习模型,在 2016 年发表的论文《使用多任务级联卷积网络的联合人脸检测与对齐》(Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks)有所描述。
我们将在 ipazc/mtcnn 项目中使用 Iván de Paz Centeno 提供的实施方案。这也可以通过 pip 进行安装,如下所示:
复制代码
我们可以通过导入库并打印版本来确认库是否已经正确安装,示例代码如下:
复制代码
|
# confirm mtcnn was installed correctly |
|
import mtcnn |
|
# print version |
|
print(mtcnn.__version__) |
运行该示例代码将会打印库的当前版本。
复制代码
我们可以使用 mtcnn 库创建人脸检测器并提取人脸,以便在后续章节中与 FaceNet 人脸检测器模型一起使用。
第一步是以 NumPy 数组的形式加载图像,我们可以使用 PIL 库和 open() 函数来实现。我们还将图像转换为 RGB,以防图像出现 alpha 通道或变成黑白。
复制代码
|
# load image from file |
|
image = Image.open(filename) |
|
# convert to RGB, if needed |
|
image = image.convert('RGB') |
|
# convert to array |
|
pixels = asarray(image) |
接下来,我们可以创建一个 MTCNN 人脸检测器类,并使用它来检测加载的照片中所有的人脸。
复制代码
|
# create the detector, using default weights |
|
detector = MTCNN() |
|
# detect faces in the image |
|
results = detector.detect_faces(pixels) |
结果是一个边界框列表,其中每个边界框定义了边界框的左下角,以及宽度和高度。如果我们假设照片中只有一张人脸用于实验,我们可以确定边界框的像素坐标如下。有时候库会返回负像素索引,我认为这是一个 bug,可以通过取坐标的绝对值来解决这一问题。
复制代码
|
# extract the bounding box from the first face |
|
x1, y1, width, height = results0 |
|
# bug fix |
|
x1, y1 = abs(x1), abs(y1) |
|
x2, y2 = x1 + width, y1 + height |
我们可以使用这些坐标来提取人脸。
复制代码
|
# extract the face |
|
face = pixels[y1:y2, x1:x2] |
然后我们可以使用 PIL 库将这个人脸的小图像调整为所需的尺寸;具体而言,模型需要形状为 160x160 的正方形输入面。
复制代码
|
# resize pixels to the model size |
|
image = Image.fromarray(face) |
|
image = image.resize((160, 160)) |
|
face_array = asarray(image) |
将所有这些结合在一起,函数 extract_face() 将从加载的文件名加载照片,并返回提取的人脸。它假定照片包含一张人脸,并将返回检测到的第一张人脸。
复制代码
|
# function for face detection with mtcnn |
|
from PIL import Image |
|
from numpy import asarray |
|
from mtcnn.mtcnn import MTCNN |
|
|
|
# extract a single face from a given photograph |
|
def extract_face(filename, required_size=(160, 160)): |
|
# load image from file |
|
image = Image.open(filename) |
|
# convert to RGB, if needed |
|
image = image.convert('RGB') |
|
# convert to array |
|
pixels = asarray(image) |
|
# create the detector, using default weights |
|
detector = MTCNN() |
|
# detect faces in the image |
|
results = detector.detect_faces(pixels) |
|
# extract the bounding box from the first face |
|
x1, y1, width, height = results0 |
|
# bug fix |
|
x1, y1 = abs(x1), abs(y1) |
|
x2, y2 = x1 + width, y1 + height |
|
# extract the face |
|
face = pixels[y1:y2, x1:x2] |
|
# resize pixels to the model size |
|
image = Image.fromarray(face) |
|
image = image.resize(required_size) |
|
face_array = asarray(image) |
|
return face_array |
|
|
|
# load the photo and extract the face |
|
pixels = extract_face('...') |
在下一节中,我们可以根据需要使用这个函数来提取人脸,这些人脸可以作为 FaceNet 模型的输入。
如何开发人脸分类系统
在本节中,我们将开发一个人脸检测系统来预测给定人脸的身份。
该模型将使用 5 Celebrity Faces Dataset 进行训练和测试,这个数据集包含五位不同名人的许多照片。
我们将使用 MTCNN 模型进行人脸检测,使用 FaceNet 模型为每个检测到的人脸创建人脸嵌入,然后我们将开发一个线性支持向量机(Linear Support Vector Machine (SVM),SVM)分类器模型来预测给定人脸的身份。
5 Celebrity Faces Dataset
The 5 Celebrity Faces Dataset 是一个包含名人照片的小型数据集。
这个数据集的名人照片包括: Ben Affleck、Elton John、 Jerry Seinfeld、 Madonna" rel="nofollow">https://en.wikipedia.org/wiki/Madonna_(entertainer) 和 Mindy Kaling。
这个数据集是由 Dan Becker 收集整理并提供给 Kaggle,可免费下载。需要注意的是,下载这个数据集需要 Kaggle 账户。
5 Celebrity Faces Dataset, Kaggle.
下载数据集(可能需要 Kaggle 账户登陆),data.zip(3 MB),并将其解压到本地目录中,文件夹名称为“5-celebrity-faces-dataset”。
你现在应该有一个具有以下结构的目录(注意:某些目录名称中存在拼写错误,在本示例代码中这些拼写错误的名称保持原样):
复制代码
|
5-celebrity-faces-dataset |
|
├── train |
|
│ ├── ben_afflek |
|
│ ├── elton_john |
|
│ ├── jerry_seinfeld |
|
│ ├── madonna |
|
│ └── mindy_kaling |
|
└── val |
|
├── ben_afflek |
|
├── elton_john |
|
├── jerry_seinfeld |
|
├── madonna |
|
└── mindy_kaling |
我们可以看到一个训练数据集和验证或测试数据集。
查看目录中的一些照片,我们可以看到这些照片提供了各种方向、光照和各种大小的人脸。重要的是,每张照片都包含一张人脸。
我们将使用这个数据集作为分类器的基础,仅对 train 数据集进行训练,并对 val 数据集中的人脸进行分类。你可以使用相同的结构来开发使用你自己的照片的分类器。
检测人脸
第一步是检测每张照片中的人脸,并将数据集缩减为一系列人脸。
让我们测试一下前面小节中定义的人脸检测器函数,具体来说就是 extract_face()。
在 5-celebrity-faces-dataset/train/ben_afflek/ 文件夹中,我们可以看到在训练数据集中有 Ben Affleck 的 14 张照片。我们可以检测每张照片中的人脸,并创建每张包含 14 个人脸的图像,每张图像有两行,每行有七张照片。
完整的示例代码如下。
复制代码
|
# demonstrate face detection on 5 Celebrity Faces Dataset |
|
from os import listdir |
|
from PIL import Image |
|
from numpy import asarray |
|
from matplotlib import pyplot |
|
from mtcnn.mtcnn import MTCNN |
|
|
|
# extract a single face from a given photograph |
|
def extract_face(filename, required_size=(160, 160)): |
|
# load image from file |
|
image = Image.open(filename) |
|
# convert to RGB, if needed |
|
image = image.convert('RGB') |
|
# convert to array |
|
pixels = asarray(image) |
|
# create the detector, using default weights |
|
detector = MTCNN() |
|
# detect faces in the image |
|
results = detector.detect_faces(pixels) |
|
# extract the bounding box from the first face |
|
x1, y1, width, height = results0 |
|
# bug fix |
|
x1, y1 = abs(x1), abs(y1) |
|
x2, y2 = x1 + width, y1 + height |
|
# extract the face |
|
face = pixels[y1:y2, x1:x2] |
|
# resize pixels to the model size |
|
image = Image.fromarray(face) |
|
image = image.resize(required_size) |
|
face_array = asarray(image) |
|
return face_array |
|
|
|
# specify folder to plot |
|
folder = '5-celebrity-faces-dataset/train/ben_afflek/' |
|
i = 1 |
|
# enumerate files |
|
for filename in listdir(folder): |
|
# path |
|
path = folder + filename |
|
# get face |
|
face = extract_face(path) |
|
print(i, face.shape) |
|
# plot |
|
pyplot.subplot(2, 7, i) |
|
pyplot.axis('off') |
|
pyplot.imshow(face) |
|
i += 1 |
|
pyplot.show() |
运行这段示例代码需要一点时间,并报告全程加载的每个照片的进度以及包含人脸像素数据的 NumPy 数据的形状。
复制代码
|
1 (160, 160, 3) |
|
2 (160, 160, 3) |
|
3 (160, 160, 3) |
|
4 (160, 160, 3) |
|
5 (160, 160, 3) |
|
6 (160, 160, 3) |
|
7 (160, 160, 3) |
|
8 (160, 160, 3) |
|
9 (160, 160, 3) |
|
10 (160, 160, 3) |
|
11 (160, 160, 3) |
|
12 (160, 160, 3) |
|
13 (160, 160, 3) |
|
14 (160, 160, 3) |
将创建一个包含在 Ben Affleck 目录中检测到的人脸的图形。
我们可以看到,每张人脸都被正确地检测到了,并且我们在检测到的人脸中有不同的光照、肤色和方向。
从 the 5 Celebrity Faces Dataset 训练数据集中检测到的 Ben Affleck 的 14 张人脸的图
到目前为止,一切还顺利。
下一步,我们可以扩展这个示例代码,遍历给定数据集的每个子目录(例如“train”或“val”),提取出人脸,并为每个检测到的人脸准备一个以名称作为输出标签的数据集。
下面的 load_faces() 函数将所有人脸加载到给定目录的列表中,例如“5-celebrity-faces-dataset/train/ben_afflek/*”。
复制代码
|
# load images and extract faces for all images in a directory |
|
def load_faces(directory): |
|
faces = list() |
|
# enumerate files |
|
for filename in listdir(directory): |
|
# path |
|
path = directory + filename |
|
# get face |
|
face = extract_face(path) |
|
# store |
|
faces.append(face) |
|
return faces |
我们可以为“train”或“val” 文件夹中的每个子目录调用 load_faces() 函数,每张人脸都有一个标签,至于名人的名字,我们可以从目录名中提取。
下面的 load_dataset() 函数采用目录名称,如“5-celebrity-faces-dataset/train/”,并每个子
目录(名人)检测人脸,为每个检测到的人脸分配标签。
它以 NumPy 数据的形式返回数据集的 X 和 y 元素。
复制代码
|
# load a dataset that contains one subdir for each class that in turn contains images |
|
def load_dataset(directory): |
|
X, y = list(), list() |
|
# enumerate folders, on per class |
|
for subdir in listdir(directory): |
|
# path |
|
path = directory + subdir + '/' |
|
# skip any files that might be in the dir |
|
if not isdir(path): |
|
continue |
|
# load all faces in the subdirectory |
|
faces = load_faces(path) |
|
# create labels |
|
labels = [subdir for _ in range(len(faces))] |
|
# summarize progress |
|
print('>loaded %d examples for class: %s' % (len(faces), subdir)) |
|
# store |
|
X.extend(faces) |
|
y.extend(labels) |
|
return asarray(X), asarray(y) |
然后,我们可以为“train”和“val”文件夹调用这个函数来加载所有的数据,然后通过 savez_compressed()函数将结果保存到一个压缩的 NupPy 数据文件中。
复制代码
|
# load train dataset |
|
trainX, trainy = load_dataset('5-celebrity-faces-dataset/train/') |
|
print(trainX.shape, trainy.shape) |
|
# load test dataset |
|
testX, testy = load_dataset('5-celebrity-faces-dataset/val/') |
|
print(testX.shape, testy.shape) |
|
# save arrays to one file in compressed format |
|
savez_compressed('5-celebrity-faces-dataset.npz', trainX, trainy, testX, testy) |
将所有这些结合在一起,下面列出了检测 5 Celebrity Faces Dataset 数据集中所有人脸的完整示例代码。
复制代码
|
# face detection for the 5 Celebrity Faces Dataset |
|
from os import listdir |
|
from os.path import isdir |
|
from PIL import Image |
|
from matplotlib import pyplot |
|
from numpy import savez_compressed |
|
from numpy import asarray |
|
from mtcnn.mtcnn import MTCNN |
|
|
|
# extract a single face from a given photograph |
|
def extract_face(filename, required_size=(160, 160)): |
|
# load image from file |
|
image = Image.open(filename) |
|
# convert to RGB, if needed |
|
image = image.convert('RGB') |
|
# convert to array |
|
pixels = asarray(image) |
|
# create the detector, using default weights |
|
detector = MTCNN() |
|
# detect faces in the image |
|
results = detector.detect_faces(pixels) |
|
# extract the bounding box from the first face |
|
x1, y1, width, height = results0 |
|
# bug fix |
|
x1, y1 = abs(x1), abs(y1) |
|
x2, y2 = x1 + width, y1 + height |
|
# extract the face |
|
face = pixels[y1:y2, x1:x2] |
|
# resize pixels to the model size |
|
image = Image.fromarray(face) |
|
image = image.resize(required_size) |
|
face_array = asarray(image) |
|
return face_array |
|
|
|
# load images and extract faces for all images in a directory |
|
def load_faces(directory): |
|
faces = list() |
|
# enumerate files |
|
for filename in listdir(directory): |
|
# path |
|
path = directory + filename |
|
# get face |
|
face = extract_face(path) |
|
# store |
|
faces.append(face) |
|
return faces |
|
|
|
# load a dataset that contains one subdir for each class that in turn contains images |
|
def load_dataset(directory): |
|
X, y = list(), list() |
|
# enumerate folders, on per class |
|
for subdir in listdir(directory): |
|
# path |
|
path = directory + subdir + '/' |
|
# skip any files that might be in the dir |
|
if not isdir(path): |
|
continue |
|
# load all faces in the subdirectory |
|
faces = load_faces(path) |
|
# create labels |
|
labels = [subdir for _ in range(len(faces))] |
|
# summarize progress |
|
print('>loaded %d examples for class: %s' % (len(faces), subdir)) |
|
# store |
|
X.extend(faces) |
|
y.extend(labels) |
|
return asarray(X), asarray(y) |
|
|
|
# load train dataset |
|
trainX, trainy = load_dataset('5-celebrity-faces-dataset/train/') |
|
print(trainX.shape, trainy.shape) |
|
# load test dataset |
|
testX, testy = load_dataset('5-celebrity-faces-dataset/val/') |
|
# save arrays to one file in compressed format |
|
savez_compressed('5-celebrity-faces-dataset.npz', trainX, trainy, testX, testy) |
运行这段示例代码可能需要一些时间。
首先,加载“train”数据集中的所有照片,然后提取人脸,得到 93 个样本,其中正方形人脸输入和类标签字符串作为输出。然后加载“val ” 数据集,提供 25 个可用作测试数据集的样本。
然后,将这两个数据集保存到“5-celebrity-faces-dataset.npz”的压缩 NymPy 数组文件中,该文件大约 3MB,存在在当前的工作目录中。
复制代码
|
>loaded 14 examples for class: ben_afflek |
|
>loaded 19 examples for class: madonna |
|
>loaded 17 examples for class: elton_john |
|
>loaded 22 examples for class: mindy_kaling |
|
>loaded 21 examples for class: jerry_seinfeld |
|
(93, 160, 160, 3) (93,) |
|
>loaded 5 examples for class: ben_afflek |
|
>loaded 5 examples for class: madonna |
|
>loaded 5 examples for class: elton_john |
|
>loaded 5 examples for class: mindy_kaling |
|
>loaded 5 examples for class: jerry_seinfeld |
|
(25, 160, 160, 3) (25,) |
该数据次已准备就绪,可提供给人脸检测模型。
创建人脸嵌入
下一步就是创建人脸嵌入。
人脸嵌入是一个向量,表示从人脸中提取的特征。然后,可以将其与为其他人脸生成的向量进行比较,例如,另一个距离较近的向量(通过某种标准)可能是同一个人,而距离较远的较量(通过某种标准)则可能是不同的人。
我们要开发的分类器模型将人脸嵌入作为输入,并预测该人脸的身份。FaceNet 模型将为给定的人脸图像生成此嵌入。
FaceNet 模型可以用作分类其本身的一部分,或者,我们可以用 FaceNet 模型对人脸进行预处理,创建可以存储并用作分类器模型输入的人脸嵌入。首选后一种方法,因为 FaceNet 模型既大又慢,不利于创建人脸嵌入。
因此,我们可以预先计算训练中所有人脸的人脸嵌入,并在我们的 5 Celebrity Faces Dataset 测试集(形式上为“val”)。
首先,我们可以使用 load() NumPy 函数加载检测到的人脸数据集。
复制代码
|
# load the face dataset |
|
data = load('5-celebrity-faces-dataset.npz') |
|
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3'] |
|
print('Loaded: ', trainX.shape, trainy.shape, testX.shape, testy.shape) |
接下来,我们可以加载 FaceNet 模型,准备讲人脸转换为人脸嵌入。
复制代码
|
# load the facenet model |
|
model = load_model('facenet_keras.h5') |
|
print('Loaded Model') |
然后,我们可以枚举训练中的每一张人脸,并测试数据集来预测嵌入。
为了预测嵌入,首先需要适当地准备图像的像素值,以满足 FaceNet 模型的要求。FaceNet 模型的这种特性实现要求像素是标准化的。
复制代码
|
# scale pixel values |
|
face_pixels = face_pixels.astype('float32') |
|
# standardize pixel values across channels (global) |
|
mean, std = face_pixels.mean(), face_pixels.std() |
|
face_pixels = (face_pixels - mean) / std |
为了对 Keras 中的每一个样本进行预测,我们必须扩展维数,使人脸数组成为一个样本。
复制代码
|
# transform face into one sample |
|
samples = expand_dims(face_pixels, axis=0) |
然后,利用该模型进行预测,提取嵌入向量。
复制代码
|
# make prediction to get embedding |
|
yhat = model.predict(samples) |
|
# get embedding |
|
embedding = yhat[0] |
下面定义的 get_embedding() 函数实现了这些行为,并在给定一个人脸的图像和加载的 FaceNet 模型的情况下返回一个人脸嵌入。
复制代码
|
# get the face embedding for one face |
|
def get_embedding(model, face_pixels): |
|
# scale pixel values |
|
face_pixels = face_pixels.astype('float32') |
|
# standardize pixel values across channels (global) |
|
mean, std = face_pixels.mean(), face_pixels.std() |
|
face_pixels = (face_pixels - mean) / std |
|
# transform face into one sample |
|
samples = expand_dims(face_pixels, axis=0) |
|
# make prediction to get embedding |
|
yhat = model.predict(samples) |
|
return yhat[0] |
将所有这些结合在一起,下面列出了将每个人脸转换成嵌入到训练和测试数据集中的人脸的完整示例代码。
复制代码
|
# calculate a face embedding for each face in the dataset using facenet |
|
from numpy import load |
|
from numpy import expand_dims |
|
from numpy import asarray |
|
from numpy import savez_compressed |
|
from keras.models import load_model |
|
|
|
# get the face embedding for one face |
|
def get_embedding(model, face_pixels): |
|
# scale pixel values |
|
face_pixels = face_pixels.astype('float32') |
|
# standardize pixel values across channels (global) |
|
mean, std = face_pixels.mean(), face_pixels.std() |
|
face_pixels = (face_pixels - mean) / std |
|
# transform face into one sample |
|
samples = expand_dims(face_pixels, axis=0) |
|
# make prediction to get embedding |
|
yhat = model.predict(samples) |
|
return yhat[0] |
|
|
|
# load the face dataset |
|
data = load('5-celebrity-faces-dataset.npz') |
|
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3'] |
|
print('Loaded: ', trainX.shape, trainy.shape, testX.shape, testy.shape) |
|
# load the facenet model |
|
model = load_model('facenet_keras.h5') |
|
print('Loaded Model') |
|
# convert each face in the train set to an embedding |
|
newTrainX = list() |
|
for face_pixels in trainX: |
|
embedding = get_embedding(model, face_pixels) |
|
newTrainX.append(embedding) |
|
newTrainX = asarray(newTrainX) |
|
print(newTrainX.shape) |
|
# convert each face in the test set to an embedding |
|
newTestX = list() |
|
for face_pixels in testX: |
|
embedding = get_embedding(model, face_pixels) |
|
newTestX.append(embedding) |
|
newTestX = asarray(newTestX) |
|
print(newTestX.shape) |
|
# save arrays to one file in compressed format |
|
savez_compressed('5-celebrity-faces-embeddings.npz', newTrainX, trainy, newTestX, testy) |
运行示例代码可以报告整个过程的进度。
我们可以看到,正确地加载了人脸数据集,模型也是如此。然后将训练数据集转换成 93 个人脸嵌入,每个嵌入由 128 个元素向量组成。测试数据集中的 25 个样本也被适当地转换为人脸嵌入。
然后将得到的数据集保存到压缩的 NumPy 数组中,该数组大约为 50KB,在当前的工作目录中,名为“5-celebrity-faces-embeddings.npz”
复制代码
|
Loaded: (93, 160, 160, 3) (93,) (25, 160, 160, 3) (25,) |
|
Loaded Model |
|
(93, 128) |
|
(25, 128) |
我们现在准备开发我们的人脸分类系统。
执行人脸分类
在本节中,我们将开发一个模型,将人脸嵌入分类为 5 Celebrity Faces Dataset 中已知的名人之一。
首先,我们必须加载人脸嵌入数据集。
复制代码
|
# load dataset |
|
data = load('5-celebrity-faces-embeddings.npz') |
|
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3'] |
|
print('Dataset: train=%d, test=%d' % (trainX.shape[0], testX.shape[0])) |
接下来,在建模之前,数据需要进行一些小小的准备。
首先,对人脸嵌入向量进行归一化是一种很好的做法。这是一个很好的实践,因为向量通常使用距离度量进行比较。
在这种情况下,向量归一化意味着对这些值进行缩放,直到向量的长度或幅值为 1 或单位长度。这可以通过 scikit-learn 的 Normalizer 类来实现。如果在前面步骤中创建人脸嵌入时,那么执行这一步骤可能会更方便。
复制代码
|
# normalize input vectors |
|
in_encoder = Normalizer(norm='l2') |
|
trainX = in_encoder.transform(trainX) |
|
testX = in_encoder.transform(testX) |
接下来,需要将每个名人姓名的字符串目标变量转换为整数。
这可以通过 scikit-learn 中的 LabelEncoder 类来实现。
复制代码
|
# label encode targets |
|
out_encoder = LabelEncoder() |
|
out_encoder.fit(trainy) |
|
trainy = out_encoder.transform(trainy) |
|
testy = out_encoder.transform(testy) |
接下来,我们可以拟合一个模型。
在处理归一化人脸嵌入输入时,通常使用线性支持向量机(SVM)。这是因为该方法在分离人脸嵌入向量方面非常有效。我们可以使用 scilit-learn 中的 SVC 类将线性 SVM 拟合到训练数据中,并将“kernel”属性设置为“linear”。我们可能还希望在以后进行预测时使用概率,可以通过将“probability”设置为“True”。
复制代码
|
# fit model |
|
model = SVC(kernel='linear') |
|
model.fit(trainX, trainy) |
接下来,我们就可以对模型进行评估了。
这可以通过使用拟合模型对训练和测试数据集中的每个样本进行预测,然后计算分类正确率来实现。
复制代码
|
# predict |
|
yhat_train = model.predict(trainX) |
|
yhat_test = model.predict(testX) |
|
# score |
|
score_train = accuracy_score(trainy, yhat_train) |
|
score_test = accuracy_score(testy, yhat_test) |
|
# summarize |
|
print('Accuracy: train=%.3f, test=%.3f' % (score_train*100, score_test*100)) |
将所有这些结合在一起,下面列出了在 5 Celebrity Faces Dataset 的人脸嵌入上拟合线性 SVM 的完整示例代码。
复制代码
|
# develop a classifier for the 5 Celebrity Faces Dataset |
|
from numpy import load |
|
from sklearn.metrics import accuracy_score |
|
from sklearn.preprocessing import LabelEncoder |
|
from sklearn.preprocessing import Normalizer |
|
from sklearn.svm import SVC |
|
# load dataset |
|
data = load('5-celebrity-faces-embeddings.npz') |
|
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3'] |
|
print('Dataset: train=%d, test=%d' % (trainX.shape[0], testX.shape[0])) |
|
# normalize input vectors |
|
in_encoder = Normalizer(norm='l2') |
|
trainX = in_encoder.transform(trainX) |
|
testX = in_encoder.transform(testX) |
|
# label encode targets |
|
out_encoder = LabelEncoder() |
|
out_encoder.fit(trainy) |
|
trainy = out_encoder.transform(trainy) |
|
testy = out_encoder.transform(testy) |
|
# fit model |
|
model = SVC(kernel='linear', probability=True) |
|
model.fit(trainX, trainy) |
|
# predict |
|
yhat_train = model.predict(trainX) |
|
yhat_test = model.predict(testX) |
|
# score |
|
score_train = accuracy_score(trainy, yhat_train) |
|
score_test = accuracy_score(testy, yhat_test) |
|
# summarize |
|
print('Accuracy: train=%.3f, test=%.3f' % (score_train*100, score_test*100)) |
运行这段示例代码,确认训练和测试数据集中的样本数量正如我们所预期的那样。
然后,在训练和测试数据集上对模型进行了评估,结果表明,该模型具有较好的分类正确率。考虑到数据集的大小以及所使用的人脸检测和人脸识别模型的强大功能,因此能取得这样的结果并不奇怪。
复制代码
|
Dataset: train=93, test=25 |
|
Accuracy: train=100.000, test=100.000 |
我们可以通过绘制原始人脸和预测,让它变得更有趣。
首先,我们需要加载人脸数据集,特别是测试数据集中的人脸。我们还可以加载原始照片,使它更有趣。
复制代码
|
# load faces |
|
data = load('5-celebrity-faces-dataset.npz') |
|
testX_faces = data['arr_2'] |
在我们对模型进行拟合之前,示例代码的其余部分都是相同的。
首先,我们需要从测试集中随机选择一个样本,然后获取嵌入、人脸像素、期望的类预测以及类的相应名称。
复制代码
|
# test model on a random example from the test dataset |
|
selection = choice([i for i in range(testX.shape[0])]) |
|
random_face_pixels = testX_faces[selection] |
|
random_face_emb = testX[selection] |
|
random_face_class = testy[selection] |
|
random_face_name = out_encoder.inverse_transform([random_face_class]) |
接下来,我们可以使用人脸嵌入作为输入,与拟合模型进行单个预测。
我们既可以预测类整数,也可以对预测的概率进行预测。
复制代码
|
# prediction for the face |
|
samples = expand_dims(random_face_emb, axis=0) |
|
yhat_class = model.predict(samples) |
|
yhat_prob = model.predict_proba(samples) |
然后,我们可以得到预测数的名称,以及这个预测的概率。
复制代码
|
# get name |
|
class_index = yhat_class[0] |
|
class_probability = yhat_prob[0,class_index] * 100 |
|
predict_names = out_encoder.inverse_transform(yhat_class) |
然后我们可以打印这些信息。
复制代码
|
print('Predicted: %s (%.3f)' % (predict_names[0], class_probability)) |
|
print('Expected: %s' % random_face_name[0]) |
我们还可以根据预测的名字和概率进行绘制人脸像素。
复制代码
|
# plot for fun |
|
pyplot.imshow(random_face_pixels) |
|
title = '%s (%.3f)' % (predict_names[0], class_probability) |
|
pyplot.title(title) |
|
pyplot.show() |
将所有这些结合在一起,下面列出了用于预测测试数据集中给定未公开照片的身份的完整实例代码。
复制代码
|
# develop a classifier for the 5 Celebrity Faces Dataset |
|
from random import choice |
|
from numpy import load |
|
from numpy import expand_dims |
|
from sklearn.preprocessing import LabelEncoder |
|
from sklearn.preprocessing import Normalizer |
|
from sklearn.svm import SVC |
|
from matplotlib import pyplot |
|
# load faces |
|
data = load('5-celebrity-faces-dataset.npz') |
|
testX_faces = data['arr_2'] |
|
# load face embeddings |
|
data = load('5-celebrity-faces-embeddings.npz') |
|
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3'] |
|
# normalize input vectors |
|
in_encoder = Normalizer(norm='l2') |
|
trainX = in_encoder.transform(trainX) |
|
testX = in_encoder.transform(testX) |
|
# label encode targets |
|
out_encoder = LabelEncoder() |
|
out_encoder.fit(trainy) |
|
trainy = out_encoder.transform(trainy) |
|
testy = out_encoder.transform(testy) |
|
# fit model |
|
model = SVC(kernel='linear', probability=True) |
|
model.fit(trainX, trainy) |
|
# test model on a random example from the test dataset |
|
selection = choice([i for i in range(testX.shape[0])]) |
|
random_face_pixels = testX_faces[selection] |
|
random_face_emb = testX[selection] |
|
random_face_class = testy[selection] |
|
random_face_name = out_encoder.inverse_transform([random_face_class]) |
|
# prediction for the face |
|
samples = expand_dims(random_face_emb, axis=0) |
|
yhat_class = model.predict(samples) |
|
yhat_prob = model.predict_proba(samples) |
|
# get name |
|
class_index = yhat_class[0] |
|
class_probability = yhat_prob[0,class_index] * 100 |
|
predict_names = out_encoder.inverse_transform(yhat_class) |
|
print('Predicted: %s (%.3f)' % (predict_names[0], class_probability)) |
|
print('Expected: %s' % random_face_name[0]) |
|
# plot for fun |
|
pyplot.imshow(random_face_pixels) |
|
title = '%s (%.3f)' % (predict_names[0], class_probability) |
|
pyplot.title(title) |
|
pyplot.show() |
每次运行代码时,都会从测试数据集中选择一个不同的随机样本。
让我们试着运行几次。
在这种情况下,选择 Jerry Seinfeld 的一张照片并做出正确的预测。
复制代码
|
Predicted: jerry_seinfeld (88.476) |
|
Expected: jerry_seinfeld |
此外,还创建了所选人脸的一幅图,在这幅图像的标题中显示了预测的名称和概率。
用支持向量机分类器正确识别 Jerry Seinfeld 的人脸
延伸阅读
如果你希望深入了解,本节将提供更多的参考资源。
论文
- 《FaceNet:用于人脸识别和聚类的统一嵌入》(FaceNet: A Unified Embedding for Face Recognition and Clustering),2015 年
书籍
- 《人脸识别手册》(Handbook of Face Recognition,), 2011 年
项目
-
OpenFace PyTorch Project.
-
OpenFace Keras Project, GitHub.
-
Keras FaceNet Project, GitHub.
-
MS-Celeb 1M Dataset.
API
作者介绍:
Jason Brownlee 博士,机器学习专家,他通过实践教程教授开发人员如何使用现代机器学习方法来获得结果。
原文链接:How to Develop a Face Recognition System Using FaceNet in Keras