face_recognition库使用教程

Jupyter Notebook文件及图片数据见 百度网盘

import cv2
import dlib
import math
import pprint
import numpy as np 
import face_recognition
import matplotlib.pyplot as plt 

1. 简介

1.1 face_recognition项目简介

face_recognition项目是世界上最简洁的人脸识别库,可以使用Python和命令行工具提取、识别、操作人脸。
本项目的人脸识别是基于业内领先的C++开源库 dlib中的深度学习模型,用Labeled Faces in the Wild人脸数据集进行测试,有高达99.38%的准确率。但对小孩和亚洲人脸的识别准确率尚待提升。
Labeled Faces in the Wild是美国麻省大学安姆斯特分校(University of Massachusetts Amherst)制作的人脸数据集,该数据集包含了从网络收集的13,000多张面部图像。

  • Github网址: https://github.com/ageitgey/face_recognition/
  • 官方指南: https://face-recognition.readthedocs.io/en/latest/readme.html

1.2 face_recognition库中参数简单说明

人脸识别实际上是对人脸进行编码后再去计算两两人脸的相似度,known_image是已知人脸库的图像,unknown_image是待检测的图像,分别利用face_encodings函数来映射成一个向量,再利用两个向量的内积来衡量相似度,compare_faces函数就是根据阈值确认是否是同一人脸。上述函数都是支持多个人脸计算的。另外compare_faces有个tolerance参数是控制阈值的,tolerance值越低越严格,默认为0.6。

1.3 人脸识别通用流程

人脸识别通用流程一般有人脸检测,人脸对齐和人脸识别三步:

  1. 图像导入:将图片导入成ndarray数组

    • load_image_file(file, mode='RGB')
  2. 人脸检测/人脸定位 face detection and location:人脸检测就是在图片中找到人脸的具体位置,并输出包含人脸位置的边界矩形框。某些检测算法可以同时输出人脸相应的关键点。

    • face_locations(img, number_of_times_to_upsample=1, model= 'hog')
  3. 人脸对齐 face alignment:所谓的人脸对齐就是有时候人脸的角度不正,根据关键点检测结果通过图像变换或其他方法,将人脸上对准到一个预设的固定位置上(通常是正脸)。这样使得不同人脸的眼睛,鼻子都被放在同一个位置,大大提高识别精度。

  4. 人脸识别 face recognition:人脸识别有很多应用方向,但是目的都是识别当前人脸对应哪个人。

    • face_landmarks(face_image, face_locations=None, model='large')
    • face_encodings(face_image, known_face_locations=None, num_jitters=1, model='small')
    • compare_faces(known_face_encodings, face_encoding_to_check, tolerance=0.6)
    • face_distance(face_encodings, face_to_compare)

简单的人脸识别通用流程示意图如下图所示。在face_recognition中所有代码都有涉及这些步骤;但是人脸对齐是直接调用dlib代码,没有实例说明。
在这里插入图片描述

当然在成熟的商业工程应用不只有这三个部分,比如还有人脸质量判断,活体检测之类的,但是一般的项目都包含这三大步骤。

2. 主要方法介绍

2.1 load_image_file 加载图像

  • 这个方法主要用于加载要识別的人脸图像,加载返回的数据是 Numpy 数組,记录了图片的所有像素的特征向量。
  • 这个方法与cv2.imread()类似,但cv2.imread()读取获得的图片数据矩阵是BGR通道排序,不可以直接用matplotlib.pyplot.imshow()显示,可以直接用cv2.imshow()显示。
  • load_image_file()读取的是矩阵颜色通道顺序是RGB,可以直接用plt.imshow()显示。

2.1.1 函数接口

load_image_file(file, mode=‘RGB’)

  • 用途:加载图像
  • 输入参数:
    • file:图像路径名
    • mode:图像颜色类型,设置RGB表示返回RGB图像,设置’L’表示返回灰度图像
  • 返回:numpy数组

2.1.2 代码示例并比较

#加载图像文件
image = face_recognition.load_image_file("images/girls1.png")
print(type(image))
print(image.shape)
print(image)
plt.imshow(image)
plt.axis('off');
<class 'numpy.ndarray'>
(800, 600, 3)
[[[115 114 244]
  [115 114 244]
  [115 114 244]
  ...
  [112 164 214]
  [112 164 214]
  [112 164 214]]

 [[115 114 244]
  [115 114 244]
  [115 114 244]
  ...
  [112 164 214]
  [112 164 214]
  [112 164 214]]

 [[115 114 242]
  [115 114 242]
  [115 114 242]
  ...
  [112 164 214]
  [112 164 214]
  [112 164 214]]

 ...

 [[158 152 164]
  [157 151 163]
  [158 152 164]
  ...
  [166 167 188]
  [166 167 188]
  [166 167 188]]

 [[166 158 171]
  [162 154 167]
  [159 151 164]
  ...
  [166 167 188]
  [166 167 188]
  [166 167 188]]

 [[170 162 175]
  [165 157 170]
  [161 153 166]
  ...
  [166 167 188]
  [166 167 188]
  [166 167 188]]]

在这里插入图片描述

