使用Python进行人脸聚类的详细教程

编译:chux

出品:ATYUN订阅号

这篇文章的灵感来自读者Leonard Bogdonoff的一个问题:

你好Adrian,能介绍下身份聚类吗?我有一个照片数据集,但我无法确定如何处理它们来识别特定的人。

类似这种“人脸聚类”或者说“身份聚类”的应用可用于辅助执法。

思考下面这个场景:两名劫匪在抢劫波士顿或纽约等繁华城市的银行。银行的安全摄像头工作正常,捕捉到了抢劫行为,但劫匪戴着头套,没办法看到他们的脸。

劫匪将现金藏在衣服下面逃离银行,摘掉面具,并将它们扔在附近的垃圾桶中,以免在公众场合显得可疑。

那么,他们会逃脱追责吗?也许会。

但安装在附近的加油站,餐馆和红灯/主要交叉路口的安全摄像头捕获了附近的所有行人活动。

在警察到达之后,他们可以利用人脸聚类来查找该区域内所有视频信息的所有独特的面孔 – 得到独特的面孔,可以:(1)手动调查它们并将它们与银行出纳员描述进行比较,(2)运行自动搜索将面孔与已知的罪犯数据库进行比较,或者(3)找好的刑警寻找可疑人员。

这当然是一个虚构的例子,但我希望你看到人脸聚类在现实世界中使用的价值。

使用Python进行人脸聚类

人脸识别和人脸聚类并不相同,但概念高度相关。当进行面部识别时,我们使用监督学习,其中我们同时具有(1)我们想要识别的面部的示例图像,以及(2)与每个面部相对应的名字(即,“类标签”)。

但对人脸聚类,我们需要执行无监督学习,我们只有没有名字或者说标签的人脸本身。从这里,我们需要识别和计算数据集中某些独特的人。

在本文的第一部分中,我们将讨论我们的人脸聚类数据集以及我们将用于构建项目的项目结构。

在这里,我将帮助你编写两个Python脚本:

  1. 一个用于提取和量化数据集中的人脸
  2. 另一个是对面部进行聚类,其中每个结果聚类(理想情况下)代表一个独特的个体

然后,我们将在样本数据集上运行我们的人脸聚类管道并检查结果。

配置开发环境

参考:https://www.pyimagesearch.com/2018/06/18/face-recognition-with-opencv-python-and-deep-learning/

以下是你在Python环境中需要的所有内容:

  • OpenCV
  • dlib
  • face_recognition
  • imutils
  • scikit-learn

如果有GPU,则需要安装带有CUDA的dlib。

我们的人脸聚类数据集

由于2018年世界杯半决赛,我认为将人脸聚类应用于著名足球运动员的面孔会很有趣。

从上面的图1中可以看出,我已经整理了五个足球运动员的数据集,包括:

  • 穆罕默德·萨拉赫
  • 内马尔
  • C罗
  • 里奥·梅西
  • 路易斯·苏亚雷斯

数据集中有129个图像。

我们的目标是提取量化图像中每个面部的特征,并将得到的“面部特征向量”聚类。理想情况下,每个足球运动员都拥有自己的簇,仅包含他们自己的脸。

面对集群项目结构

我们的项目结构如下:

$ tree--dirsfirst
.
├── dataset [129 entries]
│   ├──00000000.jpg
│   ├──00000001.jpg
│   ├──00000002.jpg
│   ├── ...
│   ├──00000126.jpg
│   ├──00000127.jpg
│   └──00000128.jpg
├── encode_faces.py
├── encodings.pickle
└── cluster_faces.py
1 directory,132 files

我们的项目有一个目录和三个文件:

  • dataset / :包含我们五个足球运动员的129张照片。请注意,在上面的输出中,文件名或其他文件中没有用于标识每个图像中的人员标识信息!根据文件名单独知道哪个足球运动员在哪个图像中是不可能的。我们将设计一个人脸聚类算法来识别数据集中相似且唯一的脸。
  • encode_faces .py :第一个脚本,它为数据集中的所有的人脸计算面部嵌入并输出一个序列化的编码文件。
  • encodings.pickle :我们的面部嵌入序列化的pickle文件。
  • cluster_faces .py :在这个脚本中我们将聚类相似的人脸并找到异常值。

