使用 dlib 面部标志裁剪面部

Posted

技术标签:

【中文标题】使用 dlib 面部标志裁剪面部【英文标题】:Cropping face using dlib facial landmarks 【发布时间】:2018-03-24 13:51:54 【问题描述】:

我正在尝试使用 dlib 识别的面部标志来裁剪面部。右眉毛会引起问题 - 作物平展而不是沿着眉毛弧线。

我在这里做错了什么?

from imutils import face_utils
import imutils
import numpy as np
import collections
import dlib
import cv2

def face_remap(shape):
   remapped_image = shape.copy()
   # left eye brow
   remapped_image[17] = shape[26]
   remapped_image[18] = shape[25]
   remapped_image[19] = shape[24]
   remapped_image[20] = shape[23]
   remapped_image[21] = shape[22]
   # right eye brow
   remapped_image[22] = shape[21]
   remapped_image[23] = shape[20]
   remapped_image[24] = shape[19]
   remapped_image[25] = shape[18]
   remapped_image[26] = shape[17]
   # neatening 
   remapped_image[27] = shape[0]

   return remapped_image

"""
MAIN CODE STARTS HERE
"""
# load the input image, resize it, and convert it to grayscale
image = cv2.imread("images/faceCM1.jpg")
image = imutils.resize(image, width=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

out_face = np.zeros_like(image)

# initialize dlib's face detector (HOG-based) and then create the facial landmark predictor
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(SHAPE_PREDICTOR)

# detect faces in the grayscale image
rects = detector(gray, 1)

# loop over the face detections
for (i, rect) in enumerate(rects):
   """
   Determine the facial landmarks for the face region, then convert the facial landmark (x, y)-coordinates to a NumPy array
   """
   shape = predictor(gray, rect)
   shape = face_utils.shape_to_np(shape)

   #initialize mask array
   remapped_shape = np.zeros_like(shape) 
   feature_mask = np.zeros((image.shape[0], image.shape[1]))   

   # we extract the face
   remapped_shape = face_remap(shape)
   cv2.fillConvexPoly(feature_mask, remapped_shape[0:27], 1)
   feature_mask = feature_mask.astype(np.bool)
   out_face[feature_mask] = image[feature_mask]
   cv2.imshow("mask_inv", out_face)
   cv2.imwrite("out_face.png", out_face)

sample image of cropped face showing the issue

【问题讨论】:

我不完全确定你做错了什么,它不应该只检测这些点吗? source 【参考方案1】:

使用由 68 个地标形成的凸包并不能完全实现所需的输出,因此我使用scikit-image 而不是 OpenCV 来解决这个问题

1.加载图像并预测 68 个地标

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

img = dlib.load_rgb_image('mean.jpg')

rect = detector(img)[0]
sp = predictor(img, rect)
landmarks = np.array([[p.x, p.y] for p in sp.parts()])

2。选择代表人脸形状的地标

(我不得不颠倒眉毛标志的顺序,因为68 landmarks 没有按顺序描述面部轮廓)

outline = landmarks[[*range(17), *range(26,16,-1)]]

3.使用scikit-image

使用这些地标绘制多边形
Y, X = skimage.draw.polygon(outline[:,1], outline[:,0])

4.创建一个带零的画布并将多边形用作原始图像的蒙版

cropped_img = np.zeros(img.shape, dtype=np.uint8)
cropped_img[Y, X] = img[Y, X]


为了完整起见,我在下面提供了一个使用scipy.spatial.ConvexHull 的解决方案,如果此选项仍然是首选

vertices = ConvexHull(landmarks).vertices
Y, X = skimage.draw.polygon(landmarks[vertices, 1], landmarks[vertices, 0])
cropped_img = np.zeros(img.shape, dtype=np.uint8)
cropped_img[Y, X] = img[Y, X]

【讨论】:

太棒了!很好的答案!顺便说一句,在地标选择上,outline = landmarks[[*range(17), *range(26,16,-1)]] 听起来会得到[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17] 的结果。 [*range(1,18), *range(27,17,-1)] 不应该更好吗?因为它会得到[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18] @tli2020 图片中看到的68 landmarks从1开始,而dlib的实现从0开始,所以我们想要的脸型索引是[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17] 【参考方案2】:

这是因为您提供的脸型不是凸的。 fillConvexPoly 仅适用于凸形,在这种情况下,有一个凹角(在点 #27),因此结果混乱。

要解决这个问题,请将函数修改为

def face_remap(shape):
    remapped_image = cv2.convexHull(shape)
    return remapped_image

这会给你一个看起来像的结果。

现在您可以编写更多代码来删除前额上的三角形部分(如果您想要这样的话)

【讨论】:

太棒了!谢谢,完美解决了问题!既然你提到它,pt #16 - pt #17 看起来也像一个凹角,并且被 fillConvexPoly() 很好地容忍。知道为什么会这样吗? 使用 dlib 拟合点时总会出现一些错误。在#15-16-17 点的情况下,它们大多在一条直线上。即使是最轻微的错误也可能将第 16 点推到左侧,使其成为凹角。解决这个问题需要你在形状中找到点的子集,而不是在 cv2.convexHull(shape) 中。然后它们会遍历这些点以覆盖该区域的其余部分。

以上是关于使用 dlib 面部标志裁剪面部的主要内容,如果未能解决你的问题,请参考以下文章

使用面部标志进行面部裁剪

如何使用凸包多边形裁剪面部区域

Android 人脸地标裁剪

Dlib 面部标志起始索引

有没有办法在使用 dlib 检测面部标志后选择面部的特定点?

带有面部检测和形状预测的 Dlib 网络摄像头捕获速度很慢