OpenCV-Python实战(17)——人脸识别详解

x33g5p2x  于2021-11-09 转载在 Python  
字(9.3k)|赞(0)|评价(0)|浏览(1035)

0. 前言

人脸处理是人工智能中的一个热门话题,人脸处理可以使用计算机视觉算法从人脸中自动提取大量信息,例如身份、意图和情感。随着计算机视觉、机器学习和深度学习的发展,人脸识别已经成为一个热门话题。在本文中,我们介绍 OpenCV 提供的与人脸识别相关的函数,同时还将探索一些用于人脸识别的深度学习方法,这些方法可以轻松集成到计算机视觉项目中以实现高精度的人脸识别。

1. 人脸识别简介

人脸识别具有广泛的应用前景,包括犯罪预防、智能监视以及社交网络。但自动人脸识别同样面临多种挑战,例如遮挡、装束变化、表情、年龄老化等。继在对象识别方面取得成功之后,CNN 已被广泛用于人脸识别。

2. 使用 OpenCV 进行人脸识别

OpenCV 提供了三种不同的实现来执行人脸识别

  • Eigenfaces
  • Fisherfaces
  • Local Binary Patterns Histograms (LBPH)

这些实现以不同的方式执行人脸识别。但是,我们仅需更改识别器的创建方式就可以独立于其内部算法使用它们了:

# 创建识别器
face_recognizer = cv2.face.LBPHFaceRecognizer_create()
face_recognizer = cv2.face.EigenFaceRecognizer_create()
face_recognizer = cv2.face.FisherFaceRecognizer_create()

一旦创建,其将独立于特定内部算法,均可以分别使用方法 train()predict() 来执行人脸识别系统的训练和测试,使用这些方法的方式与创建的识别器无关。
因此,可以很容易对比这三种识别器,然后针对特定任务选择性能最佳的识别器。但在涉及不同环境和光照条件的户外识别图像时,LBPH 通常比另外两种方法具有更好的性能。此外,LBPH 人脸识别器支持 update() 函数,可以在该方法中根据新数据更新人脸识别器,但 EigenfacesFisherfaces 方法并不支持 update() 函数。
为了训练识别器,应该调用 train() 方法:

face_recognizer.train(faces, labels)

cv2.face_FaceRecognizer.train(src, labels) 方法训练具体的人脸识别器,其中 src 表示图像(人脸)训练集,参数 labels 为训练集中的每张图像对应的标签。
要识别新面孔,应调用 predict() 方法:

label, confidence = face_recognizer.predict(face)

cv2.face_FaceRecognizer.predict(src) 方法通过输出预测的标签和相应置信度来输出(预测)对新 src 图像的识别结果。
OpenCV 还提供了 write()read() 方法用于保存创建的模型和加载之前创建的模型。对于这两种方法,文件名参数设置要保存或加载的模型的名称:

cv2.face_FaceRecognizer.write(filename)
cv2.face_FaceRecognizer.read(filename)

如果使用的是 LBPH 人脸识别器,可以使用 update() 方法进行更新:

cv2.face_FaceRecognizer.update(src, labels)

其中,srclabels 设置了用于更新 LBPH 识别器的新训练数据集。

2.1 使用 OpenCV 进行人脸识别流程示例

接下来,通过一个脚本来熟悉下使用 OpenCV 进行人脸识别的流程:

import cv2
import numpy as np
import matplotlib.pyplot as plt
import glob

face_recognizer = cv2.face.LBPHFaceRecognizer_create()

imgs = glob.glob('img/*.png')
faces = []
rects = []

def detect_img(img, faces, rects):
    img = cv2.imread(img)
    (h, w) = img.shape[:2]
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel")
    blob = cv2.dnn.blobFromImage(img, 1.0, (300, 300), [104., 117., 123.], False, False)
    net.setInput(blob)
    detections = net.forward()
    for i in range(0, detections.shape[2]):
        # 获取当前检测结果的置信度
        confidence = detections[0, 0, i, 2]
        # 如果置信大于最小置信度,则将其可视化
        if confidence > 0.7:
            # 获取当前检测结果的坐标
            box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
            (startX, startY, endX, endY) = box.astype('int')
            face_w = endX - startX
            face_h = endY - startY
            rects.append([startX, startY, face_w, face_h])
            faces.append(gray[startY:startY+face_h, startX:startX+face_w])
    return faces, rects
# 构造训练数据集
for img in imgs:
    faces, rects = detect_img(img, faces, rects)
# 根据实际情况构造标签数组
labels = [0] * len(faces)
# 训练识别器模型
face_recognizer.train(faces, np.array(labels))
# 加载测试图像并进行测试
img_test = cv2.imread('test.png')
face_test, rect_test = [], []
face_test, rect_test =  detect_img(img, face_test, rect_test)

label, confidence = face_recognizer.predict(face_test[0])
print(label, confidence)

3. 使用 dlib 进行人脸识别

