OpenCV-Python实战(15)——面部特征点检测详解(仅需5行代码学会3种面部特征点检测方法)

x33g5p2x  于2021-11-02 转载在 Python  
字(7.1k)|赞(0)|评价(0)|浏览(1093)

0. 前言

人脸处理是人工智能中的一个热门话题,人脸处理可以使用计算机视觉算法从人脸中自动提取大量信息,例如身份、意图和情感。在计算机视觉中,面部关键点(也称为面部特征点)的定位通常是许多面部分析方法和算法中的关键步骤。例如,面部表情识别、头部姿态估计和疲倦检测系统都依赖于面部特征点检测提供的面部信息。在本文中,我们将使用 OpenCVdlib 以及 face_recognition 检测面部特征点。

1. 面部特征点简介

面部特征点检测算法的目标是自动识别图像或视频中面部特征点的位置。更具体地说,这些关键点是描述面部组件位置(例如,嘴角或眼角)的主要点,或者是连接面部组件和面部轮廓主要点及其插值点。形式上,给定表示为 I 的面部图像,特征点检测算法检测 D 个特征点的位置 x = x 1 , y 1 , x 2 , y 2 , . . . , x D , y D x = {x_1, y_1, x_2, y_2, ..., x_D, y_D}x=x1​,y1​,x2​,y2​,...,xD​,yD​,其中 xy 表示图像的面部特征点的坐标。

2. 使用 OpenCV 检测面部特征点

OpenCV 人脸特征点 API 称为 Facemark。它包括特征点检测的三种不同实现:

  • FacemarkLBF
  • FacemarkKamezi
  • FacemarkAAM

以下示例展示了如何使用这些算法检测面部特征点:

import cv2
import numpy as np
# 加载图片
image = cv2.imread("example",0)
# 检测人脸
cas = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
faces = cas.detectMultiScale(image , 1.5, 5)
print("faces", faces)
# 创建特征点检测器并对其进行测试
print("testing LBF")
# 第一种面部特征点检测方法,第一行代码
# 创建特征点检测器
facemark = cv2.face.createFacemarkLBF()
# 第一种面部特征点检测方法,第二行代码
# 加载检测器模型
facemark.loadModel("lbfmodel.yaml")
# 第一种面部特征点检测方法,第三行代码
# 检测面部特征点
ok, landmarks = facemark.fit(image , faces)
print ("landmarks LBF", ok, landmarks)
# 测试其他特征点检测算法
print("testing AAM")
facemark = cv2.face.createFacemarkAAM()
facemark .loadModel("aam.xml")
ok, landmarks = facemark.fit(image , faces)
print ("landmarks AAM", ok, landmarks)
print("testing Kazemi")
facemark = cv2.face.createFacemarkKazemi()
facemark .loadModel("face_landmark_model.dat")
ok, landmarks = facemark.fit(image , faces)
print ("landmarks Kazemi", ok, landmarks)

上述示例使用 OpenCV 提供的三种不同算法来检测面部特征点。

3. 用 dlib 检测面部特征点

我们也可以使用 dlib 库来检测面部特征点,接下来,我们加载测试图像,然后使用 dlib 执行人脸检测,最后使用人脸特征检测器获取检测面部特征点。想要运行以下代码,首先需要下载检测面部特征点检测器文件 shape_predictor_68_face_landmarks.dat

# 加载图像并转换为灰度图像
test_face = cv2.imread("example.png")
gray = cv2.cvtColor(test_face, cv2.COLOR_BGR2GRAY)
# 人脸检测
detector = dlib.get_frontal_face_detector()
rects = detector(gray, 0)
# 检测面部特征点
p = "shape_predictor_68_face_landmarks.dat"
# 第二种面部特征点检测方法,第一行代码
predictor = dlib.shape_predictor(p)
# 第二种面部特征点检测方法,第二行代码
shape = predictor(gray, rect)

shape 是一个 dlib full_object_detection 对象,用于表示图像中对象的位置,接下来需要将其转换为 numpy 数组,编写 shape_to_np() 函数执行此转换:

