Upload 117 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +5 -0
- HifiFaceAPI_parallel_base.py +148 -0
- HifiFaceAPI_parallel_trt_roi_realtime_api.py +189 -0
- HifiFaceAPI_parallel_trt_roi_realtime_sr_api.py +234 -0
- LICENSE +32 -0
- app.py +175 -0
- assets/cam_demo1.gif +3 -0
- assets/cam_demo2.gif +3 -0
- assets/demo10.gif +3 -0
- assets/demo20.gif +3 -0
- color_transfer.py +337 -0
- data/image_feature_dict.pkl +3 -0
- data/source/demo.mp4 +3 -0
- data/source/elon-musk1.jpg +0 -0
- face_detect/FaceType.py +37 -0
- face_detect/LandmarksProcessor.py +1482 -0
- face_detect/__init__.py +3 -0
- face_detect/core/imagelib/SegIEPolys.py +158 -0
- face_detect/core/imagelib/__init__.py +32 -0
- face_detect/core/imagelib/blursharpen.py +38 -0
- face_detect/core/imagelib/color_transfer.py +340 -0
- face_detect/core/imagelib/common.py +62 -0
- face_detect/core/imagelib/draw.py +13 -0
- face_detect/core/imagelib/equalize_and_stack_square.py +45 -0
- face_detect/core/imagelib/estimate_sharpness.py +278 -0
- face_detect/core/imagelib/filters.py +245 -0
- face_detect/core/imagelib/morph.py +37 -0
- face_detect/core/imagelib/reduce_colors.py +14 -0
- face_detect/core/imagelib/sd/__init__.py +2 -0
- face_detect/core/imagelib/sd/calc.py +25 -0
- face_detect/core/imagelib/sd/draw.py +200 -0
- face_detect/core/imagelib/warp.py +72 -0
- face_detect/core/leras/__init__.py +1 -0
- face_detect/core/leras/archis/ArchiBase.py +17 -0
- face_detect/core/leras/archis/DeepFakeArchi.py +223 -0
- face_detect/core/leras/archis/__init__.py +2 -0
- face_detect/core/leras/device.py +272 -0
- face_detect/core/leras/layers/AdaIN.py +56 -0
- face_detect/core/leras/layers/BatchNorm2D.py +42 -0
- face_detect/core/leras/layers/BlurPool.py +50 -0
- face_detect/core/leras/layers/Conv2D.py +112 -0
- face_detect/core/leras/layers/Conv2DTranspose.py +107 -0
- face_detect/core/leras/layers/Dense.py +76 -0
- face_detect/core/leras/layers/DenseNorm.py +16 -0
- face_detect/core/leras/layers/DepthwiseConv2D.py +110 -0
- face_detect/core/leras/layers/FRNorm2D.py +38 -0
- face_detect/core/leras/layers/InstanceNorm2D.py +40 -0
- face_detect/core/leras/layers/LayerBase.py +16 -0
- face_detect/core/leras/layers/Saveable.py +106 -0
- 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
|
assets/cam_demo2.gif
ADDED
Git LFS Details
|
assets/demo10.gif
ADDED
Git LFS Details
|
assets/demo20.gif
ADDED
Git LFS Details
|
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
|