|
import colorsys |
|
import math |
|
from enum import IntEnum |
|
|
|
import cv2 |
|
import numpy as np |
|
import numpy.linalg as npla |
|
|
|
from core import imagelib |
|
from core import mathlib |
|
from facelib import FaceType |
|
from core.mathlib.umeyama import umeyama |
|
|
|
landmarks_2D = np.array([ |
|
[ 0.000213256, 0.106454 ], |
|
[ 0.0752622, 0.038915 ], |
|
[ 0.18113, 0.0187482 ], |
|
[ 0.29077, 0.0344891 ], |
|
[ 0.393397, 0.0773906 ], |
|
[ 0.586856, 0.0773906 ], |
|
[ 0.689483, 0.0344891 ], |
|
[ 0.799124, 0.0187482 ], |
|
[ 0.904991, 0.038915 ], |
|
[ 0.98004, 0.106454 ], |
|
[ 0.490127, 0.203352 ], |
|
[ 0.490127, 0.307009 ], |
|
[ 0.490127, 0.409805 ], |
|
[ 0.490127, 0.515625 ], |
|
[ 0.36688, 0.587326 ], |
|
[ 0.426036, 0.609345 ], |
|
[ 0.490127, 0.628106 ], |
|
[ 0.554217, 0.609345 ], |
|
[ 0.613373, 0.587326 ], |
|
[ 0.121737, 0.216423 ], |
|
[ 0.187122, 0.178758 ], |
|
[ 0.265825, 0.179852 ], |
|
[ 0.334606, 0.231733 ], |
|
[ 0.260918, 0.245099 ], |
|
[ 0.182743, 0.244077 ], |
|
[ 0.645647, 0.231733 ], |
|
[ 0.714428, 0.179852 ], |
|
[ 0.793132, 0.178758 ], |
|
[ 0.858516, 0.216423 ], |
|
[ 0.79751, 0.244077 ], |
|
[ 0.719335, 0.245099 ], |
|
[ 0.254149, 0.780233 ], |
|
[ 0.340985, 0.745405 ], |
|
[ 0.428858, 0.727388 ], |
|
[ 0.490127, 0.742578 ], |
|
[ 0.551395, 0.727388 ], |
|
[ 0.639268, 0.745405 ], |
|
[ 0.726104, 0.780233 ], |
|
[ 0.642159, 0.864805 ], |
|
[ 0.556721, 0.902192 ], |
|
[ 0.490127, 0.909281 ], |
|
[ 0.423532, 0.902192 ], |
|
[ 0.338094, 0.864805 ], |
|
[ 0.290379, 0.784792 ], |
|
[ 0.428096, 0.778746 ], |
|
[ 0.490127, 0.785343 ], |
|
[ 0.552157, 0.778746 ], |
|
[ 0.689874, 0.784792 ], |
|
[ 0.553364, 0.824182 ], |
|
[ 0.490127, 0.831803 ], |
|
[ 0.42689 , 0.824182 ] |
|
], dtype=np.float32) |
|
|
|
|
|
landmarks_2D_new = np.array([ |
|
[ 0.000213256, 0.106454 ], |
|
[ 0.0752622, 0.038915 ], |
|
[ 0.18113, 0.0187482 ], |
|
[ 0.29077, 0.0344891 ], |
|
[ 0.393397, 0.0773906 ], |
|
[ 0.586856, 0.0773906 ], |
|
[ 0.689483, 0.0344891 ], |
|
[ 0.799124, 0.0187482 ], |
|
[ 0.904991, 0.038915 ], |
|
[ 0.98004, 0.106454 ], |
|
[ 0.490127, 0.203352 ], |
|
[ 0.490127, 0.307009 ], |
|
[ 0.490127, 0.409805 ], |
|
[ 0.490127, 0.515625 ], |
|
[ 0.36688, 0.587326 ], |
|
[ 0.426036, 0.609345 ], |
|
[ 0.490127, 0.628106 ], |
|
[ 0.554217, 0.609345 ], |
|
[ 0.613373, 0.587326 ], |
|
[ 0.121737, 0.216423 ], |
|
[ 0.187122, 0.178758 ], |
|
[ 0.265825, 0.179852 ], |
|
[ 0.334606, 0.231733 ], |
|
[ 0.260918, 0.245099 ], |
|
[ 0.182743, 0.244077 ], |
|
[ 0.645647, 0.231733 ], |
|
[ 0.714428, 0.179852 ], |
|
[ 0.793132, 0.178758 ], |
|
[ 0.858516, 0.216423 ], |
|
[ 0.79751, 0.244077 ], |
|
[ 0.719335, 0.245099 ], |
|
[ 0.254149, 0.780233 ], |
|
[ 0.726104, 0.780233 ], |
|
], dtype=np.float32) |
|
|
|
mouth_center_landmarks_2D = np.array([ |
|
[-4.4202591e-07, 4.4916576e-01], |
|
[ 1.8399176e-01, 3.7537053e-01], |
|
[ 3.7018123e-01, 3.3719531e-01], |
|
[ 5.0000089e-01, 3.6938059e-01], |
|
[ 6.2981832e-01, 3.3719531e-01], |
|
[ 8.1600773e-01, 3.7537053e-01], |
|
[ 1.0000000e+00, 4.4916576e-01], |
|
[ 8.2213330e-01, 6.2836081e-01], |
|
[ 6.4110327e-01, 7.0757812e-01], |
|
[ 5.0000089e-01, 7.2259867e-01], |
|
[ 3.5889623e-01, 7.0757812e-01], |
|
[ 1.7786618e-01, 6.2836081e-01], |
|
[ 7.6765373e-02, 4.5882553e-01], |
|
[ 3.6856663e-01, 4.4601500e-01], |
|
[ 5.0000089e-01, 4.5999300e-01], |
|
[ 6.3143289e-01, 4.4601500e-01], |
|
[ 9.2323411e-01, 4.5882553e-01], |
|
[ 6.3399029e-01, 5.4228687e-01], |
|
[ 5.0000089e-01, 5.5843467e-01], |
|
[ 3.6601129e-01, 5.4228687e-01] |
|
], dtype=np.float32) |
|
|
|
|
|
landmarks_68_pt = { "mouth": (48,68), |
|
"right_eyebrow": (17, 22), |
|
"left_eyebrow": (22, 27), |
|
"right_eye": (36, 42), |
|
"left_eye": (42, 48), |
|
"nose": (27, 36), |
|
"jaw": (0, 17) } |
|
|
|
landmarks_68_3D = np.array( [ |
|
[-73.393523 , -29.801432 , 47.667532 ], |
|
[-72.775014 , -10.949766 , 45.909403 ], |
|
[-70.533638 , 7.929818 , 44.842580 ], |
|
[-66.850058 , 26.074280 , 43.141114 ], |
|
[-59.790187 , 42.564390 , 38.635298 ], |
|
[-48.368973 , 56.481080 , 30.750622 ], |
|
[-34.121101 , 67.246992 , 18.456453 ], |
|
[-17.875411 , 75.056892 , 3.609035 ], |
|
[0.098749 , 77.061286 , -0.881698 ], |
|
[17.477031 , 74.758448 , 5.181201 ], |
|
[32.648966 , 66.929021 , 19.176563 ], |
|
[46.372358 , 56.311389 , 30.770570 ], |
|
[57.343480 , 42.419126 , 37.628629 ], |
|
[64.388482 , 25.455880 , 40.886309 ], |
|
[68.212038 , 6.990805 , 42.281449 ], |
|
[70.486405 , -11.666193 , 44.142567 ], |
|
[71.375822 , -30.365191 , 47.140426 ], |
|
[-61.119406 , -49.361602 , 14.254422 ], |
|
[-51.287588 , -58.769795 , 7.268147 ], |
|
[-37.804800 , -61.996155 , 0.442051 ], |
|
[-24.022754 , -61.033399 , -6.606501 ], |
|
[-11.635713 , -56.686759 , -11.967398 ], |
|
[12.056636 , -57.391033 , -12.051204 ], |
|
[25.106256 , -61.902186 , -7.315098 ], |
|
[38.338588 , -62.777713 , -1.022953 ], |
|
[51.191007 , -59.302347 , 5.349435 ], |
|
[60.053851 , -50.190255 , 11.615746 ], |
|
[0.653940 , -42.193790 , -13.380835 ], |
|
[0.804809 , -30.993721 , -21.150853 ], |
|
[0.992204 , -19.944596 , -29.284036 ], |
|
[1.226783 , -8.414541 , -36.948060 ], |
|
[-14.772472 , 2.598255 , -20.132003 ], |
|
[-7.180239 , 4.751589 , -23.536684 ], |
|
[0.555920 , 6.562900 , -25.944448 ], |
|
[8.272499 , 4.661005 , -23.695741 ], |
|
[15.214351 , 2.643046 , -20.858157 ], |
|
[-46.047290 , -37.471411 , 7.037989 ], |
|
[-37.674688 , -42.730510 , 3.021217 ], |
|
[-27.883856 , -42.711517 , 1.353629 ], |
|
[-19.648268 , -36.754742 , -0.111088 ], |
|
[-28.272965 , -35.134493 , -0.147273 ], |
|
[-38.082418 , -34.919043 , 1.476612 ], |
|
[19.265868 , -37.032306 , -0.665746 ], |
|
[27.894191 , -43.342445 , 0.247660 ], |
|
[37.437529 , -43.110822 , 1.696435 ], |
|
[45.170805 , -38.086515 , 4.894163 ], |
|
[38.196454 , -35.532024 , 0.282961 ], |
|
[28.764989 , -35.484289 , -1.172675 ], |
|
[-28.916267 , 28.612716 , -2.240310 ], |
|
[-17.533194 , 22.172187 , -15.934335 ], |
|
[-6.684590 , 19.029051 , -22.611355 ], |
|
[0.381001 , 20.721118 , -23.748437 ], |
|
[8.375443 , 19.035460 , -22.721995 ], |
|
[18.876618 , 22.394109 , -15.610679 ], |
|
[28.794412 , 28.079924 , -3.217393 ], |
|
[19.057574 , 36.298248 , -14.987997 ], |
|
[8.956375 , 39.634575 , -22.554245 ], |
|
[0.381549 , 40.395647 , -23.591626 ], |
|
[-7.428895 , 39.836405 , -22.406106 ], |
|
[-18.160634 , 36.677899 , -15.121907 ], |
|
[-24.377490 , 28.677771 , -4.785684 ], |
|
[-6.897633 , 25.475976 , -20.893742 ], |
|
[0.340663 , 26.014269 , -22.220479 ], |
|
[8.444722 , 25.326198 , -21.025520 ], |
|
[24.474473 , 28.323008 , -5.712776 ], |
|
[8.449166 , 30.596216 , -20.671489 ], |
|
[0.205322 , 31.408738 , -21.903670 ], |
|
[-7.198266 , 30.844876 , -20.328022 ] |
|
], dtype=np.float32) |
|
|
|
FaceType_to_padding_remove_align = { |
|
FaceType.HALF: (0.0, False), |
|
FaceType.MID_FULL: (0.0675, False), |
|
FaceType.FULL: (0.2109375, False), |
|
FaceType.FULL_NO_ALIGN: (0.2109375, True), |
|
FaceType.WHOLE_FACE: (0.40, False), |
|
FaceType.HEAD: (0.70, False), |
|
FaceType.HEAD_NO_ALIGN: (0.70, True), |
|
} |
|
|
|
def convert_98_to_68(lmrks): |
|
|
|
result = [ lmrks[0] ] |
|
for i in range(2,16,2): |
|
result += [ ( lmrks[i] + (lmrks[i-1]+lmrks[i+1])/2 ) / 2 ] |
|
result += [ lmrks[16] ] |
|
for i in range(18,32,2): |
|
result += [ ( lmrks[i] + (lmrks[i-1]+lmrks[i+1])/2 ) / 2 ] |
|
result += [ lmrks[32] ] |
|
|
|
|
|
result += [ lmrks[33], |
|
(lmrks[34]+lmrks[41])/2, |
|
(lmrks[35]+lmrks[40])/2, |
|
(lmrks[36]+lmrks[39])/2, |
|
(lmrks[37]+lmrks[38])/2, |
|
] |
|
|
|
result += [ (lmrks[42]+lmrks[50])/2, |
|
(lmrks[43]+lmrks[49])/2, |
|
(lmrks[44]+lmrks[48])/2, |
|
(lmrks[45]+lmrks[47])/2, |
|
lmrks[46] |
|
] |
|
|
|
|
|
result += list ( lmrks[51:60] ) |
|
|
|
|
|
result += [ lmrks[60], |
|
lmrks[61], |
|
lmrks[63], |
|
lmrks[64], |
|
lmrks[65], |
|
lmrks[67] ] |
|
|
|
|
|
result += [ lmrks[68], |
|
lmrks[69], |
|
lmrks[71], |
|
lmrks[72], |
|
lmrks[73], |
|
lmrks[75] ] |
|
|
|
|
|
result += list ( lmrks[76:96] ) |
|
|
|
return np.concatenate (result).reshape ( (68,2) ) |
|
|
|
def transform_points(points, mat, invert=False): |
|
if invert: |
|
mat = cv2.invertAffineTransform (mat) |
|
points = np.expand_dims(points, axis=1) |
|
points = cv2.transform(points, mat, points.shape) |
|
points = np.squeeze(points) |
|
return points |
|
|
|
def get_transform_mat (image_landmarks, output_size, face_type, scale=1.0): |
|
if not isinstance(image_landmarks, np.ndarray): |
|
image_landmarks = np.array (image_landmarks) |
|
|
|
|
|
|
|
mat = umeyama( np.concatenate ( [ image_landmarks[17:49] , image_landmarks[54:55] ] ) , landmarks_2D_new, True)[0:2] |
|
|
|
|
|
g_p = transform_points ( np.float32([(0,0),(1,0),(1,1),(0,1),(0.5,0.5) ]) , mat, True) |
|
g_c = g_p[4] |
|
|
|
|
|
tb_diag_vec = (g_p[2]-g_p[0]).astype(np.float32) |
|
tb_diag_vec /= npla.norm(tb_diag_vec) |
|
bt_diag_vec = (g_p[1]-g_p[3]).astype(np.float32) |
|
bt_diag_vec /= npla.norm(bt_diag_vec) |
|
|
|
|
|
padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0) |
|
mod = (1.0 / scale)* ( npla.norm(g_p[0]-g_p[2])*(padding*np.sqrt(2.0) + 0.5) ) |
|
|
|
if face_type == FaceType.WHOLE_FACE: |
|
|
|
vec = (g_p[0]-g_p[3]).astype(np.float32) |
|
vec_len = npla.norm(vec) |
|
vec /= vec_len |
|
g_c += vec*vec_len*0.07 |
|
|
|
elif face_type == FaceType.HEAD: |
|
|
|
|
|
yaw = estimate_averaged_yaw(transform_points (image_landmarks, mat, False)) |
|
|
|
hvec = (g_p[0]-g_p[1]).astype(np.float32) |
|
hvec_len = npla.norm(hvec) |
|
hvec /= hvec_len |
|
|
|
yaw *= np.abs(math.tanh(yaw*2)) |
|
|
|
g_c -= hvec * (yaw * hvec_len / 2.0) |
|
|
|
|
|
vvec = (g_p[0]-g_p[3]).astype(np.float32) |
|
vvec_len = npla.norm(vvec) |
|
vvec /= vvec_len |
|
g_c += vvec*vvec_len*0.50 |
|
|
|
|
|
if not remove_align: |
|
l_t = np.array( [ g_c - tb_diag_vec*mod, |
|
g_c + bt_diag_vec*mod, |
|
g_c + tb_diag_vec*mod ] ) |
|
else: |
|
|
|
l_t = np.array( [ g_c - tb_diag_vec*mod, |
|
g_c + bt_diag_vec*mod, |
|
g_c + tb_diag_vec*mod, |
|
g_c - bt_diag_vec*mod, |
|
] ) |
|
|
|
|
|
area = mathlib.polygon_area(l_t[:,0], l_t[:,1] ) |
|
|
|
|
|
side = np.float32(math.sqrt(area) / 2) |
|
|
|
|
|
l_t = np.array( [ g_c + [-side,-side], |
|
g_c + [ side,-side], |
|
g_c + [ side, side] ] ) |
|
|
|
|
|
pts2 = np.float32(( (0,0),(output_size,0),(output_size,output_size) )) |
|
mat = cv2.getAffineTransform(l_t,pts2) |
|
return mat |
|
|
|
def get_rect_from_landmarks(image_landmarks): |
|
mat = get_transform_mat(image_landmarks, 256, FaceType.FULL_NO_ALIGN) |
|
|
|
g_p = transform_points ( np.float32([(0,0),(255,255) ]) , mat, True) |
|
|
|
(l,t,r,b) = g_p[0][0], g_p[0][1], g_p[1][0], g_p[1][1] |
|
|
|
return (l,t,r,b) |
|
|
|
def expand_eyebrows(lmrks, eyebrows_expand_mod=1.0): |
|
if len(lmrks) != 68: |
|
raise Exception('works only with 68 landmarks') |
|
lmrks = np.array( lmrks.copy(), dtype=np.int ) |
|
|
|
|
|
ml_pnt = (lmrks[36] + lmrks[0]) // 2 |
|
mr_pnt = (lmrks[16] + lmrks[45]) // 2 |
|
|
|
|
|
ql_pnt = (lmrks[36] + ml_pnt) // 2 |
|
qr_pnt = (lmrks[45] + mr_pnt) // 2 |
|
|
|
|
|
bot_l = np.array((ql_pnt, lmrks[36], lmrks[37], lmrks[38], lmrks[39])) |
|
bot_r = np.array((lmrks[42], lmrks[43], lmrks[44], lmrks[45], qr_pnt)) |
|
|
|
|
|
top_l = lmrks[17:22] |
|
top_r = lmrks[22:27] |
|
|
|
|
|
lmrks[17:22] = top_l + eyebrows_expand_mod * 0.5 * (top_l - bot_l) |
|
lmrks[22:27] = top_r + eyebrows_expand_mod * 0.5 * (top_r - bot_r) |
|
return lmrks |
|
|
|
|
|
|
|
|
|
def get_image_hull_mask (image_shape, image_landmarks, eyebrows_expand_mod=1.0 ): |
|
hull_mask = np.zeros(image_shape[0:2]+(1,),dtype=np.float32) |
|
|
|
lmrks = expand_eyebrows(image_landmarks, eyebrows_expand_mod) |
|
|
|
r_jaw = (lmrks[0:9], lmrks[17:18]) |
|
l_jaw = (lmrks[8:17], lmrks[26:27]) |
|
r_cheek = (lmrks[17:20], lmrks[8:9]) |
|
l_cheek = (lmrks[24:27], lmrks[8:9]) |
|
nose_ridge = (lmrks[19:25], lmrks[8:9],) |
|
r_eye = (lmrks[17:22], lmrks[27:28], lmrks[31:36], lmrks[8:9]) |
|
l_eye = (lmrks[22:27], lmrks[27:28], lmrks[31:36], lmrks[8:9]) |
|
nose = (lmrks[27:31], lmrks[31:36]) |
|
parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose] |
|
|
|
for item in parts: |
|
merged = np.concatenate(item) |
|
cv2.fillConvexPoly(hull_mask, cv2.convexHull(merged), (1,) ) |
|
|
|
return hull_mask |
|
|
|
def get_image_eye_mask (image_shape, image_landmarks): |
|
if len(image_landmarks) != 68: |
|
raise Exception('get_image_eye_mask works only with 68 landmarks') |
|
|
|
h,w,c = image_shape |
|
|
|
hull_mask = np.zeros( (h,w,1),dtype=np.float32) |
|
|
|
image_landmarks = image_landmarks.astype(np.int) |
|
|
|
cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[36:42]), (1,) ) |
|
cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[42:48]), (1,) ) |
|
|
|
dilate = h // 32 |
|
hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(dilate,dilate)), iterations = 1 ) |
|
|
|
blur = h // 16 |
|
blur = blur + (1-blur % 2) |
|
hull_mask = cv2.GaussianBlur(hull_mask, (blur, blur) , 0) |
|
hull_mask = hull_mask[...,None] |
|
|
|
return hull_mask |
|
|
|
def get_image_mouth_mask (image_shape, image_landmarks): |
|
if len(image_landmarks) != 68: |
|
raise Exception('get_image_eye_mask works only with 68 landmarks') |
|
|
|
h,w,c = image_shape |
|
|
|
hull_mask = np.zeros( (h,w,1),dtype=np.float32) |
|
|
|
image_landmarks = image_landmarks.astype(np.int) |
|
|
|
cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[60:]), (1,) ) |
|
|
|
dilate = h // 32 |
|
hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(dilate,dilate)), iterations = 1 ) |
|
|
|
blur = h // 16 |
|
blur = blur + (1-blur % 2) |
|
hull_mask = cv2.GaussianBlur(hull_mask, (blur, blur) , 0) |
|
hull_mask = hull_mask[...,None] |
|
|
|
return hull_mask |
|
|
|
def alpha_to_color (img_alpha, color): |
|
if len(img_alpha.shape) == 2: |
|
img_alpha = img_alpha[...,None] |
|
h,w,c = img_alpha.shape |
|
result = np.zeros( (h,w, len(color) ), dtype=np.float32 ) |
|
result[:,:] = color |
|
|
|
return result * img_alpha |
|
|
|
|
|
|
|
def get_cmask (image_shape, lmrks, eyebrows_expand_mod=1.0): |
|
h,w,c = image_shape |
|
|
|
hull = get_image_hull_mask (image_shape, lmrks, eyebrows_expand_mod ) |
|
|
|
result = np.zeros( (h,w,3), dtype=np.float32 ) |
|
|
|
|
|
|
|
def process(w,h, data ): |
|
d = {} |
|
cur_lc = 0 |
|
all_lines = [] |
|
for s, pts_loop_ar in data: |
|
lines = [] |
|
for pts, loop in pts_loop_ar: |
|
pts_len = len(pts) |
|
lines.append ( [ [ pts[i], pts[(i+1) % pts_len ] ] for i in range(pts_len - (0 if loop else 1) ) ] ) |
|
lines = np.concatenate (lines) |
|
|
|
lc = lines.shape[0] |
|
all_lines.append(lines) |
|
d[s] = cur_lc, cur_lc+lc |
|
cur_lc += lc |
|
all_lines = np.concatenate (all_lines, 0) |
|
|
|
|
|
line_count = all_lines.shape[0] |
|
pts_count = w*h |
|
|
|
all_lines = np.repeat ( all_lines[None,...], pts_count, axis=0 ).reshape ( (pts_count*line_count,2,2) ) |
|
|
|
pts = np.empty( (h,w,line_count,2), dtype=np.float32 ) |
|
pts[...,1] = np.arange(h)[:,None,None] |
|
pts[...,0] = np.arange(w)[:,None] |
|
pts = pts.reshape ( (h*w*line_count, -1) ) |
|
|
|
a = all_lines[:,0,:] |
|
b = all_lines[:,1,:] |
|
pa = pts-a |
|
ba = b-a |
|
ph = np.clip ( np.einsum('ij,ij->i', pa, ba) / np.einsum('ij,ij->i', ba, ba), 0, 1 ) |
|
dists = npla.norm ( pa - ba*ph[...,None], axis=1).reshape ( (h,w,line_count) ) |
|
|
|
def get_dists(name, thickness=0): |
|
s,e = d[name] |
|
result = dists[...,s:e] |
|
if thickness != 0: |
|
result = np.abs(result)-thickness |
|
return np.min (result, axis=-1) |
|
|
|
return get_dists |
|
|
|
l_eye = lmrks[42:48] |
|
r_eye = lmrks[36:42] |
|
l_brow = lmrks[22:27] |
|
r_brow = lmrks[17:22] |
|
mouth = lmrks[48:60] |
|
|
|
up_nose = np.concatenate( (lmrks[27:31], lmrks[33:34]) ) |
|
down_nose = lmrks[31:36] |
|
nose = np.concatenate ( (up_nose, down_nose) ) |
|
|
|
gdf = process ( w,h, |
|
( |
|
('eyes', ((l_eye, True), (r_eye, True)) ), |
|
('brows', ((l_brow, False), (r_brow,False)) ), |
|
('up_nose', ((up_nose, False),) ), |
|
('down_nose', ((down_nose, False),) ), |
|
('mouth', ((mouth, True),) ), |
|
) |
|
) |
|
|
|
eyes_fall_dist = w // 32 |
|
eyes_thickness = max( w // 64, 1 ) |
|
|
|
brows_fall_dist = w // 32 |
|
brows_thickness = max( w // 256, 1 ) |
|
|
|
nose_fall_dist = w / 12 |
|
nose_thickness = max( w // 96, 1 ) |
|
|
|
mouth_fall_dist = w // 32 |
|
mouth_thickness = max( w // 64, 1 ) |
|
|
|
eyes_mask = gdf('eyes',eyes_thickness) |
|
eyes_mask = 1-np.clip( eyes_mask/ eyes_fall_dist, 0, 1) |
|
|
|
|
|
|
|
brows_mask = gdf('brows', brows_thickness) |
|
brows_mask = 1-np.clip( brows_mask / brows_fall_dist, 0, 1) |
|
|
|
|
|
mouth_mask = gdf('mouth', mouth_thickness) |
|
mouth_mask = 1-np.clip( mouth_mask / mouth_fall_dist, 0, 1) |
|
|
|
|
|
def blend(a,b,k): |
|
x = np.clip ( 0.5+0.5*(b-a)/k, 0.0, 1.0 ) |
|
return (a-b)*x+b - k*x*(1.0-x) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nose_mask = blend ( gdf('up_nose', nose_thickness), gdf('down_nose', nose_thickness), nose_thickness*3 ) |
|
nose_mask = 1-np.clip( nose_mask / nose_fall_dist, 0, 1) |
|
|
|
up_nose_mask = gdf('up_nose', nose_thickness) |
|
up_nose_mask = 1-np.clip( up_nose_mask / nose_fall_dist, 0, 1) |
|
|
|
|
|
down_nose_mask = gdf('down_nose', nose_thickness) |
|
down_nose_mask = 1-np.clip( down_nose_mask / nose_fall_dist, 0, 1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eyes_mask = eyes_mask * (1-mouth_mask) |
|
nose_mask = nose_mask * (1-eyes_mask) |
|
|
|
hull_mask = hull[...,0].copy() |
|
hull_mask = hull_mask * (1-eyes_mask) * (1-brows_mask) * (1-nose_mask) * (1-mouth_mask) |
|
|
|
|
|
|
|
mouth_mask= mouth_mask * (1-nose_mask) |
|
|
|
brows_mask = brows_mask * (1-nose_mask)* (1-eyes_mask ) |
|
|
|
hull_mask = alpha_to_color(hull_mask, (0,1,0) ) |
|
eyes_mask = alpha_to_color(eyes_mask, (1,0,0) ) |
|
brows_mask = alpha_to_color(brows_mask, (0,0,1) ) |
|
nose_mask = alpha_to_color(nose_mask, (0,1,1) ) |
|
mouth_mask = alpha_to_color(mouth_mask, (0,0,1) ) |
|
|
|
|
|
|
|
result = hull_mask + mouth_mask+ nose_mask + brows_mask + eyes_mask |
|
result *= hull |
|
|
|
return result |
|
|
|
def blur_image_hull_mask (hull_mask): |
|
|
|
maxregion = np.argwhere(hull_mask==1.0) |
|
miny,minx = maxregion.min(axis=0)[:2] |
|
maxy,maxx = maxregion.max(axis=0)[:2] |
|
lenx = maxx - minx; |
|
leny = maxy - miny; |
|
masky = int(minx+(lenx//2)) |
|
maskx = int(miny+(leny//2)) |
|
lowest_len = min (lenx, leny) |
|
ero = int( lowest_len * 0.085 ) |
|
blur = int( lowest_len * 0.10 ) |
|
|
|
hull_mask = cv2.erode(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(ero,ero)), iterations = 1 ) |
|
hull_mask = cv2.blur(hull_mask, (blur, blur) ) |
|
hull_mask = np.expand_dims (hull_mask,-1) |
|
|
|
return hull_mask |
|
|
|
mirror_idxs = [ |
|
[0,16], |
|
[1,15], |
|
[2,14], |
|
[3,13], |
|
[4,12], |
|
[5,11], |
|
[6,10], |
|
[7,9], |
|
|
|
[17,26], |
|
[18,25], |
|
[19,24], |
|
[20,23], |
|
[21,22], |
|
|
|
[36,45], |
|
[37,44], |
|
[38,43], |
|
[39,42], |
|
[40,47], |
|
[41,46], |
|
|
|
[31,35], |
|
[32,34], |
|
|
|
[50,52], |
|
[49,53], |
|
[48,54], |
|
[59,55], |
|
[58,56], |
|
[67,65], |
|
[60,64], |
|
[61,63] ] |
|
|
|
def mirror_landmarks (landmarks, val): |
|
result = landmarks.copy() |
|
|
|
for idx in mirror_idxs: |
|
result [ idx ] = result [ idx[::-1] ] |
|
|
|
result[:,0] = val - result[:,0] - 1 |
|
return result |
|
|
|
def get_face_struct_mask (image_shape, image_landmarks, eyebrows_expand_mod=1.0, color=(1,) ): |
|
mask = np.zeros(image_shape[0:2]+( len(color),),dtype=np.float32) |
|
lmrks = expand_eyebrows(image_landmarks, eyebrows_expand_mod) |
|
draw_landmarks (mask, image_landmarks, color=color, draw_circles=False, thickness=2) |
|
return mask |
|
|
|
def draw_landmarks (image, image_landmarks, color=(0,255,0), draw_circles=True, thickness=1, transparent_mask=False): |
|
if len(image_landmarks) != 68: |
|
raise Exception('get_image_eye_mask works only with 68 landmarks') |
|
|
|
int_lmrks = np.array(image_landmarks, dtype=np.int) |
|
|
|
jaw = int_lmrks[slice(*landmarks_68_pt["jaw"])] |
|
right_eyebrow = int_lmrks[slice(*landmarks_68_pt["right_eyebrow"])] |
|
left_eyebrow = int_lmrks[slice(*landmarks_68_pt["left_eyebrow"])] |
|
mouth = int_lmrks[slice(*landmarks_68_pt["mouth"])] |
|
right_eye = int_lmrks[slice(*landmarks_68_pt["right_eye"])] |
|
left_eye = int_lmrks[slice(*landmarks_68_pt["left_eye"])] |
|
nose = int_lmrks[slice(*landmarks_68_pt["nose"])] |
|
|
|
|
|
cv2.polylines(image, tuple(np.array([v]) for v in ( right_eyebrow, jaw, left_eyebrow, np.concatenate((nose, [nose[-6]])) )), |
|
False, color, thickness=thickness, lineType=cv2.LINE_AA) |
|
|
|
cv2.polylines(image, tuple(np.array([v]) for v in (right_eye, left_eye, mouth)), |
|
True, color, thickness=thickness, lineType=cv2.LINE_AA) |
|
|
|
if draw_circles: |
|
|
|
for x, y in np.concatenate((right_eyebrow, left_eyebrow, mouth, right_eye, left_eye, nose), axis=0): |
|
cv2.circle(image, (x, y), 1, color, 1, lineType=cv2.LINE_AA) |
|
|
|
for x, y in jaw: |
|
cv2.circle(image, (x, y), 2, color, lineType=cv2.LINE_AA) |
|
|
|
if transparent_mask: |
|
mask = get_image_hull_mask (image.shape, image_landmarks) |
|
image[...] = ( image * (1-mask) + image * mask / 2 )[...] |
|
|
|
def draw_rect_landmarks (image, rect, image_landmarks, face_type, face_size=256, transparent_mask=False, landmarks_color=(0,255,0)): |
|
draw_landmarks(image, image_landmarks, color=landmarks_color, transparent_mask=transparent_mask) |
|
imagelib.draw_rect (image, rect, (255,0,0), 2 ) |
|
|
|
image_to_face_mat = get_transform_mat (image_landmarks, face_size, face_type) |
|
points = transform_points ( [ (0,0), (0,face_size-1), (face_size-1, face_size-1), (face_size-1,0) ], image_to_face_mat, True) |
|
imagelib.draw_polygon (image, points, (0,0,255), 2) |
|
|
|
points = transform_points ( [ ( int(face_size*0.05), 0), ( int(face_size*0.1), int(face_size*0.1) ), ( 0, int(face_size*0.1) ) ], image_to_face_mat, True) |
|
imagelib.draw_polygon (image, points, (0,0,255), 2) |
|
|
|
def calc_face_pitch(landmarks): |
|
if not isinstance(landmarks, np.ndarray): |
|
landmarks = np.array (landmarks) |
|
t = ( (landmarks[6][1]-landmarks[8][1]) + (landmarks[10][1]-landmarks[8][1]) ) / 2.0 |
|
b = landmarks[8][1] |
|
return float(b-t) |
|
|
|
def estimate_averaged_yaw(landmarks): |
|
|
|
if not isinstance(landmarks, np.ndarray): |
|
landmarks = np.array (landmarks) |
|
l = ( (landmarks[27][0]-landmarks[0][0]) + (landmarks[28][0]-landmarks[1][0]) + (landmarks[29][0]-landmarks[2][0]) ) / 3.0 |
|
r = ( (landmarks[16][0]-landmarks[27][0]) + (landmarks[15][0]-landmarks[28][0]) + (landmarks[14][0]-landmarks[29][0]) ) / 3.0 |
|
return float(r-l) |
|
|
|
def estimate_pitch_yaw_roll(aligned_landmarks, size=256): |
|
""" |
|
returns pitch,yaw,roll [-pi/2...+pi/2] |
|
""" |
|
shape = (size,size) |
|
focal_length = shape[1] |
|
camera_center = (shape[1] / 2, shape[0] / 2) |
|
camera_matrix = np.array( |
|
[[focal_length, 0, camera_center[0]], |
|
[0, focal_length, camera_center[1]], |
|
[0, 0, 1]], dtype=np.float32) |
|
|
|
(_, rotation_vector, _) = cv2.solvePnP( |
|
np.concatenate( (landmarks_68_3D[:27], landmarks_68_3D[30:36]) , axis=0) , |
|
np.concatenate( (aligned_landmarks[:27], aligned_landmarks[30:36]) , axis=0).astype(np.float32), |
|
camera_matrix, |
|
np.zeros((4, 1)) ) |
|
|
|
pitch, yaw, roll = mathlib.rotationMatrixToEulerAngles( cv2.Rodrigues(rotation_vector)[0] ) |
|
|
|
half_pi = math.pi / 2.0 |
|
pitch = np.clip ( pitch, -half_pi, half_pi ) |
|
yaw = np.clip ( yaw , -half_pi, half_pi ) |
|
roll = np.clip ( roll, -half_pi, half_pi ) |
|
|
|
return -pitch, yaw, roll |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
def get_averaged_transform_mat (img_landmarks, |
|
img_landmarks_prev, |
|
img_landmarks_next, |
|
average_frame_count, |
|
average_center_frame_count, |
|
output_size, face_type, scale=1.0): |
|
|
|
l_c_list = [] |
|
tb_diag_vec_list = [] |
|
bt_diag_vec_list = [] |
|
mod_list = [] |
|
|
|
count = max(average_frame_count,average_center_frame_count) |
|
for i in range ( -count, count+1, 1 ): |
|
if i < 0: |
|
lmrks = img_landmarks_prev[i] if -i < len(img_landmarks_prev) else None |
|
elif i > 0: |
|
lmrks = img_landmarks_next[i] if i < len(img_landmarks_next) else None |
|
else: |
|
lmrks = img_landmarks |
|
|
|
if lmrks is None: |
|
continue |
|
|
|
l_c, tb_diag_vec, bt_diag_vec, mod = get_transform_mat_data (lmrks, face_type, scale=scale) |
|
|
|
if i >= -average_frame_count and i <= average_frame_count: |
|
tb_diag_vec_list.append(tb_diag_vec) |
|
bt_diag_vec_list.append(bt_diag_vec) |
|
mod_list.append(mod) |
|
|
|
if i >= -average_center_frame_count and i <= average_center_frame_count: |
|
l_c_list.append(l_c) |
|
|
|
tb_diag_vec = np.mean( np.array(tb_diag_vec_list), axis=0 ) |
|
bt_diag_vec = np.mean( np.array(bt_diag_vec_list), axis=0 ) |
|
mod = np.mean( np.array(mod_list), axis=0 ) |
|
l_c = np.mean( np.array(l_c_list), axis=0 ) |
|
|
|
return get_transform_mat_by_data (l_c, tb_diag_vec, bt_diag_vec, mod, output_size, face_type) |
|
|
|
|
|
def get_transform_mat (image_landmarks, output_size, face_type, scale=1.0): |
|
if not isinstance(image_landmarks, np.ndarray): |
|
image_landmarks = np.array (image_landmarks) |
|
|
|
# get face padding value for FaceType |
|
padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0) |
|
|
|
# estimate landmarks transform from global space to local aligned space with bounds [0..1] |
|
mat = umeyama( np.concatenate ( [ image_landmarks[17:49] , image_landmarks[54:55] ] ) , landmarks_2D_new, True)[0:2] |
|
|
|
# get corner points in global space |
|
l_p = transform_points ( np.float32([(0,0),(1,0),(1,1),(0,1),(0.5,0.5)]) , mat, True) |
|
l_c = l_p[4] |
|
|
|
# calc diagonal vectors between corners in global space |
|
tb_diag_vec = (l_p[2]-l_p[0]).astype(np.float32) |
|
tb_diag_vec /= npla.norm(tb_diag_vec) |
|
bt_diag_vec = (l_p[1]-l_p[3]).astype(np.float32) |
|
bt_diag_vec /= npla.norm(bt_diag_vec) |
|
|
|
# calc modifier of diagonal vectors for scale and padding value |
|
mod = (1.0 / scale)* ( npla.norm(l_p[0]-l_p[2])*(padding*np.sqrt(2.0) + 0.5) ) |
|
|
|
# calc 3 points in global space to estimate 2d affine transform |
|
if not remove_align: |
|
l_t = np.array( [ np.round( l_c - tb_diag_vec*mod ), |
|
np.round( l_c + bt_diag_vec*mod ), |
|
np.round( l_c + tb_diag_vec*mod ) ] ) |
|
else: |
|
# remove_align - face will be centered in the frame but not aligned |
|
l_t = np.array( [ np.round( l_c - tb_diag_vec*mod ), |
|
np.round( l_c + bt_diag_vec*mod ), |
|
np.round( l_c + tb_diag_vec*mod ), |
|
np.round( l_c - bt_diag_vec*mod ), |
|
] ) |
|
|
|
# get area of face square in global space |
|
area = mathlib.polygon_area(l_t[:,0], l_t[:,1] ) |
|
|
|
# calc side of square |
|
side = np.float32(math.sqrt(area) / 2) |
|
|
|
# calc 3 points with unrotated square |
|
l_t = np.array( [ np.round( l_c + [-side,-side] ), |
|
np.round( l_c + [ side,-side] ), |
|
np.round( l_c + [ side, side] ) ] ) |
|
|
|
# calc affine transform from 3 global space points to 3 local space points size of 'output_size' |
|
pts2 = np.float32(( (0,0),(output_size,0),(output_size,output_size) )) |
|
mat = cv2.getAffineTransform(l_t,pts2) |
|
|
|
return mat |
|
""" |