Dlib 提供了基于深度学习的高性能人脸识别算法,该模型对户外数据集中标记人脸的识别准确率可以达到 99.38%。该算法基于 ResNet-34 网络实现,使用 300 万张人脸进行训练,预训练的模型文件 dlib_face_recognition_resnet_model_v1.dat 下载后就可以直接用于前向计算。
网络以生成 128 维 (128D) 描述符的方式进行训练,用于量化人脸。训练使用三元组执行,单个三元组训练数据由三个图像组成,其中两个对应于同一个人。网络为每张图像生成 128D 描述符,三元组损失函数对此进行了量化,尝试将同一个人的两个图像的 128D 描述符相距更近,同时将不同人的两个图像的 128D 描述符相距更远。
这个过程对数千个不同人的数百万张图像重复数百万次,最后,它能够为每个人生成一个 128D 描述符,最终的 128D 描述符是能够很好的对人脸进行编码:

  • 同一个人的两幅图像生成的 128D 描述符彼此非常相似
  • 不同人的两张图像生成的 128D 描述符差别很大

因此,利用 dlib 函数,我们可以使用预训练模型将人脸映射到 128D 描述符。然后使用这些特征向量来进行人脸识别。
计算 128D 描述符用于量化人脸的过程很简单:

# 使用 dlib 库加载特征点预测器、人脸编码和人脸检测器
pose_predictor_5_point = dlib.shape_predictor("shape_predictor_5_face_landmarks.dat")
face_encoder = dlib.face_recognition_model_v1("dlib_face_recognition_resnet_model_v1.dat")
detector = dlib.get_frontal_face_detector()

def face_encodings(face_image, number_of_times_to_upsample=1, num_jitters=1):
    """返回图像中每个人脸的 128D 描述符"""

    # 检测人脸
    face_locations = detector(face_image, number_of_times_to_upsample)
    # 检测面部特征点
    raw_landmarks = [pose_predictor_5_point(face_image, face_location) for face_location in face_locations]
    # 使用每个检测到的特征点计算每个检测到的人脸的编码
    return [np.array(face_encoder.compute_face_descriptor(face_image, raw_landmark_set, num_jitters)) for
            raw_landmark_set in raw_landmarks]

# 加载图像并转换为 RGB 模式
image = cv2.imread("jared_1.jpg")
rgb = image[:, :, ::-1]

# 计算图像中每个人脸的编码
encodings = face_encodings(rgb)
# 打印第一个编码的特征
print(encodings[0])

如上所示,关键是调用 dlibface_encoder.compute_face_descriptor() 函数,使用检测到的每个人脸的特征点计算每个检测到的人脸的 128D 编码,其中 num_jitters 参数用于设置每个人脸随机抖动的次数,返回值为每次计算的平均 128D 描述符,输出的 128D 描述符如下:

[-0.09235165  0.11607055  0.03648872 -0.08326858 -0.12627071 -0.01334486 -0.11334236 -0.10083835  0.20534235 -0.1636433   0.16874117 -0.05276754 -0.17746128 -0.05377002 -0.02731067  0.24751744 -0.22732623 -0.20258367 -0.03421091 -0.00150665  0.05875423 0.03020219  0.03901095  0.03496565 -0.15658092 -0.34250638 -0.08725534 -0.06245319 -0.04688681 -0.04861078 -0.07620423  0.05013577 -0.18563135 -0.04075277  0.05248301  0.09195475 -0.00887688 -0.1192601   0.18633801  0.00056917 -0.29226956  0.01442468  0.09583923  0.19053322  0.15580602 -0.04580544  0.01866002 -0.15243134  0.13535264 -0.17270051  0.03029358  0.16308595  0.04719323  0.08862312  0.01600051 -0.112983    0.06787978  0.17171389 -0.09536573 -0.02140218  0.11402114 -0.04710582 -0.01966342 -0.0705786   0.21773803  0.12153016 -0.08498291 -0.24783675  0.06667361 -0.08091511 -0.11054871  0.08837797 -0.17216064 -0.18642734 -0.27270097 -0.03300989  0.31748736  0.06824204 -0.16750985  0.0599058  -0.00497202 -0.02882685  0.07890167  0.19422579 -0.02771271  0.05871597 -0.06130363  0.04929798  0.27234387 -0.04948008 -0.00844343  0.22556995  0.00912007  0.07115038  0.01273906  0.03535268 -0.05074561  0.05441948 -0.13103089 -0.00421767  0.07432865  0.0025964 -0.06208063  0.12578207 -0.16597968  0.09258381 -0.02716768  0.02978029 -0.00216489 -0.01805471 -0.04702468 -0.05231683  0.11994087 -0.16787212  0.17464222  0.16930985  0.05848085  0.09450675  0.11558257  0.0659898 -0.00265438 -0.01509937 -0.22738113  0.01624682  0.13056616 -0.04214386  0.06433617  0.00774699]

获取到检测到的人脸编码后,下一步就是进行人脸识别。
使用 128D 描述符计算的某种距离度量可以用于执行人脸识别,如果两个人脸描述符向量之间的欧几里得距离小于 0.6 (欧几里得距离可以使用 numpy.linalg.norm() 计算),则可以认为它们属于同一个人;否则,他们是不同的人。
接下来,我们使用 5 张已知图像与另 1 张测试图像进行比较。为了比较人脸,我们需要编写两个函数:compare_faces()compare_faces_ordered()
compare_faces() 函数返回已知人脸编码与待识别人脸间的距离:

