介绍

DLib库:一个机器学习的开源库,包括了机器学习的很多算法,运用起来很便利,直接包括头文件即可,并且不依赖于其他库(自带图画编解码库源码)Dlib能够帮助您创立很多杂乱的机器学习方面的软件来帮助解决实践问题。现在Dlib现已被广泛的用在行业和学术领域,包括机器人,嵌入式设备,移动电话和大型高性能核算环境。

ps:这儿以对静态图画进行人脸辨以为例,如需进行视频实时人脸辨认,只需运用opencv调用摄像头对每帧进行辨认即可,思路和原理是相同的。

人脸辨认的一般进程以及原理:

  1. 人脸检测:
    • 输入一张带有人脸的图画,将图画中的人脸检测出来,例如常见的检测人脸方位并在对应方位绘 制矩形框
  2. 提取人脸特征点:
    • 在检测得到图画中人脸方位的基础之上,进一步对人脸进行要害点检测,每张人脸上都对应许多要害点,每个要害点都对应人脸的特定方位,这些要害点包括着该张人脸的各种信息
  3. 将人脸信息映射为特征信息并保存:
    • 运用相关现已训练好的模型进行信息映射,输入检测的人脸信息,将其转化为该人脸对应的128D特征信息并保存,以为每张人脸与128D特征信息相对应
  4. 人脸辨认:
    • 人脸辨认望文生义便是输入一张人脸图画,辨认出该人脸对应的信息,当咱们输入一张新的人脸图画后,将其进行以上三个进程得到其128D特征信息,然后将其与存储的映射信息(每个人脸128D特征信息对应姓名)进行匹配

人脸检测

思路:

  1. 准备工作:

    • 给定待检测图画途径,运用opencv将图画加载进来
    • 调用dlib的get_frontal_face_detector() 得到dlib供给的正向人脸检测器, 该检测器用于辨认图画中的人脸方位 (输入图画, 得到图画中的人脸方位信息)
  2. 人脸检测:

    • 运用正向人脸检测器直接对图画进行检测,该类完结了__call__办法,因而能够直接经过方针进行调用。此外,传入两个参数,第一个为待检测图画,因为opencv加载的图画默以为为BGR(Blue, Green, Red),而该参数需求RGB的信息格式,这儿将其转化为RGB(Red, Green, Blue)并进行传入,第二个参数默以为1,表明对图画进行上采样一次以获取更多相关信息,这儿上采样其实便是和池化相反的操作 (池化是进行信息交融/下采样以得到更小的维度但丢掉了部分信息)
    • ps:这儿不转化为RGB其实也能够,因为人脸检测对于色彩信息不敏感,用途不大
  3. 处理结果

    • 得到一个序列方针,该方针包括检测到的人脸的信息。因为一张图画中可能有着多张人脸,因而对其进行遍历,每个元素对应一张人脸,包括对应人脸的方位信息。
    • 经过opencv在检测到的特定方位进行矩形框制作并显现图画

代码:

import dlib
import cv2
import os
def detector_face(img_path):
    print(f'正在处理的图片途径: {img_path}')
    img = cv2.imread(img_path)  # 加载图片
    detector = dlib.get_frontal_face_detector()  # 正向人脸检测器
    # 上采样一次并进行人脸检测
    results_dets = detector(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), 1)  # 上采样一次并进行检测
    print(type(results_dets))
    # 输出检测结果
    print(f'共检测到 {len(results_dets)} 张人脸')
    for index, d in enumerate(results_dets):
        print(f'Detector: {index}, rectangle: position1-{(d.left(), d.top())}, position2-{d.right(), d.bottom()}')
        # 制作矩形框
        cv2.rectangle(img, (d.left(),d.top()),(d.right(), d.bottom()), color=(255,255,0), thickness=3)
    cv2.imshow('result', img)
    cv2.waitKey(0)  # 等候无限长期, 等候用户按下一个键
