deepface:让你的代码轻松地实现人脸识别功能

发布时间 2023-06-22 21:52:49作者: 古明地盆

楔子

在 GitHub 上面发现了一个非常有趣的库,叫 deepface,简直是人间宝藏。这个库主要是做人脸识别和面部属性分析的,它集合了目前全球最顶尖的开源人脸识别算法,使用卷积神经网络(CNN)对图像进行特征提取,以实现高精度的人脸识别。实验表明,人类在面部识别任务上的准确率为 97.53%,而这些模型已经达到并超过了该准确率水平。

而该库提供非常丰富的功能,比如:

  • 人脸检测:deepface 可以在图像中检测出人脸的位置,为后续的人脸识别任务提供基础。
  • 人脸对齐:为了提高识别准确性,deepface 会将检测到的人脸进行对齐操作,消除姿态、光照和表情等因素对识别结果的影响。
  • 特征提取:deepface 使用卷积神经网络(CNN)对齐后的人脸图像进行特征提取,将人脸转换为高维特征向量。
  • 人脸识别:通过比较特征向量之间的相似度,deepface 可以识别出图像中的人脸是否属于同一个人。
  • 人脸验证:deepface 可以用于人脸验证任务,即判断给定的两张人脸图像是否属于同一个人。
  • 人脸搜索:deepface 可以在大型人脸数据库中搜索特定人物,通过比较特征向量找到与目标人物最相似的人脸。
  • 人脸聚类:deepface 可以对人脸进行聚类分析,将相似的人脸分组在一起,以便进行进一步的分析和处理。
  • 人脸属性识别:deepface 还可以识别人脸的一些属性,如性别、年龄、表情等。年龄模型得到 ±4.65 MAE;性别模型可达 97.44% 的准确率、96.29% 的准确率和 95.05% 的召回率。
  • 人脸跟踪:deepface 可以在视频序列中跟踪人脸,实现实时的人脸识别和分析。
  • 人脸年龄分析:deepface 可以估计图像中的人脸年纪。
  • 人脸表情识别:deepface 可以估计图像中的人脸表情。
  • 人种识别:deepface 可以识别出图像中的人脸属于什么人种。
  • 性别分析:deepface 可以识别出图像中的人脸是什么性别。

下面我们就来实际测试一个该库,看看它的效果如何?不过使用之前需要安装,直接 pip install deepface 即可。在安装的时候,会安装一些依赖,比如 OpenCV-Python、Tensorflow 等等,这些库比较大,所以要保证网络通畅,否则耗时会比较长。

对比两张图片上的人脸是否是同一个人

假设我们有两张图片,想判断两张图片上人是否为同一人,要怎么做呢?

当前有两个文件,分别是 yui1.png 和 yui2.png,来验证一下它们是否为同一个人。

from pprint import pprint
# deep 库的所有功能都在 deep.DeepFace 子模块下面
from deepface import DeepFace

# 传入两张图片即可进行对比
result = DeepFace.verify("yui1.png", "yui2.png")
pprint(result)

现代人脸识别流程包括 5 个常见阶段:检测、对齐、归一化、表示和验证。deepface 会在后台处理好一切,我们不需要深入了解其背后的所有过程,只需要用一行代码即可调用它的验证、查找或分析函数。

然后是 DeepFace.verify 函数,它负责对两张图片上的人进行比对,判断是否为同一个人。该函数接收如下参数:

img1_path 和 img2_path:就是两张图片的路径,当然除了路径,还可以是 numpy 数组或 base64 字符串。verify 函数会将图像上的人脸部分表示为向量,然后计算相似度。如果其中一张图片出现了多张脸,比如 img1_path 里面有一张脸,但 img2_path 里面有三张脸,那么每一张脸都会进行比对,找到最相似的那一个。

model_name:deepface 已经集成了大量顶尖的人脸模型用于训练,比如:

"VGG-Face"、"OpenFace"、"Facenet"、"Facenet512"、"DeepFace"、"DeepID"
"Dlib"、"ArcFace"、"SFace"、"Emotion"、"Age"、"Gender"、"Race"