#加载图像文件
img = cv2.imread("images/girls1.png")
print(type(img))
print(img.shape)
print(img)
cv2.imshow("girls",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
plt.imshow(img)
plt.axis('off');
<class 'numpy.ndarray'>
(800, 600, 3)
[[[244 114 115]
  [244 114 115]
  [244 114 115]
  ...
  [214 164 112]
  [214 164 112]
  [214 164 112]]

 [[244 114 115]
  [244 114 115]
  [244 114 115]
  ...
  [214 164 112]
  [214 164 112]
  [214 164 112]]

 [[242 114 115]
  [242 114 115]
  [242 114 115]
  ...
  [214 164 112]
  [214 164 112]
  [214 164 112]]

 ...

 [[164 152 158]
  [163 151 157]
  [164 152 158]
  ...
  [188 167 166]
  [188 167 166]
  [188 167 166]]

 [[171 158 166]
  [167 154 162]
  [164 151 159]
  ...
  [188 167 166]
  [188 167 166]
  [188 167 166]]

 [[175 162 170]
  [170 157 165]
  [166 153 161]
  ...
  [188 167 166]
  [188 167 166]
  [188 167 166]]]

在这里插入图片描述

2.2 face_locations 定位图中所有人脸

2.2.1 函数接口

face_recognition.api.face_locations(img, number_of_times_to_upsample=1, model=‘hog’)

  • 用途:人脸检测,返回图像中人脸边界框的数组

  • 输入参数:

    • img:输入图像,numpy数组
    • number_of_times_to_upsample:对图像进行上采样次数以找到更小的人脸,默认为1
    • model:检测模型,默认是hog机器学习模型,另外可设置cnn选择卷积神经网络模型以提高检测精度
  • 返回:包含多张人脸边界框的list数组,边界框数据以人脸(top, right, bottom, left) 顺序表示

2.2.2 代码示例

#定位所有找到的脸的位置
face_locations = face_recognition.face_locations(image)
i = 1 
imgCpy = image.copy()
# 循环找到的所有人脸
for face_location in face_locations:
        # 打印每张脸的位置信息
        top, right, bottom, left = face_location
        print("A face is located at pixel location Top: {}, Left: {}, Bottom: {}, Right: {}".format(top, left, bottom, right))
        # 在原图上画人脸框
        imgCpy = cv2.rectangle(imgCpy,(left,top),(right,bottom),(0,0,255),2)
        # 指定人脸的位置信息,然后显示人脸图片
        face_image = image[top:bottom, left:right]
        plt.subplot(1,4,i)
        plt.imshow(face_image)
        plt.axis('off');
        i = i+1 

imgCpy = imgCpy[:,:,::-1]
cv2.imshow("result",imgCpy)
cv2.waitKey(0)
cv2.destroyAllWindows()
A face is located at pixel location Top: 211, Left: 163, Bottom: 319, Right: 270
A face is located at pixel location Top: 255, Left: 305, Bottom: 345, Right: 394
A face is located at pixel location Top: 211, Left: 44, Bottom: 319, Right: 151
A face is located at pixel location Top: 235, Left: 450, Bottom: 343, Right: 557

在这里插入图片描述

2.3 batch_face_locations人脸批量定位

2.3.1 函数接口

batch_face_locations(images, number_of_times_to_upsample=1, batch_size=128)

  • 用途: 使用 cnn 人脸检测器返回图像中人脸边界框的二维数组 ,使用GPU批量处理图像
  • 输入参数:
    • images:图像列表
    • number_of_times_to_upsample: 在图像上寻找人脸的次数 , 数字越大,查找精细
    • batch_size: 每个 GPU 处理批次中包含多少图像
  • 返回: 以 css(上、右、下、左)顺序找到的人脸位置的元组列表

2.4 face_landmarks 识别人脸关键点

2.4.1 函数接口

face_landmarks(face_image, face_locations=None, model=‘large’)

  • 用途:人脸关键点识别,返回图像人脸特征位置的字典
  • 输入参数:
    • face_image:输入图像,numpy数组
    • face_locations:要识别的位置列表(可选)
    • model:使用的识别模型。默认值为large表示大模型。small表示小模型,但只返回五个特征点
  • 返回:特征位置(眼睛、鼻子等)的字典列表

2.4.2 68个人脸特征点

在这里插入图片描述

面部特征包含以下几个部分 chin(下巴), left_eyebrow(左眼眉), right_eyebrow’(右眼眉), left_eye(左眼), right_eye(右眼), nose_bridge(鼻梁),nose_tip(鼻下部), bottom_lip(下嘴唇), top_lip(上嘴唇)

face_landmarks_list = face_recognition.face_landmarks(image)
pprint.pprint(face_landmarks_list)
[{'bottom_lip': [(236, 284),
                 (233, 293),
                 (228, 298),
                 (224, 300),
                 (219, 301),
                 (212, 301),
                 (202, 296),
                 (205, 296),
                 (217, 296),
                 (222, 295),
                 (226, 293),
                 (234, 285)],
  'chin': [(161, 265),
           (165, 276),
           (169, 286),
           (175, 295),
           (183, 304),
           (194, 310),
           (205, 316),
           (218, 320),
           (228, 319),
           (236, 315),
           (243, 305),
           (248, 294),
           (252, 283),
           (253, 271),
           (252, 260),
           (249, 248),
           (245, 237)],
  'left_eye': [(178, 257),
               (182, 252),
               (189, 250),
               (195, 254),
               (189, 257),
               (183, 258)],
  'left_eyebrow': [(166, 250), (170, 244), (177, 239), (186, 237), (195, 238)],
  'nose_bridge': [(206, 248), (208, 256), (211, 264), (214, 272)],
  'nose_tip': [(209, 280), (213, 280), (217, 279), (219, 277), (222, 275)],
  'right_eye': [(218, 247),
                (221, 240),
                (227, 238),
                (234, 241),
                (230, 245),
                (223, 247)],
  'right_eyebrow': [(210, 233), (216, 228), (224, 226), (232, 226), (239, 229)],
  'top_lip': [(202, 296),
              (209, 291),
              (214, 288),
              (219, 287),
              (223, 284),
              (229, 284),
              (236, 284),
              (234, 285),
              (224, 288),
              (220, 290),
              (215, 291),
              (205, 296)]},
 {'bottom_lip': [(365, 322),
                 (359, 325),
                 (355, 326),
                 (351, 326),
                 (347, 325),
                 (340, 322),
                 (333, 315),
                 (335, 316),
                 (348, 320),
                 (352, 321),
                 (356, 321),
                 (362, 322)],
  'chin': [(313, 271),
           (311, 284),
           (310, 296),
           (310, 309),
           (314, 320),
           (320, 329),
           (327, 337),
           (335, 344),
           (343, 346),
           (351, 345),
           (360, 340),
           (368, 334),
           (376, 328),
           (382, 320),
           (387, 312),
           (390, 303),
           (392, 294)],
  'left_eye': [(333, 275),
               (339, 273),
               (345, 275),
               (349, 280),
               (343, 280),
               (337, 278)],
  'left_eyebrow': [(328, 261), (337, 259), (345, 260), (352, 263), (358, 269)],
  'nose_bridge': [(363, 282), (362, 290), (361, 298), (359, 306)],
  'nose_tip': [(348, 308), (352, 310), (356, 312), (360, 312), (363, 311)],
  'right_eye': [(371, 286),
                (377, 283),
                (382, 284),
                (386, 288),
                (382, 289),
                (376, 288)],
  'right_eyebrow': [(373, 274), (379, 273), (386, 273), (391, 275), (394, 280)],
  'top_lip': [(333, 315),
              (343, 316),
              (350, 317),
              (353, 318),
              (357, 318),
              (361, 320),
              (365, 322),
              (362, 322),
              (356, 321),
              (352, 321),
              (349, 320),
              (335, 316)]},
 {'bottom_lip': [(116, 297),
                 (113, 300),
                 (109, 301),
                 (105, 302),
                 (101, 301),
                 (96, 300),
                 (89, 299),
                 (93, 298),
                 (100, 295),
                 (104, 295),
                 (107, 295),
                 (113, 297)],
  'chin': [(36, 269),
           (39, 281),
           (43, 292),
           (49, 302),
           (57, 311),
           (68, 317),
           (81, 321),
           (95, 324),
           (106, 324),
           (116, 320),
           (122, 313),
           (126, 305),
           (129, 295),
           (131, 284),
           (131, 274),
           (130, 264),
           (127, 254)],
  'left_eye': [(62, 259),
               (68, 255),
               (74, 254),
               (80, 258),
               (75, 260),
               (68, 260)],
  'left_eyebrow': [(49, 250), (55, 242), (64, 239), (74, 238), (84, 241)],
  'nose_bridge': [(95, 253), (97, 260), (99, 266), (102, 272)],
  'nose_tip': [(93, 281), (98, 282), (102, 282), (105, 281), (107, 280)],
  'right_eye': [(104, 256),
                (109, 252),
                (115, 251),
                (120, 254),
                (116, 256),
                (110, 257)],
  'right_eyebrow': [(99, 240), (106, 236), (114, 236), (121, 238), (125, 243)],
  'top_lip': [(89, 299),
              (94, 292),
              (100, 289),
              (103, 289),
              (106, 289),
              (112, 292),
              (116, 297),
              (113, 297),
              (107, 295),
              (104, 295),
              (100, 295),
              (93, 298)]},
 {'bottom_lip': [(517, 322),
                 (506, 326),
                 (497, 327),
                 (491, 325),
                 (485, 321),
                 (481, 314),
                 (478, 304),
                 (481, 306),
                 (488, 317),
                 (494, 320),
                 (500, 322),
                 (514, 321)],
  'chin': [(476, 256),
           (470, 268),
           (466, 281),
           (464, 294),
           (464, 308),
           (467, 321),
           (471, 334),
           (475, 346),
           (484, 354),
           (496, 356),
           (511, 353),
           (524, 348),
           (537, 342),
           (547, 334),
           (554, 323),
           (561, 311),
           (566, 299)],
  'left_eye': [(490, 261),
               (496, 262),
               (501, 264),
               (505, 269),
               (499, 267),
               (494, 265)],
  'left_eyebrow': [(485, 251), (490, 247), (499, 247), (507, 251), (514, 257)],
  'nose_bridge': [(516, 271), (512, 279), (508, 288), (504, 296)],
  'nose_tip': [(494, 297), (497, 301), (501, 305), (506, 306), (511, 307)],
  'right_eye': [(529, 279),
                (536, 279),
                (541, 282),
                (545, 287),
                (539, 286),
                (534, 283)],
  'right_eyebrow': [(528, 264), (538, 264), (548, 268), (556, 275), (559, 284)],
  'top_lip': [(478, 304),
              (485, 306),
              (492, 309),
              (497, 313),
              (503, 314),
              (510, 318),
              (517, 322),
              (514, 321),
              (501, 318),
              (495, 316),
              (490, 313),
              (481, 306)]}]
print(len(face_landmarks_list))
4
face_landmarks_list[0]
{'chin': [(161, 265),
  (165, 276),
  (169, 286),
  (175, 295),
  (183, 304),
  (194, 310),
  (205, 316),
  (218, 320),
  (228, 319),
  (236, 315),
  (243, 305),
  (248, 294),
  (252, 283),
  (253, 271),
  (252, 260),
  (249, 248),
  (245, 237)],
 'left_eyebrow': [(166, 250), (170, 244), (177, 239), (186, 237), (195, 238)],
 'right_eyebrow': [(210, 233), (216, 228), (224, 226), (232, 226), (239, 229)],
 'nose_bridge': [(206, 248), (208, 256), (211, 264), (214, 272)],
 'nose_tip': [(209, 280), (213, 280), (217, 279), (219, 277), (222, 275)],
 'left_eye': [(178, 257),
  (182, 252),
  (189, 250),
  (195, 254),
  (189, 257),
  (183, 258)],
 'right_eye': [(218, 247),
  (221, 240),
  (227, 238),
  (234, 241),
  (230, 245),
  (223, 247)],
 'top_lip': [(202, 296),
  (209, 291),
  (214, 288),
  (219, 287),
  (223, 284),
  (229, 284),
  (236, 284),
  (234, 285),
  (224, 288),
  (220, 290),
  (215, 291),
  (205, 296)],
 'bottom_lip': [(236, 284),
  (233, 293),
  (228, 298),
  (224, 300),
  (219, 301),
  (212, 301),
  (202, 296),
  (205, 296),
  (217, 296),
  (222, 295),
  (226, 293),
  (234, 285)]}
face_landmarks_list[0].keys()
dict_keys(['chin', 'left_eyebrow', 'right_eyebrow', 'nose_bridge', 'nose_tip', 'left_eye', 'right_eye', 'top_lip', 'bottom_lip'])
face_landmarks_list[0]['chin']
[(161, 265),
 (165, 276),
 (169, 286),
 (175, 295),
 (183, 304),
 (194, 310),
 (205, 316),
 (218, 320),
 (228, 319),
 (236, 315),
 (243, 305),
 (248, 294),
 (252, 283),
 (253, 271),
 (252, 260),
 (249, 248),
 (245, 237)]
2.4.2.1 用代码将特征点在图片上勾勒显示
import face_recognition
from PIL import Image, ImageDraw
 
# load_image_file 主要用于加载要识别的人脸图像,加载返回的数据是(多维数组)Numpy数组,记录图片的所有像数的特征向量
image = face_recognition.load_image_file('images/ym.jpg')
 
face_landmarks_list = face_recognition.face_landmarks(image)
 
# print(face_landmarks_list)
pil_image = Image.fromarray(image)
 
d = ImageDraw.Draw(pil_image)  # 生成一张pil图像
 
for face_landmarks in face_landmarks_list:
    facial_features = [
        'chin',
        'left_eyebrow',
        'right_eyebrow',
        'left_eye',
        'right_eye',
        'nose_bridge',
        'nose_tip',
        'bottom_lip',
        'top_lip'
    ]
    for facial_feature in facial_features:
        # print("{}每个人的面部特征显示在以下位置:{}".format(facial_feature,face_landmarks[facial_feature]))
        # 调用pil的line方法,绘制所有特征点
        d.line(face_landmarks[facial_feature], width=2)
pil_image.show()
plt.imshow(pil_image)
plt.axis('off');

在这里插入图片描述

2.4.2.2 用代码将特征点在图片上圆点显示
image = face_recognition.load_image_file('images/ym.jpg')
imageCpy = image.copy()
face_landmarks_list = face_recognition.face_landmarks(image)
for face_landmarks_dict in face_landmarks_list:
    for face_landmarks_key in face_landmarks_dict.keys():
        for pt in face_landmarks_dict[face_landmarks_key]:
            cv2.circle(imageCpy, pt, 2, (0, 0, 255), -1)
plt.imshow(imageCpy)
plt.axis('off');

在这里插入图片描述

2.5 face_encodings 获取图像文件中所有面部编码

  • 返回值是一个编码列表,参数仍然是要识别的图像对象,如果后续访问时需要加上索引或遍历进行访问,每张人脸的编码信息时一个128维向量
  • 面部编码信息时进行人像识别的重要参数

2.5.1 函数接口

face_encodings(face_image, known_face_locations=None, num_jitters=1, model=‘small’)

  • 用途:返回图像中每个人脸的128维人脸特征
  • 输入参数:
    • face_image:输入图像,numpy数组
    • known_face_locations:每个人脸的边界框(可选),能够大大提高识别速度
    • num_jitters:计算人脸特征时重新采样人脸的次数。更高更准确,但更慢,即设置为100慢100倍
    • model:使用的识别模型,默认值为small表示小模型,只返回五个特征点;可设置为large
  • 返回:包含人脸特征的列表

2.5.2 代码示例

face_encodings = face_recognition.face_encodings(image)
for face_encoding in face_encodings:
    print("face_encoding len = {} \nencoding:{}\n\n".format(len(face_encoding),face_encoding))
face_encoding len = 128 
encoding:[-0.07307729  0.08883394  0.05256004 -0.08409223 -0.10412137  0.0122126
 -0.13511637 -0.08564267  0.15063195 -0.1769539   0.17196961 -0.09708439
 -0.21590118  0.02664856 -0.06287956  0.23926286 -0.2337441  -0.19506027
  0.0290608  -0.00557132  0.02226843  0.02549197 -0.01845489  0.05542558
 -0.12352924 -0.37054011 -0.14638196 -0.05463945 -0.03401048 -0.1185659
 -0.06685271  0.04325735 -0.15144151  0.033684    0.03763037  0.06519675
  0.01236682 -0.15002912  0.15272273  0.08428682 -0.26995748  0.04612222
  0.10666656  0.24196357  0.17897238 -0.02009001 -0.03259763 -0.16403735
  0.15157083 -0.15376002  0.02843453  0.14898682  0.08735343  0.12231597
  0.00471953 -0.07030666  0.05507216  0.14091037 -0.1616794  -0.01445402
  0.10894115 -0.03213574  0.06045982 -0.12072809  0.2031588   0.15204293
 -0.08000464 -0.26499468  0.08195631 -0.17282443 -0.14975637  0.0684469
 -0.13645008 -0.1683697  -0.26415816 -0.01062901  0.23297453  0.16732013
 -0.13873211  0.05959025  0.03485153 -0.08272717  0.06839018  0.22448371
 -0.02007552  0.04189269 -0.01484711 -0.00703483  0.28751546 -0.0122439
 -0.01364375  0.20315818 -0.04943756  0.08240615  0.02707386  0.01718502
 -0.12960678  0.03108606 -0.14959219 -0.06416424 -0.08728628  0.03491499
 -0.04440526  0.14494671 -0.14345437  0.14518881 -0.0503884  -0.0292776
 -0.04416241  0.03105522 -0.02410117 -0.03463895  0.1111607  -0.20485429
  0.15504947  0.14378503  0.11697389  0.06616869  0.10097323  0.07302132
 -0.04523795 -0.03026703 -0.21043329  0.01008348  0.11666981 -0.05830565
  0.07127231 -0.02885219]

2.6 compare_faces 由面部编码匹配脸

  • 主要用于匹配两个面部特征编码,利用这两个特征向量的内积来衡量相似度,根据阈值确认是否是同一个人。
  • 第一个参数就是一个面部编码列表(很多张脸), 第二个参数就是给出单个面部编码(一张脸), compare_faces 会将第二个参数中的编码信息与第一个参数中的所有编码信息依次匹配,返回值是一个布尔列表,匹配成功则返回 True,匹配失败则返回 False,顺序与第一个参数中脸部编码顺序一致。
  • 参数里有一个 tolerance值,大家可以根据实际的效果进行调整,一般经验值是 0.39。tolerance 值越小,匹配越严格。

2.6.1 函数接口

compare_faces(known_face_encodings, face_encoding_to_check, tolerance=0.6)

  • 用途:将人脸特征与候选人脸特征进行比较,以查看它们是否匹配。
  • 输入参数:
    • known_face_encodings:已知人脸特征列表
    • face_encoding_to_check:与已知人脸特征列表进行比较的单个人脸特征
    • tolerance:人脸距离越小表示人脸越相近,当人脸距离小于tolerance,表示是同一个人;0.6是默认值,也是作者认为的最佳值(实际有所出入)
  • 返回:包含True或者False的列表,以表示是否为同一个人脸

2.6.2 代码示例

  • 只有一个人像
known_face_encoding = face_encoding
img2 = face_recognition.load_image_file("images/ym2.jpg")
face_encoding_to_check = face_recognition.face_encodings(img2)
compare_result = face_recognition.compare_faces(known_face_encoding, face_encoding_to_check)
if compare_result:
    print("两张照片是同一个人!")
else:
    print("两张照片不是同一个人!")
plt.subplot(121)
plt.imshow(image)
plt.axis('off')
plt.subplot(122)
plt.imshow(img2)
plt.axis('off')
plt.show();
两张照片是同一个人!

在这里插入图片描述

  • 多个人像
    • known_face:

      • obama:
        在这里插入图片描述

      • biden:
        在这里插入图片描述

    • unknow_faces:
      在这里插入图片描述

import face_recognition
from PIL import Image, ImageDraw
 
# This is an example of running face recognition on a single image
# and drawing a box around each person that was identified.
 
# Load a sample picture and learn how to recognize it.
obama_image = face_recognition.load_image_file("images/obama.jpg")
obama_face_encoding = face_recognition.face_encodings(obama_image)[0]
 
# Load a second sample picture and learn how to recognize it.
biden_image = face_recognition.load_image_file("images/biden.jpg")
biden_face_encoding = face_recognition.face_encodings(biden_image)[0]
 
# Create arrays of known face encodings and their names
known_face_encodings = [
    obama_face_encoding,
    biden_face_encoding
]
known_face_names = [
    "obama",
    "biden"
]
 
# Load an image with an unknown face
unknown_image = face_recognition.load_image_file("images/obama_biden2.jpg")
 
# Find all the faces and face encodings in the unknown image
face_locations = face_recognition.face_locations(unknown_image)
face_encodings = face_recognition.face_encodings(unknown_image, face_locations)
 
# Convert the image to a PIL-format image so that we can draw on top of it with the Pillow library
# See http://pillow.readthedocs.io/ for more about PIL/Pillow
pil_image = Image.fromarray(unknown_image)
# Create a Pillow ImageDraw Draw instance to draw with
draw = ImageDraw.Draw(pil_image)
 
# Loop through each face found in the unknown image
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
    # See if the face is a match for the known face(s)
    matches = face_recognition.compare_faces(known_face_encodings, face_encoding)
    print(matches) 
    name = "Unknown"
    
    # If a match was found in known_face_encodings, just use the first one.
    if True in matches:
        first_match_index = matches.index(True)
        name = known_face_names[first_match_index]
 
    # Draw a box around the face using the Pillow module
    draw.rectangle(((left, top), (right, bottom)), outline=(0, 0, 255))
 
    # Draw a label with a name below the face
    text_width, text_height = draw.textsize(name)
    draw.rectangle(((left, bottom - text_height - 10), (right, bottom)), fill=(0, 0, 255), outline=(0, 0, 255))
    draw.text((left + 6, bottom - text_height - 5), name, fill=(255, 255, 255, 255))
 
 
# Remove the drawing library from memory as per the Pillow docs
del draw
 
# Display the resulting image
pil_image.show()
 
# You can also save a copy of the new image to disk if you want by uncommenting this line
# pil_image.save("image_with_boxes.jpg")

[True, False]
[False, False]
[False, True]
  • 识别结果:
    在这里插入图片描述

2.7 face_distance计算人脸特征举例

2.7.1 函数接口

face_distance(face_encodings, face_to_compare)

  • 用途:给定一个人脸特征列表,将它们与已知的人脸特征进行比较,并获得人脸特征向量之间的欧几里德距离,距离越小面孔越相似。
  • 输入参数:
    • face_encodings:已知的人脸特征列表
    • face_to_compare:未知的人脸特征列表
  • 返回:代表距离的numpy数组,和face_encodings的排序方式一样

2.7.2 代码示例

  • obama.jpg
    在这里插入图片描述

  • biden.jpg
    在这里插入图片描述

  • obama2.jpg
    在这里插入图片描述

# Load a sample picture and learn how to recognize it.
obama_image = face_recognition.load_image_file("images/obama.jpg")
obama_face_encoding = face_recognition.face_encodings(obama_image)[0]
 
# Load a second sample picture and learn how to recognize it.

biden_image = face_recognition.load_image_file("images/biden.jpg")
biden_face_encoding = face_recognition.face_encodings(biden_image)[0]
 
# Create arrays of known face encodings and their names
face_encodings = [
    obama_face_encoding,
    biden_face_encoding
]
face_names = [
    "obama",
    "biden"
]

img_to_compare = face_recognition.load_image_file('images/obama2.jpg')
face_encoding_to_compare = face_recognition.face_encodings(img_to_compare)[0]

distances = face_recognition.face_distance(face_encodings, face_encoding_to_compare)
print(distances) 

result= (distances<0.4).tolist() # distance<0.4 ,认为是同一人
# print(type(result))
# print(result)
first_match_index = result.index(True)
name = face_names[first_match_index]
print(name)
[0.33064072 0.85609689]
obama

3. 人脸检测与定位

本部分主要是对人脸进行检测和定位,并输出人脸相应的矩形框。主要用到的face_recognition内置函数有:

  • face_recognition.api.face_locations(img, number_of_times_to_upsample=1, model=‘hog’)

    • 用途:人脸检测,返回图像中人脸边界框的数组
    • 输入参数:
      • img:输入图像,numpy数组
      • number_of_times_to_upsample:对图像进行上采样次数以找到更小的人脸,默认为1
      • model:检测模型,默认是hog机器学习模型,另外可设置cnn选择卷积神经网络模型以提高检测精度
    • 返回:包含多张人脸边界框的list数组,边界框数据以人脸(top, right, bottom, left) 顺序表示
  • face_recognition.api.load_image_file(file, mode=‘RGB’)

    • 用途:加载图像
    • 输入参数:
      • file:图像路径名
      • mode:图像颜色类型,设置RGB表示返回RGB图像,设置’L’表示返回灰度图像
    • 返回:numpy数组

3.1 基于机器学习实现人脸检测

来自examples/find_faces_in_picture.py

%matplotlib inline
import matplotlib.pyplot as plt 
from PIL import Image
import face_recognition

# 通过PIL加载图片
image = face_recognition.load_image_file("images/girls1.png")
# 基于hog机器学习模型进行人脸识别,不能使用gpu加速
face_locations = face_recognition.face_locations(image)

# 找到几张人脸
print("I found {} face(s) in this photograph.".format(len(face_locations)))

for face_location in face_locations:

    # 打印人脸信息
    top, right, bottom, left = face_location
    print("A face is located at pixel location Top: {}, Left: {}, Bottom: {}, Right: {}".format(top, left, bottom, right))

    # 提取人脸
    face_image = image[top:bottom, left:right]
    pil_image = Image.fromarray(face_image)
    # jupyter 绘图
    # pil_image.show()
    plt.imshow(pil_image)
    plt.axis('off')    
    plt.show()
I found 4 face(s) in this photograph.
A face is located at pixel location Top: 211, Left: 163, Bottom: 319, Right: 270

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cs2AA0Vq-1692802435292)(output_39_1.png)]