if __name__ == '__main__':
    test_img_list = ['test1.jpg', 'test2.jpeg', 'test4.jpg']
    test_data_path = os.path.join('data', 'image_data')
    for test_img in test_img_list:
        img_path = os.path.join(test_data_path, test_img)
        detector_face(img_path)

在main办法中进行测验,测验结果如下(测验图画来源于Bing图片):

【Python】基于DLib库进行人脸识别
【Python】基于DLib库进行人脸识别
【Python】基于DLib库进行人脸识别

如图所示,人脸检测部分完结,总体逻辑很简单

人脸特征点检测

思路和人脸检测基本相同: 在经过正向人脸检测器检测得到图画中的人脸方位信息之后,经过 68个要害点检测模型 输入每张人脸方位数据,得到68个特征点方位信息,并经过opencv将这些要害点制作并连线 (ps: 制作仅仅可视化操作,特征点连线非有必要)

  • 68个要害点检测模型:model/shape_predictor_68_face_landmarks.dat 从网上下载,指定途径经过DLib供给的办法进行加载即可。

人脸68个要害点:

要害点对应信息:每个要害点对应一个序号

# 例如lips: 要害点 [48, 60) 表明嘴唇相关信息, (Red, Green, Blue, 透明度)
pred_types = {
    'face': ((0, 17), (0.682, 0.780, 0.909, 0.5)), 
    'eyebrow1': ((17, 22), (1.0, 0.498, 0.055, 0.4)), 
    'eyebrow2': ((22, 27), (1.0, 0.498, 0.055, 0.4)), 
    'nose': ((27, 31), (0.345, 0.239, 0.443, 0.4)), 
    'nostril': ((31, 36), (0.345, 0.239, 0.443, 0.4)),
    'eye1': ((36, 42), (0.596, 0.875, 0.541, 0.3)), 
    'eye2': ((42, 48), (0.596, 0.875, 0.541, 0.3)), 
    'lips': ((48, 60), (0.596, 0.875, 0.541, 0.3)), 
    'teeth': ((60, 68), (0.596, 0.875, 0.541, 0.4)) 
    }

对应图片:留意图片中是从1开端的,上面的阐明是从索引0开端的

【Python】基于DLib库进行人脸识别

代码:

import dlib
import cv2
import os
# 将当时特征点与相邻的下一个特征点之间制作连线
def draw_line(img, shape, i):
    cv2.line(img, pt1=(shape.part(i).x, shape.part(i).y), pt2=(shape.part(i+1).x, shape.part(i+1).y),
             color=(255, 0, 0), thickness=1)
# 衔接要害点
def connect_points(img, shape, i):
    if i + 1 < 17:
        # face
        draw_line(img, shape, i)
    elif 17 < i + 1 < 22:
        # eyebrow1
        draw_line(img, shape, i)
    elif 22 < i + 1 < 27:
        # eyebrow2
        draw_line(img, shape, i)
    elif 27 < i + 1 < 31:
        # nose
        draw_line(img, shape, i)
    elif 31 < i + 1 < 36:
        # nostril
        draw_line(img, shape, i)
    elif 36 < i + 1 < 42:
        # eye1
        draw_line(img, shape, i)
    elif 42 < i + 1 < 48:
        # eye2
        draw_line(img, shape, i)
    elif 48 < i + 1 < 60:
        # lips
        draw_line(img, shape, i)
    elif 60 < i + 1 < 68:
        # teeth
        draw_line(img, shape, i)