def shape_to_np(dlib_shape, dtype="int"):
    # 初始化 (x, y) 坐标列表
    coordinates = np.zeros((dlib_shape.num_parts, 2), dtype=dtype)
    # 循环所有面部特征点,并将其转换为 (x, y) 坐标的元组
    for i in range(0, dlib_shape.num_parts):
        coordinates[i] = (dlib_shape.part(i).x, dlib_shape.part(i).y)
    # 返回 (x,y) 坐标的列表
    return coordinates

最后,在图像中绘制了 68 个面部特征点。我们可以使用灵活的方式以所需的格式在图像中绘制特征点,接下来,介绍集中常用的绘制检测到的面部特征点的不同方法:

(1) 使用线条绘制面部特征点形状,以连接绘制脸部的不同部分的轮廓(例如鼻子、眼睛等):

# 定义不同特征点取值切片
JAWLINE_POINTS = list(range(0, 17))
RIGHT_EYEBROW_POINTS = list(range(17, 22))
LEFT_EYEBROW_POINTS = list(range(22, 27))
NOSE_BRIDGE_POINTS = list(range(27, 31))
LOWER_NOSE_POINTS = list(range(31, 36))
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_EYE_POINTS = list(range(42, 48))
MOUTH_OUTLINE_POINTS = list(range(48, 61))
MOUTH_INNER_POINTS = list(range(61, 68))
ALL_POINTS = list(range(0, 68))
# 使用线条绘制面部特征点
def draw_shape_lines_all(np_shape, image):
    draw_shape_lines_range(np_shape, image, JAWLINE_POINTS)
    draw_shape_lines_range(np_shape, image, RIGHT_EYEBROW_POINTS)
    draw_shape_lines_range(np_shape, image, LEFT_EYEBROW_POINTS)
    draw_shape_lines_range(np_shape, image, NOSE_BRIDGE_POINTS)
    draw_shape_lines_range(np_shape, image, LOWER_NOSE_POINTS)
    draw_shape_lines_range(np_shape, image, RIGHT_EYE_POINTS, True)
    draw_shape_lines_range(np_shape, image, LEFT_EYE_POINTS, True)
    draw_shape_lines_range(np_shape, image, MOUTH_OUTLINE_POINTS, True)
    draw_shape_lines_range(np_shape, image, MOUTH_INNER_POINTS, True)
# 连接不同的点来绘制曲线形状
def draw_shape_lines_range(np_shape, image, range_points, is_closed=False):
    np_shape_display = np_shape[range_points]
    points = np.array(np_shape_display, dtype=np.int32)
    cv2.polylines(image, [points], is_closed, (255, 255, 0), thickness=2, lineType=cv2.LINE_8)
# 函数调用
draw_shape_lines_all(shape, test_face)
cv2.imshow("Landmarks detection using dlib", test_face)
cv2.waitKey(0)

(2) 调用 draw_shape_lines_range() 函数,可以仅绘制指定面部组件轮廓线,例如下颚线 JAWLINE_POINTS

draw_shape_lines_range(shape, test_face, JAWLINE_POINTS)

(3) 绘制所有特征点及其位序:

# 绘制指定的特征点
def draw_shape_points_pos_range(np_shape, image, points):
    np_shape_display = np_shape[points]
    draw_shape_points_pos(np_shape_display, image)
# 使用每个特征点及其位序绘制形状
def draw_shape_points_pos(np_shape, image):
    for idx, (x, y) in enumerate(np_shape):
        # 绘制每个检测到的特征点的位序
        cv2.putText(image, str(idx + 1), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255))
        # 在每个特征点位置上绘制一个点
        cv2.circle(image, (x, y), 2, (0, 255, 0), -1)
# 可以用两种方法来绘制检测到的所有特征点及其位序
# 方法1
draw_shape_points_pos(shape, test_face)
# 方法2
draw_shape_points_pos_range(shape, test_face, ALL_POINTS)

(4) 我们也可以仅绘制所有特征点:

# 绘制指定特征点
def draw_shape_points_range(np_shape, image, points):
    np_shape_display = np_shape[points]
    draw_shape_points(np_shape_display, image)
# 绘制所有特征点
def draw_shape_points(np_shape, image):
    for (x, y) in np_shape:
        cv2.circle(image, (x, y), 2, (0, 255, 0), -1)