A face is located at pixel location Top: 255, Left: 305, Bottom: 345, Right: 394

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUtTj6NK-1692802435293)(output_39_3.png)]

A face is located at pixel location Top: 211, Left: 44, Bottom: 319, Right: 151

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kAiiq14e-1692802435294)(output_39_5.png)]

A face is located at pixel location Top: 235, Left: 450, Bottom: 343, Right: 557

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Za5Va0mU-1692802435295)(output_39_7.png)]

3.2 基于卷积神经网络实现人脸检测

来自examples/find_faces_in_picture_cnn.py

%matplotlib inline
import matplotlib.pyplot as plt 
from PIL import Image
import face_recognition

# 通过PIL加载图片
image = face_recognition.load_image_file("images/obama_biden2.jpg")

# 基于cnn识别人脸,是否使用gpu看装机环境
face_locations = face_recognition.face_locations(image, number_of_times_to_upsample=0, model="cnn")

print("I found {} face(s) in this photograph.".format(len(face_locations)))

for face_location in face_locations:

    # 打印人脸信息
    top, right, bottom, left = face_location
    print("A face is located at pixel location Top: {}, Left: {}, Bottom: {}, Right: {}".format(top, left, bottom, right))

    # 提取人脸
    face_image = image[top:bottom, left:right]
    pil_image = Image.fromarray(face_image)
    # jupyter 绘图
    # pil_image.show()
    plt.imshow(pil_image)
    plt.axis('off')    
    plt.show()