# 运用DLib库供给的训练好的模型shape_predictor_68_face_landmarks.dat检测出人脸上的68个要害点
def face_landmarks_detector(img_path, is_point_digit=False, is_connect=False, is_save_result=False):
    print(f'正在处理图画: {img_path}')
    # 正面人脸检测器
    detector = dlib.get_frontal_face_detector()
    predictor_path = 'model/shape_predictor_68_face_landmarks.dat'
    # 将人脸要害点检测模型加载到内存中并创立一个shape_predictor实例
    shape_predictor = dlib.shape_predictor(predictor_path)
    img = cv2.imread(img_path)  # 加载图画
    dets = detector(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), 1)    # 上采样一次
    print(f'检测到的人脸数量: {len(dets)}')
    for i, d in enumerate(dets):
        print(f'Detection: {i}, pos-1: {(d.right(), d.top())}, pos-2: {(d.left(), d.bottom())}')
        cv2.rectangle(img, (d.right(), d.top()), (d.left(), d.bottom()), color=(255, 0, 0), thickness=2)  # 制作矩形框
        shape = shape_predictor(img, d)    # 调用模型经过人脸方位信息进行要害点检测
        # 打印相关信息
        print(f'要害点个数: {shape.num_parts}')
        print(f'每个面部的矩形框方位列表: {shape.rect}')
        print(f'要害点-1: {shape.part(0)}, 要害点-2: {shape.part(67)}')
        print(f'要害点坐标: {shape.parts()}')    # 68个要害点, 因而有68个坐标
        # 制作要害点
        for i, point in enumerate(shape.parts()):
            cv2.circle(img, (point.x, point.y), 1, color=(255, 0, 255), thickness=1)    # 每一个要害点经过制作一个小圆形来表明
            if is_point_digit:
                # 在每个要害点的方位制作要害点编号文本
                cv2.putText(img, str(i+1), (point.x, point.y), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                            fontScale=0.3, color=(0, 255, 0))
            if is_connect:
                connect_points(img, shape, i)
    cv2.imshow('detect landmarks', img)
    if is_save_result:
        dir, f= os.path.split(img_path)
        suffix = f.split('.')[1]
        save_f = os.path.join(dir, 'detection_results', f.split('.')[0] + '_points' + '.' + suffix)
        cv2.imwrite(save_f, img)
    cv2.waitKey(0)
if __name__ == '__main__':
    test_img_list = ['test1.jpg', 'test2.jpeg','test4.jpg']
    test_data_path = os.path.join('data', 'image_data')
    for test_img in test_img_list:
        img_path = os.path.join(test_data_path, test_img)
        face_landmarks_detector(img_path, is_point_digit=True, is_connect=True, is_save_result=True)

draw_line和connect_points办法用于对不同部位的要害点制作衔接,思路与人脸检测代码相似,在检测到人脸方位后进一步提取人脸要害点信息并制作。

测验结果(测验图画来源于Bing图片):

【Python】基于DLib库进行人脸识别
【Python】基于DLib库进行人脸识别
【Python】基于DLib库进行人脸识别

人脸辨认

人脸辨认一般包括两个进程:

  1. 注册人脸信息 (gallery)
  2. 人脸辨认

例如有张三、李四、王五三个人,咱们要对它们进行人脸辨认,要先将这几个人进行注册,得到它们的128D人脸信息并存储在特定的方位,例如{张三的人脸信息: {张三}}, 当下一次对张三进行人脸辨认时,咱们对新检测到的张三人脸与存储的人脸信息列表进行挨个比对,若成功匹配上则辨认到对应的人名。

这儿有几个问题:

  1. 怎么注册:因为项目比较简单,咱们仅仅对每个人的一张人脸图画进行128D特征信息提取保存即可,注册的人脸图画一般质量要求较高(光照、遮挡、暗影等)。在实践应用中,现实情况往往较为杂乱,咱们能够对每个人的人脸的不同视点(正脸、侧脸等)进行提取并保存,存储中这几个人脸信息都对应同一人名,能够一定程度上进步人脸辨认的准确度

  2. 怎么转换为128D特征信息:这儿运用的是dlib_face_recognition_resnet_model_v1.dat能够在网上自行下载,运用DLib供给的相关API直接加载即可。

    • 这儿运用的模型是ResNet-34模型
  3. 怎么匹配人脸特征:核算两个人脸信息的欧式间隔(L2范数),然后设定为一个阈值,当欧式举例小于该间隔时,则以为是同一张人脸

