GuijiAI commited on
Commit
89cf463
·
verified ·
1 Parent(s): 067bd13

Upload 117 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +5 -0
  2. HifiFaceAPI_parallel_base.py +148 -0
  3. HifiFaceAPI_parallel_trt_roi_realtime_api.py +189 -0
  4. HifiFaceAPI_parallel_trt_roi_realtime_sr_api.py +234 -0
  5. LICENSE +32 -0
  6. app.py +175 -0
  7. assets/cam_demo1.gif +3 -0
  8. assets/cam_demo2.gif +3 -0
  9. assets/demo10.gif +3 -0
  10. assets/demo20.gif +3 -0
  11. color_transfer.py +337 -0
  12. data/image_feature_dict.pkl +3 -0
  13. data/source/demo.mp4 +3 -0
  14. data/source/elon-musk1.jpg +0 -0
  15. face_detect/FaceType.py +37 -0
  16. face_detect/LandmarksProcessor.py +1482 -0
  17. face_detect/__init__.py +3 -0
  18. face_detect/core/imagelib/SegIEPolys.py +158 -0
  19. face_detect/core/imagelib/__init__.py +32 -0
  20. face_detect/core/imagelib/blursharpen.py +38 -0
  21. face_detect/core/imagelib/color_transfer.py +340 -0
  22. face_detect/core/imagelib/common.py +62 -0
  23. face_detect/core/imagelib/draw.py +13 -0
  24. face_detect/core/imagelib/equalize_and_stack_square.py +45 -0
  25. face_detect/core/imagelib/estimate_sharpness.py +278 -0
  26. face_detect/core/imagelib/filters.py +245 -0
  27. face_detect/core/imagelib/morph.py +37 -0
  28. face_detect/core/imagelib/reduce_colors.py +14 -0
  29. face_detect/core/imagelib/sd/__init__.py +2 -0
  30. face_detect/core/imagelib/sd/calc.py +25 -0
  31. face_detect/core/imagelib/sd/draw.py +200 -0
  32. face_detect/core/imagelib/warp.py +72 -0
  33. face_detect/core/leras/__init__.py +1 -0
  34. face_detect/core/leras/archis/ArchiBase.py +17 -0
  35. face_detect/core/leras/archis/DeepFakeArchi.py +223 -0
  36. face_detect/core/leras/archis/__init__.py +2 -0
  37. face_detect/core/leras/device.py +272 -0
  38. face_detect/core/leras/layers/AdaIN.py +56 -0
  39. face_detect/core/leras/layers/BatchNorm2D.py +42 -0
  40. face_detect/core/leras/layers/BlurPool.py +50 -0
  41. face_detect/core/leras/layers/Conv2D.py +112 -0
  42. face_detect/core/leras/layers/Conv2DTranspose.py +107 -0
  43. face_detect/core/leras/layers/Dense.py +76 -0
  44. face_detect/core/leras/layers/DenseNorm.py +16 -0
  45. face_detect/core/leras/layers/DepthwiseConv2D.py +110 -0
  46. face_detect/core/leras/layers/FRNorm2D.py +38 -0
  47. face_detect/core/leras/layers/InstanceNorm2D.py +40 -0
  48. face_detect/core/leras/layers/LayerBase.py +16 -0
  49. face_detect/core/leras/layers/Saveable.py +106 -0
  50. face_detect/core/leras/layers/ScaleAdd.py +31 -0
.gitattributes CHANGED
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/cam_demo1.gif filter=lfs diff=lfs merge=lfs -text
37
+ assets/cam_demo2.gif filter=lfs diff=lfs merge=lfs -text
38
+ assets/demo10.gif filter=lfs diff=lfs merge=lfs -text
39
+ assets/demo20.gif filter=lfs diff=lfs merge=lfs -text
40
+ data/source/demo.mp4 filter=lfs diff=lfs merge=lfs -text
HifiFaceAPI_parallel_base.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import numpy as np
4
+
5
+ import numexpr as ne
6
+ # ne.set_num_threads(10)
7
+
8
+ from multiprocessing.dummy import Process, Queue
9
+ from face_detect.face_align_68 import face_alignment_landmark
10
+ from face_detect.face_detect import FaceDetect
11
+ from face_lib.face_swap import HifiFace
12
+ from face_restore.gfpgan_onnx_api import GFPGAN
13
+ from face_restore.xseg_onnx_api import XSEG
14
+
15
+ TRACKING_THRESHOLD = 0.15
16
+
17
+ # def np_norm(x):
18
+ # return (x - np.average(x)) / np.std(x)
19
+
20
+ def cosine_vectorized_v3(array1, array2):
21
+ sumyy = np.einsum('ij,ij->i', array2, array2)
22
+ sumxx = np.einsum('ij,ij->i', array1, array1)[:, None]
23
+ sumxy = array1.dot(array2.T)
24
+ sqrt_sumxx = ne.evaluate('sqrt(sumxx)')
25
+ sqrt_sumyy = ne.evaluate('sqrt(sumyy)')
26
+ return ne.evaluate('(sumxy/sqrt_sumxx)/sqrt_sumyy')
27
+
28
+
29
+ class Consumer0Base(Process):
30
+ def __init__(self, opt, frame_queue_in, feature_dst_list=None, queue_list=None, block=True, fps_counter=False):
31
+ super().__init__()
32
+ self.queue_list = queue_list
33
+ self.fps_counter = fps_counter
34
+ self.block = block
35
+ self.pid = os.getpid()
36
+
37
+ self.opt = opt
38
+ self.frame_queue_in = frame_queue_in
39
+ self.feature_dst_list = feature_dst_list
40
+ self.crop_size = self.opt.input_size
41
+ self.scrfd_detector = FaceDetect(mode='scrfd_500m', tracking_thres=TRACKING_THRESHOLD)
42
+ self.face_alignment = face_alignment_landmark(lm_type=68)
43
+
44
+ print('init consumer {}, pid is {}.'.format(self.__class__.__name__, self.pid))
45
+
46
+
47
+ class Consumer1BaseONNX(Process):
48
+ def __init__(self, opt, feature_list, queue_list: list, block=True, fps_counter=False,provider='gpu', load_xseg=True, xseg_flag=False):
49
+ super().__init__()
50
+ self.queue_list = queue_list
51
+ self.fps_counter = fps_counter
52
+ self.block = block
53
+ self.pid = os.getpid()
54
+ self.opt = opt
55
+ self.feature_list = feature_list
56
+ # self.index_list = index_list
57
+ # self.apply_gpen = apply_gpen
58
+ self.crop_size = self.opt.input_size
59
+ self.xseg_flag = xseg_flag
60
+
61
+ print("model_name:", self.opt.model_name)
62
+ self.hf = HifiFace(model_name='er8_bs1', provider=provider)
63
+ if load_xseg:
64
+ self.xseg = XSEG(model_type='xseg_0611', provider=provider)
65
+
66
+ def switch_xseg(self):
67
+ self.xseg_flag = not self.xseg_flag
68
+
69
+ def predict(self, src_face_image, dst_face_latent):
70
+ mask_out, swap_face_out = self.hf.forward(src_face_image, dst_face_latent)
71
+ if self.xseg_flag:
72
+ mask_out = self.xseg.forward(swap_face_out)[None,None]
73
+ return [mask_out, swap_face_out]
74
+
75
+
76
+ class Consumer2Base(Process):
77
+ def __init__(self, queue_list: list, frame_queue_out, block=True, fps_counter=False):
78
+ super().__init__()
79
+ self.queue_list = queue_list
80
+ self.fps_counter = fps_counter
81
+ self.block = block
82
+ self.pid = os.getpid()
83
+ self.frame_queue_out = frame_queue_out
84
+
85
+ # from face_restore import FaceRestore
86
+ # self.fa = FaceRestore(use_gpu=True, mode='gfpgan') # gfpgan gpen dfdnet
87
+
88
+ print('init consumer {}, pid is {}.'.format(self.__class__.__name__, self.pid))
89
+
90
+ def run(self):
91
+ counter = 0
92
+ start_time = time.time()
93
+
94
+ while True:
95
+ something_in = self.queue_list[0].get()
96
+
97
+ # exit condition
98
+ if something_in is None:
99
+ print('subprocess {} exit !'.format(self.pid))
100
+ break
101
+
102
+ self.forward_func(something_in)
103
+
104
+ if self.fps_counter:
105
+ counter += 1
106
+ if (time.time() - start_time) > 4:
107
+ print("Consumer2 FPS: {}".format(counter / (time.time() - start_time)))
108
+ counter = 0
109
+ start_time = time.time()
110
+ print('c2 stop')
111
+ # cv2.destroyAllWindows()
112
+
113
+ class Consumer3Base(Process):
114
+ def __init__(self, queue_list, block=True, fps_counter=False, provider='gpu'):
115
+ super().__init__()
116
+ self.queue_list = queue_list
117
+ self.fps_counter = fps_counter
118
+ self.block = block
119
+ self.pid = os.getpid()
120
+
121
+ self.gfp = GFPGAN(model_type='GFPGANv1.4', provider=provider)
122
+
123
+ print('init consumer {}, pid is {}.'.format(self.__class__.__name__, self.pid))
124
+
125
+ def run(self):
126
+ counter = 0
127
+ start_time = time.time()
128
+
129
+ while True:
130
+ something_in = self.queue_list[0].get()
131
+
132
+ if something_in is None:
133
+ print('subprocess {} exit !'.format(self.pid))
134
+ self.queue_list[1].put(None)
135
+ break
136
+
137
+ self.forward_func(something_in)
138
+
139
+
140
+ if self.fps_counter:
141
+ counter += 1
142
+ if (time.time() - start_time) > 4:
143
+ print("Consumer3 FPS: {}".format(counter / (time.time() - start_time)))
144
+ counter = 0
145
+ start_time = time.time()
146
+
147
+ print('c3 stop')
148
+
HifiFaceAPI_parallel_trt_roi_realtime_api.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import time
4
+ import numpy as np
5
+ import numexpr as ne
6
+ from multiprocessing.dummy import Process, Queue
7
+ from options.hifi_test_options import HifiTestOptions
8
+ from HifiFaceAPI_parallel_base import Consumer0Base, Consumer2Base, Consumer1BaseONNX
9
+
10
+
11
+ def np_norm(x):
12
+ return (x - np.average(x)) / np.std(x)
13
+
14
+
15
+ def reverse2wholeimage_hifi_trt_roi(swaped_img, mat_rev, img_mask, frame, roi_img, roi_box):
16
+ target_image = cv2.warpAffine(swaped_img, mat_rev, roi_img.shape[:2][::-1], borderMode=cv2.BORDER_REPLICATE)[
17
+ ...,
18
+ ::-1]
19
+
20
+ local_dict = {
21
+ 'img_mask': img_mask,
22
+ 'target_image': target_image,
23
+ 'roi_img': roi_img,
24
+ }
25
+ img = ne.evaluate('img_mask * (target_image * 255)+(1 - img_mask) * roi_img', local_dict=local_dict,
26
+ global_dict=None)
27
+ img = img.astype(np.uint8)
28
+ frame[roi_box[1]:roi_box[3], roi_box[0]:roi_box[2]] = img
29
+ return frame
30
+
31
+
32
+ def get_max_face(np_rois):
33
+ roi_areas = []
34
+ for index in range(np_rois.shape[0]):
35
+ roi_areas.append((np_rois[index, 2] - np_rois[index, 0]) * (np_rois[index, 3] - np_rois[index, 1]))
36
+ return np.argmax(np.array(roi_areas))
37
+
38
+
39
+ class Consumer0(Consumer0Base):
40
+ def __init__(self, opt, frame_queue_in, queue_list: list, block=True, fps_counter=False):
41
+ super().__init__(opt, frame_queue_in, None, queue_list, block, fps_counter)
42
+
43
+ def run(self):
44
+ counter = 0
45
+ start_time = time.time()
46
+ kpss_old = None
47
+ rois_old = faces_old = Ms_old = masks_old = None
48
+
49
+ while True:
50
+ frame = self.frame_queue_in.get()
51
+ if frame is None:
52
+ break
53
+ try:
54
+ _, bboxes, kpss = self.scrfd_detector.get_bboxes(frame, max_num=0)
55
+ rois, faces, Ms, masks = self.face_alignment.forward(
56
+ frame, bboxes, kpss, limit=5, min_face_size=30,
57
+ crop_size=(self.crop_size, self.crop_size), apply_roi=True
58
+ )
59
+
60
+ except (TypeError, IndexError, ValueError) as e:
61
+ self.queue_list[0].put([None, frame])
62
+ continue
63
+
64
+ if len(faces)==0:
65
+ self.queue_list[0].put([None, frame])
66
+ continue
67
+ elif len(faces)==1:
68
+ face = np.array(faces[0])
69
+ mat = Ms[0]
70
+ roi_box = rois[0]
71
+ else:
72
+ max_index = get_max_face(np.array(rois))
73
+ face = np.array(faces[max_index])
74
+ mat = Ms[max_index]
75
+ roi_box = rois[max_index]
76
+ roi_img = frame[roi_box[1]:roi_box[3], roi_box[0]:roi_box[2]]
77
+
78
+ # "The default normalization to the range of -1 to 1, where the model input is in RGB format
79
+ face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
80
+
81
+ self.queue_list[0].put([face, mat, [], frame, roi_img, roi_box])
82
+
83
+ if self.fps_counter:
84
+ counter += 1
85
+ if (time.time() - start_time) > 10:
86
+ print("Consumer0 FPS: {}".format(counter / (time.time() - start_time)))
87
+ counter = 0
88
+ start_time = time.time()
89
+ self.queue_list[0].put(None)
90
+ print('co stop')
91
+
92
+
93
+ class Consumer1(Consumer1BaseONNX):
94
+ def __init__(self, opt, feature_list, queue_list: list, block=True, fps_counter=False):
95
+ super().__init__(opt, feature_list, queue_list, block, fps_counter)
96
+
97
+ def run(self):
98
+ counter = 0
99
+ start_time = time.time()
100
+
101
+ while True:
102
+ something_in = self.queue_list[0].get()
103
+ if something_in is None:
104
+ break
105
+ elif len(something_in) == 2:
106
+ self.queue_list[1].put([None, something_in[1]])
107
+ continue
108
+
109
+
110
+ if len(self.feature_list) > 1:
111
+ self.feature_list.pop(0)
112
+
113
+ image_latent = self.feature_list[0][0]
114
+
115
+ mask_out, swap_face_out = self.predict(something_in[0], image_latent[0].reshape(1, -1))
116
+
117
+ mask = cv2.warpAffine(mask_out[0][0].astype(np.float32), something_in[1],
118
+ something_in[4].shape[:2][::-1])
119
+ mask[mask > 0.2] = 1
120
+ mask = mask[:, :, np.newaxis].astype(np.uint8)
121
+ swap_face = swap_face_out[0].transpose((1, 2, 0)).astype(np.float32)
122
+
123
+ self.queue_list[1].put(
124
+ [swap_face, something_in[1], mask, something_in[3], something_in[4], something_in[5]])
125
+
126
+ if self.fps_counter:
127
+ counter += 1
128
+ if (time.time() - start_time) > 10:
129
+ print("Consumer1 FPS: {}".format(counter / (time.time() - start_time)))
130
+ counter = 0
131
+ start_time = time.time()
132
+ self.queue_list[1].put(None)
133
+ print('c1 stop')
134
+
135
+
136
+ class Consumer2(Consumer2Base):
137
+ def __init__(self, queue_list: list, frame_queue_out, block=True, fps_counter=False):
138
+ super().__init__(queue_list, frame_queue_out, block, fps_counter)
139
+ self.face_detect_flag = True
140
+
141
+ def forward_func(self, something_in):
142
+
143
+ # do your work here.
144
+ if len(something_in) == 2:
145
+ self.face_detect_flag = False
146
+ frame = something_in[1]
147
+ frame_out = frame.astype(np.uint8)
148
+ else:
149
+ self.face_detect_flag = True
150
+ # swap_face = something_in[0]
151
+ swap_face = ((something_in[0] + 1) / 2)
152
+ frame_out = reverse2wholeimage_hifi_trt_roi(
153
+ swap_face, something_in[1], something_in[2],
154
+ something_in[3], something_in[4], something_in[5]
155
+ )
156
+ self.frame_queue_out.put([frame_out, self.face_detect_flag])
157
+ # cv2.imshow('output', frame_out)
158
+ # cv2.waitKey(1)
159
+
160
+
161
+ class HifiFaceRealTime:
162
+
163
+ def __init__(self, feature_dict_list_, frame_queue_in, frame_queue_out, gpu=True, model_name=''):
164
+ self.opt = HifiTestOptions().parse()
165
+ if model_name != '':
166
+ self.opt.model_name = model_name
167
+ self.opt.input_size = 256
168
+ self.feature_dict_list = feature_dict_list_
169
+ self.frame_queue_in = frame_queue_in
170
+ self.frame_queue_out = frame_queue_out
171
+
172
+ self.gpu = gpu
173
+
174
+ def forward(self):
175
+ self.q0 = Queue(2)
176
+ self.q1 = Queue(2)
177
+
178
+ self.c0 = Consumer0(self.opt, self.frame_queue_in, [self.q0], fps_counter=False)
179
+ self.c1 = Consumer1(self.opt, self.feature_dict_list, [self.q0, self.q1], fps_counter=False)
180
+ self.c2 = Consumer2([self.q1], self.frame_queue_out, fps_counter=False)
181
+
182
+ self.c0.start()
183
+ self.c1.start()
184
+ self.c2.start()
185
+
186
+ self.c0.join()
187
+ self.c1.join()
188
+ self.c2.join()
189
+ return
HifiFaceAPI_parallel_trt_roi_realtime_sr_api.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import time
4
+ import numpy as np
5
+ import numexpr as ne
6
+ from multiprocessing.dummy import Process, Queue
7
+ from options.hifi_test_options import HifiTestOptions
8
+ from HifiFaceAPI_parallel_base import Consumer0Base, Consumer2Base, Consumer3Base,Consumer1BaseONNX
9
+ from color_transfer import color_transfer
10
+
11
+
12
+ def np_norm(x):
13
+ return (x - np.average(x)) / np.std(x)
14
+
15
+
16
+ def reverse2wholeimage_hifi_trt_roi(swaped_img, mat_rev, img_mask, frame, roi_img, roi_box):
17
+ target_image = cv2.warpAffine(swaped_img, mat_rev, roi_img.shape[:2][::-1], borderMode=cv2.BORDER_REPLICATE)[
18
+ ...,
19
+ ::-1]
20
+ local_dict = {
21
+ 'img_mask': img_mask,
22
+ 'target_image': target_image,
23
+ 'roi_img': roi_img,
24
+ }
25
+ img = ne.evaluate('img_mask * (target_image * 255)+(1 - img_mask) * roi_img', local_dict=local_dict,
26
+ global_dict=None)
27
+ img = img.astype(np.uint8)
28
+ frame[roi_box[1]:roi_box[3], roi_box[0]:roi_box[2]] = img
29
+ return frame
30
+
31
+
32
+ def get_max_face(np_rois):
33
+ roi_areas = []
34
+ for index in range(np_rois.shape[0]):
35
+ roi_areas.append((np_rois[index, 2] - np_rois[index, 0]) * (np_rois[index, 3] - np_rois[index, 1]))
36
+ return np.argmax(np.array(roi_areas))
37
+
38
+ class Consumer0(Consumer0Base):
39
+ def __init__(self, opt, frame_queue_in, queue_list: list, block=True, fps_counter=False, align_method='68'):
40
+ super().__init__(opt, frame_queue_in, None, queue_list, block, fps_counter)
41
+ self.align_method = align_method
42
+
43
+ def run(self):
44
+ counter = 0
45
+ start_time = time.time()
46
+ kpss_old = None
47
+ rois_old = faces_old = Ms_old = masks_old = None
48
+
49
+ while True:
50
+ frame = self.frame_queue_in.get()
51
+ if frame is None:
52
+ break
53
+ try:
54
+ _, bboxes, kpss = self.scrfd_detector.get_bboxes(frame, max_num=0)
55
+ if self.align_method == '5class':
56
+ rois, faces, Ms, masks = self.mtcnn_detector.align_multi_for_scrfd(
57
+ frame, bboxes, kpss, limit=1, min_face_size=30,
58
+ crop_size=(self.crop_size, self.crop_size), apply_roi=True, detector=None
59
+ )
60
+ else:
61
+ rois, faces, Ms, masks = self.face_alignment.forward(
62
+ frame, bboxes, kpss, limit=5, min_face_size=30,
63
+ crop_size=(self.crop_size, self.crop_size), apply_roi=True
64
+ )
65
+
66
+ except (TypeError, IndexError, ValueError) as e:
67
+ self.queue_list[0].put([None, frame])
68
+ continue
69
+
70
+ if len(faces)==0:
71
+ self.queue_list[0].put([None, frame])
72
+ continue
73
+ elif len(faces)==1:
74
+ face = np.array(faces[0])
75
+ mat = Ms[0]
76
+ roi_box = rois[0]
77
+ else:
78
+ max_index = get_max_face(np.array(rois))
79
+ face = np.array(faces[max_index])
80
+ mat = Ms[max_index]
81
+ roi_box = rois[max_index]
82
+ roi_img = frame[roi_box[1]:roi_box[3], roi_box[0]:roi_box[2]]
83
+
84
+ #The default normalization to the range of -1 to 1, where the model input is in RGB format
85
+ face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
86
+
87
+ self.queue_list[0].put([face, mat, [], frame, roi_img, roi_box])
88
+
89
+ if self.fps_counter:
90
+ counter += 1
91
+ if (time.time() - start_time) > 10:
92
+ print("Consumer0 FPS: {}".format(counter / (time.time() - start_time)))
93
+ counter = 0
94
+ start_time = time.time()
95
+ self.queue_list[0].put(None)
96
+ print('co stop')
97
+
98
+
99
+ class Consumer1(Consumer1BaseONNX):
100
+ def __init__(self, opt, feature_list, queue_list: list, block=True, fps_counter=False):
101
+ super().__init__(opt, feature_list, queue_list, block, fps_counter)
102
+
103
+ def run(self):
104
+ counter = 0
105
+ start_time = time.time()
106
+
107
+ while True:
108
+ something_in = self.queue_list[0].get()
109
+ if something_in is None:
110
+ break
111
+ elif len(something_in) == 2:
112
+ self.queue_list[1].put([None, something_in[1]])
113
+ continue
114
+
115
+ if len(self.feature_list) > 1:
116
+ self.feature_list.pop(0)
117
+
118
+ image_latent = self.feature_list[0][0]
119
+
120
+ mask_out, swap_face_out = self.predict(something_in[0], image_latent[0].reshape(1, -1))
121
+
122
+ mask = cv2.warpAffine(mask_out[0][0].astype(np.float32), something_in[1],
123
+ something_in[4].shape[:2][::-1])
124
+ mask[mask > 0.2] = 1
125
+ mask = mask[:, :, np.newaxis].astype(np.uint8)
126
+ swap_face = swap_face_out[0].transpose((1, 2, 0)).astype(np.float32)
127
+
128
+ self.queue_list[1].put(
129
+ [swap_face, something_in[1], mask, something_in[3], something_in[4], something_in[5], something_in[0]])
130
+
131
+ if self.fps_counter:
132
+ counter += 1
133
+ if (time.time() - start_time) > 10:
134
+ print("Consumer1 FPS: {}".format(counter / (time.time() - start_time)))
135
+ counter = 0
136
+ start_time = time.time()
137
+ self.queue_list[1].put(None)
138
+ print('c1 stop')
139
+
140
+
141
+ class Consumer2(Consumer2Base):
142
+ def __init__(self, queue_list: list, frame_queue_out, block=True, fps_counter=False):
143
+ super().__init__(queue_list, frame_queue_out, block, fps_counter)
144
+
145
+ def forward_func(self, something_in):
146
+ if len(something_in) == 2:
147
+ frame = something_in[1]
148
+ frame_out = frame.astype(np.uint8)
149
+ else:
150
+ swap_face = ((something_in[0] + 1) / 2)
151
+ frame_out = reverse2wholeimage_hifi_trt_roi(
152
+ swap_face, something_in[1], something_in[2],
153
+ something_in[3], something_in[4], something_in[5]
154
+ )
155
+ self.frame_queue_out.put(frame_out)
156
+ # cv2.imshow('output', frame_out)
157
+ # cv2.waitKey(1)
158
+
159
+ class Consumer3(Consumer3Base):
160
+ def __init__(self, queue_list, block=True, fps_counter=False, use_gfpgan=True, sr_weight=1.0,
161
+ use_color_trans=False, color_trans_mode=''):
162
+ super().__init__(queue_list, block, fps_counter)
163
+ self.use_gfpgan = use_gfpgan
164
+ self.sr_weight = sr_weight
165
+ self.use_color_trans = use_color_trans
166
+ self.color_trans_mode = color_trans_mode
167
+
168
+ def forward_func(self, something_in):
169
+ if len(something_in) == 2:
170
+ self.queue_list[1].put([None, something_in[1]])
171
+ else:
172
+ swap_face = something_in[0]
173
+ target_face = (something_in[6] / 255).astype(np.float32)
174
+ if self.use_gfpgan:
175
+ sr_face = self.gfp.forward(swap_face)
176
+ if self.sr_weight != 1.0:
177
+ sr_face = cv2.addWeighted(sr_face, alpha=self.sr_weight, src2=swap_face, beta=1.0 - self.sr_weight, gamma=0, dtype=cv2.CV_32F)
178
+ if self.use_color_trans:
179
+ transed_face = color_transfer(self.color_trans_mode, (sr_face + 1) / 2, target_face)
180
+ result_face = (transed_face * 2) - 1
181
+ else:
182
+ result_face = sr_face
183
+ else:
184
+ if self.use_color_trans:
185
+ transed_face = color_transfer(self.color_trans_mode, (swap_face + 1) / 2, target_face)
186
+ result_face = (transed_face * 2) - 1
187
+ else:
188
+ result_face = swap_face
189
+ self.queue_list[1].put([result_face, something_in[1], something_in[2], something_in[3],
190
+ something_in[4], something_in[5]])
191
+
192
+
193
+ class HifiFaceRealTime:
194
+
195
+ def __init__(self, feature_dict_list_, frame_queue_in, frame_queue_out, gpu=True, model_name='er8_bs1', align_method='68',
196
+ use_gfpgan=True, sr_weight=1.0, use_color_trans=False, color_trans_mode='rct'):
197
+ self.opt = HifiTestOptions().parse()
198
+ if model_name != '':
199
+ self.opt.model_name = model_name
200
+ self.opt.input_size = 256
201
+ self.feature_dict_list = feature_dict_list_
202
+ self.frame_queue_in = frame_queue_in
203
+ self.frame_queue_out = frame_queue_out
204
+
205
+ self.gpu = gpu
206
+ self.align_method = align_method
207
+ self.use_gfpgan = use_gfpgan
208
+ self.sr_weight = sr_weight
209
+ self.use_color_trans = use_color_trans
210
+ self.color_trans_mode = color_trans_mode
211
+
212
+
213
+ def forward(self):
214
+ self.q0 = Queue(2)
215
+ self.q1 = Queue(2)
216
+ self.q2 = Queue(2)
217
+
218
+ self.c0 = Consumer0(self.opt, self.frame_queue_in, [self.q0], fps_counter=False, align_method=self.align_method)
219
+ self.c1 = Consumer1(self.opt, self.feature_dict_list, [self.q0, self.q1], fps_counter=False)
220
+ self.c3 = Consumer3([self.q1, self.q2], fps_counter=False,
221
+ use_gfpgan=self.use_gfpgan, sr_weight=self.sr_weight,
222
+ use_color_trans=self.use_color_trans, color_trans_mode=self.color_trans_mode)
223
+ self.c2 = Consumer2([self.q2], self.frame_queue_out, fps_counter=False)
224
+
225
+ self.c0.start()
226
+ self.c1.start()
227
+ self.c3.start()
228
+ self.c2.start()
229
+
230
+ self.c0.join()
231
+ self.c1.join()
232
+ self.c3.join()
233
+ self.c2.join()
234
+ return
LICENSE ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Silicon Intelligence COMMUNITY LICENSE AGREEMENT
2
+
3
+ “Agreement” means the terms and conditions for use, reproduction, distribution and modification of this product forth herein.
4
+
5
+ “Documentation” means the specifications, manuals and documentation by Silicon Intelligence.
6
+
7
+ “Licensee” or “you” means you, or your employer or any other person or entity (if you are entering into this Agreement on such person or entity’s behalf), of the age required under applicable laws, rules or regulations to provide legal consent and that has legal authority to bind your employer or such other person or entity if you are entering in this Agreement on their behalf.
8
+
9
+ “Silicon Intelligence Materials” means, collectively, Silicon Intelligence’s proprietary code and Documentation (and any portion thereof) made available under this Agreement.
10
+
11
+ By clicking “I Accept” below or by using or distributing any portion or element of the Silicon Intelligence Materials, you agree to be bound by this Agreement.
12
+
13
+ 1. License Rights and Redistribution.
14
+
15
+ a. Grant of Rights. You are granted a non-exclusive, worldwide, non-transferable and royalty-free limited license under ’s intellectual property or other rights owned by Silicon Intelligence embodied in the SILICON INTELLIGENCE Materials to use, reproduce, distribute, copy, create derivative works of, and make modifications to the Silicon Intelligence Materials.
16
+ b. Redistribution and Use.
17
+ i. If you distribute or make available the Silicon Intelligence Materials (or any derivative works thereof), or a product or service that uses any of them, you shall (A) provide a copy of this Agreement with any such Silicon Intelligence Materials; and (B) prominently display “Built with Silicon Intelligence” on a related website, user interface, blogpost, about page, or product documentation. If you use the Silicon Intelligence Materials to create, train, fine tune, or otherwise improve an AI model, which is distributed or made available, you shall also include “Silicon Intelligence” at the beginning of any such AI model name.
18
+ ii. If you receive Silicon Intelligence Materials, or any derivative works thereof, from a Licensee as part of an integrated end user product, then Section 2 of this Agreement will not apply to you.
19
+ iii. You must retain in all copies of the Silicon Intelligence Materials that you distribute the following attribution notice within a “Notice” text file distributed as a part of such copies: “Silicon Intelligence is licensed under the Silicon Intelligence Community License, Copyright © Silicon Intelligence Platforms, Inc. All Rights Reserved.”
20
+ iv. Your use of the Silicon Intelligence Materials must comply with applicable laws and regulations (including trade compliance laws and regulations) .
21
+
22
+ 2. Additional Commercial Terms. If, on the Silicon Intelligence duix.ai version release date, the monthly active users of the products or services made available by or for Licensee, or Licensee’s affiliates, is greater than 1 thousand monthly active users in the preceding calendar month, or your product based Silicon Intelligence material your active users greater 1 thousand, you must request a license from Silicon Intelligence, which Silicon Intelligence may grant to you in its sole discretion, and you are not authorized to exercise any of the rights under this Agreement unless or until Silicon Intelligence otherwise expressly grants you such rights.
23
+
24
+ 3. Disclaimer of Warranty. UNLESS REQUIRED BY APPLICABLE LAW, THE SILICON INTELLIGENCE MATERIALS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OF ANY KIND, AND SILICON INTELLIGENCE DISCLAIMS ALL WARRANTIES OF ANY KIND, BOTH EXPRESS AND IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING OR REDISTRIBUTING THE SILICON INTELLIGENCE MATERIALS AND ASSUME ANY RISKS ASSOCIATED WITH YOUR USE OF THE SILICON INTELLIGENCE MATERIALS AND ANY OUTPUT AND RESULTS.
25
+
26
+ 4. Limitation of Liability. IN NO EVENT WILL SILICON INTELLIGENCE OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, ARISING OUT OF THIS AGREEMENT, FOR ANY LOST PROFITS OR ANY INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL, EXEMPLARY OR PUNITIVE DAMAGES, EVEN IF SILICON INTELLIGENCE OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.
27
+
28
+ 5. Intellectual Property.
29
+ a. No trademark licenses are granted under this Agreement, and in connection with the Silicon Intelligence Materials, neither Silicon Intelligence nor Licensee may use any name or mark owned by or associated with the other or any of its affiliates, except as required for reasonable and customary use in describing and redistributing the Silicon Intelligence Materials or as set forth in this Section 5(a). Silicon Intelligence hereby grants you a license to use “Silicon Intelligence” solely as required to comply with the last sentence of Section 1.b.i. You will comply with Silicon Intelligence’s brand guidelines . All goodwill arising out of your use of the Mark will inure to the benefit of Silicon Intelligence.
30
+ b. If you institute litigation or other proceedings against Silicon Intelligenceor any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Silicon Intelligence Materials or outputs or results, or any portion of any of the foregoing, constitutes infringement of intellectual property or other rights owned or licensable by you, then any licenses granted to you under this Agreement shall terminate as of the date such litigation or claim is filed or instituted. You will indemnify and hold harmless Silicon Intelligence from and against any claim by any third party arising out of or related to your use or distribution of the Silicon Intelligence Materials.
31
+
32
+ 6. Term and Termination. The term of this Agreement will commence upon your acceptance of this Agreement or access to the Silicon Intelligence Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein. Silicon Intelligence may terminate this Agreement if you are in breach of any term or condition of this Agreement. Upon termination of this Agreement, you shall delete and cease use of the Silicon Intelligence Materials. Sections 3, 4 shall survive the termination of this Agreement.
app.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import os
4
+ import numpy as np
5
+ import numexpr as ne
6
+ from concurrent.futures import ThreadPoolExecutor
7
+
8
+ from face_feature.hifi_image_api import HifiImage
9
+ from HifiFaceAPI_parallel_trt_roi_realtime_sr_api import HifiFaceRealTime
10
+ from face_lib.face_swap import HifiFace
11
+ from face_restore.gfpgan_onnx_api import GFPGAN
12
+ from face_restore.xseg_onnx_api import XSEG
13
+ from face_detect.face_align_68 import face_alignment_landmark
14
+ from face_detect.face_detect import FaceDetect
15
+ from options.hifi_test_options import HifiTestOptions
16
+ from color_transfer import color_transfer
17
+
18
+ opt = HifiTestOptions().parse()
19
+ processor = None
20
+
21
+ def initialize_processor():
22
+ global processor
23
+ if processor is None:
24
+ processor = FaceSwapProcessor(crop_size=opt.input_size)
25
+
26
+ class FaceSwapProcessor:
27
+ def __init__(self, crop_size=256):
28
+ self.hi = HifiImage(crop_size=crop_size)
29
+ self.xseg = XSEG(model_type='xseg_0611', provider='gpu')
30
+ self.hf = HifiFace(model_name='er8_bs1', provider='gpu')
31
+ self.scrfd_detector = FaceDetect(mode='scrfd_500m', tracking_thres=0.15)
32
+ self.face_alignment = face_alignment_landmark(lm_type=68)
33
+ self.gfp = GFPGAN(model_type='GFPGANv1.4', provider='gpu')
34
+ self.crop_size = crop_size
35
+
36
+ def reverse2wholeimage_hifi_trt_roi(self, swaped_img, mat_rev, img_mask, frame, roi_img, roi_box):
37
+ target_image = cv2.warpAffine(swaped_img, mat_rev, roi_img.shape[:2][::-1], borderMode=cv2.BORDER_REPLICATE)[
38
+ ...,
39
+ ::-1]
40
+ local_dict = {
41
+ 'img_mask': img_mask,
42
+ 'target_image': target_image,
43
+ 'roi_img': roi_img,
44
+ }
45
+ img = ne.evaluate('img_mask * (target_image * 255)+(1 - img_mask) * roi_img', local_dict=local_dict,
46
+ global_dict=None)
47
+ img = img.astype(np.uint8)
48
+ frame[roi_box[1]:roi_box[3], roi_box[0]:roi_box[2]] = img
49
+ return frame
50
+
51
+ def process_frame(self, frame, image_latent, use_gfpgan, sr_weight, use_color_trans, color_trans_mode):
52
+ _, bboxes, kpss = self.scrfd_detector.get_bboxes(frame, max_num=0)
53
+ rois, faces, Ms, masks = self.face_alignment.forward(
54
+ frame, bboxes, kpss, limit=5, min_face_size=30,
55
+ crop_size=(self.crop_size, self.crop_size), apply_roi=True
56
+ )
57
+
58
+ if len(faces) == 0:
59
+ return frame
60
+ elif len(faces) == 1:
61
+ face = np.array(faces[0])
62
+ mat = Ms[0]
63
+ roi_box = rois[0]
64
+ else:
65
+ max_index = np.argmax([roi[2] * roi[3] for roi in rois]) # Get the largest face
66
+ face = np.array(faces[max_index])
67
+ mat = Ms[max_index]
68
+ roi_box = rois[max_index]
69
+
70
+ roi_img = frame[roi_box[1]:roi_box[3], roi_box[0]:roi_box[2]]
71
+ face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
72
+
73
+ mask_out, swap_face_out = self.hf.forward(face, image_latent[0].reshape(1, -1))
74
+ mask_out = self.xseg.forward(swap_face_out)[None, None]
75
+
76
+ mask = cv2.warpAffine(mask_out[0][0].astype(np.float32), mat, roi_img.shape[:2][::-1])
77
+ mask[mask > 0.2] = 1
78
+ mask = mask[:, :, np.newaxis].astype(np.uint8)
79
+ swap_face = swap_face_out[0].transpose((1, 2, 0)).astype(np.float32)
80
+ target_face = (face.copy() / 255).astype(np.float32)
81
+
82
+ if use_gfpgan:
83
+ sr_face = self.gfp.forward(swap_face)
84
+ if sr_weight != 1.0:
85
+ sr_face = cv2.addWeighted(sr_face, sr_weight, swap_face, 1.0 - sr_weight, 0)
86
+ if use_color_trans:
87
+ transed_face = color_transfer(color_trans_mode, (sr_face + 1) / 2, target_face)
88
+ swap_face = (transed_face * 2) - 1
89
+ else:
90
+ swap_face = sr_face
91
+ elif use_color_trans:
92
+ transed_face = color_transfer(color_trans_mode, (swap_face + 1) / 2, target_face)
93
+ swap_face = (transed_face * 2) - 1
94
+
95
+ swap_face = ((swap_face + 1) / 2)
96
+
97
+ frame_out = self.reverse2wholeimage_hifi_trt_roi(
98
+ swap_face, mat, mask,
99
+ frame, roi_img, roi_box
100
+ )
101
+
102
+ return frame_out
103
+
104
+ def process_image_video(image, video_path, use_gfpgan, sr_weight, use_color_trans, color_trans_mode):
105
+ global processor
106
+ initialize_processor()
107
+
108
+ src_latent, _ = processor.hi.get_face_feature(image)
109
+ image_latent = [src_latent]
110
+
111
+ video = cv2.VideoCapture(video_path)
112
+ video_fps = video.get(cv2.CAP_PROP_FPS)
113
+ video_size = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)),
114
+ int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)))
115
+ output_dir = 'data/output/'
116
+ if not os.path.exists(output_dir):
117
+ os.mkdir(output_dir)
118
+ swap_video_path = output_dir + 'temp.mp4'
119
+ videoWriter = cv2.VideoWriter(swap_video_path, cv2.VideoWriter_fourcc(*'mp4v'), video_fps, video_size)
120
+
121
+ with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
122
+ futures = []
123
+ while True:
124
+ ret, frame = video.read()
125
+ if not ret:
126
+ break
127
+ future = executor.submit(processor.process_frame, frame, image_latent, use_gfpgan, sr_weight,
128
+ use_color_trans, color_trans_mode)
129
+ futures.append(future)
130
+
131
+ for future in futures:
132
+ processed_frame = future.result()
133
+ if processed_frame is not None:
134
+ videoWriter.write(processed_frame)
135
+
136
+ video.release()
137
+ videoWriter.release()
138
+
139
+ add_audio_to_video(video_path, swap_video_path)
140
+
141
+ return swap_video_path
142
+
143
+
144
+ def add_audio_to_video(original_video_path, swapped_video_path):
145
+ audio_file_path = original_video_path.split('.')[0] + '.wav'
146
+ if not os.path.exists(audio_file_path):
147
+ os.system(f'ffmpeg -y -hide_banner -loglevel error -i "{original_video_path}" -f wav -vn "{audio_file_path}"')
148
+
149
+ temp_output_path = swapped_video_path.replace('.mp4', '_with_audio.mp4')
150
+ os.system(
151
+ f'ffmpeg -y -hide_banner -loglevel error -i "{swapped_video_path}" -i "{audio_file_path}" -c:v copy -c:a aac "{temp_output_path}"')
152
+
153
+ os.remove(swapped_video_path)
154
+ os.rename(temp_output_path, swapped_video_path)
155
+
156
+
157
+ # Gradio interface setup
158
+ iface = gr.Interface(
159
+ fn=process_image_video,
160
+ inputs=[
161
+ gr.Image(type="pil", label="Source Image"),
162
+ gr.Video(label="Input Video"),
163
+ gr.Checkbox(label="Use GFPGAN [Super-Resolution]"),
164
+ gr.Slider(minimum=0.1, maximum=1.0, step=0.1, label="SR Weight [only support GFPGAN enabled]", value=1.0),
165
+ gr.Checkbox(label="Use Color Transfer"),
166
+ gr.Dropdown(choices=["rct", "lct", "mkl", "idt", "sot"],
167
+ label="Color Transfer Mode [only support Color-Transfer enabled]", value="rct")
168
+ ],
169
+ outputs=gr.Video(label="Output Video"),
170
+ title="Video Generation",
171
+ description="Upload an image and a video, and the system will generate a new video based on the input."
172
+ )
173
+
174
+ if __name__ == "__main__":
175
+ iface.launch()
assets/cam_demo1.gif ADDED