通过深度学习编码面孔

为了用数字表示人脸,我们用神经网络生成的128维特征向量对数据集中的所有人脸进行量化。

在我们对一组人脸进行聚类之前,我们首先需要对它们进行量化。这个量化人脸的过程将使用深度神经网络完成,该网络负责:

  • 接受输入图像
  • 并输出128维特征向量,量化人脸

我将讨论这个深度神经网络如何工作以及如何进行训练。我们的encode_faces .py 脚本包含为每张脸提取128维特征向量表示的所有代码。

要查看此进程的执行方式,请创建名为encode_faces .py的文件 ,并插入以下代码:

# import the necessary packages
from imutilsimport paths
import face_recognition
import argparse
import pickle
import cv2
import os
# construct the argument parser and parse the arguments
ap= argparse.ArgumentParser()
ap.add_argument("-i","--dataset", required=True,
    help="path to input directory of faces + images")
ap.add_argument("-e","--encodings", required=True,
    help="path to serialized db of facial encodings")
ap.add_argument("-d","--detection-method",type=str, default="cnn",
    help="face detection model to use: either `hog` or `cnn`")
args= vars(ap.parse_args())

我们所需的包在2-7行导入。注意:

  • paths,来自我的imutils包
  • face_recognition,(https://github.com/ageitgey/face_recognition)

然后,我们解析命令行参数上(10-17):

  • –dataset:人脸和图像输入目录的路径。
  • –encodings:包含面部编码的输出序列化pickle文件的路径。
  • –detection-method:你可以使用卷积神经网络(CNN)或方向梯度直方图(HOG)方法在量化面部之前检测输入图像中的人脸。CNN方法更准确(但更慢),而HOG方法更快(但不太准确)。

我还要提到的是,如果你认为这个脚本运行缓慢,或者你希望在没有GPU的情况下实时运行人脸聚类,可以将–detection-method设置为hog ,替代cnn。虽然CNN脸检测更准确,但在没有GPU运行实时检测速度太慢。

让我们抓取输入数据集中所有图像的路径:

# grab the paths to the input images in our dataset, then initialize
# out data list (which we'll soon populate)
print("[INFO] quantifying faces...")
imagePaths= list(paths.list_images(args["dataset"]))
data= []

在第4行,我们使用命令行参数中提供的数据集路径创建数据集中所有的imagePath列表 。

然后,初始化我们的data列表,我们稍后会填充图像路径,边界框和面部编码。

让我们开始遍历所有的imagePaths:

# loop over the image paths
for (i, imagePath)in enumerate(imagePaths):
    # load the input image and convert it from RGB (OpenCV ordering)
    # to dlib ordering (RGB)
    print("[INFO] processing image {}/{}".format(i+ 1,
        len(imagePaths)))
    print(imagePath)
    image= cv2.imread(imagePath)
    rgb= cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

在 第2行,我们开始遍历imagePaths并继续加载(第8行)。然后我们在图像中交换颜色通道, 因为dlib默认rgb排序而不是OpenCV默认的bgr(第9行)。

现在已经处理了图像,让我们检测所有的人脸并抓取它们的边界框坐标:

# detect the (x, y)-coordinates of the bounding boxes
# corresponding to each face in the input image
boxes= face_recognition.face_locations(rgb,
    model=args["detection_method"])

我们必须先检测图像中人脸的实际位置,然后再对其进行量化(3-4)。你会注意到 face_recognition API非常易于使用。

注意: 我们使用CNN面部检测器以获得更高的精度,但如果使用的是CPU而不是GPU,则运行时间会长得多。如果希望编码脚本运行得更快或系统运行更快,并且你的系统没有足够的内存或CPU支持CNN面部检测器,请改用HOG + Linear SVM方法。

让我们来看看这个脚本主要部分。在下一个部分中,我们将计算面部编码:

# compute the facial embedding for the face
encodings= face_recognition.face_encodings(rgb, boxes)
# build a dictionary of the image path, bounding box location,
# and facial encodings for the current image
d= [{"imagePath": imagePath,"loc": box,"encoding": enc}
    for (box, enc)in zip(boxes, encodings)]
data.extend(d)

在这里,我们计算rgb图像中每个检测到的人脸的128维面部编码(第2行)。

对于每个检测到的面部+编码,我们构建一个字典(第6和7行),其中包括:

  1. 输入图像的路径
  2. 图像中人脸的位置(即边界框)
  3. 128维编码本身

然后我们将字典添加到我们的data列表中(第8行)。稍后当我们想要查看哪些人脸属于哪个簇时,我们会用到此信息。

要结束此脚本,我们只需将数据列表写入序列化的pickle文件:

# dump the facial encodings data to disk
print("[INFO] serializing encodings...")
f= open(args["encodings"],"wb")
f.write(pickle.dumps(data))
f.close()

使用我们的命令行参数 args [ “encodings” ] 作为路径+文件名,我们将数据列表作为序列化的pickle文件写入磁盘(第3-5行)。

运行面部编码脚本

请访问文末链接下载代码和图像数据集,数据集可以换成自己的。

然后,打开一个终端并激活你的Python虚拟环境(如果你用了虚拟环境的话)。

然后,使用两个命令行参数,执行脚本编码球员的脸,如下:

$ python encode_faces.py--dataset dataset--encodings encodings.pickle
[INFO] quantifying faces...
[INFO] processing image1/129
dataset/00000038.jpg
[INFO] processing image2/129
dataset/00000010.jpg
[INFO] processing image3/129
dataset/00000004.jpg
...
[INFO] processing image127/129
dataset/00000009.jpg
[INFO] processing image128/129
dataset/00000021.jpg
[INFO] processing image129/129
dataset/00000035.jpg
[INFO] serializing encodings...

此过程可能需要一段时间,你可以用终端输出跟踪进度。

如果你使用GPU,大约1-2分钟。只要确保你安装DLIB与CUDA,把你的GPU的优势发挥出来。

但是,如果只使用CPU在笔记本电脑上执行脚本,则脚本可能需要运行20-30分钟。

聚类面孔

现在我们已经将数据集中的所有的人脸都量化并编码为128维向量,下一步就是将它们聚类成组。

我们希望每个人都有自己独立的簇。问题是,许多聚类算法,如k-means和Hierarchical Agglomerative Clustering,要求我们提前指定簇的数量。

在这个例子中,我们知道只有五个足球运动员,但在实际应用中,你可能并不知道数据集中有多少个人。

因此,我们需要使用基于密度或基于图的聚类算法,这样的算法不仅可以聚类数据点,还可以根据数据密度确定聚类数量。

对于人脸聚类,我推荐使用两种算法:

  1. Density-based spatial clustering of applications with noise (DBSCAN)
  2. Chinese whispers聚类

我们将在本教程中使用DBSCAN,因为我们的数据集相对较小。对于真正庞大的数据集,应该考虑使用Chinese whispers 算法,因为它是time linear的(详见wiki:Chinese Whispers)。

DBSCAN算法通过将在n维空间中紧密排列的点分组 。靠在一起的点被分到同一个簇中。

DBSCAN也可以轻易的处理异常值,如果它们落在他们的“最近邻”很远的低密度区域,则标记它们。

让我们继续使用DBSCAN实现人脸聚类。

打开一个新文件,将其命名为cluster_faces .py ,然后插入以下代码:

# import the necessary packages
from sklearn.clusterimport DBSCAN
from imutilsimport build_montages
import numpy as np
import argparse
import pickle
import cv2
# construct the argument parser and parse the arguments
ap= argparse.ArgumentParser()
ap.add_argument("-e","--encodings", required=True,
    help="path to serialized db of facial encodings")
ap.add_argument("-j","--jobs",type=int, default=-1,
    help="# of parallel jobs to run (-1 will use all CPUs)")
args= vars(ap.parse_args())

DBSCAN内置在scikit-learn中。我们在第2行导入DBSCAN实现 。

我们还从imutils导入build_montages从模块(3行)。我们将使用此函数为每个簇构建“蒙太奇的脸”。

我们的其他导入在第4-7行 。

我们解析两个命令行参数:

  • –encodings:我们在之前的脚本中生成的编码pickle文件的路径。
  • –jobs:DBSCAN是多线程的,可以将参数传递给包含要运行的并行作业数的构造函数。值 – 1 的意思为使用所有可用的CPU(也是该命令行参数的默认值)。

让我们加载面部嵌入数据:

# load the serialized face encodings + bounding box locations from
# disk, then extract the set of encodings to so we can cluster on
# them
print("[INFO] loading encodings...")
data= pickle.loads(open(args["encodings"],"rb").read())
data= np.array(data)
encodings= [d["encoding"]for din data]

在这个块中我们有:

  • 从磁盘加载面部编码的data(第5行)。
  • 将data处理为NumPy数组(第6行)。
  • 从data中提取128维编码 ,将它们放在一个列表中(第7行)。

现在我们可以 在下一个代码块中对编码进行聚类 :

# cluster the embeddings
print("[INFO] clustering...")
clt= DBSCAN(metric="euclidean", n_jobs=args["jobs"])
clt.fit(encodings)
# determine the total number of unique faces found in the dataset
labelIDs= np.unique(clt.labels_)
numUniqueFaces= len(np.where(labelIDs >-1)[0])
print("[INFO] # unique faces: {}".format(numUniqueFaces))

为了对编码进行聚类,我们只需创建一个DBSCAN 对象,然后 将模型fit(拟合)到encodings本身(第3和4行)。

现在让我们确定数据集中的独特人类!

第7行, clt 。labels_ 包含数据集中所有人脸的标签ID(即每个人脸所属的簇)。要查找独特面孔或标签的ID,我们只需使用NumPy的unique功能。结果是唯一的labelIDs列表 。

在 第8行, 我们计算numUniqueFaces 。有可能是值 – 1 ,在labelIDs中这个值对应于“异常值”类,即128维嵌入远离添加好的其他簇很多的点。这些点被称为“异常值”(或者说,离群值),根据人脸聚类的应用它可能值得研究或简单地丢弃。

在我们的例子中,我们设计 了计数中的负的labelID,因为我们知道我们的数据集只包含5个人的图像。是否这样做在很大程度上取决于你的项目。

我们接下来的三个代码块的目标是在我们的数据集中生成独特的球员的面部蒙太奇。

循环遍历所有独特的labelID :

# loop over the unique face integers
for labelIDin labelIDs:
    # find all indexes into the `data` array that belong to the
    # current label ID, then randomly sample a maximum of 25 indexes
    # from the set
    print("[INFO] faces for face ID: {}".format(labelID))
    idxs= np.where(clt.labels_== labelID)[0]
    idxs= np.random.choice(idxs, size=min(25,len(idxs)),
        replace=False)
    # initialize the list of faces to include in the montage
    faces= []

在第7-9行, 我们找到当前labelID的所有索引 ,然后抓取最多25个图像的随机样本嵌入蒙太奇中。

face列表白喊面部图像本身(10行)。我们需要另一个循环来填充此列表:

# loop over the sampled indexes
for iin idxs:
    # load the input image and extract the face ROI
    image= cv2.imread(data[i]["imagePath"])
    (top, right, bottom, left)= data[i]["loc"]
    face= image[top:bottom, left:right]
    # force resize the face ROI to 96x96 and then add it to the
    # faces montage list
    face= cv2.resize(face, (96,96))
    faces.append(face)

我们开始在随机样本中循环遍历所有的idx(第2行)。

在循环的第一部分内,我们:

  • 从磁盘加载image并使用在我们的面部嵌入步骤中找到的边界框坐标提取face ROI(第4-6行)。
  • 调整人脸到固定尺寸96×96(行10),所以我们可以把它添加到脸部蒙太奇(11行)用于可视化每个簇。

要完成我们的最外层的循环,让我们构建蒙太奇并将其显示在屏幕上:

# create a montage using 96x96 "tiles" with 5 rows and 5 columns
montage= build_montages(faces, (96,96), (5,5))[0]
# show the output montage
title= "Face ID #{}".format(labelID)
title= "Unknown Faces" if labelID== -1 else title
cv2.imshow(title, montage)
cv2.waitKey(0)

我们采用 build_montages 的功能imutils以生成单个图像 蒙太奇 含有5×5的网格 面 (2线)。

从那里,我们 标题 窗口(第5和6行),然后 在我们的屏幕上显示窗口中的蒙太奇。

只要OpenCV打开的窗口打开,你可以按一个键显示下一个人脸蒙太奇。

面对聚类结果

此脚本只需要一个命令行参数 – 编码文件的路径。要为执行人脸聚类,只需在终端中输入以下命令:

$ python cluster_faces.py--encodings encodings.pickle
[INFO] loading encodings...
[INFO] clustering...
[INFO]# unique faces: 5
[INFO] facesfor faceID:-1
[INFO] facesfor faceID:0
[INFO] facesfor faceID:1
[INFO] facesfor faceID:2
[INFO] facesfor faceID:3
[INFO] facesfor faceID:4

识别出五个人脸簇的类。face ID为-1包含找到的所有异常值。你将在屏幕上看到群集蒙太奇。按键生成下一个面部簇的蒙太奇(窗口处于焦点位置,以便OpenCV的highgui模块可以捕获你的按键)。

以下是我们的128维面部嵌入和DBSCAN聚类算法在我们的数据集上生成的人脸聚类:

最后,陌生的人类被挑了出来(实际上它是先显示的):

这张梅西的照片并没有被聚类成功,而是识别为一张“未知的面孔”。我们的Python人脸聚类算法很好地完成了对图像的聚类,只是对这个人脸图像进行了错误的聚类。

在我们数据集中的5个人的129张图像中,只有一张脸没有被分组到现有的簇中。

我们的无监督学习DBSCAN方法生成了五个簇。不幸的是,梅西有一个图片并没有与他的其他图片放在一起,但整体来说这种方法效果不错。

原文发布于微信公众号 - ATYUN订阅号(atyun_com)

原文发表时间:2018-07-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏从流域到海域

A Gentle Introduction to Autocorrelation and Partial Autocorrelation (译文)

A Gentle Introduction to Autocorrelation and Partial Autocorrelation 自相关和偏自相关的简单...

3036
来自专栏新智元

【重磅】Jeff Dean等提出自动化分层模型,优化CPU、GPU等异构环境,性能提升超 60%

谷歌大脑Jeff Dean等人最新提出一种分层模型,这是一种灵活的端到端方法,用于优化CPU、GPU等的自动化设备配置。该方法在多个主要神经网络模型上测...

3377
来自专栏深度学习入门与实践

【深度学习系列】PaddlePaddle可视化之VisualDL

  上篇文章我们讲了如何对模型进行可视化,用的keras手动绘图输出CNN训练的中途结果,本篇文章将讲述如何用PaddlePaddle新开源的VisualDL来...

4309
来自专栏深度学习之tensorflow实战篇

关于决策树ID3算法,熵,信息增益率的权威解释,稍后奉上python代码

决策树分类算法概述 决策树算法是从数据的属性(或者特征)出发,以属性作为基础,划分不同的类。 看了本书,叫知识发现,内容很好,果断扫描 这里...

3604
来自专栏机器之心

业界 | MXNet开放支持Keras,高效实现CNN与RNN的分布式训练

1883
来自专栏人工智能头条

详解TensorBoard如何调参

1023
来自专栏奇点大数据

阿里巴巴最新实践:TVM+TensorFlow优化GPU上的神经机器翻译

本文是阿里巴巴 PAI-Blade 团队发表于 TVM 的最新博文,文中阐述了如何将 TVM 引入 TensorFlow,使 TensorFlow 中的 bat...

5415
来自专栏人工智能

TensorFlow实战——图像分类神经网络模型

Learn how to classify images with TensorFlow 使用TensorFlow创建一个简单而强大的图像分类神经网络模型 by...

4156
来自专栏机器学习算法工程师

深度学习必备---用Keras和直方图均衡化---数据增强

作者:王抒伟 编辑:王抒伟 算了 爱看多久看多久 在读这技术文章之前,请大家想象一个标准河南口音的娃在读这篇文章,那么你不知不觉,你的嘴角就上扬咯。 俺、...

2K4
来自专栏ATYUN订阅号

自相关与偏自相关的简单介绍

自相关和偏自相关图在时间序列分析和预测中经常使用。这些图生动的总结了一个时间序列的观察值与他之前的时间步的观察值之间的关系强度。初学者要理解时间序列预测中自相关...

6574

扫码关注云+社区

领取腾讯云代金券