此外,face_descriptor = facerec.compute_face_descriptor(img, shape, 100, 0.25)用于提取128D特征信息,参数分别为: 处理图画, 人脸特征点信息, 从头采样次数, 填充巨细;第三个参数若从头采样次数为100,核算速度则为值为10时候的十倍;第四个参数是用来调整人脸检测框的巨细的,它表明相对于检测到的人脸框的巨细,需求扩展或缩小的比例,经过调整第四个参数的值,能够控制核算人脸特征描述子的区域的巨细,从而影响核算特征的准确性和速度。

以下代码中的face_chip是对原始图画进行对齐处理,即将人脸旋转和缩放到标准方位和巨细,以便于后续的特征提取和匹配。get_face_chip()函数能够将人脸图画进行对齐,并提取出人脸区域。它将原始人脸图画中的人脸部分旋转、缩放、裁剪等变换操作,得到一个150*150巨细的图画,使得得到的人脸区域能够更好地习惯后续的特征提取和辨认进程。

代码:

import cv2
import glob
import os
import dlib
import numpy as np
# 运用CUDA, 假如不支持CUDA将主动切换为CPU
dlib.DLIB_USE_CUDA=True
# 核算得到欧氏间隔
def get_euclidean(img1, img2):
    return np.linalg.norm(img1 - img2, ord=2)
def extracting_feature_vector(img, model_path):
    """
    # 提取图画中人脸嵌入向量信息
    :param img: 原始图画数组
    :param model_path: 模型地点途径
    :return: [(图画中每个人脸的嵌入向量信息, 矩形框方位信息)]
    """
    print('extracting feature vector...')
    # 加载模型
    detector = dlib.get_frontal_face_detector()    # 前向人脸检测器
    predictor = dlib.shape_predictor(model_path['shape_predictor'])    # 要害点提取器
    recognizer = dlib.face_recognition_model_v1(model_path['face_resnet-34'])    # 人脸辨认器
    # 检测人脸
    dets = detector(img, 1)
    print(f'图画中的人脸数量: {len(dets)}')
    # 存储每张图片中人脸的矩形框坐标以及特征
    face_features, face_rects = [], []
    # 处理当时图画中检测到的人脸
    for i, det in enumerate(dets):
        # 打印矩形框信息
        print(f'Detection: {i}, pos-1: {(det.left(), det.top())}, pos-2: {(det.right(), det.bottom())}')
        # 存储矩形框方位坐标
        face_rects.append({'pos1': (det.left(), det.top()), 'pos2': (det.right(), det.bottom())})
        # 要害点检测
        shape = predictor(img, det)
        # 这个和下面那个选一个用就行了, 这儿用的是下面那个
        # face_desc = recognizer.compute_face_descriptor(img, shape, 10, 0.25)
        # 对齐原始图画,提取人脸区域并进行归一化处理, 得到图画对应的face chip(ndarray方针)
        face_chip = dlib.get_face_chip(img, shape)
        face_chip_desc = recognizer.compute_face_descriptor(face_chip)  # 转换为128D的特征向量信息
        face_chip_desc_array = np.array(face_chip_desc)
        face_features.append(face_chip_desc_array)  # 存储起来
    # 每个人脸的嵌入向量信息和矩形框方位信息
    face_features_and_rects = list(zip(face_features, face_rects))
    return face_features_and_rects
# 提取数据集中所有图片信息, 并转化为字典, label为图片名
def extract_feature(regist_img_paths, model_path):
    register_features = {}
    for p in regist_img_paths:
        img = cv2.imread(p)
        img_name = os.path.split(p)[-1].split('.')[0]
        face_features_and_rects = extracting_feature_vector(img, model_path=model_path)
        register_features[img_name] = face_features_and_rects[0][0]    # {name: 特征向量}
    return register_features