Git LFS Details

  • SHA256: 239cad1722caf9fc14c89627d80e38e549bc940046615c7ba6f58da78ac11f5d
  • Pointer size: 132 Bytes
  • Size of remote file: 1.34 MB
assets/cam_demo2.gif ADDED

Git LFS Details

  • SHA256: bc03b463e4693951750560db273e5565edbf60b4008b6a80e30daac0e2b65aac
  • Pointer size: 132 Bytes
  • Size of remote file: 1.31 MB
assets/demo10.gif ADDED

Git LFS Details

  • SHA256: 2cf6141d4199093824e9786e4c29544f49df24b79cf63dd7535fe40662c376a7
  • Pointer size: 132 Bytes
  • Size of remote file: 5.66 MB
assets/demo20.gif ADDED

Git LFS Details

  • SHA256: 6df9f0d67063073f9874c68a6462f5c060ef91b0822a86660bd32120e63004ff
  • Pointer size: 132 Bytes
  • Size of remote file: 2.7 MB
color_transfer.py ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numexpr as ne
3
+ import numpy as np
4
+ import scipy as sp
5
+ from numpy import linalg as npla
6
+
7
+
8
+ def color_transfer_sot(src,trg, steps=10, batch_size=5, reg_sigmaXY=16.0, reg_sigmaV=5.0):
9
+ """
10
+ Color Transform via Sliced Optimal Transfer
11
+ ported by @iperov from https://github.com/dcoeurjo/OTColorTransfer
12
+
13
+ src - any float range any channel image
14
+ dst - any float range any channel image, same shape as src
15
+ steps - number of solver steps
16
+ batch_size - solver batch size
17
+ reg_sigmaXY - apply regularization and sigmaXY of filter, otherwise set to 0.0
18
+ reg_sigmaV - sigmaV of filter
19
+
20
+ return value - clip it manually
21
+ """
22
+ if not np.issubdtype(src.dtype, np.floating):
23
+ raise ValueError("src value must be float")
24
+ if not np.issubdtype(trg.dtype, np.floating):
25
+ raise ValueError("trg value must be float")
26
+
27
+ if len(src.shape) != 3:
28
+ raise ValueError("src shape must have rank 3 (h,w,c)")
29
+
30
+ if src.shape != trg.shape:
31
+ raise ValueError("src and trg shapes must be equal")
32
+
33
+ src_dtype = src.dtype
34
+ h,w,c = src.shape
35
+ new_src = src.copy()
36
+
37
+ advect = np.empty ( (h*w,c), dtype=src_dtype )
38
+ for step in range (steps):
39
+ advect.fill(0)
40
+ for batch in range (batch_size):
41
+ dir = np.random.normal(size=c).astype(src_dtype)
42
+ dir /= npla.norm(dir)
43
+
44
+ projsource = np.sum( new_src*dir, axis=-1).reshape ((h*w))
45
+ projtarget = np.sum( trg*dir, axis=-1).reshape ((h*w))
46
+
47
+ idSource = np.argsort (projsource)
48
+ idTarget = np.argsort (projtarget)
49
+
50
+ a = projtarget[idTarget]-projsource[idSource]
51
+ for i_c in range(c):
52
+ advect[idSource,i_c] += a * dir[i_c]
53
+ new_src += advect.reshape( (h,w,c) ) / batch_size
54
+
55
+ if reg_sigmaXY != 0.0:
56
+ src_diff = new_src-src
57
+ src_diff_filt = cv2.bilateralFilter (src_diff, 0, reg_sigmaV, reg_sigmaXY )
58
+ if len(src_diff_filt.shape) == 2:
59
+ src_diff_filt = src_diff_filt[...,None]
60
+ new_src = src + src_diff_filt
61
+ return new_src
62
+
63
+ def color_transfer_mkl(x0, x1):
64
+ eps = np.finfo(float).eps
65
+
66
+ h,w,c = x0.shape
67
+ h1,w1,c1 = x1.shape
68
+
69
+ x0 = x0.reshape ( (h*w,c) )
70
+ x1 = x1.reshape ( (h1*w1,c1) )
71
+
72
+ a = np.cov(x0.T)
73
+ b = np.cov(x1.T)
74
+
75
+ Da2, Ua = np.linalg.eig(a)
76
+ Da = np.diag(np.sqrt(Da2.clip(eps, None)))
77
+
78
+ C = np.dot(np.dot(np.dot(np.dot(Da, Ua.T), b), Ua), Da)
79
+
80
+ Dc2, Uc = np.linalg.eig(C)
81
+ Dc = np.diag(np.sqrt(Dc2.clip(eps, None)))
82
+
83
+ Da_inv = np.diag(1./(np.diag(Da)))
84
+
85
+ t = np.dot(np.dot(np.dot(np.dot(np.dot(np.dot(Ua, Da_inv), Uc), Dc), Uc.T), Da_inv), Ua.T)
86
+
87
+ mx0 = np.mean(x0, axis=0)
88
+ mx1 = np.mean(x1, axis=0)
89
+
90
+ result = np.dot(x0-mx0, t) + mx1
91
+ return np.clip ( result.reshape ( (h,w,c) ).astype(x0.dtype), 0, 1)
92
+
93
+ def color_transfer_idt(i0, i1, bins=256, n_rot=20):
94
+ import scipy.stats
95
+
96
+ relaxation = 1 / n_rot
97
+ h,w,c = i0.shape
98
+ h1,w1,c1 = i1.shape
99
+
100
+ i0 = i0.reshape ( (h*w,c) )
101
+ i1 = i1.reshape ( (h1*w1,c1) )
102
+
103
+ n_dims = c
104
+
105
+ d0 = i0.T
106
+ d1 = i1.T
107
+
108
+ for i in range(n_rot):
109
+
110
+ r = sp.stats.special_ortho_group.rvs(n_dims).astype(np.float32)
111
+
112
+ d0r = np.dot(r, d0)
113
+ d1r = np.dot(r, d1)
114
+ d_r = np.empty_like(d0)
115
+
116
+ for j in range(n_dims):
117
+
118
+ lo = min(d0r[j].min(), d1r[j].min())
119
+ hi = max(d0r[j].max(), d1r[j].max())
120
+
121
+ p0r, edges = np.histogram(d0r[j], bins=bins, range=[lo, hi])
122
+ p1r, _ = np.histogram(d1r[j], bins=bins, range=[lo, hi])
123
+
124
+ cp0r = p0r.cumsum().astype(np.float32)
125
+ cp0r /= cp0r[-1]
126
+
127
+ cp1r = p1r.cumsum().astype(np.float32)
128
+ cp1r /= cp1r[-1]
129
+
130
+ f = np.interp(cp0r, cp1r, edges[1:])
131
+
132
+ d_r[j] = np.interp(d0r[j], edges[1:], f, left=0, right=bins)
133
+
134
+ d0 = relaxation * np.linalg.solve(r, (d_r - d0r)) + d0
135
+
136
+ return np.clip ( d0.T.reshape ( (h,w,c) ).astype(i0.dtype) , 0, 1)
137
+
138
+ def reinhard_color_transfer(target : np.ndarray, source : np.ndarray, target_mask : np.ndarray = None, source_mask : np.ndarray = None, mask_cutoff=0.5) -> np.ndarray:
139
+ """
140
+ Transfer color using rct method.
141
+
142
+ target np.ndarray H W 3C (BGR) np.float32
143
+ source np.ndarray H W 3C (BGR) np.float32
144
+
145
+ target_mask(None) np.ndarray H W 1C np.float32
146
+ source_mask(None) np.ndarray H W 1C np.float32
147
+
148
+ mask_cutoff(0.5) float
149
+
150
+ masks are used to limit the space where color statistics will be computed to adjust the target
151
+
152
+ reference: Color Transfer between Images https://www.cs.tau.ac.il/~turkel/imagepapers/ColorTransfer.pdf
153
+ """
154
+ source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB)
155
+ target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB)
156
+
157
+ source_input = source
158
+ if source_mask is not None:
159
+ source_input = source_input.copy()
160
+ source_input[source_mask[...,0] < mask_cutoff] = [0,0,0]
161
+
162
+ target_input = target
163
+ if target_mask is not None:
164
+ target_input = target_input.copy()
165
+ target_input[target_mask[...,0] < mask_cutoff] = [0,0,0]
166
+
167
+ target_l_mean, target_l_std, target_a_mean, target_a_std, target_b_mean, target_b_std, \
168
+ = target_input[...,0].mean(), target_input[...,0].std(), target_input[...,1].mean(), target_input[...,1].std(), target_input[...,2].mean(), target_input[...,2].std()
169
+
170
+ source_l_mean, source_l_std, source_a_mean, source_a_std, source_b_mean, source_b_std, \
171
+ = source_input[...,0].mean(), source_input[...,0].std(), source_input[...,1].mean(), source_input[...,1].std(), source_input[...,2].mean(), source_input[...,2].std()
172
+
173
+ # not as in the paper: scale by the standard deviations using reciprocal of paper proposed factor
174
+ target_l = target[...,0]
175
+ target_l = ne.evaluate('(target_l - target_l_mean) * source_l_std / target_l_std + source_l_mean')
176
+
177
+ target_a = target[...,1]
178
+ target_a = ne.evaluate('(target_a - target_a_mean) * source_a_std / target_a_std + source_a_mean')
179
+
180
+ target_b = target[...,2]
181
+ target_b = ne.evaluate('(target_b - target_b_mean) * source_b_std / target_b_std + source_b_mean')
182
+
183
+ np.clip(target_l, 0, 100, out=target_l)
184
+ np.clip(target_a, -127, 127, out=target_a)
185
+ np.clip(target_b, -127, 127, out=target_b)
186
+
187
+ return cv2.cvtColor(np.stack([target_l,target_a,target_b], -1), cv2.COLOR_LAB2BGR)
188
+
189
+
190
+ def linear_color_transfer(target_img, source_img, mode='pca', eps=1e-5):
191
+ '''
192
+ Matches the colour distribution of the target image to that of the source image
193
+ using a linear transform.
194
+ Images are expected to be of form (w,h,c) and float in [0,1].
195
+ Modes are chol, pca or sym for different choices of basis.
196
+ '''
197
+ mu_t = target_img.mean(0).mean(0)
198
+ t = target_img - mu_t
199
+ t = t.transpose(2,0,1).reshape( t.shape[-1],-1)
200
+ t = t.reshape( t.shape[-1],-1)
201
+ Ct = t.dot(t.T) / t.shape[1] + eps * np.eye(t.shape[0])
202
+ mu_s = source_img.mean(0).mean(0)
203
+ s = source_img - mu_s
204
+ s = s.transpose(2,0,1).reshape( s.shape[-1],-1)
205
+ Cs = s.dot(s.T) / s.shape[1] + eps * np.eye(s.shape[0])
206
+ if mode == 'chol':
207
+ chol_t = np.linalg.cholesky(Ct)
208
+ chol_s = np.linalg.cholesky(Cs)
209
+ ts = chol_s.dot(np.linalg.inv(chol_t)).dot(t)
210
+ if mode == 'pca':
211
+ eva_t, eve_t = np.linalg.eigh(Ct)
212
+ Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T)
213
+ eva_s, eve_s = np.linalg.eigh(Cs)
214
+ Qs = eve_s.dot(np.sqrt(np.diag(eva_s))).dot(eve_s.T)
215
+ ts = Qs.dot(np.linalg.inv(Qt)).dot(t)
216
+ if mode == 'sym':
217
+ eva_t, eve_t = np.linalg.eigh(Ct)
218
+ Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T)
219
+ Qt_Cs_Qt = Qt.dot(Cs).dot(Qt)
220
+ eva_QtCsQt, eve_QtCsQt = np.linalg.eigh(Qt_Cs_Qt)
221
+ QtCsQt = eve_QtCsQt.dot(np.sqrt(np.diag(eva_QtCsQt))).dot(eve_QtCsQt.T)
222
+ ts = np.linalg.inv(Qt).dot(QtCsQt).dot(np.linalg.inv(Qt)).dot(t)
223
+ matched_img = ts.reshape(*target_img.transpose(2,0,1).shape).transpose(1,2,0)
224
+ matched_img += mu_s
225
+ matched_img[matched_img>1] = 1
226
+ matched_img[matched_img<0] = 0
227
+ return np.clip(matched_img.astype(source_img.dtype), 0, 1)
228
+
229
+ def lab_image_stats(image):
230
+ # compute the mean and standard deviation of each channel
231
+ (l, a, b) = cv2.split(image)
232
+ (lMean, lStd) = (l.mean(), l.std())
233
+ (aMean, aStd) = (a.mean(), a.std())
234
+ (bMean, bStd) = (b.mean(), b.std())
235
+
236
+ # return the color statistics
237
+ return (lMean, lStd, aMean, aStd, bMean, bStd)
238
+
239
+ def _scale_array(arr, clip=True):
240
+ if clip:
241
+ return np.clip(arr, 0, 255)
242
+
243
+ mn = arr.min()
244
+ mx = arr.max()
245
+ scale_range = (max([mn, 0]), min([mx, 255]))
246
+
247
+ if mn < scale_range[0] or mx > scale_range[1]:
248
+ return (scale_range[1] - scale_range[0]) * (arr - mn) / (mx - mn) + scale_range[0]
249
+
250
+ return arr
251
+
252
+ def channel_hist_match(source, template, hist_match_threshold=255, mask=None):
253
+ # Code borrowed from:
254
+ # https://stackoverflow.com/questions/32655686/histogram-matching-of-two-images-in-python-2-x
255
+ masked_source = source
256
+ masked_template = template
257
+
258
+ if mask is not None:
259
+ masked_source = source * mask
260
+ masked_template = template * mask
261
+
262
+ oldshape = source.shape
263
+ source = source.ravel()
264
+ template = template.ravel()
265
+ masked_source = masked_source.ravel()
266
+ masked_template = masked_template.ravel()
267
+ s_values, bin_idx, s_counts = np.unique(source, return_inverse=True,
268
+ return_counts=True)
269
+ t_values, t_counts = np.unique(template, return_counts=True)
270
+
271
+ s_quantiles = np.cumsum(s_counts).astype(np.float64)
272
+ s_quantiles = hist_match_threshold * s_quantiles / s_quantiles[-1]
273
+ t_quantiles = np.cumsum(t_counts).astype(np.float64)
274
+ t_quantiles = 255 * t_quantiles / t_quantiles[-1]
275
+ interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)
276
+
277
+ return interp_t_values[bin_idx].reshape(oldshape)
278
+
279
+ def color_hist_match(src_im, tar_im, hist_match_threshold=255):
280
+ h,w,c = src_im.shape
281
+ matched_R = channel_hist_match(src_im[:,:,0], tar_im[:,:,0], hist_match_threshold, None)
282
+ matched_G = channel_hist_match(src_im[:,:,1], tar_im[:,:,1], hist_match_threshold, None)
283
+ matched_B = channel_hist_match(src_im[:,:,2], tar_im[:,:,2], hist_match_threshold, None)
284
+
285
+ to_stack = (matched_R, matched_G, matched_B)
286
+ for i in range(3, c):
287
+ to_stack += ( src_im[:,:,i],)
288
+
289
+
290
+ matched = np.stack(to_stack, axis=-1).astype(src_im.dtype)
291
+ return matched
292
+
293
+ def color_transfer_mix(img_src,img_trg):
294
+ img_src = np.clip(img_src*255.0, 0, 255).astype(np.uint8)
295
+ img_trg = np.clip(img_trg*255.0, 0, 255).astype(np.uint8)
296
+
297
+ img_src_lab = cv2.cvtColor(img_src, cv2.COLOR_BGR2LAB)
298
+ img_trg_lab = cv2.cvtColor(img_trg, cv2.COLOR_BGR2LAB)
299
+
300
+ rct_light = np.clip ( linear_color_transfer(img_src_lab[...,0:1].astype(np.float32)/255.0,
301
+ img_trg_lab[...,0:1].astype(np.float32)/255.0 )[...,0]*255.0,
302
+ 0, 255).astype(np.uint8)
303
+
304
+ img_src_lab[...,0] = (np.ones_like (rct_light)*100).astype(np.uint8)
305
+ img_src_lab = cv2.cvtColor(img_src_lab, cv2.COLOR_LAB2BGR)
306
+
307
+ img_trg_lab[...,0] = (np.ones_like (rct_light)*100).astype(np.uint8)
308
+ img_trg_lab = cv2.cvtColor(img_trg_lab, cv2.COLOR_LAB2BGR)
309
+
310
+ img_rct = color_transfer_sot( img_src_lab.astype(np.float32), img_trg_lab.astype(np.float32) )
311
+ img_rct = np.clip(img_rct, 0, 255).astype(np.uint8)
312
+
313
+ img_rct = cv2.cvtColor(img_rct, cv2.COLOR_BGR2LAB)
314
+ img_rct[...,0] = rct_light
315
+ img_rct = cv2.cvtColor(img_rct, cv2.COLOR_LAB2BGR)
316
+
317
+
318
+ return (img_rct / 255.0).astype(np.float32)
319
+
320
+ def color_transfer(ct_mode, img_src, img_trg):
321
+ """
322
+ color transfer for [0,1] float32 inputs
323
+ """
324
+ if ct_mode == 'lct':
325
+ out = linear_color_transfer(img_src, img_trg)
326
+ elif ct_mode == 'rct':
327
+ out = reinhard_color_transfer(img_src, img_trg)
328
+ elif ct_mode == 'mkl':
329
+ out = color_transfer_mkl(img_src, img_trg)
330
+ elif ct_mode == 'idt':
331
+ out = color_transfer_idt(img_src, img_trg)
332
+ elif ct_mode == 'sot':
333
+ out = color_transfer_sot(img_src, img_trg)
334
+ out = np.clip( out, 0.0, 1.0)
335
+ else:
336
+ raise ValueError(f"unknown ct_mode {ct_mode}")
337
+ return out
data/image_feature_dict.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:32c5871c89e526e5c088cbee5db03b87135c27be4d985cfcd78c8ce02a4af482
3
+ size 3975088
data/source/demo.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:754d104f06af6d80da356c8311bbcabf1f3fd467cebd98a8b658d9e94f7507b8
3
+ size 2402911
data/source/elon-musk1.jpg ADDED
face_detect/FaceType.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import IntEnum
2
+
3
+ class FaceType(IntEnum):
4
+ #enumerating in order "next contains prev"
5
+ HALF = 0
6
+ MID_FULL = 1
7
+ FULL = 2
8
+ FULL_NO_ALIGN = 3
9
+ WHOLE_FACE = 4
10
+ HEAD = 10
11
+ HEAD_NO_ALIGN = 20
12
+
13
+ MARK_ONLY = 100, #no align at all, just embedded faceinfo
14
+
15
+ @staticmethod
16
+ def fromString (s):
17
+ r = from_string_dict.get (s.lower())
18
+ if r is None:
19
+ raise Exception ('FaceType.fromString value error')
20
+ return r
21
+
22
+ @staticmethod
23
+ def toString (face_type):
24
+ return to_string_dict[face_type]
25
+
26
+ to_string_dict = { FaceType.HALF : 'half_face',
27
+ FaceType.MID_FULL : 'midfull_face',
28
+ FaceType.FULL : 'full_face',
29
+ FaceType.FULL_NO_ALIGN : 'full_face_no_align',
30
+ FaceType.WHOLE_FACE : 'whole_face',
31
+ FaceType.HEAD : 'head',
32
+ FaceType.HEAD_NO_ALIGN : 'head_no_align',
33
+
34
+ FaceType.MARK_ONLY :'mark_only',
35
+ }
36
+
37
+ from_string_dict = { to_string_dict[x] : x for x in to_string_dict.keys() }
face_detect/LandmarksProcessor.py ADDED
@@ -0,0 +1,1482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 face_detect.core import imagelib
10
+ from face_detect.core import mathlib
11
+ from face_detect.core.mathlib.umeyama import umeyama
12
+ from face_detect.FaceType import FaceType
13
+
14
+ mesh_33=[70,63,105,66,107,336,296,334,293,300,168,197,5,4,240,99,2,328,460,33,160,158,133,153,144,362,385,387,263,373,380,57,287]
15
+ landmarks_2D_4=np.array([
16
+ [0.224152 , 0.2119465], #left iris mean 37 38 40 41
17
+ [0.75610125, 0.2119465],#right iris mean 43 44 46 47
18
+ [0.490127, 0.515625], # nose 30
19
+ [0.4901265, 0.780233] #mouth mean 48 54
20
+ ])
21
+ landmarks_2D_4_bottom=np.array([
22
+ [0.2218305, 0.244588 ], #left iris mean 40 41
23
+ [0.7584225, 0.244588],#right iris mean 46 47
24
+ [0.490127, 0.515625], # nose 30
25
+ [0.4901265, 0.780233] #mouth mean 48 54
26
+ ])
27
+ landmarks_2D = np.array([
28
+ [0.000213256, 0.106454], # 17
29
+ [0.0752622, 0.038915], # 18
30
+ [0.18113, 0.0187482], # 19
31
+ [0.29077, 0.0344891], # 20
32
+ [0.393397, 0.0773906], # 21
33
+ [0.586856, 0.0773906], # 22
34
+ [0.689483, 0.0344891], # 23
35
+ [0.799124, 0.0187482], # 24
36
+ [0.904991, 0.038915], # 25
37
+ [0.98004, 0.106454], # 26
38
+ [0.490127, 0.203352], # 27
39
+ [0.490127, 0.307009], # 28
40
+ [0.490127, 0.409805], # 29
41
+ [0.490127, 0.515625], # 30
42
+ [0.36688, 0.587326], # 31
43
+ [0.426036, 0.609345], # 32
44
+ [0.490127, 0.628106], # 33
45
+ [0.554217, 0.609345], # 34
46
+ [0.613373, 0.587326], # 35
47
+ [0.121737, 0.216423], # 36
48
+ [0.187122, 0.178758], # 37
49
+ [0.265825, 0.179852], # 38
50
+ [0.334606, 0.231733], # 39
51
+ [0.260918, 0.245099], # 40
52
+ [0.182743, 0.244077], # 41
53
+ [0.645647, 0.231733], # 42
54
+ [0.714428, 0.179852], # 43
55
+ [0.793132, 0.178758], # 44
56
+ [0.858516, 0.216423], # 45
57
+ [0.79751, 0.244077], # 46
58
+ [0.719335, 0.245099], # 47
59
+ [0.254149, 0.780233], # 48
60
+ [0.340985, 0.745405], # 49
61
+ [0.428858, 0.727388], # 50
62
+ [0.490127, 0.742578], # 51
63
+ [0.551395, 0.727388], # 52
64
+ [0.639268, 0.745405], # 53
65
+ [0.726104, 0.780233], # 54
66
+ [0.642159, 0.864805], # 55
67
+ [0.556721, 0.902192], # 56
68
+ [0.490127, 0.909281], # 57
69
+ [0.423532, 0.902192], # 58
70
+ [0.338094, 0.864805], # 59
71
+ [0.290379, 0.784792], # 60
72
+ [0.428096, 0.778746], # 61
73
+ [0.490127, 0.785343], # 62
74
+ [0.552157, 0.778746], # 63
75
+ [0.689874, 0.784792], # 64
76
+ [0.553364, 0.824182], # 65
77
+ [0.490127, 0.831803], # 66
78
+ [0.42689, 0.824182] # 67
79
+ ], dtype=np.float32)
80
+
81
+ landmarks_2D_new = np.array([
82
+ [0.000213256, 0.106454], # 17
83
+ [0.0752622, 0.038915], # 18
84
+ [0.18113, 0.0187482], # 19
85
+ [0.29077, 0.0344891], # 20
86
+ [0.393397, 0.0773906], # 21
87
+ [0.586856, 0.0773906], # 22
88
+ [0.689483, 0.0344891], # 23
89
+ [0.799124, 0.0187482], # 24
90
+ [0.904991, 0.038915], # 25
91
+ [0.98004, 0.106454], # 26
92
+ [0.490127, 0.203352], # 27
93
+ [0.490127, 0.307009], # 28
94
+ [0.490127, 0.409805], # 29
95
+ [0.490127, 0.515625], # 30
96
+ [0.36688, 0.587326], # 31
97
+ [0.426036, 0.609345], # 32
98
+ [0.490127, 0.628106], # 33
99
+ [0.554217, 0.609345], # 34
100
+ [0.613373, 0.587326], # 35
101
+ [0.121737, 0.216423], # 36
102
+ [0.187122, 0.178758], # 37
103
+ [0.265825, 0.179852], # 38
104
+ [0.334606, 0.231733], # 39
105
+ [0.260918, 0.245099], # 40
106
+ [0.182743, 0.244077], # 41
107
+ [0.645647, 0.231733], # 42
108
+ [0.714428, 0.179852], # 43
109
+ [0.793132, 0.178758], # 44
110
+ [0.858516, 0.216423], # 45
111
+ [0.79751, 0.244077], # 46
112
+ [0.719335, 0.245099], # 47
113
+ [0.254149, 0.780233], # 48
114
+ [0.726104, 0.780233], # 54
115
+ ], dtype=np.float32)
116
+ landmarks_2D_new_mesh = np.array([
117
+ [ 0.000213256, 0.106454 ], #17
118
+ [ 0.0752622, 0.038915 ], #18
119
+ [0.1281961, 0.0288316], #19[ 0.18113, 0.0187482 ]
120
+ [ 0.29077, 0.0144891 ], #20
121
+ [ 0.393397, 0.0773906 ], #21
122
+ [ 0.586856, 0.0773906 ], #22
123
+ [ 0.689483, 0.0144891 ], #23
124
+ [0.8520575, 0.0288316], #24[ 0.799124, 0.0187482 ]
125
+ [ 0.904991, 0.038915 ], #25
126
+ [ 0.98004, 0.106454 ], #26
127
+ [ 0.490127, 0.203352 ], #27
128
+ [ 0.490127, 0.307009 ], #28
129
+ [ 0.490127, 0.409805 ], #29
130
+ [ 0.490127, 0.515625 ], #30
131
+ [0.396458 , 0.5983355], #31 [ 0.36688, 0.587326 ]
132
+ [ 0.426036, 0.609345 ], #32
133
+ [ 0.490127, 0.628106 ], #33
134
+ [ 0.554217, 0.609345 ], #34
135
+ [ 0.613373, 0.587326 ], #35
136
+ [ 0.071737, 0.136423 ], #36
137
+ [ 0.137122, 0.118758 ], #37
138
+ [ 0.215825, 0.119852 ], #38
139
+ [ 0.334606, 0.151733 ], #39
140
+ [ 0.210918, 0.165099 ], #40
141
+ [ 0.132743, 0.164077 ], #41
142
+ [ 0.645647, 0.151733 ], #42
143
+ [ 0.764428, 0.119852 ], #43
144
+ [ 0.743132, 0.118758 ], #44
145
+ [ 0.908516, 0.136423 ], #45
146
+ [ 0.84751, 0.164077 ], #46
147
+ [ 0.769335, 0.165099 ], #47
148
+ [ 0.254149, 0.780233 ], #48
149
+ [ 0.726104, 0.780233 ], #54
150
+ ], dtype=np.float32)
151
+
152
+ # landmarks_468_moving_parts_indexes = [0, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 43, 46, 52, 53, 54, 55, 56, 57, 58, 61, 62, 63, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 95, 96, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 117, 118, 124, 130, 132, 133, 135, 136, 138, 139, 140, 143, 144, 145, 146, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 168, 169, 170, 171, 172, 173, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 189, 190, 191, 192, 193, 194, 199, 200, 201, 202, 204, 208, 210, 211, 212, 213, 214, 215, 221, 222, 223, 224, 225, 226, 228, 229, 230, 231, 232, 233, 243, 244, 245, 246, 247, 249, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 267, 268, 269, 270, 271, 272, 273, 276, 282, 283, 284, 285, 286, 287, 288, 291, 292, 293, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 306, 307, 308, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 324, 325, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 346, 347, 353, 359, 361, 362, 364, 365, 367, 368, 369, 372, 373, 374, 375, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 394, 395, 396, 397, 398, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 413, 414, 415, 416, 417, 418, 421, 422, 424, 428, 430, 431, 432, 433, 434, 435, 441, 442, 443, 444, 445, 446, 448, 449, 450, 451, 452, 453, 463, 464, 465, 466, 467]
153
+ # uni_landmarks_468 = np.array(
154
+ # [[ 0.49066195, 0.7133885 ],
155
+ # [ 0.49042386, 0.52723485],
156
+ # [ 0.49050152, 0.6244965 ],
157
+ # [ 0.45844677, 0.39348277],
158
+ # [ 0.4905825 , 0.49120593],
159
+ # [ 0.49006602, 0.43998772],
160
+ # [ 0.48907965, 0.26775706],
161
+ # [ 0.11721139, 0.23243594],
162
+ # [ 0.48957095, 0.11063451],
163
+ # [ 0.48949632, 0.03535742],
164
+ # [ 0.48905632, -0.25326234],
165
+ # [ 0.4907858 , 0.73766613],
166
+ # [ 0.49081355, 0.7606857 ],
167
+ # [ 0.4908666 , 0.7839426 ],
168
+ # [ 0.49079415, 0.78913504],
169
+ # [ 0.4908271 , 0.80801845],
170
+ # [ 0.49086872, 0.831855 ],
171
+ # [ 0.49092326, 0.8631041 ],
172
+ # [ 0.49104446, 0.94170016],
173
+ # [ 0.49009967, 0.5546924 ],
174
+ # [ 0.44398275, 0.5741402 ],
175
+ # [-0.2106727 , 0.00861922],
176
+ # [ 0.2523662 , 0.2832579 ],
177
+ # [ 0.2042254 , 0.28945392],
178
+ # [ 0.1552372 , 0.28322184],
179
+ # [ 0.09056008, 0.24730967],
180
+ # [ 0.30096018, 0.27277085],
181
+ # [ 0.21548809, 0.16713436],
182
+ # [ 0.2595488 , 0.17071684],
183
+ # [ 0.16957955, 0.17298089],
184
+ # [ 0.13164258, 0.18425746],
185
+ # [ 0.043018 , 0.28581 ],
186
+ # [ 0.30856833, 1.0507976 ],
187
+ # [ 0.10015843, 0.22331452],
188
+ # [-0.20773543, 0.26701325],
189
+ # [-0.02414621, 0.25144747],
190
+ # [ 0.23481508, 0.5045001 ],
191
+ # [ 0.44063616, 0.7097012 ],
192
+ # [ 0.4449884 , 0.762481 ],
193
+ # [ 0.3840104 , 0.7218947 ],
194
+ # [ 0.33943903, 0.73847425],
195
+ # [ 0.40284824, 0.76374006],
196
+ # [ 0.36457124, 0.76704985],
197
+ # [ 0.26937196, 0.84716266],
198
+ # [ 0.46683946, 0.5275276 ],
199
+ # [ 0.4642676 , 0.49167544],
200
+ # [ 0.06039319, 0.11509081],
201
+ # [ 0.31504983, 0.36394927],
202
+ # [ 0.3660137 , 0.52945083],
203
+ # [ 0.3509634 , 0.50311893],
204
+ # [ 0.09496811, 0.5005815 ],
205
+ # [ 0.46075967, 0.4424029 ],
206
+ # [ 0.20108324, 0.05883435],
207
+ # [ 0.12877828, 0.07731954],
208
+ # [-0.09675749, -0.09848522],
209
+ # [ 0.39672711, 0.09345116],
210
+ # [ 0.29908365, 0.18449144],
211
+ # [ 0.23298171, 0.7922538 ],
212
+ # [-0.27583498, 0.85219014],
213
+ # [ 0.38898414, 0.5723152 ],
214
+ # [ 0.41446668, 0.59347576],
215
+ # [ 0.28167963, 0.7884952 ],
216
+ # [ 0.30013445, 0.7875627 ],
217
+ # [ 0.09448256, 0.03961415],
218
+ # [ 0.3531811 , 0.5553779 ],
219
+ # [ 0.2873921 , 0.05599196],
220
+ # [ 0.28232294, 0.01076962],
221
+ # [ 0.1903341 , -0.23029903],
222
+ # [ 0.0108011 , -0.03099815],
223
+ # [ 0.24915197, -0.10741784],
224
+ # [ 0.01047484, 0.08868673],
225
+ # [-0.08942058, 0.05201372],
226
+ # [ 0.44268388, 0.7376863 ],
227
+ # [ 0.39652622, 0.741894 ],
228
+ # [ 0.35389552, 0.7514722 ],
229
+ # [ 0.393559 , 0.5851372 ],
230
+ # [ 0.2925385 , 0.7871472 ],
231
+ # [ 0.31904542, 0.80939215],
232
+ # [ 0.32005206, 0.787085 ],
233
+ # [ 0.4195982 , 0.5444628 ],
234
+ # [ 0.3688312 , 0.78418756],
235
+ # [ 0.40608776, 0.7841225 ],
236
+ # [ 0.4472093 , 0.78405076],
237
+ # [ 0.43053833, 0.9379409 ],
238
+ # [ 0.44192585, 0.8617842 ],
239
+ # [ 0.44321233, 0.82923037],
240
+ # [ 0.4432334 , 0.80578357],
241
+ # [ 0.44304678, 0.78921837],
242
+ # [ 0.36314115, 0.7893578 ],
243
+ # [ 0.36057413, 0.8040033 ],
244
+ # [ 0.35472178, 0.8187327 ],
245
+ # [ 0.34614718, 0.83330894],
246
+ # [ 0.2959003 , 0.69076014],
247
+ # [-0.37090415, 0.5509728 ],
248
+ # [ 0.4903264 , 0.5851119 ],
249
+ # [ 0.3370172 , 0.78961957],
250
+ # [ 0.33070365, 0.8010128 ],
251
+ # [ 0.43397966, 0.6231119 ],
252
+ # [ 0.35356513, 0.59569615],
253
+ # [ 0.42509514, 0.6093918 ],
254
+ # [ 0.2635329 , 0.39636588],
255
+ # [ 0.19704658, 0.43663597],
256
+ # [ 0.33384863, 0.52658314],
257
+ # [ 0.03225203, -0.18047164],
258
+ # [ 0.11854403, -0.08533629],
259
+ # [ 0.18350407, 0.01215954],
260
+ # [ 0.31292278, 0.8845064 ],
261
+ # [ 0.3862302 , 0.02093028],
262
+ # [ 0.36480215, -0.1098879 ],
263
+ # [ 0.33342764, -0.2497105 ],
264
+ # [ 0.11592615, 0.2646692 ],
265
+ # [-0.00803981, 0.3294946 ],
266
+ # [ 0.33535972, 0.26431814],
267
+ # [ 0.05940344, 0.18766014],
268
+ # [ 0.36188984, 0.33336782],
269
+ # [ 0.39879864, 0.50869733],
270
+ # [-0.07952328, 0.36885905],
271
+ # [ 0.04230375, 0.36800843],
272
+ # [ 0.11137532, 0.3864613 ],
273
+ # [ 0.19386435, 0.37397826],
274
+ # [ 0.25749052, 0.34993485],
275
+ # [ 0.310977 , 0.3240539 ],
276
+ # [ 0.44813582, 0.2762354 ],
277
+ # [-0.06039021, 0.4864401 ],
278
+ # [ 0.00945808, 0.17624807],
279
+ # [ 0.4739895 , 0.55369264],
280
+ # [ 0.32125092, 0.4170324 ],
281
+ # [-0.36162117, 0.27013144],
282
+ # [ 0.3592803 , 0.3023075 ],
283
+ # [ 0.30784345, 0.529875 ],
284
+ # [ 0.07601253, 0.22579695],
285
+ # [ 0.3824061 , 0.47686696],
286
+ # [-0.33810768, 0.70034444],
287
+ # [ 0.34643772, 0.24336138],
288
+ # [ 0.42429656, 0.45338264],
289
+ # [ 0.02854156, 0.939626 ],
290
+ # [-0.04352415, 1.0322431 ],
291
+ # [-0.20510256, 0.51651907],
292
+ # [-0.06969981, 0.8698207 ],
293
+ # [-0.1581445 , 0.14948419],
294
+ # [ 0.2889787 , 1.1224228 ],
295
+ # [ 0.47446907, 0.58377683],
296
+ # [ 0.2818322 , 0.4586393 ],
297
+ # [-0.08708218, 0.2627534 ],
298
+ # [ 0.16877942, 0.25976214],
299
+ # [ 0.21234928, 0.267416 ],
300
+ # [ 0.30676025, 0.81592965],
301
+ # [-0.06259334, 0.6009466 ],
302
+ # [ 0.36930662, 1.2302231 ],
303
+ # [ 0.17070079, 1.149443 ],
304
+ # [ 0.07714309, 1.0989524 ],
305
+ # [ 0.48931465, -0.1052461 ],
306
+ # [ 0.49159575, 1.2484183 ],
307
+ # [ 0.2527582 , 0.26420003],
308
+ # [ 0.30066028, 0.25829503],
309
+ # [ 0.3310663 , 0.25034374],
310
+ # [-0.05075949, 0.16421606],
311
+ # [ 0.29250854, 0.19938153],
312
+ # [ 0.2522571 , 0.18826446],
313
+ # [ 0.21220936, 0.18724632],
314
+ # [ 0.16866222, 0.19260857],
315
+ # [ 0.13789575, 0.2011967 ],
316
+ # [-0.29335994, 0.12383505],
317
+ # [ 0.1379709 , 0.24424627],
318
+ # [ 0.49057597, 0.65296 ],
319
+ # [ 0.34147182, 0.663431 ],
320
+ # [ 0.3941785 , 0.5603462 ],
321
+ # [ 0.43007633, 0.6569765 ],
322
+ # [ 0.48963526, 0.17996965],
323
+ # [ 0.11681002, 1.0107123 ],
324
+ # [ 0.19942053, 1.068824 ],
325
+ # [ 0.38605705, 1.1563928 ],
326
+ # [-0.16756529, 0.9615808 ],
327
+ # [ 0.32817602, 0.21989337],
328
+ # [ 0.41141313, 0.3578073 ],
329
+ # [ 0.49127796, 1.1678538 ],
330
+ # [ 0.27080515, 1.195178 ],
331
+ # [-0.19307071, 0.6481067 ],
332
+ # [ 0.399859 , 0.7892937 ],
333
+ # [ 0.39875022, 0.80587196],
334
+ # [ 0.39717573, 0.8256797 ],
335
+ # [ 0.3931817 , 0.85224336],
336
+ # [ 0.3670306 , 0.9161113 ],
337
+ # [ 0.3256227 , 0.7724022 ],
338
+ # [ 0.31488904, 0.76426226],
339
+ # [ 0.3001029 , 0.7583232 ],
340
+ # [ 0.2565659 , 0.73397243],
341
+ # [ 0.0438394 , 0.6234349 ],
342
+ # [ 0.40628996, 0.30296788],
343
+ # [ 0.37707803, 0.19498621],
344
+ # [ 0.34125936, 0.21069102],
345
+ # [ 0.33733743, 0.7842425 ],
346
+ # [ 0.00882016, 0.769232 ],
347
+ # [ 0.4335431 , 0.1821002 ],
348
+ # [ 0.33409703, 0.9826546 ],
349
+ # [ 0.49011812, 0.3896104 ],
350
+ # [ 0.45311242, 0.34152514],
351
+ # [ 0.4899982 , 0.33611432],
352
+ # [ 0.369907 , 0.43193236],
353
+ # [ 0.49116373, 1.0932964 ],
354
+ # [ 0.49107185, 1.0132186 ],
355
+ # [ 0.41421878, 1.008873 ],
356
+ # [ 0.21551576, 0.8785059 ],
357
+ # [ 0.27587482, 0.57461077],
358
+ # [ 0.2683325 , 0.9399872 ],
359
+ # [ 0.17091931, 0.56899554],
360
+ # [ 0.23741819, 0.6283017 ],
361
+ # [ 0.12783033, 0.65916985],
362
+ # [ 0.39875996, 1.0855893 ],
363
+ # [ 0.33251646, 0.45881665],
364
+ # [ 0.16138549, 0.93153137],
365
+ # [ 0.23269826, 0.99740875],
366
+ # [ 0.17994387, 0.8051213 ],
367
+ # [-0.06026869, 0.7033027 ],
368
+ # [ 0.10063827, 0.8241594 ],
369
+ # [-0.15810522, 0.7679798 ],
370
+ # [ 0.2014156 , 0.7000692 ],
371
+ # [ 0.365875 , 0.3839739 ],
372
+ # [ 0.4115726 , 0.5293855 ],
373
+ # [ 0.378973 , 0.5476473 ],
374
+ # [ 0.43235463, 0.49621448],
375
+ # [ 0.3385827 , 0.15134089],
376
+ # [ 0.27179635, 0.12940899],
377
+ # [ 0.21341887, 0.12485553],
378
+ # [ 0.15807948, 0.12881717],
379
+ # [ 0.10610204, 0.14814937],
380
+ # [ 0.03133116, 0.236169 ],
381
+ # [-0.21341309, 0.38895622],
382
+ # [ 0.07818349, 0.3101151 ],
383
+ # [ 0.1318462 , 0.32528982],
384
+ # [ 0.19485526, 0.32642388],
385
+ # [ 0.25329807, 0.31256682],
386
+ # [ 0.30569646, 0.29578218],
387
+ # [ 0.34839994, 0.2842457 ],
388
+ # [-0.3824783 , 0.41054142],
389
+ # [ 0.37162504, 0.5664833 ],
390
+ # [ 0.41687053, 0.40615496],
391
+ # [ 0.4433516 , 0.5242282 ],
392
+ # [ 0.44805393, 0.5562703 ],
393
+ # [ 0.43453053, 0.5407472 ],
394
+ # [ 0.37351128, 0.58924097],
395
+ # [ 0.46121803, 0.55474806],
396
+ # [ 0.45942986, 0.5810936 ],
397
+ # [ 0.35955238, 0.24802393],
398
+ # [ 0.38181108, 0.25985107],
399
+ # [ 0.40143687, 0.26679716],
400
+ # [ 0.11717269, 0.2102652 ],
401
+ # [ 0.0940459 , 0.2016577 ],
402
+ # [ 0.5217974 , 0.39331725],
403
+ # [ 0.8625129 , 0.23113514],
404
+ # [ 0.5369363 , 0.57397795],
405
+ # [ 1.1896138 , 0.00617525],
406
+ # [ 0.7275363 , 0.28242856],
407
+ # [ 0.7756985 , 0.2884565 ],
408
+ # [ 0.82466465, 0.28205347],
409
+ # [ 0.88921595, 0.24591576],
410
+ # [ 0.6788919 , 0.27210945],
411
+ # [ 0.7640089 , 0.166177 ],
412
+ # [ 0.7199609 , 0.16991326],
413
+ # [ 0.8099376 , 0.17186326],
414
+ # [ 0.8479136 , 0.18300733],
415
+ # [ 0.9368992 , 0.28424102],
416
+ # [ 0.67367214, 1.0503516 ],
417
+ # [ 0.8795338 , 0.22195426],
418
+ # [ 1.1875838 , 0.26458502],
419
+ # [ 1.0039485 , 0.24965489],
420
+ # [ 0.74551606, 0.50375396],
421
+ # [ 0.54075617, 0.7095265 ],
422
+ # [ 0.5365969 , 0.76231945],
423
+ # [ 0.59742403, 0.7215222 ],
424
+ # [ 0.6420548 , 0.7379461 ],
425
+ # [ 0.5787324 , 0.7634331 ],
426
+ # [ 0.617019 , 0.766611 ],
427
+ # [ 0.71218634, 0.8469107 ],
428
+ # [ 0.513503 , 0.52683127],
429
+ # [ 0.5170686 , 0.49132976],
430
+ # [ 0.91894245, 0.11362247],
431
+ # [ 0.66487545, 0.36299667],
432
+ # [ 0.61502695, 0.52894545],
433
+ # [ 0.6296784 , 0.50242335],
434
+ # [ 0.88566196, 0.49919614],
435
+ # [ 0.5193738 , 0.4423927 ],
436
+ # [ 0.7780587 , 0.05788935],
437
+ # [ 0.8504331 , 0.07610969],
438
+ # [ 1.0753254 , -0.1005309 ],
439
+ # [ 0.5824533 , 0.09305263],
440
+ # [ 0.6804744 , 0.18382579],
441
+ # [ 0.7485537 , 0.79121745],
442
+ # [ 1.2577202 , 0.8495136 ],
443
+ # [ 0.59192824, 0.57196105],
444
+ # [ 0.5665197 , 0.59321034],
445
+ # [ 0.6999867 , 0.7877651 ],
446
+ # [ 0.6814933 , 0.7868972 ],
447
+ # [ 0.8846023 , 0.03829005],
448
+ # [ 0.62761134, 0.5547819 ],
449
+ # [ 0.6917209 , 0.05532694],
450
+ # [ 0.6966465 , 0.01012804],
451
+ # [ 0.7876697 , -0.2309872 ],
452
+ # [ 0.9680314 , -0.03263693],
453
+ # [ 0.7294528 , -0.1080169 ],
454
+ # [ 0.96877015, 0.08704082],
455
+ # [ 1.0685298 , 0.05000517],
456
+ # [ 0.538806 , 0.7375185 ],
457
+ # [ 0.5849781 , 0.7415651 ],
458
+ # [ 0.62764204, 0.7509944 ],
459
+ # [ 0.58739805, 0.5847989 ],
460
+ # [ 0.68912315, 0.78645504],
461
+ # [ 0.6626941 , 0.8087924 ],
462
+ # [ 0.6616096 , 0.7864889 ],
463
+ # [ 0.5612171 , 0.5442156 ],
464
+ # [ 0.61282057, 0.7837617 ],
465
+ # [ 0.575564 , 0.7838267 ],
466
+ # [ 0.5344426 , 0.7838985 ],
467
+ # [ 0.551505 , 0.93764293],
468
+ # [ 0.5399973 , 0.8616131 ],
469
+ # [ 0.53859717, 0.8290639 ],
470
+ # [ 0.5384943 , 0.8056173 ],
471
+ # [ 0.53862303, 0.78905153],
472
+ # [ 0.6185288 , 0.78891206],
473
+ # [ 0.62114686, 0.8035485 ],
474
+ # [ 0.62705064, 0.81825733],
475
+ # [ 0.635676 , 0.8328036 ],
476
+ # [ 0.6854969 , 0.69067734],
477
+ # [ 1.3517375 , 0.54796624],
478
+ # [ 0.64465326, 0.78908265],
479
+ # [ 0.6510032 , 0.8004538 ],
480
+ # [ 0.5471015 , 0.62291807],
481
+ # [ 0.62742317, 0.59512955],
482
+ # [ 0.55593795, 0.6091671 ],
483
+ # [ 0.7161671 , 0.39546603],
484
+ # [ 0.7836529 , 0.435396 ],
485
+ # [ 0.64694774, 0.5258542 ],
486
+ # [ 0.94603044, -0.1820665 ],
487
+ # [ 0.86011904, -0.08652072],
488
+ # [ 0.79549086, 0.01118712],
489
+ # [ 0.66893554, 0.8840338 ],
490
+ # [ 0.59274685, 0.02056277],
491
+ # [ 0.613851 , -0.11025709],
492
+ # [ 0.64526045, -0.25000137],
493
+ # [ 0.8639107 , 0.26336375],
494
+ # [ 0.9881146 , 0.3277454 ],
495
+ # [ 0.6445285 , 0.26371115],
496
+ # [ 0.92017305, 0.18616839],
497
+ # [ 0.61790556, 0.3323734 ],
498
+ # [ 0.58225924, 0.5077285 ],
499
+ # [ 1.0597262 , 0.36687428],
500
+ # [ 0.93791103, 0.36642405],
501
+ # [ 0.86892897, 0.38505408],
502
+ # [ 0.78624976, 0.37287512],
503
+ # [ 0.7223912 , 0.34902957],
504
+ # [ 0.6687594 , 0.32310694],
505
+ # [ 0.5315497 , 0.2757726 ],
506
+ # [ 1.0409807 , 0.48452145],
507
+ # [ 0.9700836 , 0.17458573],
508
+ # [ 0.5065989 , 0.55419755],
509
+ # [ 0.6590531 , 0.41624966],
510
+ # [ 1.3414742 , 0.26715896],
511
+ # [ 0.62023264, 0.30108824],
512
+ # [ 0.67289865, 0.5290446 ],
513
+ # [ 0.9036883 , 0.22435239],
514
+ # [ 0.59769833, 0.47659585],
515
+ # [ 1.3194624 , 0.6974514 ],
516
+ # [ 0.63339525, 0.24286939],
517
+ # [ 0.5571053 , 0.45250946],
518
+ # [ 0.9535533 , 0.9380257 ],
519
+ # [ 1.0260391 , 1.0303764 ],
520
+ # [ 1.1858007 , 0.51410204],
521
+ # [ 1.0515786 , 0.867869 ],
522
+ # [ 1.1375865 , 0.14722979],
523
+ # [ 0.6935665 , 1.1218798 ],
524
+ # [ 0.5063422 , 0.58382744],
525
+ # [ 0.69926125, 0.45745537],
526
+ # [ 1.0669235 , 0.26074636],
527
+ # [ 0.8110406 , 0.25864118],
528
+ # [ 0.7674977 , 0.26644707],
529
+ # [ 0.67500204, 0.81528693],
530
+ # [ 1.0435516 , 0.5990178 ],
531
+ # [ 0.6121316 , 1.2306852 ],
532
+ # [ 0.81222653, 1.1483234 ],
533
+ # [ 0.9056057 , 1.0975065 ],
534
+ # [ 0.7270778 , 0.26337218],
535
+ # [ 0.6791554 , 0.25763443],
536
+ # [ 0.6487802 , 0.24975733],
537
+ # [ 1.0302606 , 0.16233999],
538
+ # [ 0.68710136, 0.19869283],
539
+ # [ 0.72731376, 0.18743533],
540
+ # [ 0.7673578 , 0.1862774 ],
541
+ # [ 0.81092334, 0.1914876 ],
542
+ # [ 0.84171957, 0.1999683 ],
543
+ # [ 1.2727026 , 0.12110176],
544
+ # [ 0.8417947 , 0.24301787],
545
+ # [ 0.63978463, 0.6627527 ],
546
+ # [ 0.5866921 , 0.5600102 ],
547
+ # [ 0.5511283 , 0.6567636 ],
548
+ # [ 0.8655194 , 1.009457 ],
549
+ # [ 0.78306264, 1.0678959 ],
550
+ # [ 0.59620714, 1.1564037 ],
551
+ # [ 1.149833 , 0.9592815 ],
552
+ # [ 0.65151644, 0.21932903],
553
+ # [ 0.56865776, 0.3571483 ],
554
+ # [ 0.71228063, 1.1944076 ],
555
+ # [ 1.1742088 , 0.6457327 ],
556
+ # [ 0.5818109 , 0.78897613],
557
+ # [ 0.5829775 , 0.80555046],
558
+ # [ 0.5846211 , 0.82535255],
559
+ # [ 0.5887078 , 0.8519021 ],
560
+ # [ 0.6150045 , 0.916079 ],
561
+ # [ 0.65597004, 0.771831 ],
562
+ # [ 0.66669285, 0.7636482 ],
563
+ # [ 0.6814582 , 0.7576576 ],
564
+ # [ 0.7245435 , 0.73241323],
565
+ # [ 0.9371713 , 0.62184393],
566
+ # [ 0.5736738 , 0.30186948],
567
+ # [ 0.60240346, 0.19448838],
568
+ # [ 0.6383993 , 0.21017241],
569
+ # [ 0.64431435, 0.7837067 ],
570
+ # [ 0.9726586 , 0.7675604 ],
571
+ # [ 0.54576766, 0.18157108],
572
+ # [ 0.6477745 , 0.98230904],
573
+ # [ 0.5269076 , 0.34123868],
574
+ # [ 0.61068684, 0.43131724],
575
+ # [ 0.56792 , 1.0087004 ],
576
+ # [ 0.7662271 , 0.8776794 ],
577
+ # [ 0.7048996 , 0.57387614],
578
+ # [ 0.7136024 , 0.9394351 ],
579
+ # [ 0.8097781 , 0.56784695],
580
+ # [ 0.7435453 , 0.62753886],
581
+ # [ 0.85328954, 0.6578133 ],
582
+ # [ 0.5835228 , 1.0854707 ],
583
+ # [ 0.64810187, 0.45811343],
584
+ # [ 0.82059515, 0.9304676 ],
585
+ # [ 0.7494546 , 0.9966611 ],
586
+ # [ 0.8015866 , 0.80400985],
587
+ # [ 1.0415541 , 0.70138854],
588
+ # [ 0.8809724 , 0.8228132 ],
589
+ # [ 1.1396528 , 0.7657218 ],
590
+ # [ 0.7798614 , 0.69881856],
591
+ # [ 0.6143189 , 0.383193 ],
592
+ # [ 0.56934875, 0.52867246],
593
+ # [ 0.60162777, 0.54706186],
594
+ # [ 0.5470082 , 0.4963955 ],
595
+ # [ 0.6408297 , 0.15073723],
596
+ # [ 0.7075675 , 0.12865019],
597
+ # [ 0.76593757, 0.12391254],
598
+ # [ 0.8212976 , 0.12768434],
599
+ # [ 0.87334216, 0.14682971],
600
+ # [ 0.948411 , 0.23457018],
601
+ # [ 1.1936799 , 0.38651106],
602
+ # [ 0.90181875, 0.30865455],
603
+ # [ 0.84818983, 0.3240165 ],
604
+ # [ 0.7851249 , 0.32537246],
605
+ # [ 0.72658616, 0.3116911 ],
606
+ # [ 0.6740513 , 0.2949461 ],
607
+ # [ 0.63111407, 0.28325075],
608
+ # [ 1.362823 , 0.4074953 ],
609
+ # [ 0.60951644, 0.5658945 ],
610
+ # [ 0.5634702 , 0.4055624 ],
611
+ # [ 0.5374476 , 0.5247268 ],
612
+ # [ 0.53280455, 0.5561224 ],
613
+ # [ 0.5462737 , 0.5405522 ],
614
+ # [ 0.6075077 , 0.58877414],
615
+ # [ 0.51933056, 0.55477065],
616
+ # [ 0.52143395, 0.58103496],
617
+ # [ 0.62030756, 0.24758299],
618
+ # [ 0.59746987, 0.2574137 ],
619
+ # [ 0.5780933 , 0.2652785 ],
620
+ # [ 0.8624742 , 0.2089644 ],
621
+ # [ 0.8855709 , 0.20027623]], dtype=np.float32)
622
+
623
+ # mesh_33 = np.arange(468)
624
+ # mask = np.ones(len(mesh_33), dtype=bool)
625
+ # mask[landmarks_468_moving_parts_indexes]=False
626
+ # mesh_33=mesh_33[mask,...]
627
+ # landmarks_2D_new_mesh=uni_landmarks_468[mask,...]
628
+ # mouth_center_landmarks_2D = np.array([
629
+ # [-4.4202591e-07, 4.4916576e-01], # 48
630
+ # [1.8399176e-01, 3.7537053e-01], # 49
631
+ # [3.7018123e-01, 3.3719531e-01], # 50
632
+ # [5.0000089e-01, 3.6938059e-01], # 51
633
+ # [6.2981832e-01, 3.3719531e-01], # 52
634
+ # [8.1600773e-01, 3.7537053e-01], # 53
635
+ # [1.0000000e+00, 4.4916576e-01], # 54
636
+ # [8.2213330e-01, 6.2836081e-01], # 55
637
+ # [6.4110327e-01, 7.0757812e-01], # 56
638
+ # [5.0000089e-01, 7.2259867e-01], # 57
639
+ # [3.5889623e-01, 7.0757812e-01], # 58
640
+ # [1.7786618e-01, 6.2836081e-01], # 59
641
+ # [7.6765373e-02, 4.5882553e-01], # 60
642
+ # [3.6856663e-01, 4.4601500e-01], # 61
643
+ # [5.0000089e-01, 4.5999300e-01], # 62
644
+ # [6.3143289e-01, 4.4601500e-01], # 63
645
+ # [9.2323411e-01, 4.5882553e-01], # 64
646
+ # [6.3399029e-01, 5.4228687e-01], # 65
647
+ # [5.0000089e-01, 5.5843467e-01], # 66
648
+ # [3.6601129e-01, 5.4228687e-01] # 67
649
+ # ], dtype=np.float32)
650
+
651
+ # 68 point landmark definitions
652
+ landmarks_68_pt = {"mouth": (48, 68),
653
+ "right_eyebrow": (17, 22),
654
+ "left_eyebrow": (22, 27),
655
+ "right_eye": (36, 42),
656
+ "left_eye": (42, 48),
657
+ "nose": (27, 36), # missed one point
658
+ "jaw": (0, 17)}
659
+
660
+ landmarks_68_3D = np.array([
661
+ [-73.393523, -29.801432, 47.667532], # 00
662
+ [-72.775014, -10.949766, 45.909403], # 01
663
+ [-70.533638, 7.929818, 44.842580], # 02
664
+ [-66.850058, 26.074280, 43.141114], # 03
665
+ [-59.790187, 42.564390, 38.635298], # 04
666
+ [-48.368973, 56.481080, 30.750622], # 05
667
+ [-34.121101, 67.246992, 18.456453], # 06
668
+ [-17.875411, 75.056892, 3.609035], # 07
669
+ [0.098749, 77.061286, -0.881698], # 08
670
+ [17.477031, 74.758448, 5.181201], # 09
671
+ [32.648966, 66.929021, 19.176563], # 10
672
+ [46.372358, 56.311389, 30.770570], # 11
673
+ [57.343480, 42.419126, 37.628629], # 12
674
+ [64.388482, 25.455880, 40.886309], # 13
675
+ [68.212038, 6.990805, 42.281449], # 14
676
+ [70.486405, -11.666193, 44.142567], # 15
677
+ [71.375822, -30.365191, 47.140426], # 16
678
+ [-61.119406, -49.361602, 14.254422], # 17
679
+ [-51.287588, -58.769795, 7.268147], # 18
680
+ [-37.804800, -61.996155, 0.442051], # 19
681
+ [-24.022754, -61.033399, -6.606501], # 20
682
+ [-11.635713, -56.686759, -11.967398], # 21
683
+ [12.056636, -57.391033, -12.051204], # 22
684
+ [25.106256, -61.902186, -7.315098], # 23
685
+ [38.338588, -62.777713, -1.022953], # 24
686
+ [51.191007, -59.302347, 5.349435], # 25
687
+ [60.053851, -50.190255, 11.615746], # 26
688
+ [0.653940, -42.193790, -13.380835], # 27
689
+ [0.804809, -30.993721, -21.150853], # 28
690
+ [0.992204, -19.944596, -29.284036], # 29
691
+ [1.226783, -8.414541, -36.948060], # 00
692
+ [-14.772472, 2.598255, -20.132003], # 01
693
+ [-7.180239, 4.751589, -23.536684], # 02
694
+ [0.555920, 6.562900, -25.944448], # 03
695
+ [8.272499, 4.661005, -23.695741], # 04
696
+ [15.214351, 2.643046, -20.858157], # 05
697
+ [-46.047290, -37.471411, 7.037989], # 06
698
+ [-37.674688, -42.730510, 3.021217], # 07
699
+ [-27.883856, -42.711517, 1.353629], # 08
700
+ [-19.648268, -36.754742, -0.111088], # 09
701
+ [-28.272965, -35.134493, -0.147273], # 10
702
+ [-38.082418, -34.919043, 1.476612], # 11
703
+ [19.265868, -37.032306, -0.665746], # 12
704
+ [27.894191, -43.342445, 0.247660], # 13
705
+ [37.437529, -43.110822, 1.696435], # 14
706
+ [45.170805, -38.086515, 4.894163], # 15
707
+ [38.196454, -35.532024, 0.282961], # 16
708
+ [28.764989, -35.484289, -1.172675], # 17
709
+ [-28.916267, 28.612716, -2.240310], # 18
710
+ [-17.533194, 22.172187, -15.934335], # 19
711
+ [-6.684590, 19.029051, -22.611355], # 20
712
+ [0.381001, 20.721118, -23.748437], # 21
713
+ [8.375443, 19.035460, -22.721995], # 22
714
+ [18.876618, 22.394109, -15.610679], # 23
715
+ [28.794412, 28.079924, -3.217393], # 24
716
+ [19.057574, 36.298248, -14.987997], # 25
717
+ [8.956375, 39.634575, -22.554245], # 26
718
+ [0.381549, 40.395647, -23.591626], # 27
719
+ [-7.428895, 39.836405, -22.406106], # 28
720
+ [-18.160634, 36.677899, -15.121907], # 29
721
+ [-24.377490, 28.677771, -4.785684], # 30
722
+ [-6.897633, 25.475976, -20.893742], # 31
723
+ [0.340663, 26.014269, -22.220479], # 32
724
+ [8.444722, 25.326198, -21.025520], # 33
725
+ [24.474473, 28.323008, -5.712776], # 34
726
+ [8.449166, 30.596216, -20.671489], # 35
727
+ [0.205322, 31.408738, -21.903670], # 36
728
+ [-7.198266, 30.844876, -20.328022] # 37
729
+ ], dtype=np.float32)
730
+
731
+ FaceType_to_padding_remove_align = {
732
+ FaceType.HALF: (0.0, False),
733
+ FaceType.MID_FULL: (0.0675, False),
734
+ FaceType.FULL: (0.2109375, False),
735
+ FaceType.FULL_NO_ALIGN: (0.2109375, True),
736
+ FaceType.WHOLE_FACE: (0.40, False),
737
+ FaceType.HEAD: (0.70, False),
738
+ FaceType.HEAD_NO_ALIGN: (0.70, True),
739
+ }
740
+
741
+
742
+ def convert_98_to_68(lmrks):
743
+ # jaw
744
+ result = [lmrks[0]]
745
+ for i in range(2, 16, 2):
746
+ result += [(lmrks[i] + (lmrks[i - 1] + lmrks[i + 1]) / 2) / 2]
747
+ result += [lmrks[16]]
748
+ for i in range(18, 32, 2):
749
+ result += [(lmrks[i] + (lmrks[i - 1] + lmrks[i + 1]) / 2) / 2]
750
+ result += [lmrks[32]]
751
+
752
+ # eyebrows averaging
753
+ result += [lmrks[33],
754
+ (lmrks[34] + lmrks[41]) / 2,
755
+ (lmrks[35] + lmrks[40]) / 2,
756
+ (lmrks[36] + lmrks[39]) / 2,
757
+ (lmrks[37] + lmrks[38]) / 2,
758
+ ]
759
+
760
+ result += [(lmrks[42] + lmrks[50]) / 2,
761
+ (lmrks[43] + lmrks[49]) / 2,
762
+ (lmrks[44] + lmrks[48]) / 2,
763
+ (lmrks[45] + lmrks[47]) / 2,
764
+ lmrks[46]
765
+ ]
766
+
767
+ # nose
768
+ result += list(lmrks[51:60])
769
+
770
+ # left eye (from our view)
771
+ result += [lmrks[60],
772
+ lmrks[61],
773
+ lmrks[63],
774
+ lmrks[64],
775
+ lmrks[65],
776
+ lmrks[67]]
777
+
778
+ # right eye
779
+ result += [lmrks[68],
780
+ lmrks[69],
781
+ lmrks[71],
782
+ lmrks[72],
783
+ lmrks[73],
784
+ lmrks[75]]
785
+
786
+ # mouth
787
+ result += list(lmrks[76:96])
788
+
789
+ return np.concatenate(result).reshape((68, 2))
790
+
791
+
792
+ def transform_points(points, mat, invert=False):
793
+ if invert:
794
+ mat = cv2.invertAffineTransform(mat)
795
+ points = np.expand_dims(points, axis=1)
796
+ points = cv2.transform(points, mat, points.shape)
797
+ points = np.squeeze(points)
798
+ return points
799
+
800
+
801
+ def get_transform_mat(image_landmarks, output_size, face_type, scale=1.0):
802
+ if not isinstance(image_landmarks, np.ndarray):
803
+ image_landmarks = np.array(image_landmarks)
804
+
805
+ # estimate landmarks transform from global space to local aligned space with bounds [0..1]
806
+ mat = umeyama(np.concatenate([image_landmarks[17:49], image_landmarks[54:55]]), landmarks_2D_new, True)[0:2]
807
+
808
+ # get corner points in global space
809
+ g_p = transform_points(np.float32([(0, 0), (1, 0), (1, 1), (0, 1), (0.5, 0.5)]), mat, True)
810
+ g_c = g_p[4]
811
+
812
+ # calc diagonal vectors between corners in global space
813
+ tb_diag_vec = (g_p[2] - g_p[0]).astype(np.float32)
814
+ tb_diag_vec /= npla.norm(tb_diag_vec)
815
+ bt_diag_vec = (g_p[1] - g_p[3]).astype(np.float32)
816
+ bt_diag_vec /= npla.norm(bt_diag_vec)
817
+
818
+ # calc modifier of diagonal vectors for scale and padding value
819
+ # print(face_type)
820
+ padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0)
821
+ mod = (1.0 / scale) * (npla.norm(g_p[0] - g_p[2]) * (padding * np.sqrt(2.0) + 0.5))
822
+
823
+ if face_type == FaceType.WHOLE_FACE:
824
+ # adjust vertical offset for WHOLE_FACE, 7% below in order to cover more forehead
825
+ vec = (g_p[0] - g_p[3]).astype(np.float32)
826
+ vec_len = npla.norm(vec)
827
+ vec /= vec_len
828
+ g_c += vec * vec_len * 0.07
829
+
830
+
831
+ # calc 3 points in global space to estimate 2d affine transform
832
+ if not remove_align:
833
+ l_t = np.array([g_c - tb_diag_vec * mod,
834
+ g_c + bt_diag_vec * mod,
835
+ g_c + tb_diag_vec * mod])
836
+ else:
837
+ # remove_align - face will be centered in the frame but not aligned
838
+ l_t = np.array([g_c - tb_diag_vec * mod,
839
+ g_c + bt_diag_vec * mod,
840
+ g_c + tb_diag_vec * mod,
841
+ g_c - bt_diag_vec * mod,
842
+ ])
843
+
844
+ # get area of face square in global space
845
+ area = mathlib.polygon_area(l_t[:, 0], l_t[:, 1])
846
+
847
+ # calc side of square
848
+ side = np.float32(math.sqrt(area) / 2)
849
+
850
+ # calc 3 points with unrotated square
851
+ l_t = np.array([g_c + [-side, -side],
852
+ g_c + [side, -side],
853
+ g_c + [side, side]])
854
+
855
+ # calc affine transform from 3 global space points to 3 local space points size of 'output_size'
856
+ pts2 = np.float32(((0, 0), (output_size, 0), (output_size, output_size)))
857
+ mat = cv2.getAffineTransform(l_t, pts2)
858
+ return mat
859
+
860
+
861
+ def get_rect_from_landmarks(image_landmarks):
862
+ mat = get_transform_mat(image_landmarks, 256, FaceType.FULL_NO_ALIGN)
863
+
864
+ g_p = transform_points(np.float32([(0, 0), (255, 255)]), mat, True)
865
+
866
+ (l, t, r, b) = g_p[0][0], g_p[0][1], g_p[1][0], g_p[1][1]
867
+
868
+ return (l, t, r, b)
869
+
870
+ def get_transform_mat_all(image_landmarks,uni_landmarks,output_size,scale=1,gcx=-0.02,gcy=0.15,face_type=FaceType.WHOLE_FACE):
871
+ if not isinstance(image_landmarks, np.ndarray):
872
+ image_landmarks = np.array (image_landmarks)
873
+ # estimate landmarks transform from global space to local aligned space with bounds [0..1]
874
+
875
+ mat = umeyama(image_landmarks, uni_landmarks, True)[0:2]
876
+
877
+ # get corner points in global space
878
+ g_p = transform_points ( np.float32([(0,0),(1,0),(1,1),(0,1),(0.5,0.5) ]) , mat, True)
879
+ g_c = g_p[4]
880
+ # calc diagonal vectors between corners in global space
881
+
882
+
883
+ tb_diag_vec = (g_p[2] - g_p[0]).astype(np.float32)
884
+ tb_diag_vec /= npla.norm(tb_diag_vec)
885
+ bt_diag_vec = (g_p[1] - g_p[3]).astype(np.float32)
886
+ bt_diag_vec /= npla.norm(bt_diag_vec)
887
+
888
+ # calc modifier of diagonal vectors for scale and padding value
889
+ padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0)
890
+ mod = (1.0 / scale) * (npla.norm(g_p[0] - g_p[2]) * (padding * np.sqrt(2.0) + 0.5))
891
+
892
+ vec = (g_p[0]-g_p[3]).astype(np.float32)
893
+ vec_len = npla.norm(vec)
894
+ vec /= vec_len
895
+ g_c += vec*vec_len*[gcx,gcy]
896
+
897
+
898
+ # calc 3 points in global space to estimate 2d affine transform
899
+ if not remove_align:
900
+ l_t = np.array([g_c - tb_diag_vec * mod,
901
+ g_c + bt_diag_vec * mod,
902
+ g_c + tb_diag_vec * mod])
903
+ else:
904
+ # remove_align - face will be centered in the frame but not aligned
905
+ l_t = np.array([g_c - tb_diag_vec * mod,
906
+ g_c + bt_diag_vec * mod,
907
+ g_c + tb_diag_vec * mod,
908
+ g_c - bt_diag_vec * mod,
909
+ ])
910
+
911
+ # get area of face square in global space
912
+ area = mathlib.polygon_area(l_t[:, 0], l_t[:, 1])
913
+
914
+ # calc side of square
915
+ side = np.float32(math.sqrt(area) / 2)
916
+
917
+ # calc 3 points with unrotated square
918
+ l_t = np.array([g_c + [-side, -side],
919
+ g_c + [side, -side],
920
+ g_c + [side, side]])
921
+
922
+ # calc affine transform from 3 global space points to 3 local space points size of 'output_size'
923
+ pts2 = np.float32(((0, 0), (output_size, 0), (output_size, output_size)))
924
+ mat = cv2.getAffineTransform(l_t, pts2)
925
+ return mat
926
+
927
+
928
+ def expand_eyebrows(lmrks, eyebrows_expand_mod=1.0):
929
+
930
+ if len(lmrks) != 68:
931
+ raise Exception('works only with 68 landmarks')
932
+ lmrks = np.array(lmrks.copy(), dtype=np.int)
933
+
934
+ # #nose
935
+ ml_pnt = (lmrks[36] + lmrks[0]) // 2
936
+ mr_pnt = (lmrks[16] + lmrks[45]) // 2
937
+
938
+ # mid points between the mid points and eye
939
+ ql_pnt = (lmrks[36] + ml_pnt) // 2
940
+ qr_pnt = (lmrks[45] + mr_pnt) // 2
941
+
942
+ # Top of the eye arrays
943
+ bot_l = np.array((ql_pnt, lmrks[36], lmrks[37], lmrks[38], lmrks[39]))
944
+ bot_r = np.array((lmrks[42], lmrks[43], lmrks[44], lmrks[45], qr_pnt))
945
+
946
+ # Eyebrow arrays
947
+ top_l = lmrks[17:22]
948
+ top_r = lmrks[22:27]
949
+
950
+ # Adjust eyebrow arrays
951
+ lmrks[17:22] = top_l + eyebrows_expand_mod * 0.5 * (top_l - bot_l)
952
+ lmrks[22:27] = top_r + eyebrows_expand_mod * 0.5 * (top_r - bot_r)
953
+ return lmrks
954
+
955
+
956
+ def get_image_hull_mask(image_shape, image_landmarks, eyebrows_expand_mod=1.0):
957
+ hull_mask = np.zeros(image_shape[0:2] + (1,), dtype=np.float32)
958
+
959
+ lmrks = expand_eyebrows(image_landmarks, eyebrows_expand_mod)
960
+
961
+ r_jaw = (lmrks[0:9], lmrks[17:18])
962
+ l_jaw = (lmrks[8:17], lmrks[26:27])
963
+ r_cheek = (lmrks[17:20], lmrks[8:9])
964
+ l_cheek = (lmrks[24:27], lmrks[8:9])
965
+ nose_ridge = (lmrks[19:25], lmrks[8:9],)
966
+ r_eye = (lmrks[17:22], lmrks[27:28], lmrks[31:36], lmrks[8:9])
967
+ l_eye = (lmrks[22:27], lmrks[27:28], lmrks[31:36], lmrks[8:9])
968
+ nose = (lmrks[27:31], lmrks[31:36])
969
+ parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose]
970
+
971
+ for item in parts:
972
+ merged = np.concatenate(item)
973
+ cv2.fillConvexPoly(hull_mask, cv2.convexHull(merged), (1,))
974
+
975
+ return hull_mask
976
+
977
+
978
+ def get_image_eye_mask(image_shape, image_landmarks):
979
+ if len(image_landmarks) != 68:
980
+ raise Exception('get_image_eye_mask works only with 68 landmarks')
981
+
982
+ h, w, c = image_shape
983
+
984
+ hull_mask = np.zeros((h, w, 1), dtype=np.float32)
985
+
986
+ image_landmarks = image_landmarks.astype(np.int)
987
+
988
+ cv2.fillConvexPoly(hull_mask, cv2.convexHull(image_landmarks[36:42]), (1,))
989
+ cv2.fillConvexPoly(hull_mask, cv2.convexHull(image_landmarks[42:48]), (1,))
990
+
991
+ dilate = h // 32
992
+ hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (dilate, dilate)), iterations=1)
993
+
994
+ blur = h // 16
995
+ blur = blur + (1 - blur % 2)
996
+ hull_mask = cv2.GaussianBlur(hull_mask, (blur, blur), 0)
997
+ hull_mask = hull_mask[..., None]
998
+
999
+ return hull_mask
1000
+
1001
+
1002
+ def get_image_mouth_mask(image_shape, image_landmarks):
1003
+ if len(image_landmarks) != 68:
1004
+ raise Exception('get_image_eye_mask works only with 68 landmarks')
1005
+
1006
+ h, w, c = image_shape
1007
+
1008
+ hull_mask = np.zeros((h, w, 1), dtype=np.float32)
1009
+
1010
+ image_landmarks = image_landmarks.astype(np.int)
1011
+
1012
+ cv2.fillConvexPoly(hull_mask, cv2.convexHull(image_landmarks[60:]), (1,))
1013
+
1014
+ dilate = h // 32
1015
+ hull_mask = cv2.dilate(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (dilate, dilate)), iterations=1)
1016
+
1017
+ blur = h // 16
1018
+ blur = blur + (1 - blur % 2)
1019
+ hull_mask = cv2.GaussianBlur(hull_mask, (blur, blur), 0)
1020
+ hull_mask = hull_mask[..., None]
1021
+
1022
+ return hull_mask
1023
+
1024
+
1025
+ def alpha_to_color(img_alpha, color):
1026
+ if len(img_alpha.shape) == 2:
1027
+ img_alpha = img_alpha[..., None]
1028
+ h, w, c = img_alpha.shape
1029
+ result = np.zeros((h, w, len(color)), dtype=np.float32)
1030
+ result[:, :] = color
1031
+
1032
+ return result * img_alpha
1033
+
1034
+
1035
+ def get_cmask(image_shape, lmrks, eyebrows_expand_mod=1.0):
1036
+ h, w, c = image_shape
1037
+
1038
+ hull = get_image_hull_mask(image_shape, lmrks, eyebrows_expand_mod)
1039
+
1040
+ result = np.zeros((h, w, 3), dtype=np.float32)
1041
+
1042
+ def process(w, h, data):
1043
+ d = {}
1044
+ cur_lc = 0
1045
+ all_lines = []
1046
+ for s, pts_loop_ar in data:
1047
+ lines = []
1048
+ for pts, loop in pts_loop_ar:
1049
+ pts_len = len(pts)
1050
+ lines.append([[pts[i], pts[(i + 1) % pts_len]] for i in range(pts_len - (0 if loop else 1))])
1051
+ lines = np.concatenate(lines)
1052
+
1053
+ lc = lines.shape[0]
1054
+ all_lines.append(lines)
1055
+ d[s] = cur_lc, cur_lc + lc
1056
+ cur_lc += lc
1057
+ all_lines = np.concatenate(all_lines, 0)
1058
+
1059
+ # calculate signed distance for all points and lines
1060
+ line_count = all_lines.shape[0]
1061
+ pts_count = w * h
1062
+
1063
+ all_lines = np.repeat(all_lines[None, ...], pts_count, axis=0).reshape((pts_count * line_count, 2, 2))
1064
+
1065
+ pts = np.empty((h, w, line_count, 2), dtype=np.float32)
1066
+ pts[..., 1] = np.arange(h)[:, None, None]
1067
+ pts[..., 0] = np.arange(w)[:, None]
1068
+ pts = pts.reshape((h * w * line_count, -1))
1069
+
1070
+ a = all_lines[:, 0, :]
1071
+ b = all_lines[:, 1, :]
1072
+ pa = pts - a
1073
+ ba = b - a
1074
+ ph = np.clip(np.einsum('ij,ij->i', pa, ba) / np.einsum('ij,ij->i', ba, ba), 0, 1)
1075
+ dists = npla.norm(pa - ba * ph[..., None], axis=1).reshape((h, w, line_count))
1076
+
1077
+ def get_dists(name, thickness=0):
1078
+ s, e = d[name]
1079
+ result = dists[..., s:e]
1080
+ if thickness != 0:
1081
+ result = np.abs(result) - thickness
1082
+ return np.min(result, axis=-1)
1083
+
1084
+ return get_dists
1085
+
1086
+ l_eye = lmrks[42:48]
1087
+ r_eye = lmrks[36:42]
1088
+ l_brow = lmrks[22:27]
1089
+ r_brow = lmrks[17:22]
1090
+ mouth = lmrks[48:60]
1091
+
1092
+ up_nose = np.concatenate((lmrks[27:31], lmrks[33:34]))
1093
+ down_nose = lmrks[31:36]
1094
+ nose = np.concatenate((up_nose, down_nose))
1095
+
1096
+ gdf = process(w, h,
1097
+ (
1098
+ ('eyes', ((l_eye, True), (r_eye, True))),
1099
+ ('brows', ((l_brow, False), (r_brow, False))),
1100
+ ('up_nose', ((up_nose, False),)),
1101
+ ('down_nose', ((down_nose, False),)),
1102
+ ('mouth', ((mouth, True),)),
1103
+ )
1104
+ )
1105
+
1106
+ eyes_fall_dist = w // 32
1107
+ eyes_thickness = max(w // 64, 1)
1108
+
1109
+ brows_fall_dist = w // 32
1110
+ brows_thickness = max(w // 256, 1)
1111
+
1112
+ nose_fall_dist = w / 12
1113
+ nose_thickness = max(w // 96, 1)
1114
+
1115
+ mouth_fall_dist = w // 32
1116
+ mouth_thickness = max(w // 64, 1)
1117
+
1118
+ eyes_mask = gdf('eyes', eyes_thickness)
1119
+ eyes_mask = 1 - np.clip(eyes_mask / eyes_fall_dist, 0, 1)
1120
+ # eyes_mask = np.clip ( 1- ( np.sqrt( np.maximum(eyes_mask,0) ) / eyes_fall_dist ), 0, 1)
1121
+ # eyes_mask = np.clip ( 1- ( np.cbrt( np.maximum(eyes_mask,0) ) / eyes_fall_dist ), 0, 1)
1122
+
1123
+ brows_mask = gdf('brows', brows_thickness)
1124
+ brows_mask = 1 - np.clip(brows_mask / brows_fall_dist, 0, 1)
1125
+ # brows_mask = np.clip ( 1- ( np.sqrt( np.maximum(brows_mask,0) ) / brows_fall_dist ), 0, 1)
1126
+
1127
+ mouth_mask = gdf('mouth', mouth_thickness)
1128
+ mouth_mask = 1 - np.clip(mouth_mask / mouth_fall_dist, 0, 1)
1129
+
1130
+ # mouth_mask = np.clip ( 1- ( np.sqrt( np.maximum(mouth_mask,0) ) / mouth_fall_dist ), 0, 1)
1131
+
1132
+ def blend(a, b, k):
1133
+ x = np.clip(0.5 + 0.5 * (b - a) / k, 0.0, 1.0)
1134
+ return (a - b) * x + b - k * x * (1.0 - x)
1135
+
1136
+ # nose_mask = (a-b)*x+b - k*x*(1.0-x)
1137
+
1138
+ # nose_mask = np.minimum (up_nose_mask , down_nose_mask )
1139
+ # nose_mask = 1-np.clip( nose_mask / nose_fall_dist, 0, 1)
1140
+
1141
+ nose_mask = blend(gdf('up_nose', nose_thickness), gdf('down_nose', nose_thickness), nose_thickness * 3)
1142
+ nose_mask = 1 - np.clip(nose_mask / nose_fall_dist, 0, 1)
1143
+
1144
+ up_nose_mask = gdf('up_nose', nose_thickness)
1145
+ up_nose_mask = 1 - np.clip(up_nose_mask / nose_fall_dist, 0, 1)
1146
+ # up_nose_mask = np.clip ( 1- ( np.cbrt( np.maximum(up_nose_mask,0) ) / nose_fall_dist ), 0, 1)
1147
+
1148
+ down_nose_mask = gdf('down_nose', nose_thickness)
1149
+ down_nose_mask = 1 - np.clip(down_nose_mask / nose_fall_dist, 0, 1)
1150
+ # down_nose_mask = np.clip ( 1- ( np.cbrt( np.maximum(down_nose_mask,0) ) / nose_fall_dist ), 0, 1)
1151
+
1152
+ # nose_mask = np.clip( up_nose_mask + down_nose_mask, 0, 1 )
1153
+ # nose_mask /= np.max(nose_mask)
1154
+ # nose_mask = np.maximum (up_nose_mask , down_nose_mask )
1155
+ # nose_mask = down_nose_mask
1156
+
1157
+ # nose_mask = np.zeros_like(nose_mask)
1158
+
1159
+ eyes_mask = eyes_mask * (1 - mouth_mask)
1160
+ nose_mask = nose_mask * (1 - eyes_mask)
1161
+
1162
+ hull_mask = hull[..., 0].copy()
1163
+ hull_mask = hull_mask * (1 - eyes_mask) * (1 - brows_mask) * (1 - nose_mask) * (1 - mouth_mask)
1164
+
1165
+ # eyes_mask = eyes_mask * (1-nose_mask)
1166
+
1167
+ mouth_mask = mouth_mask * (1 - nose_mask)
1168
+
1169
+ brows_mask = brows_mask * (1 - nose_mask) * (1 - eyes_mask)
1170
+
1171
+ hull_mask = alpha_to_color(hull_mask, (0, 1, 0))
1172
+ eyes_mask = alpha_to_color(eyes_mask, (1, 0, 0))
1173
+ brows_mask = alpha_to_color(brows_mask, (0, 0, 1))
1174
+ nose_mask = alpha_to_color(nose_mask, (0, 1, 1))
1175
+ mouth_mask = alpha_to_color(mouth_mask, (0, 0, 1))
1176
+
1177
+ # nose_mask = np.maximum( up_nose_mask, down_nose_mask )
1178
+
1179
+ result = hull_mask + mouth_mask + nose_mask + brows_mask + eyes_mask
1180
+ result *= hull
1181
+ # result = np.clip (result, 0, 1)
1182
+ return result
1183
+
1184
+
1185
+ def blur_image_hull_mask(hull_mask):
1186
+ maxregion = np.argwhere(hull_mask == 1.0)
1187
+ miny, minx = maxregion.min(axis=0)[:2]
1188
+ maxy, maxx = maxregion.max(axis=0)[:2]
1189
+ lenx = maxx - minx;
1190
+ leny = maxy - miny;
1191
+ masky = int(minx + (lenx // 2))
1192
+ maskx = int(miny + (leny // 2))
1193
+ lowest_len = min(lenx, leny)
1194
+ ero = int(lowest_len * 0.085)
1195
+ blur = int(lowest_len * 0.10)
1196
+
1197
+ hull_mask = cv2.erode(hull_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ero, ero)), iterations=1)
1198
+ hull_mask = cv2.blur(hull_mask, (blur, blur))
1199
+ hull_mask = np.expand_dims(hull_mask, -1)
1200
+
1201
+ return hull_mask
1202
+
1203
+
1204
+ mirror_idxs = [
1205
+ [0, 16],
1206
+ [1, 15],
1207
+ [2, 14],
1208
+ [3, 13],
1209
+ [4, 12],
1210
+ [5, 11],
1211
+ [6, 10],
1212
+ [7, 9],
1213
+
1214
+ [17, 26],
1215
+ [18, 25],
1216
+ [19, 24],
1217
+ [20, 23],
1218
+ [21, 22],
1219
+
1220
+ [36, 45],
1221
+ [37, 44],
1222
+ [38, 43],
1223
+ [39, 42],
1224
+ [40, 47],
1225
+ [41, 46],
1226
+
1227
+ [31, 35],
1228
+ [32, 34],
1229
+
1230
+ [50, 52],
1231
+ [49, 53],
1232
+ [48, 54],
1233
+ [59, 55],
1234
+ [58, 56],
1235
+ [67, 65],
1236
+ [60, 64],
1237
+ [61, 63]]
1238
+
1239
+
1240
+ def mirror_landmarks(landmarks, val):
1241
+ result = landmarks.copy()
1242
+
1243
+ for idx in mirror_idxs:
1244
+ result[idx] = result[idx[::-1]]
1245
+
1246
+ result[:, 0] = val - result[:, 0] - 1
1247
+ return result
1248
+
1249
+
1250
+ def get_face_struct_mask(image_shape, image_landmarks, eyebrows_expand_mod=1.0, color=(1,)):
1251
+ mask = np.zeros(image_shape[0:2] + (len(color),), dtype=np.float32)
1252
+ lmrks = expand_eyebrows(image_landmarks, eyebrows_expand_mod)
1253
+ draw_landmarks(mask, image_landmarks, color=color, draw_circles=False, thickness=2)
1254
+ return mask
1255
+
1256
+
1257
+ def draw_landmarks(image, image_landmarks, color=(0, 255, 0), draw_circles=True, thickness=1, transparent_mask=False):
1258
+ if len(image_landmarks) != 68:
1259
+ raise Exception('get_image_eye_mask works only with 68 landmarks')
1260
+
1261
+ int_lmrks = np.array(image_landmarks, dtype=np.int)
1262
+
1263
+ jaw = int_lmrks[slice(*landmarks_68_pt["jaw"])]
1264
+ right_eyebrow = int_lmrks[slice(*landmarks_68_pt["right_eyebrow"])]
1265
+ left_eyebrow = int_lmrks[slice(*landmarks_68_pt["left_eyebrow"])]
1266
+ mouth = int_lmrks[slice(*landmarks_68_pt["mouth"])]
1267
+ right_eye = int_lmrks[slice(*landmarks_68_pt["right_eye"])]
1268
+ left_eye = int_lmrks[slice(*landmarks_68_pt["left_eye"])]
1269
+ nose = int_lmrks[slice(*landmarks_68_pt["nose"])]
1270
+
1271
+ # open shapes
1272
+ cv2.polylines(image,
1273
+ tuple(np.array([v]) for v in (right_eyebrow, jaw, left_eyebrow, np.concatenate((nose, [nose[-6]])))),
1274
+ False, color, thickness=thickness, lineType=cv2.LINE_AA)
1275
+ # closed shapes
1276
+ cv2.polylines(image, tuple(np.array([v]) for v in (right_eye, left_eye, mouth)),
1277
+ True, color, thickness=thickness, lineType=cv2.LINE_AA)
1278
+
1279
+ if draw_circles:
1280
+ # the rest of the cicles
1281
+ for x, y in np.concatenate((right_eyebrow, left_eyebrow, mouth, right_eye, left_eye, nose), axis=0):
1282
+ cv2.circle(image, (x, y), 1, color, 1, lineType=cv2.LINE_AA)
1283
+ # jaw big circles
1284
+ for x, y in jaw:
1285
+ cv2.circle(image, (x, y), 2, color, lineType=cv2.LINE_AA)
1286
+
1287
+ if transparent_mask:
1288
+ mask = get_image_hull_mask(image.shape, image_landmarks)
1289
+ image[...] = (image * (1 - mask) + image * mask / 2)[...]
1290
+
1291
+
1292
+ def draw_rect_landmarks(image, rect, image_landmarks, face_type, face_size=256, transparent_mask=False,
1293
+ landmarks_color=(0, 255, 0)):
1294
+ draw_landmarks(image, image_landmarks, color=landmarks_color, transparent_mask=transparent_mask)
1295
+ imagelib.draw_rect(image, rect, (255, 0, 0), 2)
1296
+
1297
+ image_to_face_mat = get_transform_mat(image_landmarks, face_size, face_type)
1298
+ points = transform_points([(0, 0), (0, face_size - 1), (face_size - 1, face_size - 1), (face_size - 1, 0)],
1299
+ image_to_face_mat, True)
1300
+ imagelib.draw_polygon(image, points, (0, 0, 255), 2)
1301
+
1302
+ points = transform_points(
1303
+ [(int(face_size * 0.05), 0), (int(face_size * 0.1), int(face_size * 0.1)), (0, int(face_size * 0.1))],
1304
+ image_to_face_mat, True)
1305
+ imagelib.draw_polygon(image, points, (0, 0, 255), 2)
1306
+
1307
+
1308
+ def calc_face_pitch(landmarks):
1309
+ if not isinstance(landmarks, np.ndarray):
1310
+ landmarks = np.array(landmarks)
1311
+ t = ((landmarks[6][1] - landmarks[8][1]) + (landmarks[10][1] - landmarks[8][1])) / 2.0
1312
+ b = landmarks[8][1]
1313
+ return float(b - t)
1314
+
1315
+
1316
+ def estimate_averaged_yaw(landmarks):
1317
+ # Works much better than solvePnP if landmarks from "3DFAN"
1318
+ if not isinstance(landmarks, np.ndarray):
1319
+ landmarks = np.array(landmarks)
1320
+ l = ((landmarks[27][0] - landmarks[0][0]) + (landmarks[28][0] - landmarks[1][0]) + (
1321
+ landmarks[29][0] - landmarks[2][0])) / 3.0
1322
+ r = ((landmarks[16][0] - landmarks[27][0]) + (landmarks[15][0] - landmarks[28][0]) + (
1323
+ landmarks[14][0] - landmarks[29][0])) / 3.0
1324
+ return float(r - l)
1325
+
1326
+
1327
+ def estimate_pitch_yaw_roll(aligned_landmarks, size=256):
1328
+ """
1329
+ returns pitch,yaw,roll [-pi/2...+pi/2]
1330
+ """
1331
+ shape = (size, size)
1332
+ focal_length = shape[1]
1333
+ camera_center = (shape[1] / 2, shape[0] / 2)
1334
+ camera_matrix = np.array(
1335
+ [[focal_length, 0, camera_center[0]],
1336
+ [0, focal_length, camera_center[1]],
1337
+ [0, 0, 1]], dtype=np.float32)
1338
+
1339
+ (_, rotation_vector, _) = cv2.solvePnP(
1340
+ np.concatenate((landmarks_68_3D[:27], landmarks_68_3D[30:36]), axis=0),
1341
+ np.concatenate((aligned_landmarks[:27], aligned_landmarks[30:36]), axis=0).astype(np.float32),
1342
+ camera_matrix,
1343
+ np.zeros((4, 1)))
1344
+
1345
+ pitch, yaw, roll = mathlib.rotationMatrixToEulerAngles(cv2.Rodrigues(rotation_vector)[0])
1346
+
1347
+ half_pi = math.pi / 2.0
1348
+ pitch = np.clip(pitch, -half_pi, half_pi)
1349
+ yaw = np.clip(yaw, -half_pi, half_pi)
1350
+ roll = np.clip(roll, -half_pi, half_pi)
1351
+
1352
+ return -pitch, yaw, roll
1353
+
1354
+
1355
+ # if remove_align:
1356
+ # bbox = transform_points ( [ (0,0), (0,output_size), (output_size, output_size), (output_size,0) ], mat, True)
1357
+ # #import code
1358
+ # #code.interact(local=dict(globals(), **locals()))
1359
+ # area = mathlib.polygon_area(bbox[:,0], bbox[:,1] )
1360
+ # side = math.sqrt(area) / 2
1361
+ # center = transform_points ( [(output_size/2,output_size/2)], mat, True)
1362
+ # pts1 = np.float32(( center+[-side,-side], center+[side,-side], center+[side,-side] ))
1363
+ # pts2 = np.float32([[0,0],[output_size,0],[0,output_size]])
1364
+ # mat = cv2.getAffineTransform(pts1,pts2)
1365
+ # if full_face_align_top and (face_type == FaceType.FULL or face_type == FaceType.FULL_NO_ALIGN):
1366
+ # #lmrks2 = expand_eyebrows(image_landmarks)
1367
+ # #lmrks2_ = transform_points( [ lmrks2[19], lmrks2[24] ], mat, False )
1368
+ # #y_diff = np.float32( (0,np.min(lmrks2_[:,1])) )
1369
+ # #y_diff = transform_points( [ np.float32( (0,0) ), y_diff], mat, True)
1370
+ # #y_diff = y_diff[1]-y_diff[0]
1371
+ #
1372
+ # x_diff = np.float32((0,0))
1373
+ #
1374
+ # lmrks2_ = transform_points( [ image_landmarks[0], image_landmarks[16] ], mat, False )
1375
+ # if lmrks2_[0,0] < 0:
1376
+ # x_diff = lmrks2_[0,0]
1377
+ # x_diff = transform_points( [ np.float32( (0,0) ), np.float32((x_diff,0)) ], mat, True)
1378
+ # x_diff = x_diff[1]-x_diff[0]
1379
+ # elif lmrks2_[1,0] >= output_size:
1380
+ # x_diff = lmrks2_[1,0]-(output_size-1)
1381
+ # x_diff = transform_points( [ np.float32( (0,0) ), np.float32((x_diff,0)) ], mat, True)
1382
+ # x_diff = x_diff[1]-x_diff[0]
1383
+ #
1384
+ # mat = cv2.getAffineTransform( l_t+y_diff+x_diff ,pts2)
1385
+
1386
+
1387
+ """
1388
+ def get_averaged_transform_mat (img_landmarks,
1389
+ img_landmarks_prev,
1390
+ img_landmarks_next,
1391
+ average_frame_count,
1392
+ average_center_frame_count,
1393
+ output_size, face_type, scale=1.0):
1394
+
1395
+ l_c_list = []
1396
+ tb_diag_vec_list = []
1397
+ bt_diag_vec_list = []
1398
+ mod_list = []
1399
+
1400
+ count = max(average_frame_count,average_center_frame_count)
1401
+ for i in range ( -count, count+1, 1 ):
1402
+ if i < 0:
1403
+ lmrks = img_landmarks_prev[i] if -i < len(img_landmarks_prev) else None
1404
+ elif i > 0:
1405
+ lmrks = img_landmarks_next[i] if i < len(img_landmarks_next) else None
1406
+ else:
1407
+ lmrks = img_landmarks
1408
+
1409
+ if lmrks is None:
1410
+ continue
1411
+
1412
+ l_c, tb_diag_vec, bt_diag_vec, mod = get_transform_mat_data (lmrks, face_type, scale=scale)
1413
+
1414
+ if i >= -average_frame_count and i <= average_frame_count:
1415
+ tb_diag_vec_list.append(tb_diag_vec)
1416
+ bt_diag_vec_list.append(bt_diag_vec)
1417
+ mod_list.append(mod)
1418
+
1419
+ if i >= -average_center_frame_count and i <= average_center_frame_count:
1420
+ l_c_list.append(l_c)
1421
+
1422
+ tb_diag_vec = np.mean( np.array(tb_diag_vec_list), axis=0 )
1423
+ bt_diag_vec = np.mean( np.array(bt_diag_vec_list), axis=0 )
1424
+ mod = np.mean( np.array(mod_list), axis=0 )
1425
+ l_c = np.mean( np.array(l_c_list), axis=0 )
1426
+
1427
+ return get_transform_mat_by_data (l_c, tb_diag_vec, bt_diag_vec, mod, output_size, face_type)
1428
+
1429
+
1430
+ def get_transform_mat (image_landmarks, output_size, face_type, scale=1.0):
1431
+ if not isinstance(image_landmarks, np.ndarray):
1432
+ image_landmarks = np.array (image_landmarks)
1433
+
1434
+ # get face padding value for FaceType
1435
+ padding, remove_align = FaceType_to_padding_remove_align.get(face_type, 0.0)
1436
+
1437
+ # estimate landmarks transform from global space to local aligned space with bounds [0..1]
1438
+ mat = umeyama( np.concatenate ( [ image_landmarks[17:49] , image_landmarks[54:55] ] ) , landmarks_2D_new, True)[0:2]
1439
+
1440
+ # get corner points in global space
1441
+ l_p = transform_points ( np.float32([(0,0),(1,0),(1,1),(0,1),(0.5,0.5)]) , mat, True)
1442
+ l_c = l_p[4]
1443
+
1444
+ # calc diagonal vectors between corners in global space
1445
+ tb_diag_vec = (l_p[2]-l_p[0]).astype(np.float32)
1446
+ tb_diag_vec /= npla.norm(tb_diag_vec)
1447
+ bt_diag_vec = (l_p[1]-l_p[3]).astype(np.float32)
1448
+ bt_diag_vec /= npla.norm(bt_diag_vec)
1449
+
1450
+ # calc modifier of diagonal vectors for scale and padding value
1451
+ mod = (1.0 / scale)* ( npla.norm(l_p[0]-l_p[2])*(padding*np.sqrt(2.0) + 0.5) )
1452
+
1453
+ # calc 3 points in global space to estimate 2d affine transform
1454
+ if not remove_align:
1455
+ l_t = np.array( [ np.round( l_c - tb_diag_vec*mod ),
1456
+ np.round( l_c + bt_diag_vec*mod ),
1457
+ np.round( l_c + tb_diag_vec*mod ) ] )
1458
+ else:
1459
+ # remove_align - face will be centered in the frame but not aligned
1460
+ l_t = np.array( [ np.round( l_c - tb_diag_vec*mod ),
1461
+ np.round( l_c + bt_diag_vec*mod ),
1462
+ np.round( l_c + tb_diag_vec*mod ),
1463
+ np.round( l_c - bt_diag_vec*mod ),
1464
+ ] )
1465
+
1466
+ # get area of face square in global space
1467
+ area = mathlib.polygon_area(l_t[:,0], l_t[:,1] )
1468
+
1469
+ # calc side of square
1470
+ side = np.float32(math.sqrt(area) / 2)
1471
+
1472
+ # calc 3 points with unrotated square
1473
+ l_t = np.array( [ np.round( l_c + [-side,-side] ),
1474
+ np.round( l_c + [ side,-side] ),
1475
+ np.round( l_c + [ side, side] ) ] )
1476
+
1477
+ # calc affine transform from 3 global space points to 3 local space points size of 'output_size'
1478
+ pts2 = np.float32(( (0,0),(output_size,0),(output_size,output_size) ))
1479
+ mat = cv2.getAffineTransform(l_t,pts2)
1480
+
1481
+ return mat
1482
+ """
face_detect/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .face_align_5_landmarks import FaceDetect5Landmarks
2
+ from .face_align_utils import estimate_norm
3
+
face_detect/core/imagelib/SegIEPolys.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ from enum import IntEnum
4
+
5
+
6
+ class SegIEPolyType(IntEnum):
7
+ EXCLUDE = 0
8
+ INCLUDE = 1
9
+
10
+
11
+
12
+ class SegIEPoly():
13
+ def __init__(self, type=None, pts=None, **kwargs):
14
+ self.type = type
15
+
16
+ if pts is None:
17
+ pts = np.empty( (0,2), dtype=np.float32 )
18
+ else:
19
+ pts = np.float32(pts)
20
+ self.pts = pts
21
+ self.n_max = self.n = len(pts)
22
+
23
+ def dump(self):
24
+ return {'type': int(self.type),
25
+ 'pts' : self.get_pts(),
26
+ }
27
+
28
+ def identical(self, b):
29
+ if self.n != b.n:
30
+ return False
31
+ return (self.pts[0:self.n] == b.pts[0:b.n]).all()
32
+
33
+ def get_type(self):
34
+ return self.type
35
+
36
+ def add_pt(self, x, y):
37
+ self.pts = np.append(self.pts[0:self.n], [ ( float(x), float(y) ) ], axis=0).astype(np.float32)
38
+ self.n_max = self.n = self.n + 1
39
+
40
+ def undo(self):
41
+ self.n = max(0, self.n-1)
42
+ return self.n
43
+
44
+ def redo(self):
45
+ self.n = min(len(self.pts), self.n+1)
46
+ return self.n
47
+
48
+ def redo_clip(self):
49
+ self.pts = self.pts[0:self.n]
50
+ self.n_max = self.n
51
+
52
+ def insert_pt(self, n, pt):
53
+ if n < 0 or n > self.n:
54
+ raise ValueError("insert_pt out of range")
55
+ self.pts = np.concatenate( (self.pts[0:n], pt[None,...].astype(np.float32), self.pts[n:]), axis=0)
56
+ self.n_max = self.n = self.n+1
57
+
58
+ def remove_pt(self, n):
59
+ if n < 0 or n >= self.n:
60
+ raise ValueError("remove_pt out of range")
61
+ self.pts = np.concatenate( (self.pts[0:n], self.pts[n+1:]), axis=0)
62
+ self.n_max = self.n = self.n-1
63
+
64
+ def get_last_point(self):
65
+ return self.pts[self.n-1].copy()
66
+
67
+ def get_pts(self):
68
+ return self.pts[0:self.n].copy()
69
+
70
+ def get_pts_count(self):
71
+ return self.n
72
+
73
+ def set_point(self, id, pt):
74
+ self.pts[id] = pt
75
+
76
+ def set_points(self, pts):
77
+ self.pts = np.array(pts)
78
+ self.n_max = self.n = len(pts)
79
+
80
+ def mult_points(self, val):
81
+ self.pts *= val
82
+
83
+
84
+
85
+ class SegIEPolys():
86
+ def __init__(self):
87
+ self.polys = []
88
+
89
+ def identical(self, b):
90
+ polys_len = len(self.polys)
91
+ o_polys_len = len(b.polys)
92
+ if polys_len != o_polys_len:
93
+ return False
94
+
95
+ return all ([ a_poly.identical(b_poly) for a_poly, b_poly in zip(self.polys, b.polys) ])
96
+
97
+ def add_poly(self, ie_poly_type):
98
+ poly = SegIEPoly(ie_poly_type)
99
+ self.polys.append (poly)
100
+ return poly
101
+
102
+ def remove_poly(self, poly):
103
+ if poly in self.polys:
104
+ self.polys.remove(poly)
105
+
106
+ def has_polys(self):
107
+ return len(self.polys) != 0
108
+
109
+ def get_poly(self, id):
110
+ return self.polys[id]
111
+
112
+ def get_polys(self):
113
+ return self.polys
114
+
115
+ def get_pts_count(self):
116
+ return sum([poly.get_pts_count() for poly in self.polys])
117
+
118
+ def sort(self):
119
+ poly_by_type = { SegIEPolyType.EXCLUDE : [], SegIEPolyType.INCLUDE : [] }
120
+
121
+ for poly in self.polys:
122
+ poly_by_type[poly.type].append(poly)
123
+
124
+ self.polys = poly_by_type[SegIEPolyType.INCLUDE] + poly_by_type[SegIEPolyType.EXCLUDE]
125
+
126
+ def __iter__(self):
127
+ for poly in self.polys:
128
+ yield poly
129
+
130
+ def overlay_mask(self, mask):
131
+ h,w,c = mask.shape
132
+ white = (1,)*c
133
+ black = (0,)*c
134
+ for poly in self.polys:
135
+ pts = poly.get_pts().astype(np.int32)
136
+ if len(pts) != 0:
137
+ cv2.fillPoly(mask, [pts], white if poly.type == SegIEPolyType.INCLUDE else black )
138
+
139
+ def dump(self):
140
+ return {'polys' : [ poly.dump() for poly in self.polys ] }
141
+
142
+ def mult_points(self, val):
143
+ for poly in self.polys:
144
+ poly.mult_points(val)
145
+
146
+ @staticmethod
147
+ def load(data=None):
148
+ ie_polys = SegIEPolys()
149
+ if data is not None:
150
+ if isinstance(data, list):
151
+ # Backward comp
152
+ ie_polys.polys = [ SegIEPoly(type=type, pts=pts) for (type, pts) in data ]
153
+ elif isinstance(data, dict):
154
+ ie_polys.polys = [ SegIEPoly(**poly_cfg) for poly_cfg in data['polys'] ]
155
+
156
+ ie_polys.sort()
157
+
158
+ return ie_polys
face_detect/core/imagelib/__init__.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .estimate_sharpness import estimate_sharpness
2
+
3
+ from .equalize_and_stack_square import equalize_and_stack_square
4
+
5
+ # from .text import get_text_image, get_draw_text_lines
6
+
7
+ from .draw import draw_polygon, draw_rect
8
+
9
+ from .morph import morph_by_points
10
+
11
+ from .warp import gen_warp_params, warp_by_params
12
+
13
+ from .reduce_colors import reduce_colors
14
+
15
+ from .color_transfer import color_transfer, color_transfer_mix, color_transfer_sot, color_transfer_mkl, color_transfer_idt, color_hist_match, reinhard_color_transfer, linear_color_transfer
16
+
17
+ from .common import random_crop, normalize_channels, cut_odd_image, overlay_alpha_image
18
+
19
+ from .SegIEPolys import *
20
+
21
+ from .blursharpen import LinearMotionBlur, blursharpen
22
+
23
+ from .filters import apply_random_rgb_levels, \
24
+ apply_random_overlay_triangle, \
25
+ apply_random_hsv_shift, \
26
+ apply_random_sharpen, \
27
+ apply_random_motion_blur, \
28
+ apply_random_gaussian_blur, \
29
+ apply_random_nearest_resize, \
30
+ apply_random_bilinear_resize, \
31
+ apply_random_jpeg_compress, \
32
+ apply_random_relight
face_detect/core/imagelib/blursharpen.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ def LinearMotionBlur(image, size, angle):
5
+ k = np.zeros((size, size), dtype=np.float32)
6
+ k[ (size-1)// 2 , :] = np.ones(size, dtype=np.float32)
7
+ k = cv2.warpAffine(k, cv2.getRotationMatrix2D( (size / 2 -0.5 , size / 2 -0.5 ) , angle, 1.0), (size, size) )
8
+ k = k * ( 1.0 / np.sum(k) )
9
+ return cv2.filter2D(image, -1, k)
10
+
11
+ def blursharpen (img, sharpen_mode=0, kernel_size=3, amount=100):
12
+ if kernel_size % 2 == 0:
13
+ kernel_size += 1
14
+ if amount > 0:
15
+ if sharpen_mode == 1: #box
16
+ kernel = np.zeros( (kernel_size, kernel_size), dtype=np.float32)
17
+ kernel[ kernel_size//2, kernel_size//2] = 1.0
18
+ box_filter = np.ones( (kernel_size, kernel_size), dtype=np.float32) / (kernel_size**2)
19
+ kernel = kernel + (kernel - box_filter) * amount
20
+ return cv2.filter2D(img, -1, kernel)
21
+ elif sharpen_mode == 2: #gaussian
22
+ blur = cv2.GaussianBlur(img, (kernel_size, kernel_size) , 0)
23
+ img = cv2.addWeighted(img, 1.0 + (0.5 * amount), blur, -(0.5 * amount), 0)
24
+ return img
25
+ elif amount < 0:
26
+ n = -amount
27
+ while n > 0:
28
+
29
+ img_blur = cv2.medianBlur(img, 5)
30
+ if int(n / 10) != 0:
31
+ img = img_blur
32
+ else:
33
+ pass_power = (n % 10) / 10.0
34
+ img = img*(1.0-pass_power)+img_blur*pass_power
35
+ n = max(n-10,0)
36
+
37
+ return img
38
+ return img
face_detect/core/imagelib/color_transfer.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numexpr as ne
3
+ import numpy as np
4
+ from numpy import linalg as npla
5
+ import scipy as sp
6
+
7
+
8
+ def color_transfer_sot(src, trg, steps=10, batch_size=5, reg_sigmaXY=16.0, reg_sigmaV=5.0):
9
+ """
10
+ Color Transform via Sliced Optimal Transfer
11
+ ported by @iperov from https://github.com/dcoeurjo/OTColorTransfer
12
+
13
+ src - any float range any channel image
14
+ dst - any float range any channel image, same shape as src
15
+ steps - number of solver steps
16
+ batch_size - solver batch size
17
+ reg_sigmaXY - apply regularization and sigmaXY of filter, otherwise set to 0.0
18
+ reg_sigmaV - sigmaV of filter
19
+
20
+ return value - clip it manually
21
+ """
22
+ if not np.issubdtype(src.dtype, np.floating):
23
+ raise ValueError("src value must be float")
24
+ if not np.issubdtype(trg.dtype, np.floating):
25
+ raise ValueError("trg value must be float")
26
+
27
+ if len(src.shape) != 3:
28
+ raise ValueError("src shape must have rank 3 (h,w,c)")
29
+
30
+ if src.shape != trg.shape:
31
+ raise ValueError("src and trg shapes must be equal")
32
+
33
+ src_dtype = src.dtype
34
+ h, w, c = src.shape
35
+ new_src = src.copy()
36
+
37
+ advect = np.empty((h * w, c), dtype=src_dtype)
38
+ for step in range(steps):
39
+ advect.fill(0)
40
+ for batch in range(batch_size):
41
+ dir = np.random.normal(size=c).astype(src_dtype)
42
+ dir /= npla.norm(dir)
43
+
44
+ projsource = np.sum(new_src * dir, axis=-1).reshape((h * w))
45
+ projtarget = np.sum(trg * dir, axis=-1).reshape((h * w))
46
+
47
+ idSource = np.argsort(projsource)
48
+ idTarget = np.argsort(projtarget)
49
+
50
+ a = projtarget[idTarget] - projsource[idSource]
51
+ for i_c in range(c):
52
+ advect[idSource, i_c] += a * dir[i_c]
53
+ new_src += advect.reshape((h, w, c)) / batch_size
54
+
55
+ if reg_sigmaXY != 0.0:
56
+ src_diff = new_src - src
57
+ src_diff_filt = cv2.bilateralFilter(src_diff, 0, reg_sigmaV, reg_sigmaXY)
58
+ if len(src_diff_filt.shape) == 2:
59
+ src_diff_filt = src_diff_filt[..., None]
60
+ new_src = src + src_diff_filt
61
+ return new_src
62
+
63
+
64
+ def color_transfer_mkl(x0, x1):
65
+ eps = np.finfo(float).eps
66
+
67
+ h, w, c = x0.shape
68
+ h1, w1, c1 = x1.shape
69
+
70
+ x0 = x0.reshape((h * w, c))
71
+ x1 = x1.reshape((h1 * w1, c1))
72
+
73
+ a = np.cov(x0.T)
74
+ b = np.cov(x1.T)
75
+
76
+ Da2, Ua = np.linalg.eig(a)
77
+ Da = np.diag(np.sqrt(Da2.clip(eps, None)))
78
+
79
+ C = np.dot(np.dot(np.dot(np.dot(Da, Ua.T), b), Ua), Da)
80
+
81
+ Dc2, Uc = np.linalg.eig(C)
82
+ Dc = np.diag(np.sqrt(Dc2.clip(eps, None)))
83
+
84
+ Da_inv = np.diag(1. / (np.diag(Da)))
85
+
86
+ t = np.dot(np.dot(np.dot(np.dot(np.dot(np.dot(Ua, Da_inv), Uc), Dc), Uc.T), Da_inv), Ua.T)
87
+
88
+ mx0 = np.mean(x0, axis=0)
89
+ mx1 = np.mean(x1, axis=0)
90
+
91
+ result = np.dot(x0 - mx0, t) + mx1
92
+ return np.clip(result.reshape((h, w, c)).astype(x0.dtype), 0, 1)
93
+
94
+
95
+ def color_transfer_idt(i0, i1, bins=256, n_rot=20):
96
+ import scipy.stats
97
+
98
+ relaxation = 1 / n_rot
99
+ h, w, c = i0.shape
100
+ h1, w1, c1 = i1.shape
101
+
102
+ i0 = i0.reshape((h * w, c))
103
+ i1 = i1.reshape((h1 * w1, c1))
104
+
105
+ n_dims = c
106
+
107
+ d0 = i0.T
108
+ d1 = i1.T
109
+
110
+ for i in range(n_rot):
111
+
112
+ r = sp.stats.special_ortho_group.rvs(n_dims).astype(np.float32)
113
+
114
+ d0r = np.dot(r, d0)
115
+ d1r = np.dot(r, d1)
116
+ d_r = np.empty_like(d0)
117
+
118
+ for j in range(n_dims):
119
+ lo = min(d0r[j].min(), d1r[j].min())
120
+ hi = max(d0r[j].max(), d1r[j].max())
121
+
122
+ p0r, edges = np.histogram(d0r[j], bins=bins, range=[lo, hi])
123
+ p1r, _ = np.histogram(d1r[j], bins=bins, range=[lo, hi])
124
+
125
+ cp0r = p0r.cumsum().astype(np.float32)
126
+ cp0r /= cp0r[-1]
127
+
128
+ cp1r = p1r.cumsum().astype(np.float32)
129
+ cp1r /= cp1r[-1]
130
+
131
+ f = np.interp(cp0r, cp1r, edges[1:])
132
+
133
+ d_r[j] = np.interp(d0r[j], edges[1:], f, left=0, right=bins)
134
+
135
+ d0 = relaxation * np.linalg.solve(r, (d_r - d0r)) + d0
136
+
137
+ return np.clip(d0.T.reshape((h, w, c)).astype(i0.dtype), 0, 1)
138
+
139
+
140
+ def reinhard_color_transfer(target: np.ndarray, source: np.ndarray, target_mask: np.ndarray = None,
141
+ source_mask: np.ndarray = None, mask_cutoff=0.5) -> np.ndarray:
142
+ """
143
+ Transfer color using rct method.
144
+ target np.ndarray H W 3C (BGR) np.float32
145
+ source np.ndarray H W 3C (BGR) np.float32
146
+ target_mask(None) np.ndarray H W 1C np.float32
147
+ source_mask(None) np.ndarray H W 1C np.float32
148
+
149
+ mask_cutoff(0.5) float
150
+ masks are used to limit the space where color statistics will be computed to adjust the target
151
+ reference: Color Transfer between Images https://www.cs.tau.ac.il/~turkel/imagepapers/ColorTransfer.pdf
152
+ """
153
+ source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB)
154
+ target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB)
155
+
156
+ source_input = source
157
+ if source_mask is not None:
158
+ source_input = source_input.copy()
159
+ source_input[source_mask[..., 0] < mask_cutoff] = [0, 0, 0]
160
+
161
+ target_input = target
162
+ if target_mask is not None:
163
+ target_input = target_input.copy()
164
+ target_input[target_mask[..., 0] < mask_cutoff] = [0, 0, 0]
165
+
166
+ target_l_mean, target_l_std, target_a_mean, target_a_std, target_b_mean, target_b_std, \
167
+ = target_input[..., 0].mean(), target_input[..., 0].std(), target_input[..., 1].mean(), target_input[
168
+ ..., 1].std(), target_input[..., 2].mean(), target_input[..., 2].std()
169
+
170
+ source_l_mean, source_l_std, source_a_mean, source_a_std, source_b_mean, source_b_std, \
171
+ = source_input[..., 0].mean(), source_input[..., 0].std(), source_input[..., 1].mean(), source_input[
172
+ ..., 1].std(), source_input[..., 2].mean(), source_input[..., 2].std()
173
+
174
+ # not as in the paper: scale by the standard deviations using reciprocal of paper proposed factor
175
+ target_l = target[..., 0]
176
+ target_l = ne.evaluate('(target_l - target_l_mean) * source_l_std / target_l_std + source_l_mean')
177
+
178
+ target_a = target[..., 1]
179
+ target_a = ne.evaluate('(target_a - target_a_mean) * source_a_std / target_a_std + source_a_mean')
180
+
181
+ target_b = target[..., 2]
182
+ target_b = ne.evaluate('(target_b - target_b_mean) * source_b_std / target_b_std + source_b_mean')
183
+
184
+ np.clip(target_l, 0, 100, out=target_l)
185
+ np.clip(target_a, -127, 127, out=target_a)
186
+ np.clip(target_b, -127, 127, out=target_b)
187
+
188
+ return cv2.cvtColor(np.stack([target_l, target_a, target_b], -1), cv2.COLOR_LAB2BGR)
189
+
190
+ def linear_color_transfer(target_img, source_img, mode='pca', eps=1e-5):
191
+ '''
192
+ Matches the colour distribution of the target image to that of the source image
193
+ using a linear transform.
194
+ Images are expected to be of form (w,h,c) and float in [0,1].
195
+ Modes are chol, pca or sym for different choices of basis.
196
+ '''
197
+ mu_t = target_img.mean(0).mean(0)
198
+ t = target_img - mu_t
199
+ t = t.transpose(2, 0, 1).reshape(t.shape[-1], -1)
200
+ Ct = t.dot(t.T) / t.shape[1] + eps * np.eye(t.shape[0])
201
+ mu_s = source_img.mean(0).mean(0)
202
+ s = source_img - mu_s
203
+ s = s.transpose(2, 0, 1).reshape(s.shape[-1], -1)
204
+ Cs = s.dot(s.T) / s.shape[1] + eps * np.eye(s.shape[0])
205
+ if mode == 'chol':
206
+ chol_t = np.linalg.cholesky(Ct)
207
+ chol_s = np.linalg.cholesky(Cs)
208
+ ts = chol_s.dot(np.linalg.inv(chol_t)).dot(t)
209
+ if mode == 'pca':
210
+ eva_t, eve_t = np.linalg.eigh(Ct)
211
+ Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T)
212
+ eva_s, eve_s = np.linalg.eigh(Cs)
213
+ Qs = eve_s.dot(np.sqrt(np.diag(eva_s))).dot(eve_s.T)
214
+ ts = Qs.dot(np.linalg.inv(Qt)).dot(t)
215
+ if mode == 'sym':
216
+ eva_t, eve_t = np.linalg.eigh(Ct)
217
+ Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T)
218
+ Qt_Cs_Qt = Qt.dot(Cs).dot(Qt)
219
+ eva_QtCsQt, eve_QtCsQt = np.linalg.eigh(Qt_Cs_Qt)
220
+ QtCsQt = eve_QtCsQt.dot(np.sqrt(np.diag(eva_QtCsQt))).dot(eve_QtCsQt.T)
221
+ ts = np.linalg.inv(Qt).dot(QtCsQt).dot(np.linalg.inv(Qt)).dot(t)
222
+ matched_img = ts.reshape(*target_img.transpose(2, 0, 1).shape).transpose(1, 2, 0)
223
+ matched_img += mu_s
224
+ matched_img[matched_img > 1] = 1
225
+ matched_img[matched_img < 0] = 0
226
+ return np.clip(matched_img.astype(source_img.dtype), 0, 1)
227
+
228
+
229
+ def lab_image_stats(image):
230
+ # compute the mean and standard deviation of each channel
231
+ (l, a, b) = cv2.split(image)
232
+ (lMean, lStd) = (l.mean(), l.std())
233
+ (aMean, aStd) = (a.mean(), a.std())
234
+ (bMean, bStd) = (b.mean(), b.std())
235
+
236
+ # return the color statistics
237
+ return (lMean, lStd, aMean, aStd, bMean, bStd)
238
+
239
+
240
+ def _scale_array(arr, clip=True):
241
+ if clip:
242
+ return np.clip(arr, 0, 255)
243
+
244
+ mn = arr.min()
245
+ mx = arr.max()
246
+ scale_range = (max([mn, 0]), min([mx, 255]))
247
+
248
+ if mn < scale_range[0] or mx > scale_range[1]:
249
+ return (scale_range[1] - scale_range[0]) * (arr - mn) / (mx - mn) + scale_range[0]
250
+
251
+ return arr
252
+
253
+
254
+ def channel_hist_match(source, template, hist_match_threshold=255, mask=None):
255
+ # Code borrowed from:
256
+ # https://stackoverflow.com/questions/32655686/histogram-matching-of-two-images-in-python-2-x
257
+ masked_source = source
258
+ masked_template = template
259
+
260
+ if mask is not None:
261
+ masked_source = source * mask
262
+ masked_template = template * mask
263
+
264
+ oldshape = source.shape
265
+ source = source.ravel()
266
+ template = template.ravel()
267
+ masked_source = masked_source.ravel()
268
+ masked_template = masked_template.ravel()
269
+ s_values, bin_idx, s_counts = np.unique(source, return_inverse=True,
270
+ return_counts=True)
271
+ t_values, t_counts = np.unique(template, return_counts=True)
272
+
273
+ s_quantiles = np.cumsum(s_counts).astype(np.float64)
274
+ s_quantiles = hist_match_threshold * s_quantiles / s_quantiles[-1]
275
+ t_quantiles = np.cumsum(t_counts).astype(np.float64)
276
+ t_quantiles = 255 * t_quantiles / t_quantiles[-1]
277
+ interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)
278
+
279
+ return interp_t_values[bin_idx].reshape(oldshape)
280
+
281
+
282
+ def color_hist_match(src_im, tar_im, hist_match_threshold=255):
283
+ h, w, c = src_im.shape
284
+ matched_R = channel_hist_match(src_im[:, :, 0], tar_im[:, :, 0], hist_match_threshold, None)
285
+ matched_G = channel_hist_match(src_im[:, :, 1], tar_im[:, :, 1], hist_match_threshold, None)
286
+ matched_B = channel_hist_match(src_im[:, :, 2], tar_im[:, :, 2], hist_match_threshold, None)
287
+
288
+ to_stack = (matched_R, matched_G, matched_B)
289
+ for i in range(3, c):
290
+ to_stack += (src_im[:, :, i],)
291
+
292
+ matched = np.stack(to_stack, axis=-1).astype(src_im.dtype)
293
+ return matched
294
+
295
+
296
+ def color_transfer_mix(img_src, img_trg):
297
+ img_src = np.clip(img_src * 255.0, 0, 255).astype(np.uint8)
298
+ img_trg = np.clip(img_trg * 255.0, 0, 255).astype(np.uint8)
299
+
300
+ img_src_lab = cv2.cvtColor(img_src, cv2.COLOR_BGR2LAB)
301
+ img_trg_lab = cv2.cvtColor(img_trg, cv2.COLOR_BGR2LAB)
302
+
303
+ rct_light = np.clip(linear_color_transfer(img_src_lab[..., 0:1].astype(np.float32) / 255.0,
304
+ img_trg_lab[..., 0:1].astype(np.float32) / 255.0)[..., 0] * 255.0,
305
+ 0, 255).astype(np.uint8)
306
+
307
+ img_src_lab[..., 0] = (np.ones_like(rct_light) * 100).astype(np.uint8)
308
+ img_src_lab = cv2.cvtColor(img_src_lab, cv2.COLOR_LAB2BGR)
309
+
310
+ img_trg_lab[..., 0] = (np.ones_like(rct_light) * 100).astype(np.uint8)
311
+ img_trg_lab = cv2.cvtColor(img_trg_lab, cv2.COLOR_LAB2BGR)
312
+
313
+ img_rct = color_transfer_sot(img_src_lab.astype(np.float32), img_trg_lab.astype(np.float32))
314
+ img_rct = np.clip(img_rct, 0, 255).astype(np.uint8)
315
+
316
+ img_rct = cv2.cvtColor(img_rct, cv2.COLOR_BGR2LAB)
317
+ img_rct[..., 0] = rct_light
318
+ img_rct = cv2.cvtColor(img_rct, cv2.COLOR_LAB2BGR)
319
+
320
+ return (img_rct / 255.0).astype(np.float32)
321
+
322
+
323
+ def color_transfer(ct_mode, img_src, img_trg):
324
+ """
325
+ color transfer for [0,1] float32 inputs
326
+ """
327
+ if ct_mode == 'lct':
328
+ out = linear_color_transfer(img_src, img_trg)
329
+ elif ct_mode == 'rct':
330
+ out = reinhard_color_transfer(img_src, img_trg)
331
+ elif ct_mode == 'mkl':
332
+ out = color_transfer_mkl(img_src, img_trg)
333
+ elif ct_mode == 'idt':
334
+ out = color_transfer_idt(img_src, img_trg)
335
+ elif ct_mode == 'sot':
336
+ out = color_transfer_sot(img_src, img_trg)
337
+ out = np.clip(out, 0.0, 1.0)
338
+ else:
339
+ raise ValueError(f"unknown ct_mode {ct_mode}")
340
+ return out
face_detect/core/imagelib/common.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+
4
+ def random_crop(img, w, h):
5
+ height, width = img.shape[:2]
6
+
7
+ h_rnd = height - h
8
+ w_rnd = width - w
9
+
10
+ y = np.random.randint(0, h_rnd) if h_rnd > 0 else 0
11
+ x = np.random.randint(0, w_rnd) if w_rnd > 0 else 0
12
+
13
+ return img[y:y + height, x:x + width]
14
+
15
+
16
+ def normalize_channels(img, target_channels):
17
+ img_shape_len = len(img.shape)
18
+ if img_shape_len == 2:
19
+ h, w = img.shape
20
+ c = 0
21
+ elif img_shape_len == 3:
22
+ h, w, c = img.shape
23
+ else:
24
+ raise ValueError("normalize: incorrect image dimensions.")
25
+
26
+ if c == 0 and target_channels > 0:
27
+ img = img[..., np.newaxis]
28
+ c = 1
29
+
30
+ if c == 1 and target_channels > 1:
31
+ img = np.repeat(img, target_channels, -1)
32
+ c = target_channels
33
+
34
+ if c > target_channels:
35
+ img = img[..., 0:target_channels]
36
+ c = target_channels
37
+
38
+ return img
39
+
40
+
41
+ def cut_odd_image(img):
42
+ h, w, c = img.shape
43
+ wm, hm = w % 2, h % 2
44
+ if wm + hm != 0:
45
+ img = img[0:h - hm, 0:w - wm, :]
46
+ return img
47
+
48
+
49
+ def overlay_alpha_image(img_target, img_source, xy_offset=(0, 0)):
50
+ (h, w, c) = img_source.shape
51
+ if c != 4:
52
+ raise ValueError("overlay_alpha_image, img_source must have 4 channels")
53
+
54
+ x1, x2 = xy_offset[0], xy_offset[0] + w
55
+ y1, y2 = xy_offset[1], xy_offset[1] + h
56
+
57
+ alpha_s = img_source[:, :, 3] / 255.0
58
+ alpha_l = 1.0 - alpha_s
59
+
60
+ for c in range(0, 3):
61
+ img_target[y1:y2, x1:x2, c] = (alpha_s * img_source[:, :, c] +
62
+ alpha_l * img_target[y1:y2, x1:x2, c])
face_detect/core/imagelib/draw.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+ def draw_polygon (image, points, color, thickness = 1):
5
+ points_len = len(points)
6
+ for i in range (0, points_len):
7
+ p0 = tuple( points[i] )
8
+ p1 = tuple( points[ (i+1) % points_len] )
9
+ cv2.line (image, p0, p1, color, thickness=thickness)
10
+
11
+ def draw_rect(image, rect, color, thickness=1):
12
+ l,t,r,b = rect
13
+ draw_polygon (image, [ (l,t), (r,t), (r,b), (l,b ) ], color, thickness)
face_detect/core/imagelib/equalize_and_stack_square.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+ def equalize_and_stack_square (images, axis=1):
5
+ max_c = max ([ 1 if len(image.shape) == 2 else image.shape[2] for image in images ] )
6
+
7
+ target_wh = 99999
8
+ for i,image in enumerate(images):
9
+ if len(image.shape) == 2:
10
+ h,w = image.shape
11
+ c = 1
12
+ else:
13
+ h,w,c = image.shape
14
+
15
+ if h < target_wh:
16
+ target_wh = h
17
+
18
+ if w < target_wh:
19
+ target_wh = w
20
+
21
+ for i,image in enumerate(images):
22
+ if len(image.shape) == 2:
23
+ h,w = image.shape
24
+ c = 1
25
+ else:
26
+ h,w,c = image.shape
27
+
28
+ if c < max_c:
29
+ if c == 1:
30
+ if len(image.shape) == 2:
31
+ image = np.expand_dims ( image, -1 )
32
+ image = np.concatenate ( (image,)*max_c, -1 )
33
+ elif c == 2: #GA
34
+ image = np.expand_dims ( image[...,0], -1 )
35
+ image = np.concatenate ( (image,)*max_c, -1 )
36
+ else:
37
+ image = np.concatenate ( (image, np.ones((h,w,max_c - c))), -1 )
38
+
39
+ if h != target_wh or w != target_wh:
40
+ image = cv2.resize ( image, (target_wh, target_wh) )
41
+ h,w,c = image.shape
42
+
43
+ images[i] = image
44
+
45
+ return np.concatenate ( images, axis = 1 )
face_detect/core/imagelib/estimate_sharpness.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Copyright (c) 2009-2010 Arizona Board of Regents. All Rights Reserved.
3
+ Contact: Lina Karam (karam@asu.edu) and Niranjan Narvekar (nnarveka@asu.edu)
4
+ Image, Video, and Usabilty (IVU) Lab, http://ivulab.asu.edu , Arizona State University
5
+ This copyright statement may not be removed from any file containing it or from modifications to these files.
6
+ This copyright notice must also be included in any file or product that is derived from the source files.
7
+
8
+ Redistribution and use of this code in source and binary forms, with or without modification, are permitted provided that the
9
+ following conditions are met:
10
+ - Redistribution's of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11
+ - Redistribution's in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
12
+ in the documentation and/or other materials provided with the distribution.
13
+ - The Image, Video, and Usability Laboratory (IVU Lab, http://ivulab.asu.edu) is acknowledged in any publication that
14
+ reports research results using this code, copies of this code, or modifications of this code.
15
+ The code and our papers are to be cited in the bibliography as:
16
+
17
+ N. D. Narvekar and L. J. Karam, "CPBD Sharpness Metric Software", http://ivulab.asu.edu/Quality/CPBD
18
+
19
+ N. D. Narvekar and L. J. Karam, "A No-Reference Image Blur Metric Based on the Cumulative
20
+ Probability of Blur Detection (CPBD)," accepted and to appear in the IEEE Transactions on Image Processing, 2011.
21
+
22
+ N. D. Narvekar and L. J. Karam, "An Improved No-Reference Sharpness Metric Based on the Probability of Blur Detection," International Workshop on Video Processing and Quality Metrics for Consumer Electronics (VPQM), January 2010, http://www.vpqm.org (pdf)
23
+
24
+ N. D. Narvekar and L. J. Karam, "A No Reference Perceptual Quality Metric based on Cumulative Probability of Blur Detection," First International Workshop on the Quality of Multimedia Experience (QoMEX), pp. 87-91, July 2009.
25
+
26
+ DISCLAIMER:
27
+ This software is provided by the copyright holders and contributors "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the Arizona Board of Regents, Arizona State University, IVU Lab members, authors or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute
28
+ goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
29
+ """
30
+
31
+ import numpy as np
32
+ import cv2
33
+ from math import atan2, pi
34
+
35
+
36
+ def sobel(image):
37
+ # type: (numpy.ndarray) -> numpy.ndarray
38
+ """
39
+ Find edges using the Sobel approximation to the derivatives.
40
+
41
+ Inspired by the [Octave implementation](https://sourceforge.net/p/octave/image/ci/default/tree/inst/edge.m#l196).
42
+ """
43
+ from skimage.filters.edges import HSOBEL_WEIGHTS
44
+ h1 = np.array(HSOBEL_WEIGHTS)
45
+ h1 /= np.sum(abs(h1)) # normalize h1
46
+
47
+ from scipy.ndimage import convolve
48
+ strength2 = np.square(convolve(image, h1.T))
49
+
50
+ # Note: https://sourceforge.net/p/octave/image/ci/default/tree/inst/edge.m#l59
51
+ thresh2 = 2 * np.sqrt(np.mean(strength2))
52
+
53
+ strength2[strength2 <= thresh2] = 0
54
+ return _simple_thinning(strength2)
55
+
56
+
57
+ def _simple_thinning(strength):
58
+ # type: (numpy.ndarray) -> numpy.ndarray
59
+ """
60
+ Perform a very simple thinning.
61
+
62
+ Inspired by the [Octave implementation](https://sourceforge.net/p/octave/image/ci/default/tree/inst/edge.m#l512).
63
+ """
64
+ num_rows, num_cols = strength.shape
65
+
66
+ zero_column = np.zeros((num_rows, 1))
67
+ zero_row = np.zeros((1, num_cols))
68
+
69
+ x = (
70
+ (strength > np.c_[zero_column, strength[:, :-1]]) &
71
+ (strength > np.c_[strength[:, 1:], zero_column])
72
+ )
73
+
74
+ y = (
75
+ (strength > np.r_[zero_row, strength[:-1, :]]) &
76
+ (strength > np.r_[strength[1:, :], zero_row])
77
+ )
78
+
79
+ return x | y
80
+
81
+
82
+
83
+
84
+
85
+ # threshold to characterize blocks as edge/non-edge blocks
86
+ THRESHOLD = 0.002
87
+ # fitting parameter
88
+ BETA = 3.6
89
+ # block size
90
+ BLOCK_HEIGHT, BLOCK_WIDTH = (64, 64)
91
+ # just noticeable widths based on the perceptual experiments
92
+ WIDTH_JNB = np.concatenate([5*np.ones(51), 3*np.ones(205)])
93
+
94
+
95
+ def compute(image):
96
+ # type: (numpy.ndarray) -> float
97
+ """Compute the sharpness metric for the given data."""
98
+
99
+ # convert the image to double for further processing
100
+ image = image.astype(np.float64)
101
+
102
+ # edge detection using canny and sobel canny edge detection is done to
103
+ # classify the blocks as edge or non-edge blocks and sobel edge
104
+ # detection is done for the purpose of edge width measurement.
105
+ from skimage.feature import canny
106
+ canny_edges = canny(image)
107
+ sobel_edges = sobel(image)
108
+
109
+ # edge width calculation
110
+ marziliano_widths = marziliano_method(sobel_edges, image)
111
+
112
+ # sharpness metric calculation
113
+ return _calculate_sharpness_metric(image, canny_edges, marziliano_widths)
114
+
115
+
116
+ def marziliano_method(edges, image):
117
+ # type: (numpy.ndarray, numpy.ndarray) -> numpy.ndarray
118
+ """
119
+ Calculate the widths of the given edges.
120
+
121
+ :return: A matrix with the same dimensions as the given image with 0's at
122
+ non-edge locations and edge-widths at the edge locations.
123
+ """
124
+
125
+ # `edge_widths` consists of zero and non-zero values. A zero value
126
+ # indicates that there is no edge at that position and a non-zero value
127
+ # indicates that there is an edge at that position and the value itself
128
+ # gives the edge width.
129
+ edge_widths = np.zeros(image.shape)
130
+
131
+ # find the gradient for the image
132
+ gradient_y, gradient_x = np.gradient(image)
133
+
134
+ # dimensions of the image
135
+ img_height, img_width = image.shape
136
+
137
+ # holds the angle information of the edges
138
+ edge_angles = np.zeros(image.shape)
139
+
140
+ # calculate the angle of the edges
141
+ for row in range(img_height):
142
+ for col in range(img_width):
143
+ if gradient_x[row, col] != 0:
144
+ edge_angles[row, col] = atan2(gradient_y[row, col], gradient_x[row, col]) * (180 / pi)
145
+ elif gradient_x[row, col] == 0 and gradient_y[row, col] == 0:
146
+ edge_angles[row,col] = 0
147
+ elif gradient_x[row, col] == 0 and gradient_y[row, col] == pi/2:
148
+ edge_angles[row, col] = 90
149
+
150
+
151
+ if np.any(edge_angles):
152
+
153
+ # quantize the angle
154
+ quantized_angles = 45 * np.round(edge_angles / 45)
155
+
156
+ for row in range(1, img_height - 1):
157
+ for col in range(1, img_width - 1):
158
+ if edges[row, col] == 1:
159
+
160
+ # gradient angle = 180 or -180
161
+ if quantized_angles[row, col] == 180 or quantized_angles[row, col] == -180:
162
+ for margin in range(100 + 1):
163
+ inner_border = (col - 1) - margin
164
+ outer_border = (col - 2) - margin
165
+
166
+ # outside image or intensity increasing from left to right
167
+ if outer_border < 0 or (image[row, outer_border] - image[row, inner_border]) <= 0:
168
+ break
169
+
170
+ width_left = margin + 1
171
+
172
+ for margin in range(100 + 1):
173
+ inner_border = (col + 1) + margin
174
+ outer_border = (col + 2) + margin
175
+
176
+ # outside image or intensity increasing from left to right
177
+ if outer_border >= img_width or (image[row, outer_border] - image[row, inner_border]) >= 0:
178
+ break
179
+
180
+ width_right = margin + 1
181
+
182
+ edge_widths[row, col] = width_left + width_right
183
+
184
+
185
+ # gradient angle = 0
186
+ if quantized_angles[row, col] == 0:
187
+ for margin in range(100 + 1):
188
+ inner_border = (col - 1) - margin
189
+ outer_border = (col - 2) - margin
190
+
191
+ # outside image or intensity decreasing from left to right
192
+ if outer_border < 0 or (image[row, outer_border] - image[row, inner_border]) >= 0:
193
+ break
194
+
195
+ width_left = margin + 1
196
+
197
+ for margin in range(100 + 1):
198
+ inner_border = (col + 1) + margin
199
+ outer_border = (col + 2) + margin
200
+
201
+ # outside image or intensity decreasing from left to right
202
+ if outer_border >= img_width or (image[row, outer_border] - image[row, inner_border]) <= 0:
203
+ break
204
+
205
+ width_right = margin + 1
206
+
207
+ edge_widths[row, col] = width_right + width_left
208
+
209
+ return edge_widths
210
+
211
+
212
+ def _calculate_sharpness_metric(image, edges, edge_widths):
213
+ # type: (numpy.array, numpy.array, numpy.array) -> numpy.float64
214
+
215
+ # get the size of image
216
+ img_height, img_width = image.shape
217
+
218
+ total_num_edges = 0
219
+ hist_pblur = np.zeros(101)
220
+
221
+ # maximum block indices
222
+ num_blocks_vertically = int(img_height / BLOCK_HEIGHT)
223
+ num_blocks_horizontally = int(img_width / BLOCK_WIDTH)
224
+
225
+ # loop over the blocks
226
+ for i in range(num_blocks_vertically):
227
+ for j in range(num_blocks_horizontally):
228
+
229
+ # get the row and col indices for the block pixel positions
230
+ rows = slice(BLOCK_HEIGHT * i, BLOCK_HEIGHT * (i + 1))
231
+ cols = slice(BLOCK_WIDTH * j, BLOCK_WIDTH * (j + 1))
232
+
233
+ if is_edge_block(edges[rows, cols], THRESHOLD):
234
+ block_widths = edge_widths[rows, cols]
235
+ # rotate block to simulate column-major boolean indexing
236
+ block_widths = np.rot90(np.flipud(block_widths), 3)
237
+ block_widths = block_widths[block_widths != 0]
238
+
239
+ block_contrast = get_block_contrast(image[rows, cols])
240
+ block_jnb = WIDTH_JNB[block_contrast]
241
+
242
+ # calculate the probability of blur detection at the edges
243
+ # detected in the block
244
+ prob_blur_detection = 1 - np.exp(-abs(block_widths/block_jnb) ** BETA)
245
+
246
+ # update the statistics using the block information
247
+ for probability in prob_blur_detection:
248
+ bucket = int(round(probability * 100))
249
+ hist_pblur[bucket] += 1
250
+ total_num_edges += 1
251
+
252
+ # normalize the pdf
253
+ if total_num_edges > 0:
254
+ hist_pblur = hist_pblur / total_num_edges
255
+
256
+ # calculate the sharpness metric
257
+ return np.sum(hist_pblur[:64])
258
+
259
+
260
+ def is_edge_block(block, threshold):
261
+ # type: (numpy.ndarray, float) -> bool
262
+ """Decide whether the given block is an edge block."""
263
+ return np.count_nonzero(block) > (block.size * threshold)
264
+
265
+
266
+ def get_block_contrast(block):
267
+ # type: (numpy.ndarray) -> int
268
+ return int(np.max(block) - np.min(block))
269
+
270
+
271
+ def estimate_sharpness(image):
272
+ if image.ndim == 3:
273
+ if image.shape[2] > 1:
274
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
275
+ else:
276
+ image = image[...,0]
277
+
278
+ return compute(image)
face_detect/core/imagelib/filters.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from .blursharpen import LinearMotionBlur, blursharpen
3
+ import cv2
4
+
5
+ def apply_random_rgb_levels(img, mask=None, rnd_state=None):
6
+ if rnd_state is None:
7
+ rnd_state = np.random
8
+ np_rnd = rnd_state.rand
9
+
10
+ inBlack = np.array([np_rnd()*0.25 , np_rnd()*0.25 , np_rnd()*0.25], dtype=np.float32)
11
+ inWhite = np.array([1.0-np_rnd()*0.25, 1.0-np_rnd()*0.25, 1.0-np_rnd()*0.25], dtype=np.float32)
12
+ inGamma = np.array([0.5+np_rnd(), 0.5+np_rnd(), 0.5+np_rnd()], dtype=np.float32)
13
+
14
+ outBlack = np.array([np_rnd()*0.25 , np_rnd()*0.25 , np_rnd()*0.25], dtype=np.float32)
15
+ outWhite = np.array([1.0-np_rnd()*0.25, 1.0-np_rnd()*0.25, 1.0-np_rnd()*0.25], dtype=np.float32)
16
+
17
+ result = np.clip( (img - inBlack) / (inWhite - inBlack), 0, 1 )
18
+ result = ( result ** (1/inGamma) ) * (outWhite - outBlack) + outBlack
19
+ result = np.clip(result, 0, 1)
20
+
21
+ if mask is not None:
22
+ result = img*(1-mask) + result*mask
23
+
24
+ return result
25
+
26
+ def apply_random_hsv_shift(img, mask=None, rnd_state=None):
27
+ if rnd_state is None:
28
+ rnd_state = np.random
29
+
30
+ h, s, v = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
31
+ h = ( h + rnd_state.randint(360) ) % 360
32
+ s = np.clip ( s + rnd_state.random()-0.5, 0, 1 )
33
+ v = np.clip ( v + rnd_state.random()-0.5, 0, 1 )
34
+
35
+ result = np.clip( cv2.cvtColor(cv2.merge([h, s, v]), cv2.COLOR_HSV2BGR) , 0, 1 )
36
+ if mask is not None:
37
+ result = img*(1-mask) + result*mask
38
+
39
+ return result
40
+
41
+ def apply_random_sharpen( img, chance, kernel_max_size, mask=None, rnd_state=None ):
42
+ if rnd_state is None:
43
+ rnd_state = np.random
44
+
45
+ sharp_rnd_kernel = rnd_state.randint(kernel_max_size)+1
46
+
47
+ result = img
48
+ if rnd_state.randint(100) < np.clip(chance, 0, 100):
49
+ if rnd_state.randint(2) == 0:
50
+ result = blursharpen(result, 1, sharp_rnd_kernel, rnd_state.randint(10) )
51
+ else:
52
+ result = blursharpen(result, 2, sharp_rnd_kernel, rnd_state.randint(50) )
53
+
54
+ if mask is not None:
55
+ result = img*(1-mask) + result*mask
56
+
57
+ return result
58
+
59
+ def apply_random_motion_blur( img, chance, mb_max_size, mask=None, rnd_state=None ):
60
+ if rnd_state is None:
61
+ rnd_state = np.random
62
+
63
+ mblur_rnd_kernel = rnd_state.randint(mb_max_size)+1
64
+ mblur_rnd_deg = rnd_state.randint(360)
65
+
66
+ result = img
67
+ if rnd_state.randint(100) < np.clip(chance, 0, 100):
68
+ result = LinearMotionBlur (result, mblur_rnd_kernel, mblur_rnd_deg )
69
+ if mask is not None:
70
+ result = img*(1-mask) + result*mask
71
+
72
+ return result
73
+
74
+ def apply_random_gaussian_blur( img, chance, kernel_max_size, mask=None, rnd_state=None ):
75
+ if rnd_state is None:
76
+ rnd_state = np.random
77
+
78
+ result = img
79
+ if rnd_state.randint(100) < np.clip(chance, 0, 100):
80
+ gblur_rnd_kernel = rnd_state.randint(kernel_max_size)*2+1
81
+ result = cv2.GaussianBlur(result, (gblur_rnd_kernel,)*2 , 0)
82
+ if mask is not None:
83
+ result = img*(1-mask) + result*mask
84
+
85
+ return result
86
+
87
+ def apply_random_resize( img, chance, max_size_per, interpolation=cv2.INTER_LINEAR, mask=None, rnd_state=None ):
88
+ if rnd_state is None:
89
+ rnd_state = np.random
90
+
91
+ result = img
92
+ if rnd_state.randint(100) < np.clip(chance, 0, 100):
93
+ h,w,c = result.shape
94
+
95
+ trg = rnd_state.rand()
96
+ rw = w - int( trg * int(w*(max_size_per/100.0)) )
97
+ rh = h - int( trg * int(h*(max_size_per/100.0)) )
98
+
99
+ result = cv2.resize (result, (rw,rh), interpolation=interpolation )
100
+ result = cv2.resize (result, (w,h), interpolation=interpolation )
101
+ if mask is not None:
102
+ result = img*(1-mask) + result*mask
103
+
104
+ return result
105
+
106
+ def apply_random_nearest_resize( img, chance, max_size_per, mask=None, rnd_state=None ):
107
+ return apply_random_resize( img, chance, max_size_per, interpolation=cv2.INTER_NEAREST, mask=mask, rnd_state=rnd_state )
108
+
109
+ def apply_random_bilinear_resize( img, chance, max_size_per, mask=None, rnd_state=None ):
110
+ return apply_random_resize( img, chance, max_size_per, interpolation=cv2.INTER_LINEAR, mask=mask, rnd_state=rnd_state )
111
+
112
+ def apply_random_jpeg_compress( img, chance, mask=None, rnd_state=None ):
113
+ if rnd_state is None:
114
+ rnd_state = np.random
115
+
116
+ result = img
117
+ if rnd_state.randint(100) < np.clip(chance, 0, 100):
118
+ h,w,c = result.shape
119
+
120
+ quality = rnd_state.randint(10,101)
121
+
122
+ ret, result = cv2.imencode('.jpg', np.clip(img*255, 0,255).astype(np.uint8), [int(cv2.IMWRITE_JPEG_QUALITY), quality] )
123
+ if ret == True:
124
+ result = cv2.imdecode(result, flags=cv2.IMREAD_UNCHANGED)
125
+ result = result.astype(np.float32) / 255.0
126
+ if mask is not None:
127
+ result = img*(1-mask) + result*mask
128
+
129
+ return result
130
+
131
+ def apply_random_overlay_triangle( img, max_alpha, mask=None, rnd_state=None ):
132
+ if rnd_state is None:
133
+ rnd_state = np.random
134
+
135
+ h,w,c = img.shape
136
+ pt1 = [rnd_state.randint(w), rnd_state.randint(h) ]
137
+ pt2 = [rnd_state.randint(w), rnd_state.randint(h) ]
138
+ pt3 = [rnd_state.randint(w), rnd_state.randint(h) ]
139
+
140
+ alpha = rnd_state.uniform()*max_alpha
141
+
142
+ tri_mask = cv2.fillPoly( np.zeros_like(img), [ np.array([pt1,pt2,pt3], np.int32) ], (alpha,)*c )
143
+
144
+ if rnd_state.randint(2) == 0:
145
+ result = np.clip(img+tri_mask, 0, 1)
146
+ else:
147
+ result = np.clip(img-tri_mask, 0, 1)
148
+
149
+ if mask is not None:
150
+ result = img*(1-mask) + result*mask
151
+
152
+ return result
153
+
154
+ def _min_resize(x, m):
155
+ if x.shape[0] < x.shape[1]:
156
+ s0 = m
157
+ s1 = int(float(m) / float(x.shape[0]) * float(x.shape[1]))
158
+ else:
159
+ s0 = int(float(m) / float(x.shape[1]) * float(x.shape[0]))
160
+ s1 = m
161
+ new_max = min(s1, s0)
162
+ raw_max = min(x.shape[0], x.shape[1])
163
+ return cv2.resize(x, (s1, s0), interpolation=cv2.INTER_LANCZOS4)
164
+
165
+ def _d_resize(x, d, fac=1.0):
166
+ new_min = min(int(d[1] * fac), int(d[0] * fac))
167
+ raw_min = min(x.shape[0], x.shape[1])
168
+ if new_min < raw_min:
169
+ interpolation = cv2.INTER_AREA
170
+ else:
171
+ interpolation = cv2.INTER_LANCZOS4
172
+ y = cv2.resize(x, (int(d[1] * fac), int(d[0] * fac)), interpolation=interpolation)
173
+ return y
174
+
175
+ def _get_image_gradient(dist):
176
+ cols = cv2.filter2D(dist, cv2.CV_32F, np.array([[-1, 0, +1], [-2, 0, +2], [-1, 0, +1]]))
177
+ rows = cv2.filter2D(dist, cv2.CV_32F, np.array([[-1, -2, -1], [0, 0, 0], [+1, +2, +1]]))
178
+ return cols, rows
179
+
180
+ def _generate_lighting_effects(content):
181
+ h512 = content
182
+ h256 = cv2.pyrDown(h512)
183
+ h128 = cv2.pyrDown(h256)
184
+ h64 = cv2.pyrDown(h128)
185
+ h32 = cv2.pyrDown(h64)
186
+ h16 = cv2.pyrDown(h32)
187
+ c512, r512 = _get_image_gradient(h512)
188
+ c256, r256 = _get_image_gradient(h256)
189
+ c128, r128 = _get_image_gradient(h128)
190
+ c64, r64 = _get_image_gradient(h64)
191
+ c32, r32 = _get_image_gradient(h32)
192
+ c16, r16 = _get_image_gradient(h16)
193
+ c = c16
194
+ c = _d_resize(cv2.pyrUp(c), c32.shape) * 4.0 + c32
195
+ c = _d_resize(cv2.pyrUp(c), c64.shape) * 4.0 + c64
196
+ c = _d_resize(cv2.pyrUp(c), c128.shape) * 4.0 + c128
197
+ c = _d_resize(cv2.pyrUp(c), c256.shape) * 4.0 + c256
198
+ c = _d_resize(cv2.pyrUp(c), c512.shape) * 4.0 + c512
199
+ r = r16
200
+ r = _d_resize(cv2.pyrUp(r), r32.shape) * 4.0 + r32
201
+ r = _d_resize(cv2.pyrUp(r), r64.shape) * 4.0 + r64
202
+ r = _d_resize(cv2.pyrUp(r), r128.shape) * 4.0 + r128
203
+ r = _d_resize(cv2.pyrUp(r), r256.shape) * 4.0 + r256
204
+ r = _d_resize(cv2.pyrUp(r), r512.shape) * 4.0 + r512
205
+ coarse_effect_cols = c
206
+ coarse_effect_rows = r
207
+ EPS = 1e-10
208
+
209
+ max_effect = np.max((coarse_effect_cols**2 + coarse_effect_rows**2)**0.5, axis=0, keepdims=True, ).max(1, keepdims=True)
210
+ coarse_effect_cols = (coarse_effect_cols + EPS) / (max_effect + EPS)
211
+ coarse_effect_rows = (coarse_effect_rows + EPS) / (max_effect + EPS)
212
+
213
+ return np.stack([ np.zeros_like(coarse_effect_rows), coarse_effect_rows, coarse_effect_cols], axis=-1)
214
+
215
+ def apply_random_relight(img, mask=None, rnd_state=None):
216
+ if rnd_state is None:
217
+ rnd_state = np.random
218
+
219
+ def_img = img
220
+
221
+ if rnd_state.randint(2) == 0:
222
+ light_pos_y = 1.0 if rnd_state.randint(2) == 0 else -1.0
223
+ light_pos_x = rnd_state.uniform()*2-1.0
224
+ else:
225
+ light_pos_y = rnd_state.uniform()*2-1.0
226
+ light_pos_x = 1.0 if rnd_state.randint(2) == 0 else -1.0
227
+
228
+ light_source_height = 0.3*rnd_state.uniform()*0.7
229
+ light_intensity = 1.0+rnd_state.uniform()
230
+ ambient_intensity = 0.5
231
+
232
+ light_source_location = np.array([[[light_source_height, light_pos_y, light_pos_x ]]], dtype=np.float32)
233
+ light_source_direction = light_source_location / np.sqrt(np.sum(np.square(light_source_location)))
234
+
235
+ lighting_effect = _generate_lighting_effects(img)
236
+ lighting_effect = np.sum(lighting_effect * light_source_direction, axis=-1).clip(0, 1)
237
+ lighting_effect = np.mean(lighting_effect, axis=-1, keepdims=True)
238
+
239
+ result = def_img * (ambient_intensity + lighting_effect * light_intensity) #light_source_color
240
+ result = np.clip(result, 0, 1)
241
+
242
+ if mask is not None:
243
+ result = def_img*(1-mask) + result*mask
244
+
245
+ return result
face_detect/core/imagelib/morph.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ from scipy.spatial import Delaunay
4
+
5
+
6
+ def applyAffineTransform(src, srcTri, dstTri, size) :
7
+ warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) )
8
+ return cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )
9
+
10
+ def morphTriangle(dst_img, src_img, st, dt) :
11
+ (h,w,c) = dst_img.shape
12
+ sr = np.array( cv2.boundingRect(np.float32(st)) )
13
+ dr = np.array( cv2.boundingRect(np.float32(dt)) )
14
+ sRect = st - sr[0:2]
15
+ dRect = dt - dr[0:2]
16
+ d_mask = np.zeros((dr[3], dr[2], c), dtype = np.float32)
17
+ cv2.fillConvexPoly(d_mask, np.int32(dRect), (1.0,)*c, 8, 0);
18
+ imgRect = src_img[sr[1]:sr[1] + sr[3], sr[0]:sr[0] + sr[2]]
19
+ size = (dr[2], dr[3])
20
+ warpImage1 = applyAffineTransform(imgRect, sRect, dRect, size)
21
+
22
+ if c == 1:
23
+ warpImage1 = np.expand_dims( warpImage1, -1 )
24
+
25
+ dst_img[dr[1]:dr[1]+dr[3], dr[0]:dr[0]+dr[2]] = dst_img[dr[1]:dr[1]+dr[3], dr[0]:dr[0]+dr[2]]*(1-d_mask) + warpImage1 * d_mask
26
+
27
+ def morph_by_points (image, sp, dp):
28
+ if sp.shape != dp.shape:
29
+ raise ValueError ('morph_by_points() sp.shape != dp.shape')
30
+ (h,w,c) = image.shape
31
+
32
+ result_image = np.zeros(image.shape, dtype = image.dtype)
33
+
34
+ for tri in Delaunay(dp).simplices:
35
+ morphTriangle(result_image, image, sp[tri], dp[tri])
36
+
37
+ return result_image
face_detect/core/imagelib/reduce_colors.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ from PIL import Image
4
+
5
+ #n_colors = [0..256]
6
+ def reduce_colors (img_bgr, n_colors):
7
+ img_rgb = (img_bgr[...,::-1] * 255.0).astype(np.uint8)
8
+ img_rgb_pil = Image.fromarray(img_rgb)
9
+ img_rgb_pil_p = img_rgb_pil.convert('P', palette=Image.ADAPTIVE, colors=n_colors)
10
+
11
+ img_rgb_p = img_rgb_pil_p.convert('RGB')
12
+ img_bgr = cv2.cvtColor( np.array(img_rgb_p, dtype=np.float32) / 255.0, cv2.COLOR_RGB2BGR )
13
+
14
+ return img_bgr
face_detect/core/imagelib/sd/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .draw import circle_faded, random_circle_faded, bezier, random_bezier_split_faded, random_faded
2
+ from .calc import *
face_detect/core/imagelib/sd/calc.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import numpy.linalg as npla
3
+
4
+ def dist_to_edges(pts, pt, is_closed=False):
5
+ """
6
+ returns array of dist from pt to edge and projection pt to edges
7
+ """
8
+ if is_closed:
9
+ a = pts
10
+ b = np.concatenate( (pts[1:,:], pts[0:1,:]), axis=0 )
11
+ else:
12
+ a = pts[:-1,:]
13
+ b = pts[1:,:]
14
+
15
+ pa = pt-a
16
+ ba = b-a
17
+
18
+ div = np.einsum('ij,ij->i', ba, ba)
19
+ div[div==0]=1
20
+ h = np.clip( np.einsum('ij,ij->i', pa, ba) / div, 0, 1 )
21
+
22
+ x = npla.norm ( pa - ba*h[...,None], axis=1 )
23
+
24
+ return x, a+ba*h[...,None]
25
+
face_detect/core/imagelib/sd/draw.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Signed distance drawing functions using numpy.
3
+ """
4
+ import math
5
+
6
+ import numpy as np
7
+ from numpy import linalg as npla
8
+
9
+
10
+ def vector2_dot(a,b):
11
+ return a[...,0]*b[...,0]+a[...,1]*b[...,1]
12
+
13
+ def vector2_dot2(a):
14
+ return a[...,0]*a[...,0]+a[...,1]*a[...,1]
15
+
16
+ def vector2_cross(a,b):
17
+ return a[...,0]*b[...,1]-a[...,1]*b[...,0]
18
+
19
+
20
+ def circle_faded( wh, center, fade_dists ):
21
+ """
22
+ returns drawn circle in [h,w,1] output range [0..1.0] float32
23
+
24
+ wh = [w,h] resolution
25
+ center = [x,y] center of circle
26
+ fade_dists = [fade_start, fade_end] fade values
27
+ """
28
+ w,h = wh
29
+
30
+ pts = np.empty( (h,w,2), dtype=np.float32 )
31
+ pts[...,0] = np.arange(w)[:,None]
32
+ pts[...,1] = np.arange(h)[None,:]
33
+
34
+ pts = pts.reshape ( (h*w, -1) )
35
+
36
+ pts_dists = np.abs ( npla.norm(pts-center, axis=-1) )
37
+
38
+ if fade_dists[1] == 0:
39
+ fade_dists[1] = 1
40
+
41
+ pts_dists = ( pts_dists - fade_dists[0] ) / fade_dists[1]
42
+
43
+ pts_dists = np.clip( 1-pts_dists, 0, 1)
44
+
45
+ return pts_dists.reshape ( (h,w,1) ).astype(np.float32)
46
+
47
+
48
+ def bezier( wh, A, B, C ):
49
+ """
50
+ returns drawn bezier in [h,w,1] output range float32,
51
+ every pixel contains signed distance to bezier line
52
+
53
+ wh [w,h] resolution
54
+ A,B,C points [x,y]
55
+ """
56
+
57
+ width,height = wh
58
+
59
+ A = np.float32(A)
60
+ B = np.float32(B)
61
+ C = np.float32(C)
62
+
63
+
64
+ pos = np.empty( (height,width,2), dtype=np.float32 )
65
+ pos[...,0] = np.arange(width)[:,None]
66
+ pos[...,1] = np.arange(height)[None,:]
67
+
68
+
69
+ a = B-A
70
+ b = A - 2.0*B + C
71
+ c = a * 2.0
72
+ d = A - pos
73
+
74
+ b_dot = vector2_dot(b,b)
75
+ if b_dot == 0.0:
76
+ return np.zeros( (height,width), dtype=np.float32 )
77
+
78
+ kk = 1.0 / b_dot
79
+
80
+ kx = kk * vector2_dot(a,b)
81
+ ky = kk * (2.0*vector2_dot(a,a)+vector2_dot(d,b))/3.0;
82
+ kz = kk * vector2_dot(d,a);
83
+
84
+ res = 0.0;
85
+ sgn = 0.0;
86
+
87
+ p = ky - kx*kx;
88
+
89
+ p3 = p*p*p;
90
+ q = kx*(2.0*kx*kx - 3.0*ky) + kz;
91
+ h = q*q + 4.0*p3;
92
+
93
+ hp_sel = h >= 0.0
94
+
95
+ hp_p = h[hp_sel]
96
+ hp_p = np.sqrt(hp_p)
97
+
98
+ hp_x = ( np.stack( (hp_p,-hp_p), -1) -q[hp_sel,None] ) / 2.0
99
+ hp_uv = np.sign(hp_x) * np.power( np.abs(hp_x), [1.0/3.0, 1.0/3.0] )
100
+ hp_t = np.clip( hp_uv[...,0] + hp_uv[...,1] - kx, 0.0, 1.0 )
101
+
102
+ hp_t = hp_t[...,None]
103
+ hp_q = d[hp_sel]+(c+b*hp_t)*hp_t
104
+ hp_res = vector2_dot2(hp_q)
105
+ hp_sgn = vector2_cross(c+2.0*b*hp_t,hp_q)
106
+
107
+ hl_sel = h < 0.0
108
+
109
+ hl_q = q[hl_sel]
110
+ hl_p = p[hl_sel]
111
+ hl_z = np.sqrt(-hl_p)
112
+ hl_v = np.arccos( hl_q / (hl_p*hl_z*2.0)) / 3.0
113
+
114
+ hl_m = np.cos(hl_v)
115
+ hl_n = np.sin(hl_v)*1.732050808;
116
+
117
+ hl_t = np.clip( np.stack( (hl_m+hl_m,-hl_n-hl_m,hl_n-hl_m), -1)*hl_z[...,None]-kx, 0.0, 1.0 );
118
+
119
+ hl_d = d[hl_sel]
120
+
121
+ hl_qx = hl_d+(c+b*hl_t[...,0:1])*hl_t[...,0:1]
122
+
123
+ hl_dx = vector2_dot2(hl_qx)
124
+ hl_sx = vector2_cross(c+2.0*b*hl_t[...,0:1], hl_qx)
125
+
126
+ hl_qy = hl_d+(c+b*hl_t[...,1:2])*hl_t[...,1:2]
127
+ hl_dy = vector2_dot2(hl_qy)
128
+ hl_sy = vector2_cross(c+2.0*b*hl_t[...,1:2],hl_qy);
129
+
130
+ hl_dx_l_dy = hl_dx<hl_dy
131
+ hl_dx_ge_dy = hl_dx>=hl_dy
132
+
133
+ hl_res = np.empty_like(hl_dx)
134
+ hl_res[hl_dx_l_dy] = hl_dx[hl_dx_l_dy]
135
+ hl_res[hl_dx_ge_dy] = hl_dy[hl_dx_ge_dy]
136
+
137
+ hl_sgn = np.empty_like(hl_sx)
138
+ hl_sgn[hl_dx_l_dy] = hl_sx[hl_dx_l_dy]
139
+ hl_sgn[hl_dx_ge_dy] = hl_sy[hl_dx_ge_dy]
140
+
141
+ res = np.empty( (height, width), np.float32 )
142
+ res[hp_sel] = hp_res
143
+ res[hl_sel] = hl_res
144
+
145
+ sgn = np.empty( (height, width), np.float32 )
146
+ sgn[hp_sel] = hp_sgn
147
+ sgn[hl_sel] = hl_sgn
148
+
149
+ sgn = np.sign(sgn)
150
+ res = np.sqrt(res)*sgn
151
+
152
+ return res[...,None]
153
+
154
+ def random_faded(wh):
155
+ """
156
+ apply one of them:
157
+ random_circle_faded
158
+ random_bezier_split_faded
159
+ """
160
+ rnd = np.random.randint(2)
161
+ if rnd == 0:
162
+ return random_circle_faded(wh)
163
+ elif rnd == 1:
164
+ return random_bezier_split_faded(wh)
165
+
166
+ def random_circle_faded ( wh, rnd_state=None ):
167
+ if rnd_state is None:
168
+ rnd_state = np.random
169
+
170
+ w,h = wh
171
+ wh_max = max(w,h)
172
+ fade_start = rnd_state.randint(wh_max)
173
+ fade_end = fade_start + rnd_state.randint(wh_max- fade_start)
174
+
175
+ return circle_faded (wh, [ rnd_state.randint(h), rnd_state.randint(w) ],
176
+ [fade_start, fade_end] )
177
+
178
+ def random_bezier_split_faded( wh ):
179
+ width, height = wh
180
+
181
+ degA = np.random.randint(360)
182
+ degB = np.random.randint(360)
183
+ degC = np.random.randint(360)
184
+
185
+ deg_2_rad = math.pi / 180.0
186
+
187
+ center = np.float32([width / 2.0, height / 2.0])
188
+
189
+ radius = max(width, height)
190
+
191
+ A = center + radius*np.float32([ math.sin( degA * deg_2_rad), math.cos( degA * deg_2_rad) ] )
192
+ B = center + np.random.randint(radius)*np.float32([ math.sin( degB * deg_2_rad), math.cos( degB * deg_2_rad) ] )
193
+ C = center + radius*np.float32([ math.sin( degC * deg_2_rad), math.cos( degC * deg_2_rad) ] )
194
+
195
+ x = bezier( (width,height), A, B, C )
196
+
197
+ x = x / (1+np.random.randint(radius)) + 0.5
198
+
199
+ x = np.clip(x, 0, 1)
200
+ return x
face_detect/core/imagelib/warp.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ from face_detect.core import randomex
4
+
5
+ def gen_warp_params (w, flip=False, rotation_range=[-10,10], scale_range=[-0.5, 0.5], tx_range=[-0.05, 0.05], ty_range=[-0.05, 0.05], rnd_state=None ):
6
+ if rnd_state is None:
7
+ rnd_state = np.random
8
+
9
+ rw = None
10
+ if w < 64:
11
+ rw = w
12
+ w = 64
13
+
14
+ rotation = rnd_state.uniform( rotation_range[0], rotation_range[1] )
15
+ scale = rnd_state.uniform(1 +scale_range[0], 1 +scale_range[1])
16
+ tx = rnd_state.uniform( tx_range[0], tx_range[1] )
17
+ ty = rnd_state.uniform( ty_range[0], ty_range[1] )
18
+ p_flip = flip and rnd_state.randint(10) < 4
19
+
20
+ #random warp by grid
21
+ cell_size = [ w // (2**i) for i in range(1,4) ] [ rnd_state.randint(3) ]
22
+ cell_count = w // cell_size + 1
23
+
24
+ grid_points = np.linspace( 0, w, cell_count)
25
+ mapx = np.broadcast_to(grid_points, (cell_count, cell_count)).copy()
26
+ mapy = mapx.T
27
+
28
+ mapx[1:-1,1:-1] = mapx[1:-1,1:-1] + randomex.random_normal( size=(cell_count-2, cell_count-2) )*(cell_size*0.24)
29
+ mapy[1:-1,1:-1] = mapy[1:-1,1:-1] + randomex.random_normal( size=(cell_count-2, cell_count-2) )*(cell_size*0.24)
30
+
31
+ half_cell_size = cell_size // 2
32
+
33
+ mapx = cv2.resize(mapx, (w+cell_size,)*2 )[half_cell_size:-half_cell_size,half_cell_size:-half_cell_size].astype(np.float32)
34
+ mapy = cv2.resize(mapy, (w+cell_size,)*2 )[half_cell_size:-half_cell_size,half_cell_size:-half_cell_size].astype(np.float32)
35
+
36
+ #random transform
37
+ random_transform_mat = cv2.getRotationMatrix2D((w // 2, w // 2), rotation, scale)
38
+ random_transform_mat[:, 2] += (tx*w, ty*w)
39
+
40
+ params = dict()
41
+ params['mapx'] = mapx
42
+ params['mapy'] = mapy
43
+ params['rmat'] = random_transform_mat
44
+ u_mat = random_transform_mat.copy()
45
+ u_mat[:,2] /= w
46
+ params['umat'] = u_mat
47
+ params['w'] = w
48
+ params['rw'] = rw
49
+ params['flip'] = p_flip
50
+
51
+ return params
52
+
53
+ def warp_by_params (params, img, can_warp, can_transform, can_flip, border_replicate, cv2_inter=cv2.INTER_CUBIC):
54
+ rw = params['rw']
55
+
56
+ if (can_warp or can_transform) and rw is not None:
57
+ img = cv2.resize(img, (64,64), interpolation=cv2_inter)
58
+
59
+ if can_warp:
60
+ img = cv2.remap(img, params['mapx'], params['mapy'], cv2_inter )
61
+ if can_transform:
62
+ img = cv2.warpAffine( img, params['rmat'], (params['w'], params['w']), borderMode=(cv2.BORDER_REPLICATE if border_replicate else cv2.BORDER_CONSTANT), flags=cv2_inter )
63
+
64
+
65
+ if (can_warp or can_transform) and rw is not None:
66
+ img = cv2.resize(img, (rw,rw), interpolation=cv2_inter)
67
+
68
+ if len(img.shape) == 2:
69
+ img = img[...,None]
70
+ if can_flip and params['flip']:
71
+ img = img[:,::-1,...]
72
+ return img
face_detect/core/leras/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .nn import nn
face_detect/core/leras/archis/ArchiBase.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+
3
+ class ArchiBase():
4
+
5
+ def __init__(self, *args, name=None, **kwargs):
6
+ self.name=name
7
+
8
+
9
+ #overridable
10
+ def flow(self, *args, **kwargs):
11
+ raise Exception("this archi does not support flow. Use model classes directly.")
12
+
13
+ #overridable
14
+ def get_weights(self):
15
+ pass
16
+
17
+ nn.ArchiBase = ArchiBase
face_detect/core/leras/archis/DeepFakeArchi.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+
3
+ tf = nn.tf
4
+
5
+
6
+ class DeepFakeArchi(nn.ArchiBase):
7
+ """
8
+ resolution
9
+
10
+ mod None - default
11
+ 'quick'
12
+ """
13
+
14
+ def __init__(self, resolution, mod=None, opts=None):
15
+ super().__init__()
16
+
17
+ if opts is None:
18
+ opts = ''
19
+
20
+ if mod is None:
21
+ class Downscale(nn.ModelBase):
22
+ def __init__(self, in_ch, out_ch, kernel_size=5, *kwargs):
23
+ self.in_ch = in_ch
24
+ self.out_ch = out_ch
25
+ self.kernel_size = kernel_size
26
+ super().__init__(*kwargs)
27
+
28
+ def on_build(self, *args, **kwargs):
29
+ self.conv1 = nn.Conv2D(self.in_ch, self.out_ch, kernel_size=self.kernel_size, strides=2,
30
+ padding='SAME')
31
+
32
+ def forward(self, x):
33
+ x = self.conv1(x)
34
+ x = tf.nn.leaky_relu(x, 0.1)
35
+ return x
36
+
37
+ def get_out_ch(self):
38
+ return self.out_ch
39
+
40
+ class DownscaleBlock(nn.ModelBase):
41
+ def on_build(self, in_ch, ch, n_downscales, kernel_size):
42
+ self.downs = []
43
+
44
+ last_ch = in_ch
45
+ for i in range(n_downscales):
46
+ cur_ch = ch * (min(2 ** i, 8))
47
+ self.downs.append(Downscale(last_ch, cur_ch, kernel_size=kernel_size))
48
+ last_ch = self.downs[-1].get_out_ch()
49
+
50
+ def forward(self, inp):
51
+ x = inp
52
+ for down in self.downs:
53
+ x = down(x)
54
+ return x
55
+
56
+ class Upscale(nn.ModelBase):
57
+ def on_build(self, in_ch, out_ch, kernel_size=3):
58
+ self.conv1 = nn.Conv2D(in_ch, out_ch * 4, kernel_size=kernel_size, padding='SAME')
59
+
60
+ def forward(self, x):
61
+ x = self.conv1(x)
62
+ x = tf.nn.leaky_relu(x, 0.1)
63
+ x = nn.depth_to_space(x, 2)
64
+ return x
65
+
66
+ class ResidualBlock(nn.ModelBase):
67
+ def on_build(self, ch, kernel_size=3):
68
+ self.conv1 = nn.Conv2D(ch, ch, kernel_size=kernel_size, padding='SAME')
69
+ self.conv2 = nn.Conv2D(ch, ch, kernel_size=kernel_size, padding='SAME')
70
+
71
+ def forward(self, inp):
72
+ x = self.conv1(inp)
73
+ x = tf.nn.leaky_relu(x, 0.2)
74
+ x = self.conv2(x)
75
+ x = tf.nn.leaky_relu(inp + x, 0.2)
76
+ return x
77
+
78
+ class Encoder(nn.ModelBase):
79
+ def __init__(self, in_ch, e_ch, **kwargs):
80
+ self.in_ch = in_ch
81
+ self.e_ch = e_ch
82
+ super().__init__(**kwargs)
83
+
84
+ def on_build(self):
85
+ self.down1 = DownscaleBlock(self.in_ch, self.e_ch, n_downscales=4, kernel_size=5)
86
+
87
+ def forward(self, inp):
88
+ return nn.flatten(self.down1(inp))
89
+
90
+ def get_out_res(self, res):
91
+ return res // (2 ** 4)
92
+
93
+ def get_out_ch(self):
94
+ return self.e_ch * 8
95
+
96
+ lowest_dense_res = resolution // (32 if 'd' in opts else 16)
97
+
98
+ class Inter(nn.ModelBase):
99
+ def __init__(self, in_ch, ae_ch, ae_out_ch, **kwargs):
100
+ self.in_ch, self.ae_ch, self.ae_out_ch = in_ch, ae_ch, ae_out_ch
101
+ super().__init__(**kwargs)
102
+
103
+ def on_build(self):
104
+ in_ch, ae_ch, ae_out_ch = self.in_ch, self.ae_ch, self.ae_out_ch
105
+ if 'u' in opts:
106
+ self.dense_norm = nn.DenseNorm()
107
+
108
+ self.dense1 = nn.Dense(in_ch, ae_ch)
109
+ self.dense2 = nn.Dense(ae_ch, lowest_dense_res * lowest_dense_res * ae_out_ch)
110
+ self.upscale1 = Upscale(ae_out_ch, ae_out_ch)
111
+
112
+ def forward(self, inp):
113
+ x = inp
114
+ if 'u' in opts:
115
+ x = self.dense_norm(x)
116
+ x = self.dense1(x)
117
+ x = self.dense2(x)
118
+ x = nn.reshape_4D(x, lowest_dense_res, lowest_dense_res, self.ae_out_ch)
119
+ x = self.upscale1(x)
120
+ return x
121
+
122
+ def get_out_res(self):
123
+ return lowest_dense_res * 2
124
+
125
+ def get_out_ch(self):
126
+ return self.ae_out_ch
127
+
128
+ class Decoder(nn.ModelBase):
129
+ def on_build(self, in_ch, d_ch, d_mask_ch):
130
+ self.upscale0 = Upscale(in_ch, d_ch * 8, kernel_size=3)
131
+ self.upscale1 = Upscale(d_ch * 8, d_ch * 4, kernel_size=3)
132
+ self.upscale2 = Upscale(d_ch * 4, d_ch * 2, kernel_size=3)
133
+
134
+ self.res0 = ResidualBlock(d_ch * 8, kernel_size=3)
135
+ self.res1 = ResidualBlock(d_ch * 4, kernel_size=3)
136
+ self.res2 = ResidualBlock(d_ch * 2, kernel_size=3)
137
+
138
+ self.out_conv = nn.Conv2D(d_ch * 2, 3, kernel_size=1, padding='SAME')
139
+
140
+ # self.upscalem0 = Upscale(in_ch, d_mask_ch * 8, kernel_size=3)
141
+ # self.upscalem1 = Upscale(d_mask_ch * 8, d_mask_ch * 4, kernel_size=3)
142
+ # self.upscalem2 = Upscale(d_mask_ch * 4, d_mask_ch * 2, kernel_size=3)
143
+ # self.out_convm = nn.Conv2D(d_mask_ch * 2, 1, kernel_size=1, padding='SAME')
144
+
145
+ if 'd' in opts:
146
+ self.out_conv1 = nn.Conv2D(d_ch * 2, 3, kernel_size=3, padding='SAME')
147
+ self.out_conv2 = nn.Conv2D(d_ch * 2, 3, kernel_size=3, padding='SAME')
148
+ self.out_conv3 = nn.Conv2D(d_ch * 2, 3, kernel_size=3, padding='SAME')
149
+ # self.upscalem3 = Upscale(d_mask_ch * 2, d_mask_ch * 1, kernel_size=3)
150
+ # self.out_convm = nn.Conv2D(d_mask_ch * 1, 1, kernel_size=1, padding='SAME')
151
+ else:
152
+ # self.out_convm = nn.Conv2D(d_mask_ch * 2, 1, kernel_size=1, padding='SAME')
153
+ pass
154
+
155
+ def forward(self, inp):
156
+ z = inp
157
+
158
+ x = self.upscale0(z)
159
+ x = self.res0(x)
160
+ x = self.upscale1(x)
161
+ x = self.res1(x)
162
+ x = self.upscale2(x)
163
+ x = self.res2(x)
164
+
165
+ if 'd' in opts:
166
+ x0 = tf.nn.sigmoid(self.out_conv(x))
167
+ x0 = nn.upsample2d(x0)
168
+ x1 = tf.nn.sigmoid(self.out_conv1(x))
169
+ x1 = nn.upsample2d(x1)
170
+ x2 = tf.nn.sigmoid(self.out_conv2(x))
171
+ x2 = nn.upsample2d(x2)
172
+ x3 = tf.nn.sigmoid(self.out_conv3(x))
173
+ x3 = nn.upsample2d(x3)
174
+
175
+ if nn.data_format == "NHWC":
176
+ tile_cfg = (1, resolution // 2, resolution // 2, 1)
177
+ else:
178
+ tile_cfg = (1, 1, resolution // 2, resolution // 2)
179
+
180
+ z0 = tf.concat(
181
+ (tf.concat((tf.ones((1, 1, 1, 1)), tf.zeros((1, 1, 1, 1))), axis=nn.conv2d_spatial_axes[1]),
182
+ tf.concat((tf.zeros((1, 1, 1, 1)), tf.zeros((1, 1, 1, 1))),
183
+ axis=nn.conv2d_spatial_axes[1])), axis=nn.conv2d_spatial_axes[0])
184
+
185
+ z0 = tf.tile(z0, tile_cfg)
186
+
187
+ z1 = tf.concat(
188
+ (tf.concat((tf.zeros((1, 1, 1, 1)), tf.ones((1, 1, 1, 1))), axis=nn.conv2d_spatial_axes[1]),
189
+ tf.concat((tf.zeros((1, 1, 1, 1)), tf.zeros((1, 1, 1, 1))),
190
+ axis=nn.conv2d_spatial_axes[1])), axis=nn.conv2d_spatial_axes[0])
191
+ z1 = tf.tile(z1, tile_cfg)
192
+
193
+ z2 = tf.concat((tf.concat((tf.zeros((1, 1, 1, 1)), tf.zeros((1, 1, 1, 1))),
194
+ axis=nn.conv2d_spatial_axes[1]),
195
+ tf.concat((tf.ones((1, 1, 1, 1)), tf.zeros((1, 1, 1, 1))),
196
+ axis=nn.conv2d_spatial_axes[1])), axis=nn.conv2d_spatial_axes[0])
197
+ z2 = tf.tile(z2, tile_cfg)
198
+
199
+ z3 = tf.concat((tf.concat((tf.zeros((1, 1, 1, 1)), tf.zeros((1, 1, 1, 1))),
200
+ axis=nn.conv2d_spatial_axes[1]),
201
+ tf.concat((tf.zeros((1, 1, 1, 1)), tf.ones((1, 1, 1, 1))),
202
+ axis=nn.conv2d_spatial_axes[1])), axis=nn.conv2d_spatial_axes[0])
203
+ z3 = tf.tile(z3, tile_cfg)
204
+
205
+ x = x0 * z0 + x1 * z1 + x2 * z2 + x3 * z3
206
+ else:
207
+ x = tf.nn.sigmoid(self.out_conv(x))
208
+
209
+ # m = self.upscalem0(z)
210
+ # m = self.upscalem1(m)
211
+ # m = self.upscalem2(m)
212
+ # if 'd' in opts:
213
+ # m = self.upscalem3(m)
214
+ # m = tf.nn.sigmoid(self.out_convm(m))
215
+
216
+ return x
217
+
218
+ self.Encoder = Encoder
219
+ self.Inter = Inter
220
+ self.Decoder = Decoder
221
+
222
+
223
+ nn.DeepFakeArchi = DeepFakeArchi
face_detect/core/leras/archis/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .ArchiBase import *
2
+ from .DeepFakeArchi import *
face_detect/core/leras/device.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import ctypes
3
+ import os
4
+ import multiprocessing
5
+ import json
6
+ import time
7
+ from pathlib import Path
8
+ # from face_feature.core.interact import interact as io
9
+
10
+
11
+ class Device(object):
12
+ def __init__(self, index, tf_dev_type, name, total_mem, free_mem):
13
+ self.index = index
14
+ self.tf_dev_type = tf_dev_type
15
+ self.name = name
16
+
17
+ self.total_mem = total_mem
18
+ self.total_mem_gb = total_mem / 1024**3
19
+ self.free_mem = free_mem
20
+ self.free_mem_gb = free_mem / 1024**3
21
+
22
+ def __str__(self):
23
+ return f"[{self.index}]:[{self.name}][{self.free_mem_gb:.3}/{self.total_mem_gb :.3}]"
24
+
25
+ class Devices(object):
26
+ all_devices = None
27
+
28
+ def __init__(self, devices):
29
+ self.devices = devices
30
+
31
+ def __len__(self):
32
+ return len(self.devices)
33
+
34
+ def __getitem__(self, key):
35
+ result = self.devices[key]
36
+ if isinstance(key, slice):
37
+ return Devices(result)
38
+ return result
39
+
40
+ def __iter__(self):
41
+ for device in self.devices:
42
+ yield device
43
+
44
+ def get_best_device(self):
45
+ result = None
46
+ idx_mem = 0
47
+ for device in self.devices:
48
+ mem = device.total_mem
49
+ if mem > idx_mem:
50
+ result = device
51
+ idx_mem = mem
52
+ return result
53
+
54
+ def get_worst_device(self):
55
+ result = None
56
+ idx_mem = sys.maxsize
57
+ for device in self.devices:
58
+ mem = device.total_mem
59
+ if mem < idx_mem:
60
+ result = device
61
+ idx_mem = mem
62
+ return result
63
+
64
+ def get_device_by_index(self, idx):
65
+ for device in self.devices:
66
+ if device.index == idx:
67
+ return device
68
+ return None
69
+
70
+ def get_devices_from_index_list(self, idx_list):
71
+ result = []
72
+ for device in self.devices:
73
+ if device.index in idx_list:
74
+ result += [device]
75
+ return Devices(result)
76
+
77
+ def get_equal_devices(self, device):
78
+ device_name = device.name
79
+ result = []
80
+ for device in self.devices:
81
+ if device.name == device_name:
82
+ result.append (device)
83
+ return Devices(result)
84
+
85
+ def get_devices_at_least_mem(self, totalmemsize_gb):
86
+ result = []
87
+ for device in self.devices:
88
+ if device.total_mem >= totalmemsize_gb*(1024**3):
89
+ result.append (device)
90
+ return Devices(result)
91
+
92
+ @staticmethod
93
+ def _get_tf_devices_proc(q : multiprocessing.Queue):
94
+
95
+ if sys.platform[0:3] == 'win':
96
+ compute_cache_path = Path(os.environ['APPDATA']) / 'NVIDIA' / ('ComputeCache_ALL')
97
+ os.environ['CUDA_CACHE_PATH'] = str(compute_cache_path)
98
+ if not compute_cache_path.exists():
99
+ # io.log_info("Caching GPU kernels...")
100
+ compute_cache_path.mkdir(parents=True, exist_ok=True)
101
+
102
+ import tensorflow
103
+
104
+ tf_version = tensorflow.version.VERSION
105
+ #if tf_version is None:
106
+ # tf_version = tensorflow.version.GIT_VERSION
107
+ if tf_version[0] == 'v':
108
+ tf_version = tf_version[1:]
109
+ if tf_version[0] == '2':
110
+ tf = tensorflow.compat.v1
111
+ else:
112
+ tf = tensorflow
113
+
114
+ import logging
115
+ # Disable tensorflow warnings
116
+ tf_logger = logging.getLogger('tensorflow')
117
+ tf_logger.setLevel(logging.ERROR)
118
+
119
+ from tensorflow.python.client import device_lib
120
+
121
+ devices = []
122
+
123
+ physical_devices = device_lib.list_local_devices()
124
+ physical_devices_f = {}
125
+ for dev in physical_devices:
126
+ dev_type = dev.device_type
127
+ dev_tf_name = dev.name
128
+ dev_tf_name = dev_tf_name[ dev_tf_name.index(dev_type) : ]
129
+
130
+ dev_idx = int(dev_tf_name.split(':')[-1])
131
+
132
+ if dev_type in ['GPU','DML']:
133
+ dev_name = dev_tf_name
134
+
135
+ dev_desc = dev.physical_device_desc
136
+ if len(dev_desc) != 0:
137
+ if dev_desc[0] == '{':
138
+ dev_desc_json = json.loads(dev_desc)
139
+ dev_desc_json_name = dev_desc_json.get('name',None)
140
+ if dev_desc_json_name is not None:
141
+ dev_name = dev_desc_json_name
142
+ else:
143
+ for param, value in ( v.split(':') for v in dev_desc.split(',') ):
144
+ param = param.strip()
145
+ value = value.strip()
146
+ if param == 'name':
147
+ dev_name = value
148
+ break
149
+
150
+ physical_devices_f[dev_idx] = (dev_type, dev_name, dev.memory_limit)
151
+
152
+ q.put(physical_devices_f)
153
+ time.sleep(0.1)
154
+
155
+
156
+ @staticmethod
157
+ def initialize_main_env():
158
+ if int(os.environ.get("NN_DEVICES_INITIALIZED", 0)) != 0:
159
+ return
160
+
161
+ if 'CUDA_VISIBLE_DEVICES' in os.environ.keys():
162
+ os.environ.pop('CUDA_VISIBLE_DEVICES')
163
+
164
+ os.environ['CUDA_​CACHE_​MAXSIZE'] = '2147483647'
165
+ os.environ['TF_MIN_GPU_MULTIPROCESSOR_COUNT'] = '2'
166
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # tf log errors only
167
+
168
+ q = multiprocessing.Queue()
169
+ p = multiprocessing.Process(target=Devices._get_tf_devices_proc, args=(q,), daemon=True)
170
+ p.start()
171
+ p.join()
172
+
173
+ visible_devices = q.get()
174
+
175
+ os.environ['NN_DEVICES_INITIALIZED'] = '1'
176
+ os.environ['NN_DEVICES_COUNT'] = str(len(visible_devices))
177
+
178
+ for i in visible_devices:
179
+ dev_type, name, total_mem = visible_devices[i]
180
+
181
+ os.environ[f'NN_DEVICE_{i}_TF_DEV_TYPE'] = dev_type
182
+ os.environ[f'NN_DEVICE_{i}_NAME'] = name
183
+ os.environ[f'NN_DEVICE_{i}_TOTAL_MEM'] = str(total_mem)
184
+ os.environ[f'NN_DEVICE_{i}_FREE_MEM'] = str(total_mem)
185
+
186
+
187
+
188
+ @staticmethod
189
+ def getDevices():
190
+ if Devices.all_devices is None:
191
+ if int(os.environ.get("NN_DEVICES_INITIALIZED", 0)) != 1:
192
+ raise Exception("nn devices are not initialized. Run initialize_main_env() in main process.")
193
+ devices = []
194
+ for i in range ( int(os.environ['NN_DEVICES_COUNT']) ):
195
+ devices.append ( Device(index=i,
196
+ tf_dev_type=os.environ[f'NN_DEVICE_{i}_TF_DEV_TYPE'],
197
+ name=os.environ[f'NN_DEVICE_{i}_NAME'],
198
+ total_mem=int(os.environ[f'NN_DEVICE_{i}_TOTAL_MEM']),
199
+ free_mem=int(os.environ[f'NN_DEVICE_{i}_FREE_MEM']), )
200
+ )
201
+ Devices.all_devices = Devices(devices)
202
+
203
+ return Devices.all_devices
204
+
205
+ """
206
+
207
+
208
+ # {'name' : name.split(b'\0', 1)[0].decode(),
209
+ # 'total_mem' : totalMem.value
210
+ # }
211
+
212
+
213
+
214
+
215
+
216
+ return
217
+
218
+
219
+
220
+
221
+ min_cc = int(os.environ.get("TF_MIN_REQ_CAP", 35))
222
+ libnames = ('libcuda.so', 'libcuda.dylib', 'nvcuda.dll')
223
+ for libname in libnames:
224
+ try:
225
+ cuda = ctypes.CDLL(libname)
226
+ except:
227
+ continue
228
+ else:
229
+ break
230
+ else:
231
+ return Devices([])
232
+
233
+ nGpus = ctypes.c_int()
234
+ name = b' ' * 200
235
+ cc_major = ctypes.c_int()
236
+ cc_minor = ctypes.c_int()
237
+ freeMem = ctypes.c_size_t()
238
+ totalMem = ctypes.c_size_t()
239
+
240
+ result = ctypes.c_int()
241
+ device = ctypes.c_int()
242
+ context = ctypes.c_void_p()
243
+ error_str = ctypes.c_char_p()
244
+
245
+ devices = []
246
+
247
+ if cuda.cuInit(0) == 0 and \
248
+ cuda.cuDeviceGetCount(ctypes.byref(nGpus)) == 0:
249
+ for i in range(nGpus.value):
250
+ if cuda.cuDeviceGet(ctypes.byref(device), i) != 0 or \
251
+ cuda.cuDeviceGetName(ctypes.c_char_p(name), len(name), device) != 0 or \
252
+ cuda.cuDeviceComputeCapability(ctypes.byref(cc_major), ctypes.byref(cc_minor), device) != 0:
253
+ continue
254
+
255
+ if cuda.cuCtxCreate_v2(ctypes.byref(context), 0, device) == 0:
256
+ if cuda.cuMemGetInfo_v2(ctypes.byref(freeMem), ctypes.byref(totalMem)) == 0:
257
+ cc = cc_major.value * 10 + cc_minor.value
258
+ if cc >= min_cc:
259
+ devices.append ( {'name' : name.split(b'\0', 1)[0].decode(),
260
+ 'total_mem' : totalMem.value,
261
+ 'free_mem' : freeMem.value,
262
+ 'cc' : cc
263
+ })
264
+ cuda.cuCtxDetach(context)
265
+
266
+ os.environ['NN_DEVICES_COUNT'] = str(len(devices))
267
+ for i, device in enumerate(devices):
268
+ os.environ[f'NN_DEVICE_{i}_NAME'] = device['name']
269
+ os.environ[f'NN_DEVICE_{i}_TOTAL_MEM'] = str(device['total_mem'])
270
+ os.environ[f'NN_DEVICE_{i}_FREE_MEM'] = str(device['free_mem'])
271
+ os.environ[f'NN_DEVICE_{i}_CC'] = str(device['cc'])
272
+ """
face_detect/core/leras/layers/AdaIN.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class AdaIN(nn.LayerBase):
5
+ """
6
+ """
7
+ def __init__(self, in_ch, mlp_ch, kernel_initializer=None, dtype=None, **kwargs):
8
+ self.in_ch = in_ch
9
+ self.mlp_ch = mlp_ch
10
+ self.kernel_initializer = kernel_initializer
11
+
12
+ if dtype is None:
13
+ dtype = nn.floatx
14
+ self.dtype = dtype
15
+
16
+ super().__init__(**kwargs)
17
+
18
+ def build_weights(self):
19
+ kernel_initializer = self.kernel_initializer
20
+ if kernel_initializer is None:
21
+ kernel_initializer = tf.initializers.he_normal()
22
+
23
+ self.weight1 = tf.get_variable("weight1", (self.mlp_ch, self.in_ch), dtype=self.dtype, initializer=kernel_initializer)
24
+ self.bias1 = tf.get_variable("bias1", (self.in_ch,), dtype=self.dtype, initializer=tf.initializers.zeros())
25
+ self.weight2 = tf.get_variable("weight2", (self.mlp_ch, self.in_ch), dtype=self.dtype, initializer=kernel_initializer)
26
+ self.bias2 = tf.get_variable("bias2", (self.in_ch,), dtype=self.dtype, initializer=tf.initializers.zeros())
27
+
28
+ def get_weights(self):
29
+ return [self.weight1, self.bias1, self.weight2, self.bias2]
30
+
31
+ def forward(self, inputs):
32
+ x, mlp = inputs
33
+
34
+ gamma = tf.matmul(mlp, self.weight1)
35
+ gamma = tf.add(gamma, tf.reshape(self.bias1, (1,self.in_ch) ) )
36
+
37
+ beta = tf.matmul(mlp, self.weight2)
38
+ beta = tf.add(beta, tf.reshape(self.bias2, (1,self.in_ch) ) )
39
+
40
+
41
+ if nn.data_format == "NHWC":
42
+ shape = (-1,1,1,self.in_ch)
43
+ else:
44
+ shape = (-1,self.in_ch,1,1)
45
+
46
+ x_mean = tf.reduce_mean(x, axis=nn.conv2d_spatial_axes, keepdims=True )
47
+ x_std = tf.math.reduce_std(x, axis=nn.conv2d_spatial_axes, keepdims=True ) + 1e-5
48
+
49
+ x = (x - x_mean) / x_std
50
+ x *= tf.reshape(gamma, shape)
51
+
52
+ x += tf.reshape(beta, shape)
53
+
54
+ return x
55
+
56
+ nn.AdaIN = AdaIN
face_detect/core/leras/layers/BatchNorm2D.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class BatchNorm2D(nn.LayerBase):
5
+ """
6
+ currently not for training
7
+ """
8
+ def __init__(self, dim, eps=1e-05, momentum=0.1, dtype=None, **kwargs):
9
+ self.dim = dim
10
+ self.eps = eps
11
+ self.momentum = momentum
12
+ if dtype is None:
13
+ dtype = nn.floatx
14
+ self.dtype = dtype
15
+ super().__init__(**kwargs)
16
+
17
+ def build_weights(self):
18
+ self.weight = tf.get_variable("weight", (self.dim,), dtype=self.dtype, initializer=tf.initializers.ones() )
19
+ self.bias = tf.get_variable("bias", (self.dim,), dtype=self.dtype, initializer=tf.initializers.zeros() )
20
+ self.running_mean = tf.get_variable("running_mean", (self.dim,), dtype=self.dtype, initializer=tf.initializers.zeros(), trainable=False )
21
+ self.running_var = tf.get_variable("running_var", (self.dim,), dtype=self.dtype, initializer=tf.initializers.zeros(), trainable=False )
22
+
23
+ def get_weights(self):
24
+ return [self.weight, self.bias, self.running_mean, self.running_var]
25
+
26
+ def forward(self, x):
27
+ if nn.data_format == "NHWC":
28
+ shape = (1,1,1,self.dim)
29
+ else:
30
+ shape = (1,self.dim,1,1)
31
+
32
+ weight = tf.reshape ( self.weight , shape )
33
+ bias = tf.reshape ( self.bias , shape )
34
+ running_mean = tf.reshape ( self.running_mean, shape )
35
+ running_var = tf.reshape ( self.running_var , shape )
36
+
37
+ x = (x - running_mean) / tf.sqrt( running_var + self.eps )
38
+ x *= weight
39
+ x += bias
40
+ return x
41
+
42
+ nn.BatchNorm2D = BatchNorm2D
face_detect/core/leras/layers/BlurPool.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from face_feature.core.leras import nn
3
+ tf = nn.tf
4
+
5
+ class BlurPool(nn.LayerBase):
6
+ def __init__(self, filt_size=3, stride=2, **kwargs ):
7
+
8
+ if nn.data_format == "NHWC":
9
+ self.strides = [1,stride,stride,1]
10
+ else:
11
+ self.strides = [1,1,stride,stride]
12
+
13
+ self.filt_size = filt_size
14
+ pad = [ int(1.*(filt_size-1)/2), int(np.ceil(1.*(filt_size-1)/2)) ]
15
+
16
+ if nn.data_format == "NHWC":
17
+ self.padding = [ [0,0], pad, pad, [0,0] ]
18
+ else:
19
+ self.padding = [ [0,0], [0,0], pad, pad ]
20
+
21
+ if(self.filt_size==1):
22
+ a = np.array([1.,])
23
+ elif(self.filt_size==2):
24
+ a = np.array([1., 1.])
25
+ elif(self.filt_size==3):
26
+ a = np.array([1., 2., 1.])
27
+ elif(self.filt_size==4):
28
+ a = np.array([1., 3., 3., 1.])
29
+ elif(self.filt_size==5):
30
+ a = np.array([1., 4., 6., 4., 1.])
31
+ elif(self.filt_size==6):
32
+ a = np.array([1., 5., 10., 10., 5., 1.])
33
+ elif(self.filt_size==7):
34
+ a = np.array([1., 6., 15., 20., 15., 6., 1.])
35
+
36
+ a = a[:,None]*a[None,:]
37
+ a = a / np.sum(a)
38
+ a = a[:,:,None,None]
39
+ self.a = a
40
+ super().__init__(**kwargs)
41
+
42
+ def build_weights(self):
43
+ self.k = tf.constant (self.a, dtype=nn.floatx )
44
+
45
+ def forward(self, x):
46
+ k = tf.tile (self.k, (1,1,x.shape[nn.conv2d_ch_axis],1) )
47
+ x = tf.pad(x, self.padding )
48
+ x = tf.nn.depthwise_conv2d(x, k, self.strides, 'VALID', data_format=nn.data_format)
49
+ return x
50
+ nn.BlurPool = BlurPool
face_detect/core/leras/layers/Conv2D.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from face_feature.core.leras import nn
3
+ tf = nn.tf
4
+
5
+ class Conv2D(nn.LayerBase):
6
+ """
7
+ default kernel_initializer - CA
8
+ use_wscale bool enables equalized learning rate, if kernel_initializer is None, it will be forced to random_normal
9
+
10
+
11
+ """
12
+ def __init__(self, in_ch, out_ch, kernel_size, strides=1, padding='SAME', dilations=1, use_bias=True, use_wscale=False, kernel_initializer=None, bias_initializer=None, trainable=True, dtype=None, **kwargs ):
13
+ if not isinstance(strides, int):
14
+ raise ValueError ("strides must be an int type")
15
+ if not isinstance(dilations, int):
16
+ raise ValueError ("dilations must be an int type")
17
+ kernel_size = int(kernel_size)
18
+
19
+ if dtype is None:
20
+ dtype = nn.floatx
21
+
22
+ if isinstance(padding, str):
23
+ if padding == "SAME":
24
+ padding = ( (kernel_size - 1) * dilations + 1 ) // 2
25
+ elif padding == "VALID":
26
+ padding = 0
27
+ else:
28
+ raise ValueError ("Wrong padding type. Should be VALID SAME or INT or 4x INTs")
29
+
30
+ if isinstance(padding, int):
31
+ if padding != 0:
32
+ if nn.data_format == "NHWC":
33
+ padding = [ [0,0], [padding,padding], [padding,padding], [0,0] ]
34
+ else:
35
+ padding = [ [0,0], [0,0], [padding,padding], [padding,padding] ]
36
+ else:
37
+ padding = None
38
+
39
+ if nn.data_format == "NHWC":
40
+ strides = [1,strides,strides,1]
41
+ else:
42
+ strides = [1,1,strides,strides]
43
+
44
+ if nn.data_format == "NHWC":
45
+ dilations = [1,dilations,dilations,1]
46
+ else:
47
+ dilations = [1,1,dilations,dilations]
48
+
49
+ self.in_ch = in_ch
50
+ self.out_ch = out_ch
51
+ self.kernel_size = kernel_size
52
+ self.strides = strides
53
+ self.padding = padding
54
+ self.dilations = dilations
55
+ self.use_bias = use_bias
56
+ self.use_wscale = use_wscale
57
+ self.kernel_initializer = kernel_initializer
58
+ self.bias_initializer = bias_initializer
59
+ self.trainable = trainable
60
+ self.dtype = dtype
61
+ super().__init__(**kwargs)
62
+
63
+ def build_weights(self):
64
+ kernel_initializer = self.kernel_initializer
65
+ if self.use_wscale:
66
+ gain = 1.0 if self.kernel_size == 1 else np.sqrt(2)
67
+ fan_in = self.kernel_size*self.kernel_size*self.in_ch
68
+ he_std = gain / np.sqrt(fan_in)
69
+ self.wscale = tf.constant(he_std, dtype=self.dtype )
70
+ if kernel_initializer is None:
71
+ kernel_initializer = tf.initializers.random_normal(0, 1.0, dtype=self.dtype)
72
+
73
+ if kernel_initializer is None:
74
+ kernel_initializer = nn.initializers.ca()
75
+
76
+ self.weight = tf.get_variable("weight", (self.kernel_size,self.kernel_size,self.in_ch,self.out_ch), dtype=self.dtype, initializer=kernel_initializer, trainable=self.trainable )
77
+
78
+ if self.use_bias:
79
+ bias_initializer = self.bias_initializer
80
+ if bias_initializer is None:
81
+ bias_initializer = tf.initializers.zeros(dtype=self.dtype)
82
+
83
+ self.bias = tf.get_variable("bias", (self.out_ch,), dtype=self.dtype, initializer=bias_initializer, trainable=self.trainable )
84
+
85
+ def get_weights(self):
86
+ weights = [self.weight]
87
+ if self.use_bias:
88
+ weights += [self.bias]
89
+ return weights
90
+
91
+ def forward(self, x):
92
+ weight = self.weight
93
+ if self.use_wscale:
94
+ weight = weight * self.wscale
95
+
96
+ if self.padding is not None:
97
+ x = tf.pad (x, self.padding, mode='CONSTANT')
98
+
99
+ x = tf.nn.conv2d(x, weight, self.strides, 'VALID', dilations=self.dilations, data_format=nn.data_format)
100
+ if self.use_bias:
101
+ if nn.data_format == "NHWC":
102
+ bias = tf.reshape (self.bias, (1,1,1,self.out_ch) )
103
+ else:
104
+ bias = tf.reshape (self.bias, (1,self.out_ch,1,1) )
105
+ x = tf.add(x, bias)
106
+ return x
107
+
108
+ def __str__(self):
109
+ r = f"{self.__class__.__name__} : in_ch:{self.in_ch} out_ch:{self.out_ch} "
110
+
111
+ return r
112
+ nn.Conv2D = Conv2D
face_detect/core/leras/layers/Conv2DTranspose.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from face_feature.core.leras import nn
3
+ tf = nn.tf
4
+
5
+ class Conv2DTranspose(nn.LayerBase):
6
+ """
7
+ use_wscale enables weight scale (equalized learning rate)
8
+ if kernel_initializer is None, it will be forced to random_normal
9
+ """
10
+ def __init__(self, in_ch, out_ch, kernel_size, strides=2, padding='SAME', use_bias=True, use_wscale=False, kernel_initializer=None, bias_initializer=None, trainable=True, dtype=None, **kwargs ):
11
+ if not isinstance(strides, int):
12
+ raise ValueError ("strides must be an int type")
13
+ kernel_size = int(kernel_size)
14
+
15
+ if dtype is None:
16
+ dtype = nn.floatx
17
+
18
+ self.in_ch = in_ch
19
+ self.out_ch = out_ch
20
+ self.kernel_size = kernel_size
21
+ self.strides = strides
22
+ self.padding = padding
23
+ self.use_bias = use_bias
24
+ self.use_wscale = use_wscale
25
+ self.kernel_initializer = kernel_initializer
26
+ self.bias_initializer = bias_initializer
27
+ self.trainable = trainable
28
+ self.dtype = dtype
29
+ super().__init__(**kwargs)
30
+
31
+ def build_weights(self):
32
+ kernel_initializer = self.kernel_initializer
33
+ if self.use_wscale:
34
+ gain = 1.0 if self.kernel_size == 1 else np.sqrt(2)
35
+ fan_in = self.kernel_size*self.kernel_size*self.in_ch
36
+ he_std = gain / np.sqrt(fan_in) # He init
37
+ self.wscale = tf.constant(he_std, dtype=self.dtype )
38
+ if kernel_initializer is None:
39
+ kernel_initializer = tf.initializers.random_normal(0, 1.0, dtype=self.dtype)
40
+
41
+ if kernel_initializer is None:
42
+ kernel_initializer = nn.initializers.ca()
43
+ self.weight = tf.get_variable("weight", (self.kernel_size,self.kernel_size,self.out_ch,self.in_ch), dtype=self.dtype, initializer=kernel_initializer, trainable=self.trainable )
44
+
45
+ if self.use_bias:
46
+ bias_initializer = self.bias_initializer
47
+ if bias_initializer is None:
48
+ bias_initializer = tf.initializers.zeros(dtype=self.dtype)
49
+
50
+ self.bias = tf.get_variable("bias", (self.out_ch,), dtype=self.dtype, initializer=bias_initializer, trainable=self.trainable )
51
+
52
+ def get_weights(self):
53
+ weights = [self.weight]
54
+ if self.use_bias:
55
+ weights += [self.bias]
56
+ return weights
57
+
58
+ def forward(self, x):
59
+ shape = x.shape
60
+
61
+ if nn.data_format == "NHWC":
62
+ h,w,c = shape[1], shape[2], shape[3]
63
+ output_shape = tf.stack ( (tf.shape(x)[0],
64
+ self.deconv_length(w, self.strides, self.kernel_size, self.padding),
65
+ self.deconv_length(h, self.strides, self.kernel_size, self.padding),
66
+ self.out_ch) )
67
+
68
+ strides = [1,self.strides,self.strides,1]
69
+ else:
70
+ c,h,w = shape[1], shape[2], shape[3]
71
+ output_shape = tf.stack ( (tf.shape(x)[0],
72
+ self.out_ch,
73
+ self.deconv_length(w, self.strides, self.kernel_size, self.padding),
74
+ self.deconv_length(h, self.strides, self.kernel_size, self.padding),
75
+ ) )
76
+ strides = [1,1,self.strides,self.strides]
77
+ weight = self.weight
78
+ if self.use_wscale:
79
+ weight = weight * self.wscale
80
+
81
+ x = tf.nn.conv2d_transpose(x, weight, output_shape, strides, padding=self.padding, data_format=nn.data_format)
82
+
83
+ if self.use_bias:
84
+ if nn.data_format == "NHWC":
85
+ bias = tf.reshape (self.bias, (1,1,1,self.out_ch) )
86
+ else:
87
+ bias = tf.reshape (self.bias, (1,self.out_ch,1,1) )
88
+ x = tf.add(x, bias)
89
+ return x
90
+
91
+ def __str__(self):
92
+ r = f"{self.__class__.__name__} : in_ch:{self.in_ch} out_ch:{self.out_ch} "
93
+
94
+ return r
95
+
96
+ def deconv_length(self, dim_size, stride_size, kernel_size, padding):
97
+ assert padding in {'SAME', 'VALID', 'FULL'}
98
+ if dim_size is None:
99
+ return None
100
+ if padding == 'VALID':
101
+ dim_size = dim_size * stride_size + max(kernel_size - stride_size, 0)
102
+ elif padding == 'FULL':
103
+ dim_size = dim_size * stride_size - (stride_size + kernel_size - 2)
104
+ elif padding == 'SAME':
105
+ dim_size = dim_size * stride_size
106
+ return dim_size
107
+ nn.Conv2DTranspose = Conv2DTranspose
face_detect/core/leras/layers/Dense.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from face_feature.core.leras import nn
3
+ tf = nn.tf
4
+
5
+ class Dense(nn.LayerBase):
6
+ def __init__(self, in_ch, out_ch, use_bias=True, use_wscale=False, maxout_ch=0, kernel_initializer=None, bias_initializer=None, trainable=True, dtype=None, **kwargs ):
7
+ """
8
+ use_wscale enables weight scale (equalized learning rate)
9
+ if kernel_initializer is None, it will be forced to random_normal
10
+
11
+ maxout_ch https://link.springer.com/article/10.1186/s40537-019-0233-0
12
+ typical 2-4 if you want to enable DenseMaxout behaviour
13
+ """
14
+ self.in_ch = in_ch
15
+ self.out_ch = out_ch
16
+ self.use_bias = use_bias
17
+ self.use_wscale = use_wscale
18
+ self.maxout_ch = maxout_ch
19
+ self.kernel_initializer = kernel_initializer
20
+ self.bias_initializer = bias_initializer
21
+ self.trainable = trainable
22
+ if dtype is None:
23
+ dtype = nn.floatx
24
+
25
+ self.dtype = dtype
26
+ super().__init__(**kwargs)
27
+
28
+ def build_weights(self):
29
+ if self.maxout_ch > 1:
30
+ weight_shape = (self.in_ch,self.out_ch*self.maxout_ch)
31
+ else:
32
+ weight_shape = (self.in_ch,self.out_ch)
33
+
34
+ kernel_initializer = self.kernel_initializer
35
+
36
+ if self.use_wscale:
37
+ gain = 1.0
38
+ fan_in = np.prod( weight_shape[:-1] )
39
+ he_std = gain / np.sqrt(fan_in) # He init
40
+ self.wscale = tf.constant(he_std, dtype=self.dtype )
41
+ if kernel_initializer is None:
42
+ kernel_initializer = tf.initializers.random_normal(0, 1.0, dtype=self.dtype)
43
+
44
+ if kernel_initializer is None:
45
+ kernel_initializer = tf.initializers.glorot_uniform(dtype=self.dtype)
46
+
47
+ self.weight = tf.get_variable("weight", weight_shape, dtype=self.dtype, initializer=kernel_initializer, trainable=self.trainable )
48
+
49
+ if self.use_bias:
50
+ bias_initializer = self.bias_initializer
51
+ if bias_initializer is None:
52
+ bias_initializer = tf.initializers.zeros(dtype=self.dtype)
53
+ self.bias = tf.get_variable("bias", (self.out_ch,), dtype=self.dtype, initializer=bias_initializer, trainable=self.trainable )
54
+
55
+ def get_weights(self):
56
+ weights = [self.weight]
57
+ if self.use_bias:
58
+ weights += [self.bias]
59
+ return weights
60
+
61
+ def forward(self, x):
62
+ weight = self.weight
63
+ if self.use_wscale:
64
+ weight = weight * self.wscale
65
+
66
+ x = tf.matmul(x, weight)
67
+
68
+ if self.maxout_ch > 1:
69
+ x = tf.reshape (x, (-1, self.out_ch, self.maxout_ch) )
70
+ x = tf.reduce_max(x, axis=-1)
71
+
72
+ if self.use_bias:
73
+ x = tf.add(x, tf.reshape(self.bias, (1,self.out_ch) ) )
74
+
75
+ return x
76
+ nn.Dense = Dense
face_detect/core/leras/layers/DenseNorm.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class DenseNorm(nn.LayerBase):
5
+ def __init__(self, dense=False, eps=1e-06, dtype=None, **kwargs):
6
+ self.dense = dense
7
+ if dtype is None:
8
+ dtype = nn.floatx
9
+ self.eps = tf.constant(eps, dtype=dtype, name="epsilon")
10
+
11
+ super().__init__(**kwargs)
12
+
13
+ def __call__(self, x):
14
+ return x * tf.rsqrt(tf.reduce_mean(tf.square(x), axis=-1, keepdims=True) + self.eps)
15
+
16
+ nn.DenseNorm = DenseNorm
face_detect/core/leras/layers/DepthwiseConv2D.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from face_feature.core.leras import nn
3
+ tf = nn.tf
4
+
5
+ class DepthwiseConv2D(nn.LayerBase):
6
+ """
7
+ default kernel_initializer - CA
8
+ use_wscale bool enables equalized learning rate, if kernel_initializer is None, it will be forced to random_normal
9
+ """
10
+ def __init__(self, in_ch, kernel_size, strides=1, padding='SAME', depth_multiplier=1, dilations=1, use_bias=True, use_wscale=False, kernel_initializer=None, bias_initializer=None, trainable=True, dtype=None, **kwargs ):
11
+ if not isinstance(strides, int):
12
+ raise ValueError ("strides must be an int type")
13
+ if not isinstance(dilations, int):
14
+ raise ValueError ("dilations must be an int type")
15
+ kernel_size = int(kernel_size)
16
+
17
+ if dtype is None:
18
+ dtype = nn.floatx
19
+
20
+ if isinstance(padding, str):
21
+ if padding == "SAME":
22
+ padding = ( (kernel_size - 1) * dilations + 1 ) // 2
23
+ elif padding == "VALID":
24
+ padding = 0
25
+ else:
26
+ raise ValueError ("Wrong padding type. Should be VALID SAME or INT or 4x INTs")
27
+
28
+ if isinstance(padding, int):
29
+ if padding != 0:
30
+ if nn.data_format == "NHWC":
31
+ padding = [ [0,0], [padding,padding], [padding,padding], [0,0] ]
32
+ else:
33
+ padding = [ [0,0], [0,0], [padding,padding], [padding,padding] ]
34
+ else:
35
+ padding = None
36
+
37
+ if nn.data_format == "NHWC":
38
+ strides = [1,strides,strides,1]
39
+ else:
40
+ strides = [1,1,strides,strides]
41
+
42
+ if nn.data_format == "NHWC":
43
+ dilations = [1,dilations,dilations,1]
44
+ else:
45
+ dilations = [1,1,dilations,dilations]
46
+
47
+ self.in_ch = in_ch
48
+ self.depth_multiplier = depth_multiplier
49
+ self.kernel_size = kernel_size
50
+ self.strides = strides
51
+ self.padding = padding
52
+ self.dilations = dilations
53
+ self.use_bias = use_bias
54
+ self.use_wscale = use_wscale
55
+ self.kernel_initializer = kernel_initializer
56
+ self.bias_initializer = bias_initializer
57
+ self.trainable = trainable
58
+ self.dtype = dtype
59
+ super().__init__(**kwargs)
60
+
61
+ def build_weights(self):
62
+ kernel_initializer = self.kernel_initializer
63
+ if self.use_wscale:
64
+ gain = 1.0 if self.kernel_size == 1 else np.sqrt(2)
65
+ fan_in = self.kernel_size*self.kernel_size*self.in_ch
66
+ he_std = gain / np.sqrt(fan_in)
67
+ self.wscale = tf.constant(he_std, dtype=self.dtype )
68
+ if kernel_initializer is None:
69
+ kernel_initializer = tf.initializers.random_normal(0, 1.0, dtype=self.dtype)
70
+
71
+ if kernel_initializer is None:
72
+ kernel_initializer = nn.initializers.ca()
73
+
74
+ self.weight = tf.get_variable("weight", (self.kernel_size,self.kernel_size,self.in_ch,self.depth_multiplier), dtype=self.dtype, initializer=kernel_initializer, trainable=self.trainable )
75
+
76
+ if self.use_bias:
77
+ bias_initializer = self.bias_initializer
78
+ if bias_initializer is None:
79
+ bias_initializer = tf.initializers.zeros(dtype=self.dtype)
80
+
81
+ self.bias = tf.get_variable("bias", (self.in_ch*self.depth_multiplier,), dtype=self.dtype, initializer=bias_initializer, trainable=self.trainable )
82
+
83
+ def get_weights(self):
84
+ weights = [self.weight]
85
+ if self.use_bias:
86
+ weights += [self.bias]
87
+ return weights
88
+
89
+ def forward(self, x):
90
+ weight = self.weight
91
+ if self.use_wscale:
92
+ weight = weight * self.wscale
93
+
94
+ if self.padding is not None:
95
+ x = tf.pad (x, self.padding, mode='CONSTANT')
96
+
97
+ x = tf.nn.depthwise_conv2d(x, weight, self.strides, 'VALID', data_format=nn.data_format)
98
+ if self.use_bias:
99
+ if nn.data_format == "NHWC":
100
+ bias = tf.reshape (self.bias, (1,1,1,self.in_ch*self.depth_multiplier) )
101
+ else:
102
+ bias = tf.reshape (self.bias, (1,self.in_ch*self.depth_multiplier,1,1) )
103
+ x = tf.add(x, bias)
104
+ return x
105
+
106
+ def __str__(self):
107
+ r = f"{self.__class__.__name__} : in_ch:{self.in_ch} depth_multiplier:{self.depth_multiplier} "
108
+ return r
109
+
110
+ nn.DepthwiseConv2D = DepthwiseConv2D
face_detect/core/leras/layers/FRNorm2D.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class FRNorm2D(nn.LayerBase):
5
+ """
6
+ Tensorflow implementation of
7
+ Filter Response Normalization Layer: Eliminating Batch Dependence in theTraining of Deep Neural Networks
8
+ https://arxiv.org/pdf/1911.09737.pdf
9
+ """
10
+ def __init__(self, in_ch, dtype=None, **kwargs):
11
+ self.in_ch = in_ch
12
+
13
+ if dtype is None:
14
+ dtype = nn.floatx
15
+ self.dtype = dtype
16
+
17
+ super().__init__(**kwargs)
18
+
19
+ def build_weights(self):
20
+ self.weight = tf.get_variable("weight", (self.in_ch,), dtype=self.dtype, initializer=tf.initializers.ones() )
21
+ self.bias = tf.get_variable("bias", (self.in_ch,), dtype=self.dtype, initializer=tf.initializers.zeros() )
22
+ self.eps = tf.get_variable("eps", (1,), dtype=self.dtype, initializer=tf.initializers.constant(1e-6) )
23
+
24
+ def get_weights(self):
25
+ return [self.weight, self.bias, self.eps]
26
+
27
+ def forward(self, x):
28
+ if nn.data_format == "NHWC":
29
+ shape = (1,1,1,self.in_ch)
30
+ else:
31
+ shape = (1,self.in_ch,1,1)
32
+ weight = tf.reshape ( self.weight, shape )
33
+ bias = tf.reshape ( self.bias , shape )
34
+ nu2 = tf.reduce_mean(tf.square(x), axis=nn.conv2d_spatial_axes, keepdims=True)
35
+ x = x * ( 1.0/tf.sqrt(nu2 + tf.abs(self.eps) ) )
36
+
37
+ return x*weight + bias
38
+ nn.FRNorm2D = FRNorm2D
face_detect/core/leras/layers/InstanceNorm2D.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class InstanceNorm2D(nn.LayerBase):
5
+ def __init__(self, in_ch, dtype=None, **kwargs):
6
+ self.in_ch = in_ch
7
+
8
+ if dtype is None:
9
+ dtype = nn.floatx
10
+ self.dtype = dtype
11
+
12
+ super().__init__(**kwargs)
13
+
14
+ def build_weights(self):
15
+ kernel_initializer = tf.initializers.glorot_uniform(dtype=self.dtype)
16
+ self.weight = tf.get_variable("weight", (self.in_ch,), dtype=self.dtype, initializer=kernel_initializer )
17
+ self.bias = tf.get_variable("bias", (self.in_ch,), dtype=self.dtype, initializer=tf.initializers.zeros() )
18
+
19
+ def get_weights(self):
20
+ return [self.weight, self.bias]
21
+
22
+ def forward(self, x):
23
+ if nn.data_format == "NHWC":
24
+ shape = (1,1,1,self.in_ch)
25
+ else:
26
+ shape = (1,self.in_ch,1,1)
27
+
28
+ weight = tf.reshape ( self.weight , shape )
29
+ bias = tf.reshape ( self.bias , shape )
30
+
31
+ x_mean = tf.reduce_mean(x, axis=nn.conv2d_spatial_axes, keepdims=True )
32
+ x_std = tf.math.reduce_std(x, axis=nn.conv2d_spatial_axes, keepdims=True ) + 1e-5
33
+
34
+ x = (x - x_mean) / x_std
35
+ x *= weight
36
+ x += bias
37
+
38
+ return x
39
+
40
+ nn.InstanceNorm2D = InstanceNorm2D
face_detect/core/leras/layers/LayerBase.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class LayerBase(nn.Saveable):
5
+ #override
6
+ def build_weights(self):
7
+ pass
8
+
9
+ #override
10
+ def forward(self, *args, **kwargs):
11
+ pass
12
+
13
+ def __call__(self, *args, **kwargs):
14
+ return self.forward(*args, **kwargs)
15
+
16
+ nn.LayerBase = LayerBase
face_detect/core/leras/layers/Saveable.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from pathlib import Path
3
+ from face_feature.core import pathex
4
+ import numpy as np
5
+
6
+ from face_feature.core.leras import nn
7
+
8
+ tf = nn.tf
9
+
10
+ class Saveable():
11
+ def __init__(self, name=None):
12
+ self.name = name
13
+
14
+ #override
15
+ def get_weights(self):
16
+ #return tf tensors that should be initialized/loaded/saved
17
+ return []
18
+
19
+ #override
20
+ def get_weights_np(self):
21
+ weights = self.get_weights()
22
+ if len(weights) == 0:
23
+ return []
24
+ return nn.tf_sess.run (weights)
25
+
26
+ def set_weights(self, new_weights):
27
+ weights = self.get_weights()
28
+ if len(weights) != len(new_weights):
29
+ raise ValueError ('len of lists mismatch')
30
+
31
+ tuples = []
32
+ for w, new_w in zip(weights, new_weights):
33
+
34
+ if len(w.shape) != new_w.shape:
35
+ new_w = new_w.reshape(w.shape)
36
+
37
+ tuples.append ( (w, new_w) )
38
+
39
+ nn.batch_set_value (tuples)
40
+
41
+ def save_weights(self, filename, force_dtype=None):
42
+ d = {}
43
+ weights = self.get_weights()
44
+
45
+ if self.name is None:
46
+ raise Exception("name must be defined.")
47
+
48
+ name = self.name
49
+ for w, w_val in zip(weights, nn.tf_sess.run (weights)):
50
+ w_name_split = w.name.split('/', 1)
51
+ if name != w_name_split[0]:
52
+ raise Exception("weight first name != Saveable.name")
53
+
54
+ if force_dtype is not None:
55
+ w_val = w_val.astype(force_dtype)
56
+
57
+ d[ w_name_split[1] ] = w_val
58
+
59
+ d_dumped = pickle.dumps (d, 4)
60
+ pathex.write_bytes_safe ( Path(filename), d_dumped )
61
+
62
+ def load_weights(self, filename):
63
+ """
64
+ returns True if file exists
65
+ """
66
+ filepath = Path(filename)
67
+ if filepath.exists():
68
+ result = True
69
+ d_dumped = filepath.read_bytes()
70
+ d = pickle.loads(d_dumped)
71
+ else:
72
+ return False
73
+
74
+ weights = self.get_weights()
75
+
76
+ if self.name is None:
77
+ raise Exception("name must be defined.")
78
+
79
+ try:
80
+ tuples = []
81
+ for w in weights:
82
+ w_name_split = w.name.split('/')
83
+ if self.name != w_name_split[0]:
84
+ raise Exception("weight first name != Saveable.name")
85
+
86
+ sub_w_name = "/".join(w_name_split[1:])
87
+
88
+ w_val = d.get(sub_w_name, None)
89
+
90
+ if w_val is None:
91
+ #io.log_err(f"Weight {w.name} was not loaded from file {filename}")
92
+ tuples.append ( (w, w.initializer) )
93
+ else:
94
+ w_val = np.reshape( w_val, w.shape.as_list() )
95
+ tuples.append ( (w, w_val) )
96
+
97
+ nn.batch_set_value(tuples)
98
+ except:
99
+ return False
100
+
101
+ return True
102
+
103
+ def init_weights(self):
104
+ nn.init_weights(self.get_weights())
105
+
106
+ nn.Saveable = Saveable
face_detect/core/leras/layers/ScaleAdd.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from face_feature.core.leras import nn
2
+ tf = nn.tf
3
+
4
+ class ScaleAdd(nn.LayerBase):
5
+ def __init__(self, ch, dtype=None, **kwargs):
6
+ if dtype is None:
7
+ dtype = nn.floatx
8
+ self.dtype = dtype
9
+ self.ch = ch
10
+
11
+ super().__init__(**kwargs)
12
+
13
+ def build_weights(self):
14
+ self.weight = tf.get_variable("weight",(self.ch,), dtype=self.dtype, initializer=tf.initializers.zeros() )
15
+
16
+ def get_weights(self):
17
+ return [self.weight]
18
+
19
+ def forward(self, inputs):
20
+ if nn.data_format == "NHWC":
21
+ shape = (1,1,1,self.ch)
22
+ else:
23
+ shape = (1,self.ch,1,1)
24
+
25
+ weight = tf.reshape ( self.weight, shape )
26
+
27
+ x0, x1 = inputs
28
+ x = x0 + x1*weight
29
+
30
+ return x
31
+ nn.ScaleAdd = ScaleAdd