I found 3 face(s) in this photograph.
A face is located at pixel location Top: 115, Left: 553, Bottom: 312, Right: 750

在这里插入图片描述

A face is located at pixel location Top: 394, Left: 653, Bottom: 591, Right: 850

在这里插入图片描述

A face is located at pixel location Top: 215, Left: 56, Bottom: 412, Right: 252

在这里插入图片描述

3.3 人脸马赛克

来自examples/blur_faces_on_webcam.py

%matplotlib inline
import matplotlib.pyplot as plt
import face_recognition
import cv2

frame = cv2.imread("images/obama_biden2.jpg")

# 缩小图像以加快速度
small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)

# 找到人脸
face_locations = face_recognition.face_locations(small_frame, model="cnn")
for top, right, bottom, left in face_locations:
    # 提取边界框在原图比例的边界框
    top *= 4
    right *= 4
    bottom *= 4
    left *= 4

    # 提取人脸
    face_image = frame[top:bottom, left:right]

    # 高斯模糊人脸
    face_image = cv2.GaussianBlur(face_image, (99, 99), 30)

    # 原图人脸替换
    frame[top:bottom, left:right] = face_image

# 展示图像
img = frame[:,:,::-1] 
plt.axis('off')
plt.imshow(img);

在这里插入图片描述

4. 人脸关键点识别

本部分主要是对人脸进行关键点识别,并输出人脸特征位置。主要用到的face_recognition内置函数有:

  • face_recognition.api.face_landmarks(face_image, face_locations=None, model=‘large’)
    • 用途:人脸关键点识别,返回图像人脸特征位置的字典
    • 输入参数:
      • face_image:输入图像,numpy数组
      • face_locations:要识别的位置列表(可选)
      • model:使用的识别模型。默认值为large表示大模型。small表示小模型,但只返回五个特征点
    • 返回:特征位置(眼睛、鼻子等)的字典列表

4.1 提取图像中的人脸关键点

来自examples/find_facial_features_in_picture.py

%matplotlib inline
import matplotlib.pyplot as plt 
from PIL import Image, ImageDraw
import face_recognition

# 通过PIL加载图片
image = face_recognition.load_image_file("images/girls1.png")

# 找到图像中所有人脸的所有面部特征,返回字典
face_landmarks_list = face_recognition.face_landmarks(image)

# 发现人脸数
print("I found {} face(s) in this photograph.".format(len(face_landmarks_list)))

# 创建展示结果的图像
pil_image = Image.fromarray(image)
d = ImageDraw.Draw(pil_image)

# 绘制关键点
for face_landmarks in face_landmarks_list:

    # 打印此图像中每个面部特征的位置
    # for facial_feature in face_landmarks.keys():
       # print("The {} in this face has the following points: {}".format(facial_feature, face_landmarks[facial_feature]))

    # 用一条线勾勒出图像中的每个面部特征
    for facial_feature in face_landmarks.keys():
        d.line(face_landmarks[facial_feature], width=5)


# jupyter 绘图
# pil_image.show()
plt.imshow(pil_image)
plt.axis('off')    
plt.show()
I found 4 face(s) in this photograph.

在这里插入图片描述

4.2 人脸涂色

来自examples/digital_makeup.py

%matplotlib inline
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
import face_recognition

# 通过PIL加载图片
image = face_recognition.load_image_file("images/girls2.png")

# 找到图像中所有人脸的所有面部特征,返回字典
face_landmarks_list = face_recognition.face_landmarks(image)

pil_image = Image.fromarray(image)

# 绘图
for face_landmarks in face_landmarks_list:
    d = ImageDraw.Draw(pil_image, 'RGBA')

    # 眉毛涂色
    d.polygon(face_landmarks['left_eyebrow'], fill=(68, 54, 39, 128))
    d.polygon(face_landmarks['right_eyebrow'], fill=(68, 54, 39, 128))
    d.line(face_landmarks['left_eyebrow'], fill=(68, 54, 39, 150), width=5)
    d.line(face_landmarks['right_eyebrow'], fill=(68, 54, 39, 150), width=5)

    # 嘴唇涂色
    d.polygon(face_landmarks['top_lip'], fill=(150, 0, 0, 128))
    d.polygon(face_landmarks['bottom_lip'], fill=(150, 0, 0, 128))
    d.line(face_landmarks['top_lip'], fill=(150, 0, 0, 64), width=8)
    d.line(face_landmarks['bottom_lip'], fill=(150, 0, 0, 64), width=8)

    # 眼睛涂色
    d.polygon(face_landmarks['left_eye'], fill=(255, 255, 255, 30))
    d.polygon(face_landmarks['right_eye'], fill=(255, 255, 255, 30))

    # 眼线涂色
    d.line(face_landmarks['left_eye'] + [face_landmarks['left_eye'][0]], fill=(0, 0, 0, 110), width=6)
    d.line(face_landmarks['right_eye'] + [face_landmarks['right_eye'][0]], fill=(0, 0, 0, 110), width=6)

# jupyter 绘图
# pil_image.show()
plt.imshow(pil_image)
plt.axis('off')
plt.show()

在这里插入图片描述

4.3 人眼睁闭状态识别

来自examples / blink_detection.py

该部分代码作用为根据人眼关键点数据计算人眼的纵横比。人眼睁开的时候纵横比较高,人眼闭上的时候纵横比较小。如果眼睛闭上次数超过设定阈值,则输出人眼处于闭眼状态。

import matplotlib.pylab as plt
import face_recognition
import cv2
from scipy.spatial import distance as dist

# 这是一个检测眼睛状态的演示
# 人眼闭上次数超过设定阈值EYES_CLOSED_SECONDS,判定人眼处于闭眼状态
EYES_CLOSED_SECONDS = 2