喜欢哪个就用哪个,但是模型需要下载,如果 deepface 检测到当前机器上没有指定的模型,那么会自动下载。所以当前第一次执行上面代码的时候,会下载 VGG-Face 模型(大小有好几百 M)。关于这些模型的区别,有兴趣可以自己了解一下,我们直接使用默认的即可。

detector_backend:检测器后端,负责提供人脸识别算法,因为 deepface 所使用的算法是由其它模块提供的,默认是 opencv。但除了 opencv 之外,还有其它选择。

"opencv", "retinaface", "mtcnn", "ssd", "dlib", "mediapipe"

这些人脸检测器之间的区别,还是很重要的,我们来解释一下。

  • "opencv":最轻量级的人脸检测器,使用不基于深度学习技术的 haar-cascade 算法,因此速度很快,但准确率较低。而为了使 OpenCV 正常工作,需要正面图像,如果脸侧了一下或者局部发生遮挡,准确率就会受到影响。此外也不擅长对眼睛的检测,容易导致对齐问题。目前 DeepFace 使用的默认检测器就是 OpenCV。
  • "dlib":该检测器在后台使用 hog 算法,与 OpenCV 类似,它也不是基于深度学习的,但它的检测和对齐分数相对较高。
  • "ssd":单次检测器,它是一种流行的基于深度学习的检测器,但性能可与 OpenCV 相媲美。只是 SSD 不支持面部特征点,并且依赖于 OpenCV 的眼睛检测模块来对齐,因此尽管其检测性能很高,但对准分数仅为平均水平。
  • "mtcnn":基于深度学习的人脸检测器,并带有面部特征点,所以它的检测和对齐得分都很高但是,但速度比 OpenCV,SSD 和 Dlib 慢。另外 MTCNN 是一种多任务级联卷积神经网络的人脸检测算法,能够同时实现人脸检测、关键点定位和人脸对齐等功能。其对于大尺寸人脸的检测效果较好,并且模型规模相对于 RetinaFace 的较小。
  • "retinaface":一种基于卷积神经网络的人脸检测算法,具有高精度的特点,被公认为是最先进的人脸检测算法,但需要很高的计算能力。相比 MTCNN,检测小尺寸人脸的效果更好。

因此如果你希望结果更加精确,那么使用 RetinaFace 或 MTCNN;如果希望检测速度更快,比如清洗一部分没有人脸的照片,那么可以使用 OpenCV 或 SSD。

distance_metric:距离(面部嵌入)度量方法,可以是 cosine、euclidean 或 euclidean_l2。

enforce_detection:如果没有检测到人脸时,是否引发异常,可以将其设置为 False。

align:是否执行面部对齐。

normalization:用于预处理图像的归一化技术。

from pprint import pprint
from deepface import DeepFace

result = DeepFace.verify("yui1.png", "yui2.png")
pprint(result)
"""
{'detector_backend': 'opencv',
 'distance': 0.2437701450344817,
 'facial_areas': {'img1': {'h': 115, 'w': 115, 'x': 73, 'y': 408},
                  'img2': {'h': 327, 'w': 327, 'x': 159, 'y': 38}},
 'model': 'VGG-Face',
 'similarity_metric': 'cosine',
 'threshold': 0.4,
 'time': 1.25,
 'verified': True}
"""

返回的是一个字典,里面有两个重要字段:

  • verified:表示比对结果,即两张图片上的人是否为同一人。
  • facial_areas:由矩形表示的面部区域,x、y 表示矩形的左上顶点的坐标,w 和 h 分别表示矩形的宽度和高度。

我们修改一下代码,将人脸所在的矩形描绘出来。

from deepface import DeepFace
import cv2

result = DeepFace.verify("yui1.png", "yui2.png",
                         detector_backend="mtcnn")

if not result["verified"]:
    print("人脸不一致")
    exit(0)

# 获取人脸位置坐标
rect_img1 = result["facial_areas"]["img1"]
rect_img2 = result["facial_areas"]["img2"]

