人脸对齐
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 npclass 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, bottomdef 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) // 6def 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 = p1x2, y2 = p2tan = (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 = p1x2, y2 = p2xc = (x1 + x2) // 2yc = (y1 + y2) // 2M = cv2.getRotationMatrix2D((xc, yc), angle, 1)return Mdef 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 inputimage format that return alignment face image crop or image path as inputwill 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 Grayimage_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 facedets = self.detector(image_gray, 1)# i donate the i_th face detected in imagecrop_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 croppedelse: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 cv2def detect_landmark(image, detector):'''image as numpy format with RGB formatnote that cv2 read is BGR format'''face = detector.detect_faces(image)[0]#draw pointsleft_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 transsrc1 = 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 == 112src = arcface_srcelse: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.Terror = np.sum(np.sqrt(np.sum((results - src[i])**2, axis=1)))# print(error)if error < min_error:min_error = errormin_M = Mmin_index = ireturn min_M, min_indexdef 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 mathfrom MTCNN_Portable.mtcnn import MTCNN# load input images and corresponding 5 landmarks
def load_img_and_box(img_path, detector):#Reading imageimage = Image.open(img_path)if img_path.split('.')[-1]=='png':image = image.convert("RGB")# BGRimg = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)#Detect 5 key pointface = detector.detect_faces(img)[0]box = face["box"]image = cv2.imread(img_path)return image, boxdef 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 = 0top_ = -int(np.floor(box[1]-box[3]*0.15))else:top = int(np.floor(box[1]-box[3]*0.15))top_ = 0if 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 = 0left_ = -int(np.floor(box[0]-box[2]*0.15))else:left = int(np.floor(box[0]-box[2]*0.15))left_ = 0if 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] = imgimg = img_zeroim_shape = img.shape[:2]ratio = float(shape[0]) / np.min(im_shape)img = cv2.resize(img,dsize=(math.ceil(im_shape[1] * ratio), # widthmath.ceil(im_shape[0] * ratio)) # height)new_shape = img.shape[:2]h_start = (new_shape[0] - shape[0])//2w_start = (new_shape[1] - shape[1])//2img = 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
即为最终结果。