def main():
    # 闭眼次数
    closed_count = 0
    # 读取两张图像模仿人睁闭眼
    img_eye_opened = cv2.imread('images/eye_opened.jpg')
    img_eye_closed = cv2.imread('images/eye_closed.jpg')
    # 设置图像输入序列,前1张睁眼,中间3张闭眼,最后1张睁眼
    frame_inputs = [img_eye_opened] + [img_eye_closed] * 3 + [img_eye_opened] * 1

    for frame_num, frame in enumerate(frame_inputs):
        # 缩小图片
        small_frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5)
        # bgr通道变为rgb通道
        rgb_small_frame = small_frame[:, :, ::-1]
        # 人脸关键点检测
        face_landmarks_list = face_recognition.face_landmarks(rgb_small_frame)
        # 没有检测到关键点
        if len(face_landmarks_list) < 1:
            continue

        # 获得人眼特征点位置
        for face_landmark in face_landmarks_list:
            # 每只眼睛有六个关键点,以眼睛最左边顺时针顺序排列
            left_eye = face_landmark['left_eye']
            right_eye = face_landmark['right_eye']

            # 计算眼睛的纵横比ear,ear这里不是耳朵的意思
            ear_left = get_ear(left_eye)
            ear_right = get_ear(right_eye)
            # 判断眼睛是否闭上
            # 如果两只眼睛纵横比小于0.3,视为眼睛闭上
            closed = ear_left < 0.3 and ear_right < 0.3
            # 设置眼睛检测闭上次数
            if closed:
                closed_count += 1
            else:
                closed_count = 0
            # 如果眼睛检测闭上次数大于EYES_CLOSED_SECONDS,输出眼睛闭上
            if closed_count > EYES_CLOSED_SECONDS:
                eye_status = "frame {} | EYES CLOSED".format(frame_num)
            elif closed_count > 0:
                eye_status = "frame {} | MAYBE EYES CLOSED ".format(frame_num)
            else:
                eye_status = "frame {} | EYES OPENED ".format(frame_num)
            print(eye_status)

            plt.imshow(rgb_small_frame)
            # 左右眼轮廓第一个关键点颜色为red,最后一个关键点颜色为blue,其他关键点为yellow
            color = ['red'] + ['yellow'] * int(len(left_eye) - 2) + ['blue']
            # 按照顺序依次绘制眼睛关键点
            for index in range(len(left_eye)):
                leye = left_eye[index]
                reye = right_eye[index]
                plt.plot(leye[0], leye[1], 'bo', color=color[index])
                plt.plot(reye[0], reye[1], 'bo', color=color[index])
                plt.title(eye_status)

            plt.show()

# 计算人眼纵横比
def get_ear(eye):
    # 计算眼睛轮廓垂直方向上下关键点的距离
    A = dist.euclidean(eye[1], eye[5])
    B = dist.euclidean(eye[2], eye[4])

    # 计算水平方向上的关键点的距离
    C = dist.euclidean(eye[0], eye[3])

    # 计算眼睛的纵横比
    ear = (A + B) / (2.0 * C)

    # 返回眼睛的纵横比
    return ear


if __name__ == "__main__":
    main()
frame 0 | EYES OPENED 

在这里插入图片描述

frame 1 | MAYBE EYES CLOSED 

在这里插入图片描述

frame 2 | MAYBE EYES CLOSED 

在这里插入图片描述

frame 3 | EYES CLOSED

在这里插入图片描述

frame 4 | EYES OPENED 

在这里插入图片描述

5. 人脸对齐

人脸对齐又叫人脸配准,是很多其他功能的前提,比如我们最近做的一个脸型预测功能,就必须对人脸进行配准之后才能得到更好的准确率,否则脸在水平平面和垂直水平平面进行旋转都会影响准确率的计算。

Dlib中人脸配准有两种方式:

  • 一种是使用 get_face_chip()方法,使用5个关键点模型来进行配准,这种方法Dlib已经提供了完整的接口。
  • 另一种是自己使用68点关键点模型,根据关键点信息求解变换矩阵,然后把变换矩阵应用到整个图像上。

5.1 dlib.get_face_chip()进行人脸对齐

5.1.1 接口详解

  • get_face_chip(img: numpy.ndarray[(rows,cols,3),uint8], face: dlib.full_object_detection, size: int=150, padding: float=0.25) -> numpy.ndarray[(rows,cols,3),uint8]
  • 功能: Takes an image and a full_object_detection that references a face in that image and returns the face as a Numpy array representing the image. The face will be rotated upright and scaled to 150x150 pixels or with the optional specified size and padding.
  • 输入参数:
    • img:待对齐人脸的图像,ndarray类型、3通道、uint8
    • face:dlib的full_object_detection对象,在dlib中dlib.shape_predictor返回的即为full_object_detection对象
    • size: 返回的人脸图像尺寸,默认为150*150
    • padding:使用0.25为系数进行padding之后得到的人脸区域明显内收,得到了包含更大面部区域的对齐图像
  • 返回:ndarry形式的旋转对齐并缩放后的人脸图像

5.1.2 代码示例

"""
代码功能:
1. 用dlib人脸检测器检测出人脸,返回的人脸矩形框
2. 对检测出的人脸进行关键点检测并切割出人脸
"""
import cv2
import dlib
import matplotlib.pyplot as plt

predictor_model = 'shape_predictor_68_face_landmarks.dat'
detector = dlib.get_frontal_face_detector()# dlib人脸检测器
predictor = dlib.shape_predictor(predictor_model)

# cv2读取图像
test_img_path = "images/girls1.png"
img = cv2.imread(test_img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 人脸数rects
rects = detector(img, 0)
# faces存储full_object_detection对象
faces = dlib.full_object_detections()

for i in range(len(rects)):
    faces.append(predictor(img,rects[i]))

face_images = dlib.get_face_chips(img, faces, size=320)
# print(type(face_images))
# print(type(face_images[0]))
num_faces = len(face_images)

# 指定字体
plt.rcParams['font.sans-serif'] = ['FangSong']  # 横众轴显示字体为‘仿宋’的中文标签
plt.rcParams['axes.unicode_minus'] = False
fig1 = plt.figure(figsize=(8,2))
ax1 = plt.gca()
plt.suptitle("切分对齐的图片")
for i,image in enumerate(face_images):
    plt.subplot(1,num_faces,i+1)
    plt.imshow(image)
    plt.axis('off')

plt.figure(figsize=(7,7))
ax2 = plt.gca()
plt.imshow(img)
plt.axis('off')
plt.title("原待分割的图片")
plt.show()

在这里插入图片描述

在这里插入图片描述

5.2 使用关键点进行仿射变换进行人脸对齐

根据图像中人脸的几何结构对图像进行仿射变换(旋转、缩放、平移等),将人脸变换到一个统一的状态。
OpenCV中主要相关的函数有:

  • getAffineTransform:返回一个仿射变换矩阵
  • estimateAffine2D: 用来估计最优的仿射变换矩阵
  • warpAffine:仿射变换

5.2.1 cv2.getAffineTransform函数

getAffineTransform(src, dst) -> retval
返回一个仿射变换矩阵

  • 第一个参数src:原始图像的三个点
  • 第二个参数dst:目标图像的三个点

5.2.2 cv2.getRotationMatrix2D()

  • 函数接口:
    getRotationMatrix2D(center, angle, scale) -> retval

  • 功能:
    设定中心点和旋转角度来生成转换矩阵M

  • 输入参数:

    • 第一个参数center:旋转的中心点,一般是图片的中心,用img.shape取得长宽,然后去一半
    • 第二个参数angle:旋转的角度,正值是逆时针旋转,负值是顺时针旋转
    • 第三个参数scale:缩放因子

5.2.3 cv2.estimateAffine2D函数

  • 函数接口:
    estimateAffine2D(from_, to[, inliers[, method[, ransacReprojThreshold[, maxIters[, confidence[, refineIters]]]]]]) -> retval, inliers
  • 功能:
    返回估计最优的2*3的仿射变换矩阵
  • 输入参数:
    • from:原图关键点位置坐标
    • to: 转换后关键点位置坐标

5.2.4 cv2.warpAffine函数

warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) -> dst

通过仿射,进行图像的平移

  • 第一个参数是src:输入图像

  • 第二个参数M:变换矩阵

  • 第三个参数dsize : 输出图像的大小

  • 第四个参数flags:插值方法的组合(int类型)

    • cv.INTER_LINEAR 表示线性插值,默认选项。
    • cv.INTER_NEAREST 表示最近邻插值。
    • cv.INTER_AREA 表示区域插值。
    • cv.INTER_CUBIC 表示三次样条插值。
    • cv.INTER_LANCZOS4 表示Lanczos 插值。
  • 第五个参数borderMode:边界像素模式(int类型)

    • cv2.BORDER_CONSTANT 添加常数值为边界值,还需要指定另外一个参数 borderValue,borderValue默认值为(0, 0, 0),当然你可以指定其他值,三个元素分别表示 BGR(不是RGB,要注意哦)
    • cv2.BORDER_REPLICATE 重复最后一个元素。例如: aaaaaa| abcdefgh|hhhhhhh
    • cv2.BORDER_REFLECT 边界元素的镜像。比如: fedcba|abcdefgh|hgfedcb
    • cv2.BORDER_REFLECT_101 / cv2.BORDER_REFLECT101 / cv2.BORDER_DEFAULT 这三个效果一样,例如: gfedcb|abcdefgh|gfedcba
    • cv2.BORDER_WRAP 重复图片: cdefgh| abcdefgh|abcdefg
    • cv.BORDER_TRANSPARENT 原图像该位置的像素值
  • 第六个参数borderValue:(重点!)边界填充值,默认情况下为0

5.2.5 仿射变换人脸对齐示例

5.2.5.1 简单旋转

思路比较简单,计算两眼连线与水平线的夹角,然后通过角度得到对应的旋转矩阵。对图片进行相应的变换。

def single_face_alignment(face, landmarks):
    eye_center = ((landmarks[36,0] + landmarks[45,0]) * 1. / 2,  # 计算两眼的中心坐标
                  (landmarks[36,1] + landmarks[45,1]) * 1. / 2)
    dx = (landmarks[45,0] - landmarks[36,0])  # note: right - left
    dy = (landmarks[45,1] - landmarks[36,1])
   
    angle = math.atan2(dy, dx) * 180. / math.pi  # 计算角度
    RotateMatrix = cv2.getRotationMatrix2D(eye_center, angle, scale=1)  # 计算仿射矩阵
    align_face = cv2.warpAffine(face, RotateMatrix, (face.shape[0], face.shape[1]))  # 进行放射变换,即旋转
    
    return align_face