# cv2 将图片读取进来之后,会得到一个三维数组
# 第一个维度表示图片的每一行,第二个维度表示图片的每一列,第三个维度就是图片的像素点
im1 = cv2.imread("yui1.png")
im2 = cv2.imread("yui2.png")
# 将人脸所在的矩形绘制出来,调用 cv2.rectangle 函数,该函数接收 5 个参数
# 分别是:原始图像、矩形的左上顶点、矩形的右下顶点、线条颜色、线条粗细
cv2.rectangle(
    im1, (rect_img1["x"], rect_img1["y"]),
    (rect_img1["x"] + rect_img1["w"], rect_img1["y"] + rect_img1["h"]),
    (0, 255, 0),  3
)
cv2.rectangle(
    im2, (rect_img2["x"], rect_img2["y"]),
    (rect_img2["x"] + rect_img2["w"], rect_img2["y"] + rect_img2["h"]),
    (255, 0, 0),  3
)
cv2.imshow("yui1.png", im1)
cv2.imshow("yui2.png", im2)
cv2.waitKey(0)

看一下结果:

你也可以将 detector_backend 换成别的,看看效果。首先换成 retinaface,结果也是正常的,但如果换成 opencv 结果就有问题了。换成 opencv 的话,也是提示人物是同一人,但第一张图片在返回面部所在矩形位置的时候出现了很大的问题,它返回的位置是人物手中的饮料所在的矩形区域。

这就有点离谱了,当然这只是我当前的结果是这样的,总之建议在识别的时候使用 MTCNN 和 Retinaface。

补充:图片的路径不能包含中文,或者说图片的路径必须是 ASCII 字符。

查找一张图片上的人脸所在的位置

现在有一张图片,如果让你绘制出人脸所在的矩形区域,要怎么做呢?

from deepface import DeepFace
import cv2

im = cv2.imread(r"nigehaji.png")

# 除了传递路径,也可以传一个 Numpy 数组
# 因为一张图片可能包含多张人脸,所以返回的是一个列表,列表的每个元素是字典
results = DeepFace.extract_faces(
    im,
    detector_backend="retinaface",
    enforce_detection=False)

# 每个字典里面有如下字段:
# face: 一个 Numpy 数组,对应人脸的部分,大小可以通过 extract_faces 里面的 target_size 指定
#       此外 extract_faces 里面还有一个 grayscale 参数,表示 face 对应的图像是否为灰度图(默认 False)
# facial_area: 脸部所在的矩形区域
# confidence: 准确率,有多大把握确定该位置是人脸
for result in results:
    facial_area = result["facial_area"]
    cv2.rectangle(
        im, (facial_area["x"], facial_area["y"]),
        (facial_area["x"] + facial_area["w"], facial_area["y"] + facial_area["h"]),
        (0, 255, 0),  1
    )

cv2.imshow("nigehaji.png", im)
cv2.waitKey(0)

图片上的人脸能否全部找出来呢?看一下结果。

功能还是比较强的,然后注意第一张图片,里面其实有 3 个人脸,我一开始都没看出来。

判断图片上的人脸是否在现有的数据集中

这也是一个比较常用的功能,比如你是一个公司老总,手下员工无数,但信息、照片都存储在你的系统中。但某天有个在逃犯人被摄像头排到了脸,警察问你这个人是不是你公司的员工,你要怎么做呢?很简单,直接拿这个犯人的照片和手下的员工的照片挨个进行比对即可。针对这个需求,你可以依次读取每个员工的图像,然后调用 DeepFace.verify 函数,但 deepface 提供了一个更简单的函数。

可以调用 DeepFace.find 函数,第一个参数就是图片,第二个参数是目录,里面包含了一系列的图片,会依次进行比对。然后里面的 silent 参数表示是否禁用一些日志记录和进度条,至于其它参数我们都说过了。

然后返回值是一个列表,列表元素是 pandas.DataFrame,里面包含如下字段:VGG-Face_cosine、identity(图片路径)、source_h、source_w、source_x、source_y。

需要注意的是,该函数执行完后,会在图片目录里面生成一个 pkl 文件。如果后续目录里面的图片发生了变化,那么需要先将 pkl 文件删除,然后才能执行,否则报错。