# 可以用两种方法绘制所有特征点
# 方法1
draw_shape_points(shape, test_face)
# 方法2
draw_shape_points_range(shape, test_face, ALL_POINTS)

(5) 利用上述函数,我们可以仅绘制指定特征点,例如仅绘制眼部和鼻子特征点:

draw_shape_points_pos_range(shape, test_face, LEFT_EYE_POINTS + RIGHT_EYE_POINTS + NOSE_BRIDGE_POINTS)

dlib 还提供了检测与双眼和鼻尖位置相对应的 5 个面部特征点检测器,如果想要使用此检测器,需要首先下载 shape_predictor_5_face_landmarks.dat,然后加载它:

p = "shape_predictor_5_face_landmarks.dat"

4. 使用 face_recognition 检测面部特征点

如果想要使用 face_recognition 包检测和绘制面部特征点,需要调用 face_recognition.face_landmarks() 函数:

# 加载图像
image = cv2.imread("example.png")
image_68 = image.copy()
# 将图像从 BGR 颜色转换为 RGB 颜色
rgb = image[:, :, ::-1]
# 检测 68 个特征点
# 第三种面部特征点检测方法,第一行代码
face_landmarks_list_68 = face_recognition.face_landmarks(rgb)

此函数返回图像中每张脸的面部特征点(例如,眼睛和鼻子)的字典,如果打印检测到的特征点,则可以看到输出如下:

[{'chin': [(113, 251), (111, 283), (115, 315), (122, 346), (136, 376), (154, 402), (177, 425), (203, 442), (231, 447), (260, 442), (285, 426), (306, 403), (323, 377), (334, 347), (340, 315), (343, 282), (343, 251)], 'left_eyebrow': [(123, 223), (140, 211), (163, 208), (185, 211), (206, 220)], 'right_eyebrow': [(240, 221), (263, 212), (288, 209), (312, 211), (332, 223)], 'nose_bridge': [(225, 249), (225, 272), (225, 295), (226, 319)], 'nose_tip': [(201, 337), (213, 340), (226, 343), (239, 339), (252, 336)], 'left_eye': [(144, 248), (158, 239), (175, 240), (188, 254), (173, 255), (156, 254)], 'right_eye': [(262, 254), (276, 240), (293, 239), (308, 248), (295, 254), (278, 255)], 'top_lip': [(185, 377), (200, 370), (216, 364), (226, 367), (238, 364), (255, 370), (274, 377), (267, 378), (238, 378), (227, 380), (215, 379), (192, 378)], 'bottom_lip': [(274, 377), (257, 391), (240, 399), (228, 400), (215, 398), (200, 391), (185, 377), (192, 378), (215, 381), (227, 382), (239, 380), (267, 378)]}]

最后绘制检测到的特征点:

for face_landmarks in face_landmarks_list_68:
    for facial_feature in face_landmarks.keys():
        for p in face_landmarks[facial_feature]:
            cv2.circle(image_68, p, 2, (255, 255, 0), -1)

face_recognition.face_landmarks() 函数的用法如下:

face_landmarks(face_image, face_locations=None, model="large")

默认情况下会检测到 68 个特征点,如果 model="small",只会检测 5 个特征点:

# 检测 5 个特征点
face_landmarks_list_5 = face_recognition.face_landmarks(rgb, None, "small")

如果打印返回的结果 face_landmarks_list_5,可以得到以下输出:

[{'nose_tip': [(227, 343)], 'left_eye': [(145, 248), (191, 253)], 'right_eye': [(307, 248), (262, 252)]}]

在这种情况下,结果字典只包含双眼和鼻尖的面部特征点位置。
接下来,同样绘制检测到的特征点:

for face_landmarks in face_landmarks_list_5:
    for facial_feature in face_landmarks.keys():
        for p in face_landmarks[facial_feature]:
            cv2.circle(image_68, p, 2, (255, 255, 0), -1)


Facemark
在图中,可以看到使用 face_recognition 包检测到的 68 个和 5 个面部特征点。

小结

在本文中,介绍了如何使用 OpenCVFacemark API
dlibshape_predictor() 函数以及 face_recognitionface_landmarks() 函数进行面部特征点检测。

相关文章