predictor_model = 'shape_predictor_68_face_landmarks.dat'
detector = dlib.get_frontal_face_detector()# dlib人脸检测器
predictor = dlib.shape_predictor(predictor_model)

# cv2读取图像
test_img_path = "images/girls1.png"
img = face_recognition.load_image_file(test_img_path)
imgCpy = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

# 人脸数rects
facesRect = detector(gray, 1)
num_faces = len(facesRect)
faces_img = []
faces_ladmarks = []
# 指定字体
plt.rcParams['font.sans-serif'] = ['FangSong']  # 横众轴显示字体为‘仿宋’的中文标签
plt.rcParams['axes.unicode_minus'] = False

fig1 = plt.figure(figsize=(15,8))
plt.title("待切分对齐的图片")
plt.imshow(img)
plt.axis('off')

fig2 = plt.figure(figsize=(8,2))
plt.suptitle("切分后未对齐的图片")
plt.axis('off')
# ax2 = plt.gca()
for i,face in enumerate(facesRect):
    # 切分人脸图像
    face_img = img[face.top():face.bottom(),face.left():face.right()]
    
    face_rect = detector(face_img, 1)[0]
    
    # 预测关键点
    landmarks = predictor(face_img,face_rect)
   
    # 保存到faces
    faces_img.append(face_img)
    faces_ladmarks.append(landmarks)

    fig2.add_subplot(1,num_faces,i+1)
    plt.imshow(face_img)
    plt.axis('off')

# 对齐处理
fig3 = plt.figure(figsize=(8,2))
plt.suptitle("切分后并对齐的图片")
plt.axis('off')
# ax3 = plt.gca()
for i,face_img in enumerate(faces_img):
    shape = faces_ladmarks[i]
    landmarks =  np.matrix([[p.x, p.y] for p in shape.parts()])
    face_alignment = single_face_alignment(face_img, landmarks)
    fig3.add_subplot(1,num_faces,i+1)
    plt.imshow(face_alignment)
    plt.axis('off')

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.2.5.2 采用estimateAffine2D最优化变换矩阵
def get_template_point(align_size):
    REFERENCE_FACIAL_POINTS = [
        [30.29459953, 51.69630051],
        [65.53179932, 51.50139999],
        [48.02519989, 71.73660278],
        [33.54930115, 92.3655014],
        [62.72990036, 92.20410156]
    ]
    REFERENCE_FACIAL_POINTS = np.array(REFERENCE_FACIAL_POINTS, np.float32)
    if align_size[0] == align_size[1] and align_size[0] != 112:
        REFERENCE_FACIAL_POINTS[:, 0] += 8
        # make the mark points up 8 pixels, for crop the chin in the cropped image 用于裁剪下巴
        REFERENCE_FACIAL_POINTS[:, 1] -= 8
        REFERENCE_FACIAL_POINTS = REFERENCE_FACIAL_POINTS / 112 * align_size[0]

    return REFERENCE_FACIAL_POINTS
def three_point_align(image,src_point,align_size):
    templat_point = get_template_point(align_size)
    M,_= cv2.estimateAffinePartial2D(src_point.reshape(1,5,2),templat_point.reshape(1,5,2))
    image = cv2.warpAffine(image,M,align_size,cv2.INTER_LINEAR)
    return image
predictor_model = 'shape_predictor_5_face_landmarks.dat'
detector = dlib.get_frontal_face_detector()# dlib人脸检测器
predictor = dlib.shape_predictor(predictor_model)
img = face_recognition.load_image_file('images/ym5.jpg')
imgCpy = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

# 人脸数rects
facesRect = detector(gray, 1)
num_faces = len(facesRect)

# 预测关键点
shape = predictor(face_img,facesRect[0])
landmarks =  np.matrix([[p.x, p.y] for p in shape.parts()])
print(shape.parts()[0].x,shape.parts()[0].y)
cv2.circle(imgCpy, (shape.parts()[4].x,shape.parts()[4].y), 12, [255,0,0],-1)

# img_result = three_point_align(imgCpy,landmarks,(112,112))
# plt.imshow(img_result)
plt.imshow(imgCpy)
plt.axis('off')
plt.show();
233 180

在这里插入图片描述

6. 人脸识别

本部分主要是对人脸进行识别,提供多种实际任务案例。主要用到的face_recognition内置函数有:

  • face_recognition.api.face_encodings(face_image, known_face_locations=None, num_jitters=1, model=‘small’)

    • 用途:返回图像中每个人脸的128维人脸特征
    • 输入参数:
      • face_image:输入图像,numpy数组
      • known_face_locations:每个人脸的边界框(可选),能够大大提高识别速度
      • num_jitters:计算人脸特征时重新采样人脸的次数。更高更准确,但更慢,即设置为100慢100倍
      • model:使用的识别模型,默认值为small表示小模型,只返回五个特征点;可设置为large
    • 返回:包含人脸特征的列表
  • face_recognition.api.compare_faces(known_face_encodings, face_encoding_to_check, tolerance=0.6)

    • 用途:将人脸特征与候选人脸特征进行比较,以查看它们是否匹配。
    • 输入参数:
      • known_face_encodings:已知人脸特征列表
      • face_encoding_to_check:与已知人脸特征列表进行比较的单个人脸特征
      • tolerance:人脸距离越小表示人脸越相近,当人脸距离小于tolerance,表示是同一个人;0.6是默认值,也是作者认为的最佳值(实际有所出入)
    • 返回:包含True或者False的列表,以表示是否为同一个人脸
  • face_recognition.api.face_distance(face_encodings, face_to_compare)

    • 用途:给定一个人脸特征列表,将它们与已知的人脸特征进行比较,并获得人脸特征向量之间的欧几里德距离,距离越小面孔越相似。
    • 输入参数:
      • face_encodings:已知的人脸特征列表
      • face_to_compare:未知的人脸特征列表
    • 返回:代表距离的numpy数组,和face_encodings的排序方式一样

6.1 人脸比对

来自examples/recognize_faces_in_pictures.py

该部分代码就是输入两张已知人脸图像和一张未知人脸图像,看未知人脸图像和已知人脸的哪一张图像表示的是同一个人。

%matplotlib inline
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
import face_recognition

# 通过PIL加载图片
biden_image = face_recognition.load_image_file("images/biden.jpg")
obama_image = face_recognition.load_image_file("images/obama.jpg")
unknown_image = face_recognition.load_image_file("images/obama2.jpg")

plt.imshow(biden_image)
plt.title('biden')
plt.axis('off')
plt.show()
plt.imshow(obama_image)
plt.title('obama')
plt.axis('off')
plt.show()
plt.imshow(unknown_image)
plt.title('unknown')
plt.axis('off')
plt.show()

# 获取输入图像文件中每个人脸的人脸特征,人脸特征维度为128
# 由于输入图像中可能有多张脸,因此它会返回一个特征列表。
# 默认输入图像只有一张人脸,只关心每个图像中的第一个特征,所以设置特征获取索引为0
# 建议单步看看该函数运行机制
try:
    biden_face_encoding = face_recognition.face_encodings(biden_image)[0]
    obama_face_encoding = face_recognition.face_encodings(obama_image)[0]
    unknown_face_encoding = face_recognition.face_encodings(unknown_image)[0]
except IndexError:
    # 没有找到人脸的情况
    print("I wasn't able to locate any faces in at least one of the images. Check the image files. Aborting...")
    quit()

# 已知人脸列表,按照顺序为拜登的人脸特征,奥巴马的人脸特征
known_faces = [
    biden_face_encoding,
    obama_face_encoding
]

# 如果未知人脸与已知人脸数组中的某个人匹配,则匹配结果为真
# 这个函数调用了face_distance人脸特征距离计算函数,可以单步调试看看源代码
results = face_recognition.compare_faces(known_faces, unknown_face_encoding)
print('results:',results)

# 是否和第一个人匹配
print("Is the unknown face a picture of Biden? {}".format(results[0]))
# 是否和第二个人匹配
print("Is the unknown face a picture of Obama? {}".format(results[1]))
# 这张人脸是否曾经见过
print("Is the unknown face a new person that we've never seen before? {}".format(not True in results))

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

results: [False, True]
Is the unknown face a picture of Biden? False
Is the unknown face a picture of Obama? True
Is the unknown face a new person that we've never seen before? False

6.2 人脸识别之后在原图上画框并标注姓名

来自examples/identify_and_draw_boxes_on_faces.py

该部分代码就是输入两张已知人脸图像和一张未知人脸图像,然后进行人脸识别并在未知人脸图像标注各个人脸身份信息

%matplotlib inline
import matplotlib.pyplot as plt
import face_recognition
from PIL import Image, ImageDraw
import numpy as np

# 加载第一张示例图片并提取特征
obama_image = face_recognition.load_image_file("images/obama.jpg")
obama_face_encoding = face_recognition.face_encodings(obama_image)[0]

# 加载第二张示例图片并提取特征
biden_image = face_recognition.load_image_file("images/biden.jpg")
biden_face_encoding = face_recognition.face_encodings(biden_image)[0]

# 创建已知人脸特征和其名字的数据
known_face_encodings = [
    obama_face_encoding,
    biden_face_encoding
]
known_face_names = [
    "Barack Obama",
    "Joe Biden"
]

# 加载未知人脸图片
unknown_image = face_recognition.load_image_file("images/obama_biden2.jpg")

# 人脸检测
face_locations = face_recognition.face_locations(unknown_image)
# 人脸特征提取
face_encodings = face_recognition.face_encodings(unknown_image, face_locations)

# 查看输入图像
plt.imshow(biden_image)
plt.title('biden')
plt.axis('off')
plt.show()
plt.imshow(obama_image)
plt.title('obama')
plt.axis('off')
plt.show()
plt.imshow(unknown_image)
plt.title('to be labelled')
plt.axis('off')
plt.show()