判断图片上人物的年龄、情绪等

deepface 还有一个很强的功能,就是它可以推测出人物的年龄、情绪以及人种等等。

from deepface import DeepFace
import cv2

im = cv2.imread(r"C:\Users\86185\Desktop\moon\nigehaji.png")

results = DeepFace.analyze(
    im,
    detector_backend="mtcnn",
    enforce_detection=False)

返回值是一个列表,列表里面是字典,每个字典对应一个人物的相关属性。

{ 'age': 28,  # 年龄
  'dominant_emotion': 'surprise',  # 表示人脸的主要情绪
  'dominant_gender': 'Woman',  # 人脸的主要性别
  'dominant_race': 'asian',  # 人脸的主要种族
  # 情绪有多种可能,所以 emotion 表示每种情绪的概率,而概率最高的会用 dominant_emotion 单独保存
  'emotion': {'angry': 0.005373688327381387,
              'disgust': 3.8977976402065906e-08,
              'fear': 20.51267772912979,
              'happy': 0.006043683970347047,
              'neutral': 4.0025247471930925e-05,
              'sad': 8.856896016595783e-06,
              'surprise': 79.47585582733154},
  # 每种性别的概率,概率最高会用 dominant_gender 单独保存
  'gender': {'Man': 2.714412286877632, 'Woman': 97.2855806350708},
  # 每个人种的概念,概率最高的会用 dominant_race 单独保存
  'race': {'asian': 63.200453710583076,
           'black': 0.10846455762324081,
           'indian': 0.21811715695960265,
           'latino hispanic': 3.3837137898036116,
           'middle eastern': 2.7975549726220947,
           'white': 30.291696208220493},
  # 位置
  'region': {'h': 47, 'w': 37, 'x': 143, 'y': 262}}

然后我们测试一下:

from deepface import DeepFace
import cv2

im = cv2.imread(r"nigehaji.png")
# 图片有点小,为了有显示文字的空间,这里放大两倍
# 从这里也能看出,即便图片放大了,对识别依旧没有什么影响
im = cv2.resize(im, (1280, 960))

results = DeepFace.analyze(
    im,
    detector_backend="mtcnn",
    enforce_detection=False)

for result in results:
    age = result["age"]
    emotion = result["dominant_emotion"]
    gender = result["dominant_gender"]
    race = result["dominant_race"]
    region = result["region"]

    # 绘制人脸矩形
    cv2.rectangle(
        im, (region["x"], region["y"]),
        (region["x"] + region["w"], region["y"] + region["h"]),
        (255, 0, 0), 1
    )
    # 绘制文字,该函数接收 9 个参数
    # 参数一: 原始图像;参数二: 绘制的文字内容;参数三: 在什么位置开始绘制
    # 参数四: 字体;参数五: 文字大小,等于字体的基础大小乘上传入的值,比如传入 1.5 表示放大 1.5 倍
    # 参数六: 文字颜色;参数七: 文字的线条粗细;
    # 参数八: 线条类型,可取值为 cv2.LINE_4、cv2.LINE_8 分别表示 8 邻接连接线、4 邻接连接线
    #        但是更推荐使用 cv2.LINE_AA,它是反锯齿连接线,背后采用了高斯滤波
    cv2.putText(im,
                f"age: {age}, emotion: {emotion}, gender: {gender}, race: {race}",
                (region["x"] - 135, region["y"]),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.6,
                (161, 16, 203),
                1,
                cv2.LINE_AA)

cv2.imshow("...", im)
cv2.waitKey(0)

总的来说,识别效果还是不错的。然后 analyze 函数默认会分析年龄、性别、情绪、人种,如果你不想分析这么多,也可以通过参数指定。

def analyze(
    img_path,
    actions=("emotion", "age", "gender", "race"),
    enforce_detection=True,
    detector_backend="opencv",
    align=True,
    silent=False,
):

比如你只想分析年龄和情绪,那么就指定 actions 为 ("age", "emotion") 即可。

小结

以上就是该模块的用法,还是很有趣的。如果你对背后的原理感兴趣,那么可以尝试去了解一下。