def compare_faces(encodings, encoding_to_check):
    return list(np.linalg.norm(encodings - encoding_to_check, axis=1))

compare_faces_ordered() 函数返回排序后的已知人脸编码与待识别人脸间的距离和相应的名称:

def compare_faces_ordered(encodings, face_names, encoding_to_check):
    distances = list(np.linalg.norm(encodings - encoding_to_check, axis=1))
    return zip(*sorted(zip(distances, face_names)))

接下来,将 5 个已标记图像与 1 个未标记图像进行比较,第一步是加载所有图像并转换为 RGB 格式:

# 加载所有图像并转换为 RGB 格式
known_image_1 = cv2.imread("小新_1.png")
known_image_2 = cv2.imread("小甜_1.png")
known_image_3 = cv2.imread("小甜_2.png")
known_image_4 = cv2.imread("小甜_3.png")
known_image_5 = cv2.imread("小新_2.png")
unknown_image = cv2.imread("test.png")
known_image_1 = known_image_1[:, :, ::-1]
known_image_2 = known_image_2[:, :, ::-1]
known_image_3 = known_image_3[:, :, ::-1]
known_image_4 = known_image_4[:, :, ::-1]
known_image_5 = known_image_5[:, :, ::-1]
unknown_image = unknown_image[:, :, ::-1]
# 标记人脸
names = ["小新_1.png", "小甜_1.png", "小甜_2.png", "小甜_3.png", "小新_2.png"]

下一步是计算每个图像的 128D 编码:

# 计算每个图像的 128D 编码
known_image_1_encoding = face_encodings(known_image_1)[0]
known_image_2_encoding = face_encodings(known_image_2)[0]
known_image_3_encoding = face_encodings(known_image_3)[0]
known_image_4_encoding = face_encodings(known_image_4)[0]
known_image_5_encoding = face_encodings(known_image_5)[0]
known_encodings = [known_image_1_encoding, known_image_2_encoding, known_image_3_encoding, known_image_4_encoding, known_image_5_encoding]
unknown_encoding = face_encodings(unknown_image)[0]

最后,可以使用 compare_faces_ordered() 函数比较与识别人脸:

computed_distances_ordered, ordered_names = compare_faces_ordered(known_encodings, names, unknown_encoding)
# 打印返回信息
print(computed_distances)
print(computed_distances_ordered)
print(ordered_names)

打印的返回信息如下:

(0.26459402873041915, 0.2728113455078627, 0.2945116087102425, 0.42567525558949304, 0.42899966791571725)
('小甜', '小甜', '小甜', '小新', '小新')

通过上示的打印信息,可以得出结论待识别的人脸属于小甜,而小新的人脸编码信息与待识别人脸的编码信息距离较远,表示不是同一个人:

4. 使用 face_recognition 进行人脸识别

使用 face_recognition 进行人脸识别内部使用了 dlib 函数对人脸进行编码并计算已编码人脸的距离。因此,无需自己编写 face_encodings()compare_faces() 函数,只需直接调用 face_recognition 库中已经封装好的相应函数。
接下来,首先使用 face_recognition.face_encodings() 函数创建 128D 描述符,然后使用 face_recognition.compare_faces() 比较人脸:

# 加载图像
known_image_1 = face_recognition.load_image_file("小美_1.png")
known_image_2 = face_recognition.load_image_file("小美_2.png")
known_image_3 = face_recognition.load_image_file("小美_3.png")
known_image_4 = face_recognition.load_image_file("小可_1.png")
# 为每个图像创建标签
names = ["小美_1.png", "小美_2.png", "小美_3.png", "小可_1.png"]
# 加载待识别图像(用于与已加载的标记图像进行比较)
unknown_image = face_recognition.load_image_file("unrecognition.jpg")
# 将每张图片中的人脸编码为 128D 向量
known_image_1_encoding = face_recognition.face_encodings(known_image_1)[0]
known_image_2_encoding = face_recognition.face_encodings(known_image_2)[0]
known_image_3_encoding = face_recognition.face_encodings(known_image_3)[0]
known_image_4_encoding = face_recognition.face_encodings(known_image_4)[0]
known_encodings = [known_image_1_encoding, known_image_2_encoding,
known_image_3_encoding, known_image_4_encoding]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]
# 人脸对比
results = face_recognition.compare_faces(known_encodings, unknown_encoding)
# 打印结果
print(results)

得到的结果是 [True, True, True, False]。因此,前三个加载的图像 ("小美_1.png", “小美_2.png” 和 “小美_3.png”) 与待识别图像 ("unrecognition.jpg") 是同一个人,而第四个加载的图像 ("小可_1.png") 被判定为待识别图像属于不同的人。

小结

在本文中,我们介绍 OpenCV 提供的与人脸识别相关的函数,同时还将探索一些用于人脸识别的深度学习方法,主要包括 dlibface_recognition 库,这些方法可以轻松集成到计算机视觉项目中以实现高精度的人脸识别。

相关文章