# 绘图
pil_image = Image.fromarray(unknown_image)
draw = ImageDraw.Draw(pil_image)

# 未知人脸图片中每张人脸处理
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
    # 判断和哪张人脸匹配
    matches = face_recognition.compare_faces(known_face_encodings, face_encoding)

    name = "Unknown"

    # 结果匹配方式1
    # 有多张人脸匹配成功,只以匹配的第一张人脸为结果
    # if True in matches:
    #     first_match_index = matches.index(True)
    #     name = known_face_names[first_match_index]

    # 结果匹配方式2
    # 一种更好的结果匹配方式,使用距离新面孔最小的已知面孔为结果
    # 计算已知人脸和未知人脸特征向量的距离,距离越小表示两张人脸为同一个人的可能性越大
    face_distances = face_recognition.face_distance(known_face_encodings, face_encoding)
    # 提取和未知人脸距离最小的已知人脸编号
    best_match_index = np.argmin(face_distances)
    # 提取匹配的已知人脸名
    if matches[best_match_index]:
        name = known_face_names[best_match_index]

    # 为人脸画边界框
    draw.rectangle(((left, top), (right, bottom)), outline=(0, 0, 255))

    # 在人脸边界框下方绘制该人脸所属人的名字
    text_width, text_height = draw.textsize(name)
    draw.rectangle(((left, bottom - text_height - 10), (right, bottom)), fill=(0, 0, 255), outline=(0, 0, 255))
    draw.text((left + 6, bottom - text_height - 5), name, fill=(255, 255, 255, 255))

del draw

# jupyter 绘图
pil_image.show()
plt.imshow(pil_image)
plt.title("result")
plt.axis('off')
plt.show()

# 保存识别结果
# pil_image.save("image_with_boxes.jpg")

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

6.3 在不同精度上进行人脸比对

来自examples/face_distance.py

该部分代码功能类似4.1,区别在于根据人脸特征向量的距离和不同距离阈值来判断两张人脸是否表示的是同一个人

import face_recognition

# 加载图像
known_obama_image = face_recognition.load_image_file("images/obama.jpg")
known_biden_image = face_recognition.load_image_file("images/biden.jpg")

# 获得人脸图像特征
obama_face_encoding = face_recognition.face_encodings(known_obama_image)[0]
biden_face_encoding = face_recognition.face_encodings(known_biden_image)[0]

known_encodings = [
    obama_face_encoding,
    biden_face_encoding
]

# 加载未知人脸图像
image_to_test = face_recognition.load_image_file("images/obama2.jpg")
image_to_test_encoding = face_recognition.face_encodings(image_to_test)[0]

# 计算未知人脸和已知人脸的距离
face_distances = face_recognition.face_distance(known_encodings, image_to_test_encoding)

# 查看不同距离阈值下的人脸匹配结果
for i, face_distance in enumerate(face_distances):
    # 打印距离
    print("The test image has a distance of {:.2} from known image #{}".format(face_distance, i))
    # 当阈值为0.6,是否匹配
    print("- With a normal cutoff of 0.6, would the test image match the known image? {}".format(face_distance < 0.6))
    # 当阈值为更严格的0.5,是否匹配
    print("- With a very strict cutoff of 0.5, would the test image match the known image? {}".format(
        face_distance < 0.5))
    print()
The test image has a distance of 0.33 from known image #0
- With a normal cutoff of 0.6, would the test image match the known image? True
- With a very strict cutoff of 0.5, would the test image match the known image? True

The test image has a distance of 0.86 from known image #1
- With a normal cutoff of 0.6, would the test image match the known image? False
- With a very strict cutoff of 0.5, would the test image match the known image? False

6.4 基于K最近邻KNN分类算法进行人脸识别

来自examples/face_recognition_knn.py

该部分代码和前面部分代码是一样的,只是最后提取人脸特征后用KNN近邻算法进行分类,而不是用距离来判断。

%matplotlib inline
"""
使用k-最近邻(KNN)算法进行人脸识别的示例
"""

from matplotlib import pyplot as plt
import math
from sklearn import neighbors
import os
import os.path
import pickle
from PIL import Image, ImageDraw
import face_recognition
from face_recognition.face_recognition_cli import image_files_in_folder

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}


def train(train_dir, model_save_path=None, n_neighbors=None, knn_algo='ball_tree', verbose=False):
    """
    训练k近邻分类器进行人脸识别。
    :param train_dir: 包含每个已知人员及其人脸的目录。
     Structure:
        <train_dir>/
        ├── <person1>/
        │   ├── <somename1>.jpeg
        │   ├── <somename2>.jpeg
        │   ├── ...
        ├── <person2>/
        │   ├── <somename1>.jpeg
        │   └── <somename2>.jpeg
        └── ...
    :param model_save_path: (可选) 模型保存目录
    :param n_neighbors: (可选) 分类中要加权的邻居数。如果未指定,则自动选择,就是k-NN的k的值,选取最近的k个点
    :param knn_algo: (可选) knn底层的搜索算法
    :param verbose: 打印训练信息
    :return: 返回训练好的模型
    """
    X = []
    y = []

    # 读取人员路径
    for class_dir in os.listdir(train_dir):
        if not os.path.isdir(os.path.join(train_dir, class_dir)):
            continue

        # 读取当前人员的人脸图片
        for img_path in image_files_in_folder(os.path.join(train_dir, class_dir)):
            # 加载图片
            image = face_recognition.load_image_file(img_path)
            # 人脸检测
            face_bounding_boxes = face_recognition.face_locations(image)

            if len(face_bounding_boxes) != 1:
                # 没有人就跳过当前图片
                if verbose:
                    print("Image {} not suitable for training: {}".format(img_path, "Didn't find a face" if len(
                        face_bounding_boxes) < 1 else "Found more than one face"))
            else:
                # 保存人脸特征和类别
                X.append(face_recognition.face_encodings(image, known_face_locations=face_bounding_boxes)[0])
                y.append(class_dir)

    # 自定设置n_neighbors
    if n_neighbors is None:
        n_neighbors = int(round(math.sqrt(len(X))))
        if verbose:
            print("Chose n_neighbors automatically:", n_neighbors)

    # 训练KNN分类器
    knn_clf = neighbors.KNeighborsClassifier(n_neighbors=n_neighbors, algorithm=knn_algo, weights='distance')
    knn_clf.fit(X, y)

    # 保存分类器
    if model_save_path is not None:
        with open(model_save_path, 'wb') as f:
            pickle.dump(knn_clf, f)

    return knn_clf


def predict(X_img_path, knn_clf=None, model_path=None, distance_threshold=0.6):
    """
    使用经过训练的KNN分类器识别给定图像中的人脸
    :param X_img_path: 输入图像
    :param knn_clf: (可选) knn模型,和model_path必须有一个可用
    :param model_path: (可选) knn模型路径,和knn_clf必须有一个可用
    :param distance_threshold: (可选) 人脸分类的距离阈值。阈值越大,就越容易误报。
    :return: 人脸对应的人名和其边界框
    """
    if not os.path.isfile(X_img_path) or os.path.splitext(X_img_path)[1][1:] not in ALLOWED_EXTENSIONS:
        raise Exception("Invalid image path: {}".format(X_img_path))

    if knn_clf is None and model_path is None:
        raise Exception("Must supply knn classifier either thourgh knn_clf or model_path")

    # 加载模型
    if knn_clf is None:
        with open(model_path, 'rb') as f:
            knn_clf = pickle.load(f)

    # 读取图片和进行人脸检测
    X_img = face_recognition.load_image_file(X_img_path)
    X_face_locations = face_recognition.face_locations(X_img)

    # 如果没有检测到人脸就返回空list
    if len(X_face_locations) == 0:
        return []

    # 提取人脸特征
    faces_encodings = face_recognition.face_encodings(X_img, known_face_locations=X_face_locations)

    # 使用K近邻进行分类
    closest_distances = knn_clf.kneighbors(faces_encodings, n_neighbors=1)
    are_matches = [closest_distances[0][i][0] <= distance_threshold for i in range(len(X_face_locations))]

    # 返回预测结果
    return [(pred, loc) if rec else ("unknown", loc) for pred, loc, rec in
            zip(knn_clf.predict(faces_encodings), X_face_locations, are_matches)]


def show_prediction_labels_on_image(img_path, predictions):
    """
    预测结果可视化
    :param img_path: 预测图像
    :param predictions: 预测结果
    :return:
    """
    pil_image = Image.open(img_path).convert("RGB")
    draw = ImageDraw.Draw(pil_image)

    for name, (top, right, bottom, left) in predictions:
        # 画框
        draw.rectangle(((left, top), (right, bottom)), outline=(0, 0, 255))

        # 设置名字,需要用uft-8编码
        name = name.encode("UTF-8")

        # 标注人名
        text_width, text_height = draw.textsize(name)
        draw.rectangle(((left, bottom - text_height - 10), (right, bottom)), fill=(0, 0, 255), outline=(0, 0, 255))
        draw.text((left + 6, bottom - text_height - 5), name, fill=(255, 255, 255, 255))

    del draw

    # jupyter 绘图
    # pil_image.show()
    plt.imshow(pil_image)
    plt.axis('off')
    plt.show()


if __name__ == "__main__":
    # 训练图片下载地址:https://github.com/ageitgey/face_recognition/tree/master/examples/knn_examples
    # STEP 1 训练KNN分类器
    print("Training KNN classifier...")
    classifier = train("./test_img/knn_examples/train", model_save_path="trained_knn_model.clf", n_neighbors=2)
    print("Training complete!")

    # STEP 2 使用训练好的KNN分类器对测试的人脸图像进行识别
    for image_file in os.listdir("./test_img/knn_examples/test"):
        # 待测试人脸图像路径
        full_file_path = os.path.join("./test_img/knn_examples/test", image_file)

        print("Looking for faces in {}".format(image_file))

        # 用经过训练的分类器模型查找图像中的所有人
        predictions = predict(full_file_path, model_path="trained_knn_model.clf")

        # 打印结果
        for name, (top, right, bottom, left) in predictions:
            print("- Found {} at ({}, {})".format(name, left, top))

        # 展示结果
        show_prediction_labels_on_image(os.path.join("./test_img/knn_examples/test", image_file), predictions)

