dlib实现脸部分割与人脸对齐

参考资料

  1. 【Dlib】人脸检测、特征点检测、人脸对齐、人脸识别
  2. 深度学习与人脸识别之-脸部分割与校正
  3. github项目FaceSegmentation
  4. http://dlib.net/face_alignment.py.html
  5. 知乎问答

关键点检测

首先获取模型,下载地址在,我使用的是获取脸部68个关键点的模型shape_predictor_68_face_landmarks.dat 68关键点位置示意图如下: 首先贴出python代码 ` ```python
“””
代码功能:

  1. 用dlib人脸检测器检测出人脸,返回的人脸矩形框
  2. 对检测出的人脸进行关键点检测并用圈进行标记
  3. 将检测出的人脸关键点信息写到txt文本中
    “””
    import cv2
    import dlib
    import numpy as np

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 = “input/Messi.jpg”
output_pos_info = “output_pos_info/Messi.txt”
img = cv2.imread(test_img_path)
file_handle = open(output_pos_info, ‘a’)

取灰度

img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

人脸数rects(rectangles)

rects = detector(img_gray, 0)

for i in range(len(rects)):
landmarks = np.matrix([[p.x, p.y] for p in predictor(img,rects[i]).parts()])
for idx, point in enumerate(landmarks):

    # 68点的坐标
    pos = (point[0, 0], point[0, 1])
    print(idx+1, pos)
    pos_info = str(point[0, 0]) + ' ' + str(point[0, 1]) + '\n'
    file_handle.write(pos_info)
    # 利用cv2.circle给每个特征点画一个圈,共68个
    cv2.circle(img, pos, 3, color=(0, 255, 0))
    # 利用cv2.putText输出1-68
    #font = cv2.FONT_HERSHEY_SIMPLEX
    #cv2.putText(img, str(idx+1), pos, font, 0.5, (0, 0, 255), 1, cv2.LINE_AA)

file_handle.close()
cv2.imwrite(“output/Messi_keypoints.png”, img)

大致过程如下:先用人脸检测器获取到人脸矩形框rectangles,再用68点shape模型获取`full_object_detection`对象。最后将关键点标记出来,并写入文本中。 `rects
1
2
3
4
5

```python
predictor = dlib.shape_predictor(predictor_model)
predictor(img,rects[i]).parts()
predictor(img, rects[i]).part(1)

predictor返回的是一个full_object_detection对象,通过parts()可以获得所有关键点的位置,通过part(idx)idx从0开始,可以获取某个关键点的信息。 测试图片的原图与标注关键点后图片如下图所示。

脸部分割

两种方式:矩形框分割人脸和 不规则形状分割人脸

矩形框分割人脸

可以采用dlib.get_face_chip()来分割人脸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""
代码功能:
1. 用dlib人脸检测器检测出人脸,返回的人脸矩形框
2. 对检测出的人脸进行关键点检测并切割出人脸
"""
import cv2
import dlib
import numpy as np

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 = "input/Messi.jpg"
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)
for image in face_images:
cv_bgr_img = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
cv2.imwrite('output/Messi_clip.png', cv_bgr_img)

Dlib检测出的脸部区域对于下巴和额头区域会做过多的裁剪,并且分割使用的是矩形框。 分割结果如图

不规则形状分割人脸

先用dlib等打点工具把人脸最外层的landmark点打出来,然后利用opencv的convexhull得到凸包然后就可以抠出人脸区域了.

python代码(获取人脸的掩模)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def get_image_hull_mask(image_shape, image_landmarks, ie_polys=None):
# get the mask of the image
if image_landmarks.shape[0] != 68:
raise Exception(
'get_image_hull_mask works only with 68 landmarks')
int_lmrks = np.array(image_landmarks, dtype=np.int)

#hull_mask = np.zeros(image_shape[0:2]+(1,), dtype=np.float32)
hull_mask = np.full(image_shape[0:2] + (1,), 0, dtype=np.float32)

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[0:9],
int_lmrks[17:18]))), (1,))

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[8:17],
int_lmrks[26:27]))), (1,))

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[17:20],
int_lmrks[8:9]))), (1,))

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[24:27],
int_lmrks[8:9]))), (1,))

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[19:25],
int_lmrks[8:9],
))), (1,))

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[17:22],
int_lmrks[27:28],
int_lmrks[31:36],
int_lmrks[8:9]
))), (1,))

cv2.fillConvexPoly(hull_mask, cv2.convexHull(
np.concatenate((int_lmrks[22:27],
int_lmrks[27:28],
int_lmrks[31:36],
int_lmrks[8:9]
))), (1,))

# nose
cv2.fillConvexPoly(
hull_mask, cv2.convexHull(int_lmrks[27:36]), (1,))

if ie_polys is not None:
ie_polys.overlay_mask(hull_mask)
print()
return hull_mask

方法利用的就是opencv的convexhull得到凸包然后就可以抠出人脸区域。 得到掩模,这里使用两种方式来得到人脸区域 1. 将mask作为$\\alpha$通道,来控制图片区域的透明度,最后得到图片是4通道的

1
2
3
4
5
6
7
8
9
10
def merge_add_alpha(img_1, mask):
# merge rgb and mask into a rgba image
r_channel, g_channel, b_channel = cv2.split(img_1)
if mask is not None:
alpha_channel = np.ones(mask.shape, dtype=img_1.dtype)
alpha_channel *= mask*255
else:
alpha_channel = np.zeros(img_1.shape[:2], dtype=img_1.dtype)
img_BGRA = cv2.merge((b_channel, g_channel, r_channel, alpha_channel))
return img_BGRA

分割结果

  1. 掩模与原始图像进行与运算,返回图像是三通道。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def merge_add_mask(img_1, mask):
if mask is not None:
height = mask.shape[0]
width = mask.shape[1]
channel_num = mask.shape[2]
for row in range(height):
for col in range(width):
for c in range(channel_num):
if mask[row, col, c] == 0:
mask[row, col, c] = 0
else:
mask[row, col, c] = 255

r_channel, g_channel, b_channel = cv2.split(img_1)
r_channel = cv2.bitwise_and(r_channel, mask)
g_channel = cv2.bitwise_and(g_channel, mask)
b_channel = cv2.bitwise_and(b_channel, mask)
res_img = cv2.merge((b_channel, g_channel, r_channel))
else:
res_img = img_1
return res_img

分割结果为

人脸对齐

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

1
2
3
4
5
6
7
8
9
10
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 - right
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

人脸对齐后如图 这样就完成人脸检测—>人脸关键点检测—>人脸分割—>人脸对齐。 具体代码参照github 可以用以上方法对fer2013数据集进行预处理,如图所示。