Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
iperov
GitHub Repository: iperov/deepfacelab
Path: blob/master/facelib/LandmarksProcessor.py
628 views
1
import colorsys
2
import math
3
from enum import IntEnum
4
5
import cv2
6
import numpy as np
7
import numpy.linalg as npla
8
9
from core import imagelib
10
from core import mathlib
11
from facelib import FaceType
12
from core.mathlib.umeyama import umeyama
13
14
landmarks_2D = np.array([
15
[ 0.000213256, 0.106454 ], #17
16
[ 0.0752622, 0.038915 ], #18
17
[ 0.18113, 0.0187482 ], #19
18
[ 0.29077, 0.0344891 ], #20
19
[ 0.393397, 0.0773906 ], #21
20
[ 0.586856, 0.0773906 ], #22
21
[ 0.689483, 0.0344891 ], #23
22
[ 0.799124, 0.0187482 ], #24
23
[ 0.904991, 0.038915 ], #25
24
[ 0.98004, 0.106454 ], #26
25
[ 0.490127, 0.203352 ], #27
26
[ 0.490127, 0.307009 ], #28
27
[ 0.490127, 0.409805 ], #29
28
[ 0.490127, 0.515625 ], #30
29
[ 0.36688, 0.587326 ], #31
30
[ 0.426036, 0.609345 ], #32
31
[ 0.490127, 0.628106 ], #33
32
[ 0.554217, 0.609345 ], #34
33
[ 0.613373, 0.587326 ], #35
34
[ 0.121737, 0.216423 ], #36
35
[ 0.187122, 0.178758 ], #37
36
[ 0.265825, 0.179852 ], #38
37
[ 0.334606, 0.231733 ], #39
38
[ 0.260918, 0.245099 ], #40
39
[ 0.182743, 0.244077 ], #41
40
[ 0.645647, 0.231733 ], #42
41
[ 0.714428, 0.179852 ], #43
42
[ 0.793132, 0.178758 ], #44
43
[ 0.858516, 0.216423 ], #45
44
[ 0.79751, 0.244077 ], #46
45
[ 0.719335, 0.245099 ], #47
46
[ 0.254149, 0.780233 ], #48
47
[ 0.340985, 0.745405 ], #49
48
[ 0.428858, 0.727388 ], #50
49
[ 0.490127, 0.742578 ], #51
50
[ 0.551395, 0.727388 ], #52
51
[ 0.639268, 0.745405 ], #53
52
[ 0.726104, 0.780233 ], #54
53
[ 0.642159, 0.864805 ], #55
54
[ 0.556721, 0.902192 ], #56
55
[ 0.490127, 0.909281 ], #57
56
[ 0.423532, 0.902192 ], #58
57
[ 0.338094, 0.864805 ], #59
58
[ 0.290379, 0.784792 ], #60
59
[ 0.428096, 0.778746 ], #61
60
[ 0.490127, 0.785343 ], #62
61
[ 0.552157, 0.778746 ], #63
62
[ 0.689874, 0.784792 ], #64
63
[ 0.553364, 0.824182 ], #65
64
[ 0.490127, 0.831803 ], #66
65
[ 0.42689 , 0.824182 ] #67
66
], dtype=np.float32)
67
68
69
landmarks_2D_new = np.array([
70
[ 0.000213256, 0.106454 ], #17
71
[ 0.0752622, 0.038915 ], #18
72
[ 0.18113, 0.0187482 ], #19
73
[ 0.29077, 0.0344891 ], #20
74
[ 0.393397, 0.0773906 ], #21
75
[ 0.586856, 0.0773906 ], #22
76
[ 0.689483, 0.0344891 ], #23
77
[ 0.799124, 0.0187482 ], #24
78
[ 0.904991, 0.038915 ], #25
79
[ 0.98004, 0.106454 ], #26
80
[ 0.490127, 0.203352 ], #27
81
[ 0.490127, 0.307009 ], #28
82
[ 0.490127, 0.409805 ], #29
83
[ 0.490127, 0.515625 ], #30
84
[ 0.36688, 0.587326 ], #31
85
[ 0.426036, 0.609345 ], #32
86
[ 0.490127, 0.628106 ], #33
87
[ 0.554217, 0.609345 ], #34
88
[ 0.613373, 0.587326 ], #35
89
[ 0.121737, 0.216423 ], #36
90
[ 0.187122, 0.178758 ], #37
91
[ 0.265825, 0.179852 ], #38
92
[ 0.334606, 0.231733 ], #39
93
[ 0.260918, 0.245099 ], #40
94
[ 0.182743, 0.244077 ], #41
95
[ 0.645647, 0.231733 ], #42
96
[ 0.714428, 0.179852 ], #43
97
[ 0.793132, 0.178758 ], #44
98
[ 0.858516, 0.216423 ], #45
99
[ 0.79751, 0.244077 ], #46
100
[ 0.719335, 0.245099 ], #47
101
[ 0.254149, 0.780233 ], #48
102
[ 0.726104, 0.780233 ], #54
103
], dtype=np.float32)
104
105
mouth_center_landmarks_2D = np.array([
106
[-4.4202591e-07, 4.4916576e-01], #48
107
[ 1.8399176e-01, 3.7537053e-01], #49
108
[ 3.7018123e-01, 3.3719531e-01], #50
109
[ 5.0000089e-01, 3.6938059e-01], #51
110
[ 6.2981832e-01, 3.3719531e-01], #52
111
[ 8.1600773e-01, 3.7537053e-01], #53
112
[ 1.0000000e+00, 4.4916576e-01], #54
113
[ 8.2213330e-01, 6.2836081e-01], #55
114
[ 6.4110327e-01, 7.0757812e-01], #56
115
[ 5.0000089e-01, 7.2259867e-01], #57
116
[ 3.5889623e-01, 7.0757812e-01], #58
117
[ 1.7786618e-01, 6.2836081e-01], #59
118
[ 7.6765373e-02, 4.5882553e-01], #60
119
[ 3.6856663e-01, 4.4601500e-01], #61
120
[ 5.0000089e-01, 4.5999300e-01], #62
121
[ 6.3143289e-01, 4.4601500e-01], #63
122
[ 9.2323411e-01, 4.5882553e-01], #64
123
[ 6.3399029e-01, 5.4228687e-01], #65
124
[ 5.0000089e-01, 5.5843467e-01], #66
125
[ 3.6601129e-01, 5.4228687e-01] #67
126
], dtype=np.float32)
127
128
# 68 point landmark definitions
129
landmarks_68_pt = { "mouth": (48,68),
130
"right_eyebrow": (17, 22),
131
"left_eyebrow": (22, 27),
132
"right_eye": (36, 42),
133
"left_eye": (42, 48),
134
"nose": (27, 36), # missed one point
135
"jaw": (0, 17) }
136
137
landmarks_68_3D = np.array( [
138
[-73.393523 , -29.801432 , 47.667532 ], #00
139
[-72.775014 , -10.949766 , 45.909403 ], #01
140
[-70.533638 , 7.929818 , 44.842580 ], #02
141
[-66.850058 , 26.074280 , 43.141114 ], #03
142
[-59.790187 , 42.564390 , 38.635298 ], #04
143
[-48.368973 , 56.481080 , 30.750622 ], #05
144
[-34.121101 , 67.246992 , 18.456453 ], #06
145
[-17.875411 , 75.056892 , 3.609035 ], #07
146
[0.098749 , 77.061286 , -0.881698 ], #08
147
[17.477031 , 74.758448 , 5.181201 ], #09
148
[32.648966 , 66.929021 , 19.176563 ], #10
149
[46.372358 , 56.311389 , 30.770570 ], #11
150
[57.343480 , 42.419126 , 37.628629 ], #12
151
[64.388482 , 25.455880 , 40.886309 ], #13
152
[68.212038 , 6.990805 , 42.281449 ], #14
153
[70.486405 , -11.666193 , 44.142567 ], #15
154
[71.375822 , -30.365191 , 47.140426 ], #16
155
[-61.119406 , -49.361602 , 14.254422 ], #17
156
[-51.287588 , -58.769795 , 7.268147 ], #18
157
[-37.804800 , -61.996155 , 0.442051 ], #19
158
[-24.022754 , -61.033399 , -6.606501 ], #20
159
[-11.635713 , -56.686759 , -11.967398 ], #21
160
[12.056636 , -57.391033 , -12.051204 ], #22
161
[25.106256 , -61.902186 , -7.315098 ], #23
162
[38.338588 , -62.777713 , -1.022953 ], #24
163
[51.191007 , -59.302347 , 5.349435 ], #25
164
[60.053851 , -50.190255 , 11.615746 ], #26
165
[0.653940 , -42.193790 , -13.380835 ], #27
166
[0.804809 , -30.993721 , -21.150853 ], #28
167
[0.992204 , -19.944596 , -29.284036 ], #29
168
[1.226783 , -8.414541 , -36.948060 ], #00
169
[-14.772472 , 2.598255 , -20.132003 ], #01
170
[-7.180239 , 4.751589 , -23.536684 ], #02
171
[0.555920 , 6.562900 , -25.944448 ], #03
172
[8.272499 , 4.661005 , -23.695741 ], #04
173
[15.214351 , 2.643046 , -20.858157 ], #05
174
[-46.047290 , -37.471411 , 7.037989 ], #06
175
[-37.674688 , -42.730510 , 3.021217 ], #07
176
[-27.883856 , -42.711517 , 1.353629 ], #08
177
[-19.648268 , -36.754742 , -0.111088 ], #09
178
[-28.272965 , -35.134493 , -0.147273 ], #10
179
[-38.082418 , -34.919043 , 1.476612 ], #11
180
[19.265868 , -37.032306 , -0.665746 ], #12
181
[27.894191 , -43.342445 , 0.247660 ], #13
182
[37.437529 , -43.110822 , 1.696435 ], #14
183
[45.170805 , -38.086515 , 4.894163 ], #15
184
[38.196454 , -35.532024 , 0.282961 ], #16
185
[28.764989 , -35.484289 , -1.172675 ], #17
186
[-28.916267 , 28.612716 , -2.240310 ], #18
187
[-17.533194 , 22.172187 , -15.934335 ], #19
188
[-6.684590 , 19.029051 , -22.611355 ], #20
189
[0.381001 , 20.721118 , -23.748437 ], #21
190
[8.375443 , 19.035460 , -22.721995 ], #22
191
[18.876618 , 22.394109 , -15.610679 ], #23
192
[28.794412 , 28.079924 , -3.217393 ], #24
193
[19.057574 , 36.298248 , -14.987997 ], #25
194
[8.956375 , 39.634575 , -22.554245 ], #26
195
[0.381549 , 40.395647 , -23.591626 ], #27
196
[-7.428895 , 39.836405 , -22.406106 ], #28
197
[-18.160634 , 36.677899 , -15.121907 ], #29
198
[-24.377490 , 28.677771 , -4.785684 ], #30
199
[-6.897633 , 25.475976 , -20.893742 ], #31
200
[0.340663 , 26.014269 , -22.220479 ], #32
201
[8.444722 , 25.326198 , -21.025520 ], #33
202
[24.474473 , 28.323008 , -5.712776 ], #34
203
[8.449166 , 30.596216 , -20.671489 ], #35
204
[0.205322 , 31.408738 , -21.903670 ], #36
205
[-7.198266 , 30.844876 , -20.328022 ] #37
206
], dtype=np.float32)
207
208
FaceType_to_padding_remove_align = {
209
FaceType.HALF: (0.0, False),
210
FaceType.MID_FULL: (0.0675, False),
211
FaceType.FULL: (0.2109375, False),
212
FaceType.FULL_NO_ALIGN: (0.2109375, True),
213
FaceType.WHOLE_FACE: (0.40, False),
214
FaceType.HEAD: (0.70, False),
215
FaceType.HEAD_NO_ALIGN: (0.70, True),
216
}
217
218
def convert_98_to_68(lmrks):
219
#jaw
220
result = [ lmrks[0] ]
221
for i in range(2,16,2):
222
result += [ ( lmrks[i] + (lmrks[i-1]+lmrks[i+1])/2 ) / 2 ]
223
result += [ lmrks[16] ]
224
for i in range(18,32,2):
225
result += [ ( lmrks[i] + (lmrks[i-1]+lmrks[i+1])/2 ) / 2 ]
226
result += [ lmrks[32] ]
227
228
#eyebrows averaging
229
result += [ lmrks[33],
230
(lmrks[34]+lmrks[41])/2,
231
(lmrks[35]+lmrks[40])/2,
232
(lmrks[36]+lmrks[39])/2,
233
(lmrks[37]+lmrks[38])/2,
234
]
235
236
result += [ (lmrks[42]+lmrks[50])/2,
237
(lmrks[43]+lmrks[49])/2,
238
(lmrks[44]+lmrks[48])/2,
239
(lmrks[45]+lmrks[47])/2,
240
lmrks[46]
241
]
242
243
#nose
244
result += list ( lmrks[51:60] )
245
246
#left eye (from our view)
247
result += [ lmrks[60],
248
lmrks[61],
249
lmrks[63],
250
lmrks[64],
251
lmrks[65],
252
lmrks[67] ]
253
254
#right eye
255
result += [ lmrks[68],
256
lmrks[69],
257
lmrks[71],
258
lmrks[72],
259
lmrks[73],
260
lmrks[75] ]
261
262
#mouth
263
result += list ( lmrks[76:96] )
264
265
return np.concatenate (result).reshape ( (68,2) )
266
267
def transform_points(points, mat, invert=False):
268
if invert:
269
mat = cv2.invertAffineTransform (mat)
270
points = np.expand_dims(points, axis=1)
271
points = cv2.transform(points, mat, points.shape)
272
points = np.squeeze(points)
273
return points
274
275
def get_transform_mat (image_landmarks, output_size, face_type, scale=1.0):
276
if not isinstance(image_landmarks, np.ndarray):
277
image_landmarks = np.array (image_landmarks)
278
279
280
# estimate landmarks transform from global space to local aligned space with bounds [0..1]
281
mat = umeyama( np.concatenate ( [ image_landmarks[17:49] , image_landmarks[54:55] ] ) , landmarks_2D_new, True)[0:2]
282
283
# get corner points in global space
284
g_p = transform_points ( np.float32([(0,0),(1,0),(1,1),(0,1),(0.5,0.5) ]) , mat, True)
285
g_c = g_p[4]
286
287
# calc diagonal vectors between corners in global space
288
tb_diag_vec = (g_p[2]-g_p[0]).astype(np.float32)
289
tb_diag_vec /= npla.norm(tb_diag_vec)
290
bt_diag_vec = (g_p[1]-g_p[3]).astype(np.float32)
291
bt_diag_vec /= npla.norm(bt_diag_vec)
292
293
# calc modifier of diagonal vectors for scale and padding value
294
padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0)
295
mod = (1.0 / scale)* ( npla.norm(g_p[0]-g_p[2])*(padding*np.sqrt(2.0) + 0.5) )
296
297
if face_type == FaceType.WHOLE_FACE:
298
# adjust vertical offset for WHOLE_FACE, 7% below in order to cover more forehead
299
vec = (g_p[0]-g_p[3]).astype(np.float32)
300
vec_len = npla.norm(vec)
301
vec /= vec_len
302
g_c += vec*vec_len*0.07
303
304
elif face_type == FaceType.HEAD:
305
# assuming image_landmarks are 3D_Landmarks extracted for HEAD,
306
# adjust horizontal offset according to estimated yaw
307
yaw = estimate_averaged_yaw(transform_points (image_landmarks, mat, False))
308
309
hvec = (g_p[0]-g_p[1]).astype(np.float32)
310
hvec_len = npla.norm(hvec)
311
hvec /= hvec_len
312
313
yaw *= np.abs(math.tanh(yaw*2)) # Damp near zero
314
315
g_c -= hvec * (yaw * hvec_len / 2.0)
316
317
# adjust vertical offset for HEAD, 50% below
318
vvec = (g_p[0]-g_p[3]).astype(np.float32)
319
vvec_len = npla.norm(vvec)
320
vvec /= vvec_len
321
g_c += vvec*vvec_len*0.50
322
323
# calc 3 points in global space to estimate 2d affine transform
324
if not remove_align:
325
l_t = np.array( [ g_c - tb_diag_vec*mod,
326
g_c + bt_diag_vec*mod,
327
g_c + tb_diag_vec*mod ] )
328
else:
329
# remove_align - face will be centered in the frame but not aligned
330
l_t = np.array( [ g_c - tb_diag_vec*mod,
331
g_c + bt_diag_vec*mod,
332
g_c + tb_diag_vec*mod,
333
g_c - bt_diag_vec*mod,
334
] )
335
336
# get area of face square in global space
337
area = mathlib.polygon_area(l_t[:,0], l_t[:,1] )
338
339
# calc side of square
340
side = np.float32(math.sqrt(area) / 2)
341
342
# calc 3 points with unrotated square
343
l_t = np.array( [ g_c + [-side,-side],
344
g_c + [ side,-side],
345
g_c + [ side, side] ] )
346
347
# calc affine transform from 3 global space points to 3 local space points size of 'output_size'
348
pts2 = np.float32(( (0,0),(output_size,0),(output_size,output_size) ))
349
mat = cv2.getAffineTransform(l_t,pts2)
350
return mat
351
352
def get_rect_from_landmarks(image_landmarks):
353
mat = get_transform_mat(image_landmarks, 256, FaceType.FULL_NO_ALIGN)
354
355
g_p = transform_points ( np.float32([(0,0),(255,255) ]) , mat, True)
356
357
(l,t,r,b) = g_p[0][0], g_p[0][1], g_p[1][0], g_p[1][1]
358
359
return (l,t,r,b)
360
361
def expand_eyebrows(lmrks, eyebrows_expand_mod=1.0):
362
if len(lmrks) != 68:
363
raise Exception('works only with 68 landmarks')
364
lmrks = np.array( lmrks.copy(), dtype=np.int )
365
366
# #nose
367
ml_pnt = (lmrks[36] + lmrks[0]) // 2
368
mr_pnt = (lmrks[16] + lmrks[45]) // 2
369
370
# mid points between the mid points and eye
371
ql_pnt = (lmrks[36] + ml_pnt) // 2
372
qr_pnt = (lmrks[45] + mr_pnt) // 2
373
374
# Top of the eye arrays
375
bot_l = np.array((ql_pnt, lmrks[36], lmrks[37], lmrks[38], lmrks[39]))
376
bot_r = np.array((lmrks[42], lmrks[43], lmrks[44], lmrks[45], qr_pnt))
377
378
# Eyebrow arrays
379
top_l = lmrks[17:22]
380
top_r = lmrks[22:27]
381
382
# Adjust eyebrow arrays
383
lmrks[17:22] = top_l + eyebrows_expand_mod * 0.5 * (top_l - bot_l)
384
lmrks[22:27] = top_r + eyebrows_expand_mod * 0.5 * (top_r - bot_r)
385
return lmrks
386
387
388
389
390
def get_image_hull_mask (image_shape, image_landmarks, eyebrows_expand_mod=1.0 ):
391
hull_mask = np.zeros(image_shape[0:2]+(1,),dtype=np.float32)
392
393
lmrks = expand_eyebrows(image_landmarks, eyebrows_expand_mod)
394
395
r_jaw = (lmrks[0:9], lmrks[17:18])
396
l_jaw = (lmrks[8:17], lmrks[26:27])
397
r_cheek = (lmrks[17:20], lmrks[8:9])
398
l_cheek = (lmrks[24:27], lmrks[8:9])
399
nose_ridge = (lmrks[19:25], lmrks[8:9],)
400
r_eye = (lmrks[17:22], lmrks[27:28], lmrks[31:36], lmrks[8:9])
401
l_eye = (lmrks[22:27], lmrks[27:28], lmrks[31:36], lmrks[8:9])
402
nose = (lmrks[27:31], lmrks[31:36])
403
parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose]
404
405
for item in parts:
406
merged = np.concatenate(item)
407
cv2.fillConvexPoly(hull_mask, cv2.convexHull(merged), (1,) )
408
409
return hull_mask
410
411
def get_image_eye_mask (image_shape, image_landmarks):
412
if len(image_landmarks) != 68:
413
raise Exception('get_image_eye_mask works only with 68 landmarks')
414
415
h,w,c = image_shape
416
417
hull_mask = np.zeros( (h,w,1),dtype=np.float32)
418
419
image_landmarks = image_landmarks.astype(np.int)
420
421
cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[36:42]), (1,) )
422
cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[42:48]), (1,) )
423
424
dilate = h // 32
425
hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(dilate,dilate)), iterations = 1 )
426
427
blur = h // 16
428
blur = blur + (1-blur % 2)
429
hull_mask = cv2.GaussianBlur(hull_mask, (blur, blur) , 0)
430
hull_mask = hull_mask[...,None]
431
432
return hull_mask
433
434
def get_image_mouth_mask (image_shape, image_landmarks):
435
if len(image_landmarks) != 68:
436
raise Exception('get_image_eye_mask works only with 68 landmarks')
437
438
h,w,c = image_shape
439
440
hull_mask = np.zeros( (h,w,1),dtype=np.float32)
441
442
image_landmarks = image_landmarks.astype(np.int)
443
444
cv2.fillConvexPoly( hull_mask, cv2.convexHull( image_landmarks[60:]), (1,) )
445
446
dilate = h // 32
447
hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(dilate,dilate)), iterations = 1 )
448
449
blur = h // 16
450
blur = blur + (1-blur % 2)
451
hull_mask = cv2.GaussianBlur(hull_mask, (blur, blur) , 0)
452
hull_mask = hull_mask[...,None]
453
454
return hull_mask
455
456
def alpha_to_color (img_alpha, color):
457
if len(img_alpha.shape) == 2:
458
img_alpha = img_alpha[...,None]
459
h,w,c = img_alpha.shape
460
result = np.zeros( (h,w, len(color) ), dtype=np.float32 )
461
result[:,:] = color
462
463
return result * img_alpha
464
465
466
467
def get_cmask (image_shape, lmrks, eyebrows_expand_mod=1.0):
468
h,w,c = image_shape
469
470
hull = get_image_hull_mask (image_shape, lmrks, eyebrows_expand_mod )
471
472
result = np.zeros( (h,w,3), dtype=np.float32 )
473
474
475
476
def process(w,h, data ):
477
d = {}
478
cur_lc = 0
479
all_lines = []
480
for s, pts_loop_ar in data:
481
lines = []
482
for pts, loop in pts_loop_ar:
483
pts_len = len(pts)
484
lines.append ( [ [ pts[i], pts[(i+1) % pts_len ] ] for i in range(pts_len - (0 if loop else 1) ) ] )
485
lines = np.concatenate (lines)
486
487
lc = lines.shape[0]
488
all_lines.append(lines)
489
d[s] = cur_lc, cur_lc+lc
490
cur_lc += lc
491
all_lines = np.concatenate (all_lines, 0)
492
493
#calculate signed distance for all points and lines
494
line_count = all_lines.shape[0]
495
pts_count = w*h
496
497
all_lines = np.repeat ( all_lines[None,...], pts_count, axis=0 ).reshape ( (pts_count*line_count,2,2) )
498
499
pts = np.empty( (h,w,line_count,2), dtype=np.float32 )
500
pts[...,1] = np.arange(h)[:,None,None]
501
pts[...,0] = np.arange(w)[:,None]
502
pts = pts.reshape ( (h*w*line_count, -1) )
503
504
a = all_lines[:,0,:]
505
b = all_lines[:,1,:]
506
pa = pts-a
507
ba = b-a
508
ph = np.clip ( np.einsum('ij,ij->i', pa, ba) / np.einsum('ij,ij->i', ba, ba), 0, 1 )
509
dists = npla.norm ( pa - ba*ph[...,None], axis=1).reshape ( (h,w,line_count) )
510
511
def get_dists(name, thickness=0):
512
s,e = d[name]
513
result = dists[...,s:e]
514
if thickness != 0:
515
result = np.abs(result)-thickness
516
return np.min (result, axis=-1)
517
518
return get_dists
519
520
l_eye = lmrks[42:48]
521
r_eye = lmrks[36:42]
522
l_brow = lmrks[22:27]
523
r_brow = lmrks[17:22]
524
mouth = lmrks[48:60]
525
526
up_nose = np.concatenate( (lmrks[27:31], lmrks[33:34]) )
527
down_nose = lmrks[31:36]
528
nose = np.concatenate ( (up_nose, down_nose) )
529
530
gdf = process ( w,h,
531
(
532
('eyes', ((l_eye, True), (r_eye, True)) ),
533
('brows', ((l_brow, False), (r_brow,False)) ),
534
('up_nose', ((up_nose, False),) ),
535
('down_nose', ((down_nose, False),) ),
536
('mouth', ((mouth, True),) ),
537
)
538
)
539
540
eyes_fall_dist = w // 32
541
eyes_thickness = max( w // 64, 1 )
542
543
brows_fall_dist = w // 32
544
brows_thickness = max( w // 256, 1 )
545
546
nose_fall_dist = w / 12
547
nose_thickness = max( w // 96, 1 )
548
549
mouth_fall_dist = w // 32
550
mouth_thickness = max( w // 64, 1 )
551
552
eyes_mask = gdf('eyes',eyes_thickness)
553
eyes_mask = 1-np.clip( eyes_mask/ eyes_fall_dist, 0, 1)
554
#eyes_mask = np.clip ( 1- ( np.sqrt( np.maximum(eyes_mask,0) ) / eyes_fall_dist ), 0, 1)
555
#eyes_mask = np.clip ( 1- ( np.cbrt( np.maximum(eyes_mask,0) ) / eyes_fall_dist ), 0, 1)
556
557
brows_mask = gdf('brows', brows_thickness)
558
brows_mask = 1-np.clip( brows_mask / brows_fall_dist, 0, 1)
559
#brows_mask = np.clip ( 1- ( np.sqrt( np.maximum(brows_mask,0) ) / brows_fall_dist ), 0, 1)
560
561
mouth_mask = gdf('mouth', mouth_thickness)
562
mouth_mask = 1-np.clip( mouth_mask / mouth_fall_dist, 0, 1)
563
#mouth_mask = np.clip ( 1- ( np.sqrt( np.maximum(mouth_mask,0) ) / mouth_fall_dist ), 0, 1)
564
565
def blend(a,b,k):
566
x = np.clip ( 0.5+0.5*(b-a)/k, 0.0, 1.0 )
567
return (a-b)*x+b - k*x*(1.0-x)
568
569
570
#nose_mask = (a-b)*x+b - k*x*(1.0-x)
571
572
#nose_mask = np.minimum (up_nose_mask , down_nose_mask )
573
#nose_mask = 1-np.clip( nose_mask / nose_fall_dist, 0, 1)
574
575
nose_mask = blend ( gdf('up_nose', nose_thickness), gdf('down_nose', nose_thickness), nose_thickness*3 )
576
nose_mask = 1-np.clip( nose_mask / nose_fall_dist, 0, 1)
577
578
up_nose_mask = gdf('up_nose', nose_thickness)
579
up_nose_mask = 1-np.clip( up_nose_mask / nose_fall_dist, 0, 1)
580
#up_nose_mask = np.clip ( 1- ( np.cbrt( np.maximum(up_nose_mask,0) ) / nose_fall_dist ), 0, 1)
581
582
down_nose_mask = gdf('down_nose', nose_thickness)
583
down_nose_mask = 1-np.clip( down_nose_mask / nose_fall_dist, 0, 1)
584
#down_nose_mask = np.clip ( 1- ( np.cbrt( np.maximum(down_nose_mask,0) ) / nose_fall_dist ), 0, 1)
585
586
#nose_mask = np.clip( up_nose_mask + down_nose_mask, 0, 1 )
587
#nose_mask /= np.max(nose_mask)
588
#nose_mask = np.maximum (up_nose_mask , down_nose_mask )
589
#nose_mask = down_nose_mask
590
591
#nose_mask = np.zeros_like(nose_mask)
592
593
eyes_mask = eyes_mask * (1-mouth_mask)
594
nose_mask = nose_mask * (1-eyes_mask)
595
596
hull_mask = hull[...,0].copy()
597
hull_mask = hull_mask * (1-eyes_mask) * (1-brows_mask) * (1-nose_mask) * (1-mouth_mask)
598
599
#eyes_mask = eyes_mask * (1-nose_mask)
600
601
mouth_mask= mouth_mask * (1-nose_mask)
602
603
brows_mask = brows_mask * (1-nose_mask)* (1-eyes_mask )
604
605
hull_mask = alpha_to_color(hull_mask, (0,1,0) )
606
eyes_mask = alpha_to_color(eyes_mask, (1,0,0) )
607
brows_mask = alpha_to_color(brows_mask, (0,0,1) )
608
nose_mask = alpha_to_color(nose_mask, (0,1,1) )
609
mouth_mask = alpha_to_color(mouth_mask, (0,0,1) )
610
611
#nose_mask = np.maximum( up_nose_mask, down_nose_mask )
612
613
result = hull_mask + mouth_mask+ nose_mask + brows_mask + eyes_mask
614
result *= hull
615
#result = np.clip (result, 0, 1)
616
return result
617
618
def blur_image_hull_mask (hull_mask):
619
620
maxregion = np.argwhere(hull_mask==1.0)
621
miny,minx = maxregion.min(axis=0)[:2]
622
maxy,maxx = maxregion.max(axis=0)[:2]
623
lenx = maxx - minx;
624
leny = maxy - miny;
625
masky = int(minx+(lenx//2))
626
maskx = int(miny+(leny//2))
627
lowest_len = min (lenx, leny)
628
ero = int( lowest_len * 0.085 )
629
blur = int( lowest_len * 0.10 )
630
631
hull_mask = cv2.erode(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(ero,ero)), iterations = 1 )
632
hull_mask = cv2.blur(hull_mask, (blur, blur) )
633
hull_mask = np.expand_dims (hull_mask,-1)
634
635
return hull_mask
636
637
mirror_idxs = [
638
[0,16],
639
[1,15],
640
[2,14],
641
[3,13],
642
[4,12],
643
[5,11],
644
[6,10],
645
[7,9],
646
647
[17,26],
648
[18,25],
649
[19,24],
650
[20,23],
651
[21,22],
652
653
[36,45],
654
[37,44],
655
[38,43],
656
[39,42],
657
[40,47],
658
[41,46],
659
660
[31,35],
661
[32,34],
662
663
[50,52],
664
[49,53],
665
[48,54],
666
[59,55],
667
[58,56],
668
[67,65],
669
[60,64],
670
[61,63] ]
671
672
def mirror_landmarks (landmarks, val):
673
result = landmarks.copy()
674
675
for idx in mirror_idxs:
676
result [ idx ] = result [ idx[::-1] ]
677
678
result[:,0] = val - result[:,0] - 1
679
return result
680
681
def get_face_struct_mask (image_shape, image_landmarks, eyebrows_expand_mod=1.0, color=(1,) ):
682
mask = np.zeros(image_shape[0:2]+( len(color),),dtype=np.float32)
683
lmrks = expand_eyebrows(image_landmarks, eyebrows_expand_mod)
684
draw_landmarks (mask, image_landmarks, color=color, draw_circles=False, thickness=2)
685
return mask
686
687
def draw_landmarks (image, image_landmarks, color=(0,255,0), draw_circles=True, thickness=1, transparent_mask=False):
688
if len(image_landmarks) != 68:
689
raise Exception('get_image_eye_mask works only with 68 landmarks')
690
691
int_lmrks = np.array(image_landmarks, dtype=np.int)
692
693
jaw = int_lmrks[slice(*landmarks_68_pt["jaw"])]
694
right_eyebrow = int_lmrks[slice(*landmarks_68_pt["right_eyebrow"])]
695
left_eyebrow = int_lmrks[slice(*landmarks_68_pt["left_eyebrow"])]
696
mouth = int_lmrks[slice(*landmarks_68_pt["mouth"])]
697
right_eye = int_lmrks[slice(*landmarks_68_pt["right_eye"])]
698
left_eye = int_lmrks[slice(*landmarks_68_pt["left_eye"])]
699
nose = int_lmrks[slice(*landmarks_68_pt["nose"])]
700
701
# open shapes
702
cv2.polylines(image, tuple(np.array([v]) for v in ( right_eyebrow, jaw, left_eyebrow, np.concatenate((nose, [nose[-6]])) )),
703
False, color, thickness=thickness, lineType=cv2.LINE_AA)
704
# closed shapes
705
cv2.polylines(image, tuple(np.array([v]) for v in (right_eye, left_eye, mouth)),
706
True, color, thickness=thickness, lineType=cv2.LINE_AA)
707
708
if draw_circles:
709
# the rest of the cicles
710
for x, y in np.concatenate((right_eyebrow, left_eyebrow, mouth, right_eye, left_eye, nose), axis=0):
711
cv2.circle(image, (x, y), 1, color, 1, lineType=cv2.LINE_AA)
712
# jaw big circles
713
for x, y in jaw:
714
cv2.circle(image, (x, y), 2, color, lineType=cv2.LINE_AA)
715
716
if transparent_mask:
717
mask = get_image_hull_mask (image.shape, image_landmarks)
718
image[...] = ( image * (1-mask) + image * mask / 2 )[...]
719
720
def draw_rect_landmarks (image, rect, image_landmarks, face_type, face_size=256, transparent_mask=False, landmarks_color=(0,255,0)):
721
draw_landmarks(image, image_landmarks, color=landmarks_color, transparent_mask=transparent_mask)
722
imagelib.draw_rect (image, rect, (255,0,0), 2 )
723
724
image_to_face_mat = get_transform_mat (image_landmarks, face_size, face_type)
725
points = transform_points ( [ (0,0), (0,face_size-1), (face_size-1, face_size-1), (face_size-1,0) ], image_to_face_mat, True)
726
imagelib.draw_polygon (image, points, (0,0,255), 2)
727
728
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)
729
imagelib.draw_polygon (image, points, (0,0,255), 2)
730
731
def calc_face_pitch(landmarks):
732
if not isinstance(landmarks, np.ndarray):
733
landmarks = np.array (landmarks)
734
t = ( (landmarks[6][1]-landmarks[8][1]) + (landmarks[10][1]-landmarks[8][1]) ) / 2.0
735
b = landmarks[8][1]
736
return float(b-t)
737
738
def estimate_averaged_yaw(landmarks):
739
# Works much better than solvePnP if landmarks from "3DFAN"
740
if not isinstance(landmarks, np.ndarray):
741
landmarks = np.array (landmarks)
742
l = ( (landmarks[27][0]-landmarks[0][0]) + (landmarks[28][0]-landmarks[1][0]) + (landmarks[29][0]-landmarks[2][0]) ) / 3.0
743
r = ( (landmarks[16][0]-landmarks[27][0]) + (landmarks[15][0]-landmarks[28][0]) + (landmarks[14][0]-landmarks[29][0]) ) / 3.0
744
return float(r-l)
745
746
def estimate_pitch_yaw_roll(aligned_landmarks, size=256):
747
"""
748
returns pitch,yaw,roll [-pi/2...+pi/2]
749
"""
750
shape = (size,size)
751
focal_length = shape[1]
752
camera_center = (shape[1] / 2, shape[0] / 2)
753
camera_matrix = np.array(
754
[[focal_length, 0, camera_center[0]],
755
[0, focal_length, camera_center[1]],
756
[0, 0, 1]], dtype=np.float32)
757
758
(_, rotation_vector, _) = cv2.solvePnP(
759
np.concatenate( (landmarks_68_3D[:27], landmarks_68_3D[30:36]) , axis=0) ,
760
np.concatenate( (aligned_landmarks[:27], aligned_landmarks[30:36]) , axis=0).astype(np.float32),
761
camera_matrix,
762
np.zeros((4, 1)) )
763
764
pitch, yaw, roll = mathlib.rotationMatrixToEulerAngles( cv2.Rodrigues(rotation_vector)[0] )
765
766
half_pi = math.pi / 2.0
767
pitch = np.clip ( pitch, -half_pi, half_pi )
768
yaw = np.clip ( yaw , -half_pi, half_pi )
769
roll = np.clip ( roll, -half_pi, half_pi )
770
771
return -pitch, yaw, roll
772
773
#if remove_align:
774
# bbox = transform_points ( [ (0,0), (0,output_size), (output_size, output_size), (output_size,0) ], mat, True)
775
# #import code
776
# #code.interact(local=dict(globals(), **locals()))
777
# area = mathlib.polygon_area(bbox[:,0], bbox[:,1] )
778
# side = math.sqrt(area) / 2
779
# center = transform_points ( [(output_size/2,output_size/2)], mat, True)
780
# pts1 = np.float32(( center+[-side,-side], center+[side,-side], center+[side,-side] ))
781
# pts2 = np.float32([[0,0],[output_size,0],[0,output_size]])
782
# mat = cv2.getAffineTransform(pts1,pts2)
783
#if full_face_align_top and (face_type == FaceType.FULL or face_type == FaceType.FULL_NO_ALIGN):
784
# #lmrks2 = expand_eyebrows(image_landmarks)
785
# #lmrks2_ = transform_points( [ lmrks2[19], lmrks2[24] ], mat, False )
786
# #y_diff = np.float32( (0,np.min(lmrks2_[:,1])) )
787
# #y_diff = transform_points( [ np.float32( (0,0) ), y_diff], mat, True)
788
# #y_diff = y_diff[1]-y_diff[0]
789
#
790
# x_diff = np.float32((0,0))
791
#
792
# lmrks2_ = transform_points( [ image_landmarks[0], image_landmarks[16] ], mat, False )
793
# if lmrks2_[0,0] < 0:
794
# x_diff = lmrks2_[0,0]
795
# x_diff = transform_points( [ np.float32( (0,0) ), np.float32((x_diff,0)) ], mat, True)
796
# x_diff = x_diff[1]-x_diff[0]
797
# elif lmrks2_[1,0] >= output_size:
798
# x_diff = lmrks2_[1,0]-(output_size-1)
799
# x_diff = transform_points( [ np.float32( (0,0) ), np.float32((x_diff,0)) ], mat, True)
800
# x_diff = x_diff[1]-x_diff[0]
801
#
802
# mat = cv2.getAffineTransform( l_t+y_diff+x_diff ,pts2)
803
804
805
"""
806
def get_averaged_transform_mat (img_landmarks,
807
img_landmarks_prev,
808
img_landmarks_next,
809
average_frame_count,
810
average_center_frame_count,
811
output_size, face_type, scale=1.0):
812
813
l_c_list = []
814
tb_diag_vec_list = []
815
bt_diag_vec_list = []
816
mod_list = []
817
818
count = max(average_frame_count,average_center_frame_count)
819
for i in range ( -count, count+1, 1 ):
820
if i < 0:
821
lmrks = img_landmarks_prev[i] if -i < len(img_landmarks_prev) else None
822
elif i > 0:
823
lmrks = img_landmarks_next[i] if i < len(img_landmarks_next) else None
824
else:
825
lmrks = img_landmarks
826
827
if lmrks is None:
828
continue
829
830
l_c, tb_diag_vec, bt_diag_vec, mod = get_transform_mat_data (lmrks, face_type, scale=scale)
831
832
if i >= -average_frame_count and i <= average_frame_count:
833
tb_diag_vec_list.append(tb_diag_vec)
834
bt_diag_vec_list.append(bt_diag_vec)
835
mod_list.append(mod)
836
837
if i >= -average_center_frame_count and i <= average_center_frame_count:
838
l_c_list.append(l_c)
839
840
tb_diag_vec = np.mean( np.array(tb_diag_vec_list), axis=0 )
841
bt_diag_vec = np.mean( np.array(bt_diag_vec_list), axis=0 )
842
mod = np.mean( np.array(mod_list), axis=0 )
843
l_c = np.mean( np.array(l_c_list), axis=0 )
844
845
return get_transform_mat_by_data (l_c, tb_diag_vec, bt_diag_vec, mod, output_size, face_type)
846
847
848
def get_transform_mat (image_landmarks, output_size, face_type, scale=1.0):
849
if not isinstance(image_landmarks, np.ndarray):
850
image_landmarks = np.array (image_landmarks)
851
852
# get face padding value for FaceType
853
padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0)
854
855
# estimate landmarks transform from global space to local aligned space with bounds [0..1]
856
mat = umeyama( np.concatenate ( [ image_landmarks[17:49] , image_landmarks[54:55] ] ) , landmarks_2D_new, True)[0:2]
857
858
# get corner points in global space
859
l_p = transform_points ( np.float32([(0,0),(1,0),(1,1),(0,1),(0.5,0.5)]) , mat, True)
860
l_c = l_p[4]
861
862
# calc diagonal vectors between corners in global space
863
tb_diag_vec = (l_p[2]-l_p[0]).astype(np.float32)
864
tb_diag_vec /= npla.norm(tb_diag_vec)
865
bt_diag_vec = (l_p[1]-l_p[3]).astype(np.float32)
866
bt_diag_vec /= npla.norm(bt_diag_vec)
867
868
# calc modifier of diagonal vectors for scale and padding value
869
mod = (1.0 / scale)* ( npla.norm(l_p[0]-l_p[2])*(padding*np.sqrt(2.0) + 0.5) )
870
871
# calc 3 points in global space to estimate 2d affine transform
872
if not remove_align:
873
l_t = np.array( [ np.round( l_c - tb_diag_vec*mod ),
874
np.round( l_c + bt_diag_vec*mod ),
875
np.round( l_c + tb_diag_vec*mod ) ] )
876
else:
877
# remove_align - face will be centered in the frame but not aligned
878
l_t = np.array( [ np.round( l_c - tb_diag_vec*mod ),
879
np.round( l_c + bt_diag_vec*mod ),
880
np.round( l_c + tb_diag_vec*mod ),
881
np.round( l_c - bt_diag_vec*mod ),
882
] )
883
884
# get area of face square in global space
885
area = mathlib.polygon_area(l_t[:,0], l_t[:,1] )
886
887
# calc side of square
888
side = np.float32(math.sqrt(area) / 2)
889
890
# calc 3 points with unrotated square
891
l_t = np.array( [ np.round( l_c + [-side,-side] ),
892
np.round( l_c + [ side,-side] ),
893
np.round( l_c + [ side, side] ) ] )
894
895
# calc affine transform from 3 global space points to 3 local space points size of 'output_size'
896
pts2 = np.float32(( (0,0),(output_size,0),(output_size,output_size) ))
897
mat = cv2.getAffineTransform(l_t,pts2)
898
899
return mat
900
"""
901