6.5 基准性能测试

来自examples/benchmark.py

该部分代码实现一个非常简单的基准测试,可以让您了解人脸识别的每一步在您的系统上运行的速度

import timeit

# 这是一个非常简单的基准测试,可以让您了解人脸识别的每一步在您的系统上运行的速度。请注意,在较大的图像大小下,人脸检测变得非常缓慢
TEST_IMAGES = [
    "test_img/obama-240p.jpg",
    "test_img/obama-480p.jpg",
    "test_img/obama-720p.jpg",
    "test_img/obama-1080p.jpg"
]


# 测试函数
def run_test(setup, test, iterations_per_test=2, tests_to_run=3):
    """
    :param setup: 数据加载函数
    :param test: 数据测试函数
    :param iterations_per_test: 测试次数
    :param tests_to_run: 每轮测试调用函数多少次
    :return: execution_time单次函数推理时间,fps每秒处理次数
    """
    fastest_execution = min(timeit.Timer(test, setup=setup).repeat(tests_to_run, iterations_per_test))
    execution_time = fastest_execution / iterations_per_test
    fps = 1.0 / execution_time
    return execution_time, fps


# 以下设置不同的测试函数代码
# setup开头的是数据加载代码,test开头的是函数测试代码
setup_locate_faces = """
import face_recognition

image = face_recognition.load_image_file("{}")
"""

test_locate_faces = """
face_locations = face_recognition.face_locations(image)
"""

setup_face_landmarks = """
import face_recognition

image = face_recognition.load_image_file("{}")
face_locations = face_recognition.face_locations(image)
"""

test_face_landmarks = """
landmarks = face_recognition.face_landmarks(image, face_locations=face_locations)[0]
"""

setup_encode_face = """
import face_recognition

image = face_recognition.load_image_file("{}")
face_locations = face_recognition.face_locations(image)
"""

test_encode_face = """
encoding = face_recognition.face_encodings(image, known_face_locations=face_locations)[0]
"""

setup_end_to_end = """
import face_recognition

image = face_recognition.load_image_file("{}")
"""

test_end_to_end = """
encoding = face_recognition.face_encodings(image)[0]
"""

# 所有的基准测试都只使用一个CPU核心
print("Benchmarks (Note: All benchmarks are only using a single CPU core)")
print()

for image in TEST_IMAGES:
    size = image.split("-")[1].split(".")[0]
    print("Timings at {}:".format(size))

    # 测试人脸检测
    print(" - Face locations: {:.4f}s ({:.2f} fps)".format(
        *run_test(setup_locate_faces.format(image), test_locate_faces)))
    print(" - Face landmarks: {:.4f}s ({:.2f} fps)".format(
        *run_test(setup_face_landmarks.format(image), test_face_landmarks)))
    print(" - Encode face (inc. landmarks): {:.4f}s ({:.2f} fps)".format(
        *run_test(setup_encode_face.format(image), test_encode_face)))
    print(" - End-to-end: {:.4f}s ({:.2f} fps)".format(*run_test(setup_end_to_end.format(image), test_end_to_end)))
    print()

6.6 多线程人脸识别

来自facerec_from_webcam_multiprocessing.py

该部分代码实现多线程读取视频进行人脸识别,很简单但是实际不这样写,看看代码就好。

import face_recognition
import cv2
from multiprocessing import Process, Manager, cpu_count, set_start_method
import time
import numpy
import threading
import platform


# 多线程运行人脸识别
# 获取下一个线程的id
def next_id(current_id, worker_num):
    if current_id == worker_num:
        return 1
    else:
        return current_id + 1


# 获取上一个线程的id
def prev_id(current_id, worker_num):
    if current_id == 1:
        return worker_num
    else:
        return current_id - 1


# 读图线程
def capture(read_frame_list, Global, worker_num):
    # 读取视频
    video_capture = cv2.VideoCapture('./test_img/short_hamilton_clip.mp4')
    print("Width: %d, Height: %d, FPS: %d" % (video_capture.get(3), video_capture.get(4), video_capture.get(5)))

    while not Global.is_exit:
        # 判断是否该读图 确保当前缓存图像的线程和下一个处理图像的线程不是一个线程,以确保在处理线程开始前缓存图像
        if Global.buff_num != next_id(Global.read_num, worker_num):
            # 读取一张图像
            ret, frame = video_capture.read()
            read_frame_list[Global.buff_num] = frame # 保存对应图像处理线程要处理的图像
            Global.buff_num = next_id(Global.buff_num, worker_num) # 下一个要缓存图像的图像处理线程
        else:
            time.sleep(0.01)

    # 释放视频
    video_capture.release()


# 图片处理线程
def process(worker_id, read_frame_list, write_frame_list, Global, worker_num):
    known_face_encodings = Global.known_face_encodings
    known_face_names = Global.known_face_names
    while not Global.is_exit:

        # 等待读取图片 当线程是需要处理图像的线程时开始处理图像,同时要确保图像已经缓存
        while Global.read_num != worker_id or Global.read_num != prev_id(Global.buff_num, worker_num):
            # 判断是否退出
            if Global.is_exit:
                break

            time.sleep(0.01)

        # 延迟读取保证计算量
        time.sleep(Global.frame_delay)

        # 读取一张图像
        frame_process = read_frame_list[worker_id]

        # 设置下一个读取视频的线程
        Global.read_num = next_id(Global.read_num, worker_num)

        # 交换通道
        rgb_frame = frame_process[:, :, ::-1]

        # 人脸识别
        face_locations = face_recognition.face_locations(rgb_frame)
        face_encodings = face_recognition.face_encodings(rgb_frame, face_locations)

        # 人脸绘图
        for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
            # 是否和已知人脸匹配
            matches = face_recognition.compare_faces(known_face_encodings, face_encoding)

            name = "Unknown"

            # 如果有匹配人脸,就替换为匹配到人脸的名字
            if True in matches:
                first_match_index = matches.index(True)
                name = known_face_names[first_match_index]

            # 绘制边界框
            cv2.rectangle(frame_process, (left, top), (right, bottom), (0, 0, 255), 2)

            # 绘制人脸标签
            cv2.rectangle(frame_process, (left, bottom - 35), (right, bottom), (0, 0, 255), cv2.FILLED)
            font = cv2.FONT_HERSHEY_DUPLEX
            cv2.putText(frame_process, name, (left + 6, bottom - 6), font, 1.0, (255, 255, 255), 1)

        # 当前线程是否允许保存图像
        while Global.write_num != worker_id:
            time.sleep(0.01)

        # 保存结果
        write_frame_list[worker_id] = frame_process

        # 下一个保存图像的图像处理线程
        Global.write_num = next_id(Global.write_num, worker_num)


if __name__ == '__main__':

    # Macos设置
    if platform.system() == 'Darwin':
        set_start_method('forkserver')

    # 全局变量
    Global = Manager().Namespace()
    Global.buff_num = 1 # 正在缓存图像的图像处理线程
    Global.read_num = 1 # 正在处理图像的图像线程
    Global.write_num = 1 # 正在保存结果的图像处理线程
    Global.frame_delay = 0 # 延迟时间
    Global.is_exit = False # 是否退出
    read_frame_list = Manager().dict()
    write_frame_list = Manager().dict()

    # 处理线程数
    if cpu_count() > 2:
        # 减1是为了留出一个线程读取视频
        worker_num = cpu_count() - 1
    else:
        worker_num = 2

    # 子线程列表
    p = []

    # 创建一个线程来捕获帧(如果使用子线程,它将在Mac上崩溃)
    # 线程0为读图线程
    p.append(threading.Thread(target=capture, args=(read_frame_list, Global, worker_num,)))
    p[0].start()

    # 读取已有图像
    obama_image = face_recognition.load_image_file("./test_img/obama.jpg")
    obama_face_encoding = face_recognition.face_encodings(obama_image)[0]
    biden_image = face_recognition.load_image_file("./test_img/lin-manuel-miranda.png")
    biden_face_encoding = face_recognition.face_encodings(biden_image)[0]

    # 创建已有数据信息
    Global.known_face_encodings = [
        obama_face_encoding,
        biden_face_encoding
    ]
    Global.known_face_names = [
        "Barack Obama",
        "lin-manuel-miranda."
    ]

    # 创建图像处理子线程
    for worker_id in range(1, worker_num + 1):
        p.append(Process(target=process, args=(worker_id, read_frame_list, write_frame_list, Global, worker_num,)))
        p[worker_id].start()

    # 开始读取视频
    last_num = 1 # 已经处理好的图像序列号
    fps_list = []
    tmp_time = time.time()
    while not Global.is_exit:
        while Global.write_num != last_num:
            last_num = int(Global.write_num)

            # 计算FPS
            delay = time.time() - tmp_time
            tmp_time = time.time()
            fps_list.append(delay)
            if len(fps_list) > 5 * worker_num:
                fps_list.pop(0)
            fps = len(fps_list) / numpy.sum(fps_list)
            print("fps: %.2f" % fps)

            # 根据延时动态调整检测性能
            if fps < 6:
                Global.frame_delay = (1 / fps) * 0.75
            elif fps < 20:
                Global.frame_delay = (1 / fps) * 0.5
            elif fps < 30:
                Global.frame_delay = (1 / fps) * 0.25
            else:
                Global.frame_delay = 0

            # 展示结果
            cv2.imshow('Video', write_frame_list[prev_id(Global.write_num, worker_num)])

        # 退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            Global.is_exit = True
            break

        time.sleep(0.01)
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值