人脸对齐

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即最终结果。

python 人脸对比 人脸对齐 python_python 人脸对比

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即为最终结果。