def face_recognition(img_paths, regist_img_paths, model_path, is_display_info=False, is_save=False):
    # 注册图画信息
    register_features_dict = extract_feature(regist_img_paths, model_path=model_path)
    # 读取图片
    for p in img_paths:
        tar_img = cv2.imread(p)
        tar_img_name = os.path.split(p)[-1]
        print(f'正在辨认: {tar_img_name}...')
        tar_face_features_and_rects = extracting_feature_vector(tar_img, model_path=model_path)  # 将该图画转换为128D信息
        # 制作显现方针图片信息
        if is_display_info:
            cv2.putText(tar_img, tar_img_name.split('.')[0], (20, tar_img.shape[0] - 20), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                        fontScale=0.65, color=(255, 0, 0), thickness=2)
            cv2.putText(tar_img, f'Face Num: {len(tar_face_features_and_rects)}', (20, tar_img.shape[0] - 50),
                        fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.8, color=(255, 0, 0), thickness=2)
        for feature_and_rect in tar_face_features_and_rects:  # 对方针图画中辨认到的每个人脸进行比照
            tar_face_feature = feature_and_rect[0]    # 128D信息
            tar_face_rect = feature_and_rect[1]    # 矩形框方位信息
            # 对当时图画中的当时人物进行矩形框制作
            cv2.rectangle(tar_img, tar_face_rect['pos1'], tar_face_rect['pos2'], color=(0,255,255),
                          thickness=2)
            # 比照方针人脸和数据库中的每张人脸
            euclideans_dict = dict()    # {欧氏间隔: 图片名}
            for src_name, src_face_feature in register_features_dict.items():
                # 核算源图画和方针图画之间的欧氏间隔
                euclideans_distance = get_euclidean(src_face_feature, tar_face_feature)
                euclideans_dict[euclideans_distance] = src_name
            # 得到记录中最小的欧氏间隔及图片名
            min_eucli = min(euclideans_dict.keys())
            # 这儿将阈值设为0.6
            if min_eucli < 0.6:
                predict_name = euclideans_dict[min_eucli]
                # 将辨认结果制作在图片上
                cv2.putText(tar_img, predict_name, (tar_face_rect['pos1'][0], tar_face_rect['pos1'][1]-2),
                            fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.65, color=(0,255,255), thickness=2)
            else:
                cv2.putText(tar_img, 'unknown', (tar_face_rect['pos1'][0], tar_face_rect['pos1'][1] - 2),
                            fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.65, color=(255, 0, 0), thickness=2)
            print(f'匹配成功: {predict_name}')
            if is_save:
                save_name = tar_img_name.split('.')[0] + '_reco.' + tar_img_name.split('.')[1]
                save_filename = os.path.join('data', 'reco_results', save_name)
                cv2.imwrite(save_filename, tar_img)
                print('输出结果已保存...')
if __name__ == '__main__':
    # 模型途径
    model_path = {'shape_predictor': os.path.join('model', 'shape_predictor_68_face_landmarks.dat'),
                  'face_resnet-34': os.path.join('model', 'dlib_face_recognition_resnet_model_v1.dat')}
    # 图片数据途径
    img_paths = glob.glob(os.path.join('data', 'test_data', '*.jpg'))
    register_img_paths = glob.glob(os.path.join('data', 'register_image_data', '*.jpg'))
    # 人脸辨认
    face_recognition(img_paths=img_paths,regist_img_paths=register_img_paths , model_path=model_path, is_display_info=True, is_save=True)

这儿仅仅是简单地将图画的名称作为对应label,将检测结果制作到图画上,若没有匹配到,则为unknown。

总结

人脸辨认的一般进程:

  1. 人脸检测
  2. 人脸要害点提取
  3. 人脸辨认

以上人脸辨认代码还有许多地方能够优化,例如能够将上面提到的用多张人脸进行注册并放到对应数据库中,此外,当图画中出现人脸数量过多时会导致辨认速度变慢,这儿能够运用Python供给的线程池对人脸辨认/检测进行多线程检测,另外对于人脸特征匹配的算法也能够进一步优化等等… 这些就不再做逐个介绍了,在一般思路上面进行优化即可。

参阅:

  • www.cnblogs.com/AdaminXie/p…
  • blog.csdn.net/ebzxw/artic…