人脸对齐
1. 通过Dlib库
1.1.环境需求:
opencv-python
dlib
下载dlib库
的68关键点文件:
http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 然后解压后得到shape_predictor_68_face_landmarks.dat
。
其次,下面可能需要有一定python基础才能快速调用。
注意:Dlib库有cuda
加速版本,用这个更快,否则CPU计算。
1.2.程序:
import cv2
import dlib
import numpy as np
class Face_Align(object):
def __init__(self,shape_predictor_path):
self.detector = dlib.get_frontal_face_detector()
self.predictor = dlib.shape_predictor(shape_predictor_path)
self.LEFT_EYE_INDICES = [36, 37, 38, 39, 40, 41]
self.RIGHT_EYE_INDICES = [42, 43, 44, 45, 46, 47]
def rect_to_tuple(self, rect):
left = rect.left()
right = rect.right()
top = rect.top()
bottom = rect.bottom()
return left, top, right, bottom
def extract_eye(self, shape, eye_indices):
points = map(lambda i: shape.part(i), eye_indices)
return list(points)
def extract_eye_center(self, shape, eye_indices):
points = self.extract_eye(shape, eye_indices)
xs = map(lambda p: p.x, points)
ys = map(lambda p: p.y, points)
return sum(xs) // 6, sum(ys) // 6
def extract_left_eye_center(self, shape):
return self.extract_eye_center(shape, self.LEFT_EYE_INDICES)
def extract_right_eye_center(self, shape):
return self.extract_eye_center(shape, self.RIGHT_EYE_INDICES)
def angle_between_2_points(self, p1, p2):
x1, y1 = p1
x2, y2 = p2
tan = (y2 - y1) / (x2 - x1)
return np.degrees(np.arctan(tan))
def get_rotation_matrix(self, p1, p2):
angle = self.angle_between_2_points(p1, p2)
x1, y1 = p1
x2, y2 = p2
xc = (x1 + x2) // 2
yc = (y1 + y2) // 2
M = cv2.getRotationMatrix2D((xc, yc), angle, 1)
return M
def crop_image(self, image, det):
left, top, right, bottom = self.rect_to_tuple(det)
return image[top:bottom, left:right]
def __call__(self, image=None,image_path=None,save_path=None,only_one=True):
'''
Face alignment, can select input image variable or image path, when input
image format that return alignment face image crop or image path as input
will return None but save image to the save path.
:image: Face image input
:image_path: if image is None than can input image
:save_path: path to save image
:detector: detector = dlib.get_frontal_face_detector()
:predictor: predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
'''
if image is not None:
# convert BGR format to Gray
image_gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
elif image_path is not None:
image_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = cv2.imread(image_path)
height, width = image.shape[:2]
# Dector face
dets = self.detector(image_gray, 1)
# i donate the i_th face detected in image
crop_images = []
for i, det in enumerate(dets):
shape = self.predictor(image_gray, det)
left_eye = self.extract_left_eye_center(shape)
right_eye = self.extract_right_eye_center(shape)
M = self.get_rotation_matrix(left_eye, right_eye)
rotated = cv2.warpAffine(image, M, (width, height), flags=cv2.INTER_CUBIC)
cropped = self.crop_image(rotated, det)
if only_one == True:
if save_path is not None:
cv2.imwrite(save_path, cropped)
return cropped
else:
crop_images.append(cropped)
return crop_images
1.3. 调用:
if __name__ == "__main__":
align = Face_Align("./face-alignment-dlib/shape_predictor_68_face_landmarks.dat")
align(image_path="./face-alignment-dlib/123.jpg",save_path="test.jpg")
即可
2. 针对ArcFace:
2.1 环境:
克隆MTCNN的项目:
git clone https://github.com/RuoyuChen10/MTCNN_Portable.git
这个需要安装tensorflow 1
的版本,以及opencv
。
优点就是可以GPU加速。
2.2 代码:
识别人脸的landmark:
from MTCNN_Portable.mtcnn import MTCNN
import cv2
def detect_landmark(image, detector):
'''
image as numpy format with RGB format
note that cv2 read is BGR format
'''
face = detector.detect_faces(image)[0]
#draw points
left_eye = face["keypoints"]["left_eye"]
right_eye = face["keypoints"]["right_eye"]
nose = face["keypoints"]["nose"]
mouth_left = face["keypoints"]["mouth_left"]
mouth_right = face["keypoints"]["mouth_right"]
landmark = [[left_eye[0], left_eye[1]],
[right_eye[0], right_eye[1]],
[nose[0], nose[1]],
[mouth_left[0], mouth_left[1]],
[mouth_right[0], mouth_right[1]]]
return landmark
人脸对齐,针对ArcFace:
来自官方源码参考:https://github.com/deepinsight/insightface/blob/master/recognition/common/face_align.py
import numpy as np
from skimage import transform as trans
src1 = np.array([[51.642, 50.115], [57.617, 49.990], [35.740, 69.007],
[51.157, 89.050], [57.025, 89.702]],
dtype=np.float32)
#<--left
src2 = np.array([[45.031, 50.118], [65.568, 50.872], [39.677, 68.111],
[45.177, 86.190], [64.246, 86.758]],
dtype=np.float32)
#---frontal
src3 = np.array([[39.730, 51.138], [72.270, 51.138], [56.000, 68.493],
[42.463, 87.010], [69.537, 87.010]],
dtype=np.float32)
#-->right
src4 = np.array([[46.845, 50.872], [67.382, 50.118], [72.737, 68.111],
[48.167, 86.758], [67.236, 86.190]],
dtype=np.float32)
#-->right profile
src5 = np.array([[54.796, 49.990], [60.771, 50.115], [76.673, 69.007],
[55.388, 89.702], [61.257, 89.050]],
dtype=np.float32)
src = np.array([src1, src2, src3, src4, src5])
src_map = {112: src, 224: src * 2}
arcface_src = np.array(
[[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366],
[41.5493, 92.3655], [70.7299, 92.2041]],
dtype=np.float32)
arcface_src = np.expand_dims(arcface_src, axis=0)
# lmk is prediction; src is template
def estimate_norm(lmk, image_size=112, mode='arcface'):
assert lmk.shape == (5, 2)
tform = trans.SimilarityTransform()
lmk_tran = np.insert(lmk, 2, values=np.ones(5), axis=1)
min_M = []
min_index = []
min_error = float('inf')
if mode == 'arcface':
assert image_size == 112
src = arcface_src
else:
src = src_map[image_size]
for i in np.arange(src.shape[0]):
tform.estimate(lmk, src[i])
M = tform.params[0:2, :]
results = np.dot(M, lmk_tran.T)
results = results.T
error = np.sum(np.sqrt(np.sum((results - src[i])**2, axis=1)))
# print(error)
if error < min_error:
min_error = error
min_M = M
min_index = i
return min_M, min_index
def norm_crop(img, landmark, image_size=112, mode='arcface'):
M, pose_index = estimate_norm(landmark, image_size, mode)
warped = cv2.warpAffine(img, M, (image_size, image_size), borderValue=0.0)
return warped
调用:
detector = MTCNN()
img = cv2.cvtColor(cv2.imread("./MTCNN_Portable/test.jpg"), cv2.COLOR_BGR2RGB) # To RGB
landmark = detect_landmark(img, detector)
# 不需要把img变换回BGR格式,wrap还会自动出BGR
wrap = norm_crop(img, np.array(landmark), image_size=112, mode='arcface')
wrap即最终结果。
3. VGGFace2对齐
3.1 说明
这种仅采用了crop的方式,具体是MTCNN检测时有检测到人脸的框,在这个框基础上扩展0.15倍,然后取最长边为正方形,crop下来,再resize。
3.2 环境
MTCNN检测:
git clone https://github.com/RuoyuChen10/MTCNN_Portable.git
3.3 代码:
import cv2
import numpy as np
import os
import random
import tensorflow as tf
import math
from MTCNN_Portable.mtcnn import MTCNN
# load input images and corresponding 5 landmarks
def load_img_and_box(img_path, detector):
#Reading image
image = Image.open(img_path)
if img_path.split('.')[-1]=='png':
image = image.convert("RGB")
# BGR
img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
#Detect 5 key point
face = detector.detect_faces(img)[0]
box = face["box"]
image = cv2.imread(img_path)
return image, box
def box_crop(image, box):
shape=(224,224)
# print(image.shape)
# print((int(np.floor(box[1]-box[3]*0.15)),int(np.ceil(box[1]+box[3]*1.15))))
# print((int(np.floor(box[0]-box[2]*0.15)),int(np.ceil(box[0]+box[2]*1.15))))
if int(np.floor(box[1]-box[3]*0.15)) < 0:
top = 0
top_ = -int(np.floor(box[1]-box[3]*0.15))
else:
top = int(np.floor(box[1]-box[3]*0.15))
top_ = 0
if int(np.ceil(box[1]+box[3]*1.15)) > image.shape[0]:
bottom = image.shape[0]
else:
bottom = int(np.ceil(box[1]+box[3]*1.15))
if int(np.floor(box[0]-box[2]*0.15)) < 0:
left = 0
left_ = -int(np.floor(box[0]-box[2]*0.15))
else:
left = int(np.floor(box[0]-box[2]*0.15))
left_ = 0
if int(np.ceil(box[0]+box[2]*1.15)) > image.shape[1]:
right = image.shape[1]
else:
right = int(np.ceil(box[0]+box[2]*1.15))
img_zero = np.zeros((int(np.ceil(box[1]+box[3]*1.15))-int(np.floor(box[1]-box[3]*0.15)),
int(np.ceil(box[0]+box[2]*1.15))-int(np.floor(box[0]-box[2]*0.15)),
3))
img = image[
top:bottom,
left:right]
img_zero[top_:top_+bottom-top,left_:left_+right-left] = img
img = img_zero
im_shape = img.shape[:2]
ratio = float(shape[0]) / np.min(im_shape)
img = cv2.resize(
img,
dsize=(math.ceil(im_shape[1] * ratio), # width
math.ceil(im_shape[0] * ratio)) # height
)
new_shape = img.shape[:2]
h_start = (new_shape[0] - shape[0])//2
w_start = (new_shape[1] - shape[1])//2
img = img[h_start:h_start+shape[0], w_start:w_start+shape[1]]
return img
调用:
detector = MTCNN()
image_path = "Path to image!"
img,box = load_img_and_box(image_path, detector)
crop_image = box_crop(img, box)
crop_image
即为最终结果。