Akjava commited on
Commit
e3e4e64
·
1 Parent(s): 9b3b6ac
.gitattributes CHANGED
@@ -33,3 +33,4 @@ 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
+ *.task filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__
2
+ files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Mediapipe Facial Expression Transfer
3
  emoji: 💻
4
  colorFrom: purple
5
  colorTo: pink
@@ -8,7 +8,7 @@ sdk_version: 5.6.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: transfer face expression
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Mediapipe Face Landmark/Skin Transform
3
  emoji: 💻
4
  colorFrom: purple
5
  colorTo: pink
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: Transform face landmark/skin,half of FaceSwap
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ import gradio as gr
3
+ import subprocess
4
+ from PIL import Image,ImageOps,ImageDraw,ImageFilter
5
+ import json
6
+ import os
7
+ import time
8
+
9
+ from mp_utils import get_pixel_cordinate_list,extract_landmark,get_pixel_cordinate
10
+ from glibvision.draw_utils import points_to_box,box_to_xy,plus_point
11
+
12
+ import numpy as np
13
+ from glibvision.pil_utils import fill_points,create_color_image,draw_points,draw_box
14
+
15
+ from gradio_utils import save_image,save_buffer,clear_old_files ,read_file
16
+
17
+ from mediapipe_transform import process_landmark_transform_pil
18
+
19
+
20
+ '''
21
+ innner_eyes_blur - inner eyes blur
22
+ iris_mask_blur - final iris edge blur
23
+ '''
24
+
25
+
26
+ def process_images(image,transform_target,innner_mouth,innner_eyes,
27
+ color_matching,transparent_background,add_align_mouth,add_align_eyes,blur_size,
28
+ progress=gr.Progress(track_tqdm=True)):
29
+ clear_old_files()
30
+ if image == None:
31
+ raise gr.Error("Need Image")
32
+ if transform_target == None:
33
+ raise gr.Error("Need one more Target Image")
34
+
35
+ result = process_landmark_transform_pil(image,transform_target,innner_mouth,innner_eyes,
36
+ color_matching,transparent_background,add_align_mouth,add_align_eyes,blur_size)
37
+
38
+ return result
39
+
40
+
41
+ css="""
42
+ #col-left {
43
+ margin: 0 auto;
44
+ max-width: 640px;
45
+ }
46
+ #col-right {
47
+ margin: 0 auto;
48
+ max-width: 640px;
49
+ }
50
+ .grid-container {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ gap:10px
55
+ }
56
+
57
+ .image {
58
+ width: 128px;
59
+ height: 128px;
60
+ object-fit: cover;
61
+ }
62
+
63
+ .text {
64
+ font-size: 16px;
65
+ }
66
+ """
67
+
68
+ #css=css,
69
+
70
+ def align_check_control(is_transparent):
71
+ if is_transparent:
72
+ return gr.Checkbox(visible=True),gr.Checkbox(visible=True)
73
+ else:
74
+ return gr.Checkbox(visible=False),gr.Checkbox(visible=False)
75
+
76
+ with gr.Blocks(css=css, elem_id="demo-container") as demo:
77
+ with gr.Column():
78
+ gr.HTML(read_file("demo_header.html"))
79
+ gr.HTML(read_file("demo_tools.html"))
80
+ with gr.Row():
81
+ with gr.Column():
82
+ image = gr.Image(height=800,sources=['upload','clipboard'],image_mode='RGB',elem_id="image_upload", type="pil", label="Image")
83
+ align_image = gr.Image(height=800,sources=['upload','clipboard'],image_mode='RGB',elem_id="image_align", type="pil", label="Align Target")
84
+
85
+ with gr.Row(elem_id="prompt-container", equal_height=False):
86
+ with gr.Row():
87
+ btn = gr.Button("Transform landmarks", elem_id="run_button",variant="primary")
88
+
89
+
90
+
91
+ with gr.Accordion(label="Advanced Settings", open=True):
92
+ with gr.Row( equal_height=True):
93
+ innner_mouth = gr.Checkbox(label="Include inner-Mouth",value=True,info="Transform teeesh and tongue")
94
+ innner_eyes = gr.Checkbox(label="Include inner Eyes",value=True,info="Transform Iris")
95
+ with gr.Row( equal_height=True):
96
+ color_matching = gr.Checkbox(label="color_matching",value=True,info="histgram color matching(not good,when mouth opened)")
97
+ transparent_background = gr.Checkbox(label="transparent-background",value=False,info="no background picture")
98
+ with gr.Row( equal_height=True):
99
+ add_align_mouth = gr.Checkbox(label="Add Align Mouth",value=True,info="add align-mouth when transparent",visible=False)
100
+ add_align_eyes = gr.Checkbox(label="Add Align Eyes",value=True,info="add align-eyes when transparent",visible=False)
101
+ blur_size = gr.Slider(info="blur-size",
102
+ label="Blur-Size",
103
+ minimum=0,
104
+ maximum=100,
105
+ step=1,
106
+ value=50)
107
+
108
+
109
+ with gr.Column():
110
+ result_image = gr.Image(height=760,label="Result", elem_id="output-animation",image_mode='RGBA')
111
+
112
+
113
+ transparent_background.change(fn=align_check_control,inputs=[transparent_background],outputs=[add_align_mouth,add_align_eyes])
114
+ btn.click(fn=process_images, inputs=[image,align_image,innner_mouth,innner_eyes,
115
+ color_matching,transparent_background,add_align_mouth,add_align_eyes,blur_size
116
+ ],outputs=[result_image] ,api_name='infer')
117
+
118
+ example_images = [
119
+ ["examples/02316230.jpg"],
120
+ ["examples/00003245_00.jpg"],
121
+ ["examples/00827009.jpg"],
122
+ ["examples/00002062.jpg"],
123
+ ["examples/00824008.jpg"],
124
+ ["examples/00825000.jpg"],
125
+ ["examples/00826007.jpg"],
126
+ ["examples/00824006.jpg"],
127
+ ["examples/00828003.jpg"],
128
+ ["examples/01090260.jpg"],
129
+
130
+
131
+ ["examples/00002200.jpg"],
132
+ ["examples/00005259.jpg"],
133
+ ["examples/00018022.jpg"],
134
+ ["examples/img-above.jpg"],
135
+ ["examples/00100265.jpg"],
136
+ ["examples/02250075.jpg"],
137
+
138
+ ]
139
+ example1=gr.Examples(
140
+ examples = example_images,label="Image",
141
+ inputs=[image],examples_per_page=8
142
+ )
143
+ example2=gr.Examples(
144
+ examples =example_images[::-1],label="Align Image",
145
+ inputs=[align_image],examples_per_page=8
146
+ )
147
+ gr.HTML(read_file("demo_footer.html"))
148
+
149
+ if __name__ == "__main__":
150
+ demo.launch()
demo_footer.html ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ <div>
2
+ <P> Images are generated with <a href="https://huggingface.co/black-forest-labs/FLUX.1-schnell">FLUX.1-schnell</a> and licensed under <a href="http://www.apache.org/licenses/LICENSE-2.0">the Apache 2.0 License</a>
3
+ </div>
demo_header.html ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div style="text-align: center;">
2
+ <h1>
3
+ Mediapipe Face Landmark/Skin Transform
4
+ </h1>
5
+ <div class="grid-container">
6
+ <img src="https://akjava.github.io/AIDiagramChatWithVoice-FaceCharacter/webp/128/00544245.webp" alt="Mediapipe Face Detection" class="image">
7
+
8
+ <p class="text">
9
+ This Space use <a href="http://www.apache.org/licenses/LICENSE-2.0">the Apache 2.0</a> Licensed <a href="https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker">Mediapipe FaceLandmarker</a> <br>
10
+ This's not a face swap, it's more like applying A's skin to B's face.<br>
11
+ To make face-swap I need to find a way to keep face-balnaces way.<br>
12
+ technically cv2.warpAffine + blend-rgb is enough quality<br>
13
+ numpy + numba is fast than expected<br>
14
+ </p>
15
+ </div>
16
+
17
+ </div>
demo_tools.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div style="text-align: center;">
2
+ <p>
3
+ <a href="https://huggingface.co/spaces/Akjava/flux1-schnell-img2img">Flux1-Img2Img(GPU)</a> |
4
+ <a href="https://huggingface.co/spaces/Akjava/flux1-schnell-mask-inpaint">Flux1-Inpaint(GPU)</a> |
5
+ <a href="https://huggingface.co/spaces/Akjava/mediapipe-68-points-facial-mask">Create 68 points Parts Mask</a> |
6
+ <a href="https://huggingface.co/spaces/Akjava/histgram-color-matching">Histgram Color Matching</a> |
7
+ <a href="https://huggingface.co/spaces/Akjava/WebPTalkHead">WebP anime with 3 images</a> |
8
+ <a href="https://huggingface.co/spaces/Akjava/WebP-Resize-Convert">WebP Resize Animation</a>
9
+ </p>
10
+ <p></p>
11
+ </div>
examples/00002062.jpg ADDED
examples/00002200.jpg ADDED
examples/00003245_00.jpg ADDED
examples/00005259.jpg ADDED
examples/00018022.jpg ADDED
examples/00100265.jpg ADDED
examples/00824006.jpg ADDED
examples/00824008.jpg ADDED
examples/00825000.jpg ADDED
examples/00826007.jpg ADDED
examples/00827009.jpg ADDED
examples/00828003.jpg ADDED
examples/01090260.jpg ADDED
examples/02250075.jpg ADDED
examples/02316230.jpg ADDED
examples/img-above.jpg ADDED
face_landmarker.task ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:64184e229b263107bc2b804c6625db1341ff2bb731874b0bcc2fe6544e0bc9ff
3
+ size 3758596
face_landmarker.task.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Face landmark detection
2
+ https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker
3
+
4
+ model card page is
5
+ https://storage.googleapis.com/mediapipe-assets/MediaPipe%20BlazeFace%20Model%20Card%20(Short%20Range).pdf
6
+
7
+ license is Apache2.0
8
+ https://www.apache.org/licenses/LICENSE-2.0.html
glibvision/common_utils.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ def check_exists_files(files,dirs,exit_on_error=True):
3
+ if files is not None:
4
+ if isinstance(files, str):
5
+ files = [files]
6
+ for file in files:
7
+ if not os.path.isfile(file):
8
+ print(f"File {file} not found")
9
+ if exit_on_error:
10
+ exit(1)
11
+ else:
12
+ return 1
13
+ if dirs is not None:
14
+ if isinstance(dirs, str):
15
+ dirs = [dirs]
16
+ for dir in dirs:
17
+ if not os.path.isdir(dir):
18
+ print(f"Dir {dir} not found")
19
+ if exit_on_error:
20
+ exit(1)
21
+ else:
22
+ return 1
23
+ return 0
24
+
25
+ image_extensions =[".jpg"]
26
+
27
+ def add_name_suffix(file_name,suffix,replace_suffix=False):
28
+ if not suffix.startswith("_"):#force add
29
+ suffix="_"+suffix
30
+
31
+ name,ext = os.path.splitext(file_name)
32
+ if replace_suffix:
33
+ index = name.rfind("_")
34
+ if index!=-1:
35
+ return f"{name[0:index]}{suffix}{ext}"
36
+
37
+ return f"{name}{suffix}{ext}"
38
+
39
+ def replace_extension(file_name,new_extension,suffix=None,replace_suffix=False):
40
+ if not new_extension.startswith("."):
41
+ new_extension="."+new_extension
42
+
43
+ name,ext = os.path.splitext(file_name)
44
+ new_file = f"{name}{new_extension}"
45
+ if suffix:
46
+ return add_name_suffix(name+new_extension,suffix,replace_suffix)
47
+ return new_file
48
+
49
+ def list_digit_images(input_dir,sort=True):
50
+ digit_images = []
51
+ global image_extensions
52
+ files = os.listdir(input_dir)
53
+ for file in files:
54
+ if file.endswith(".jpg"):#TODO check image
55
+ base,ext = os.path.splitext(file)
56
+ if not base.isdigit():
57
+ continue
58
+ digit_images.append(file)
59
+
60
+ if sort:
61
+ digit_images.sort()
62
+
63
+ return digit_images
64
+ def list_suffix_images(input_dir,suffix,is_digit=True,sort=True):
65
+ digit_images = []
66
+ global image_extensions
67
+ files = os.listdir(input_dir)
68
+ for file in files:
69
+ if file.endswith(".jpg"):#TODO check image
70
+ base,ext = os.path.splitext(file)
71
+ if base.endswith(suffix):
72
+ if is_digit:
73
+ if not base.replace(suffix,"").isdigit():
74
+ continue
75
+ digit_images.append(file)
76
+
77
+ if sort:
78
+ digit_images.sort()
79
+
80
+ return digit_images
81
+
82
+ import time
83
+
84
+ class ProgressTracker:
85
+ """
86
+ 処理の進捗状況を追跡し、経過時間と残り時間を表示するクラス。
87
+ """
88
+
89
+ def __init__(self,key, total_target):
90
+ """
91
+ コンストラクタ
92
+
93
+ Args:
94
+ total_target (int): 処理対象の総数
95
+ """
96
+ self.key = key
97
+ self.total_target = total_target
98
+ self.complete_target = 0
99
+ self.start_time = time.time()
100
+
101
+ def update(self):
102
+ """
103
+ 進捗を1つ進める。
104
+ 経過時間と残り時間を表示する。
105
+ """
106
+ self.complete_target += 1
107
+ current_time = time.time()
108
+ consumed_time = current_time - self.start_time
109
+ remain_time = (consumed_time / self.complete_target) * (self.total_target - self.complete_target) if self.complete_target > 0 else 0
110
+ print(f"stepped {self.key} {self.total_target} of {self.complete_target}, consumed {(consumed_time / 60):.1f} min, remain {(remain_time / 60):.1f} min")
111
+
112
+
glibvision/cv2_utils.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+
5
+ #2024-11-30 copy paste
6
+ def draw_bbox(image,box,color=(255,0,0),thickness=1):
7
+ if thickness==0:
8
+ return
9
+
10
+ left = int(box[0])
11
+ top = int(box[1])
12
+ right = int(box[0]+box[2])
13
+ bottom = int(box[1]+box[3])
14
+ box_points =[(left,top),(right,top),(right,bottom),(left,bottom)]
15
+
16
+ cv2.polylines(image, [np.array(box_points)], isClosed=True, color=color, thickness=thickness)
17
+
18
+
19
+ def to_int_points(points):
20
+ int_points=[]
21
+ for point in points:
22
+ int_points.append([int(point[0]),int(point[1])])
23
+ return int_points
24
+
25
+ def draw_text(img, text, point, font_scale=0.5, color=(200, 200, 200), thickness=1):
26
+ font = cv2.FONT_HERSHEY_SIMPLEX
27
+ cv2.putText(img, str(text), point, font, font_scale, color, thickness, cv2.LINE_AA)
28
+
29
+ plot_text_color = (200, 200, 200)
30
+ plot_text_font_scale = 0.5
31
+ plot_index = 1
32
+ plot_text = True
33
+
34
+ def set_plot_text(is_plot,text_font_scale,text_color):
35
+ global plot_index,plot_text,plot_text_font_scale,plot_text_color
36
+ plot_text = is_plot
37
+ plot_index = 1
38
+ plot_text_font_scale = text_font_scale
39
+ plot_text_color = text_color
40
+
41
+ def plot_points(image,points,isClosed=False,circle_size=3,circle_color=(255,0,0),line_size=1,line_color=(0,0,255)):
42
+ global plot_index,plot_text
43
+ int_points = to_int_points(points)
44
+ if circle_size>0:
45
+ for point in int_points:
46
+ cv2.circle(image,point,circle_size,circle_color,-1)
47
+ if plot_text:
48
+ draw_text(image,plot_index,point,plot_text_font_scale,plot_text_color)
49
+ plot_index+=1
50
+ if line_size>0:
51
+ cv2.polylines(image, [np.array(int_points)], isClosed=isClosed, color=line_color, thickness=line_size)
52
+
53
+ def fill_points(image,points,thickness=1,line_color=(255,255,255),fill_color = (255,255,255)):
54
+ np_points = np.array(points,dtype=np.int32)
55
+ cv2.fillPoly(image, [np_points], fill_color)
56
+ cv2.polylines(image, [np_points], isClosed=True, color=line_color, thickness=thickness)
57
+
58
+ def get_image_size(cv2_image):
59
+ return cv2_image.shape[:2]
60
+
61
+ def get_channel(np_array):
62
+ return np_array.shape[2] if np_array.ndim == 3 else 1
63
+
64
+ def get_numpy_text(np_array,key=""):
65
+ channel = get_channel(np_array)
66
+ return f"{key} shape = {np_array.shape} channel = {channel} ndim = {np_array.ndim} size = {np_array.size}"
67
+
68
+
69
+ def gray3d_to_2d(grayscale: np.ndarray) -> np.ndarray:
70
+ channel = get_channel(grayscale)
71
+ if channel!=1:
72
+ raise ValueError(f"color maybe rgb or rgba {get_numpy_text(grayscale)}")
73
+ """
74
+ 3 次元グレースケール画像 (チャンネル数 1) を 2 次元に変換する。
75
+
76
+ Args:
77
+ grayscale (np.ndarray): 3 次元グレースケール画像 (チャンネル数 1)。
78
+
79
+ Returns:
80
+ np.ndarray: 2 次元グレースケール画像。
81
+ """
82
+
83
+ if grayscale.ndim == 2:
84
+ return grayscale
85
+ return np.squeeze(grayscale)
86
+
87
+ def blend_rgb_images(image1: np.ndarray, image2: np.ndarray, mask: np.ndarray) -> np.ndarray:
88
+ """
89
+ 2 つの RGB 画像をマスク画像を使用してブレンドする。
90
+
91
+ Args:
92
+ image1 (np.ndarray): 最初の画像 (RGB)。
93
+ image2 (np.ndarray): 2 番目の画像 (RGB)。
94
+ mask (np.ndarray): マスク画像 (グレースケール)。
95
+
96
+ Returns:
97
+ np.ndarray: ブレンドされた画像 (RGB)。
98
+
99
+ Raises:
100
+ ValueError: 入力画像の形状が一致しない場合。
101
+ """
102
+
103
+ if image1.shape != image2.shape or image1.shape[:2] != mask.shape:
104
+ raise ValueError("入力画像の形状が一致しません。")
105
+
106
+ # 画像を float 型に変換
107
+ image1 = image1.astype(float)
108
+ image2 = image2.astype(float)
109
+
110
+ # マスクを 3 チャンネルに変換し、0-1 の範囲にスケール
111
+ alpha = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR).astype(float) / 255.0
112
+
113
+ # ブレンド計算
114
+ blended = (1 - alpha) * image1 + alpha * image2
115
+
116
+ return blended.astype(np.uint8)
117
+
118
+ def create_color_image(img,color=(255,255,255)):
119
+ mask = np.zeros_like(img)
120
+
121
+ h, w = img.shape[:2]
122
+ cv2.rectangle(mask, (0, 0), (w, h), color, -1)
123
+ return mask
124
+
125
+ def pil_to_bgr_image(image):
126
+ np_image = np.array(image, dtype=np.uint8)
127
+ if np_image.shape[2] == 4:
128
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_RGBA2BGRA)
129
+ else:
130
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_RGB2BGR)
131
+ return bgr_img
132
+
133
+ def bgr_to_rgb(np_image):
134
+ if np_image.shape[2] == 4:
135
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_RBGRA2RGBA)
136
+ else:
137
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_BGR2RGB)
138
+ return bgr_img
139
+
140
+ def crop(image,bbox):
141
+ x,y,width,height = bbox
142
+ return image[y:y+height, x:x+width]
143
+ #not check safe
144
+ def paste(image,replace_image,x,y):
145
+ height,width = replace_image.shape[:2]
146
+ image[y:y+height, x:x+width] = replace_image
glibvision/draw_utils.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DrawUtils
2
+ # not PIL,CV2,Numpy drawing method
3
+
4
+
5
+ def points_to_box(points):
6
+ x1=float('inf')
7
+ x2=0
8
+ y1=float('inf')
9
+ y2=0
10
+ for point in points:
11
+ if point[0]<x1:
12
+ x1=point[0]
13
+ if point[0]>x2:
14
+ x2=point[0]
15
+ if point[1]<y1:
16
+ y1=point[1]
17
+ if point[1]>y2:
18
+ y2=point[1]
19
+ return [x1,y1,x2-x1,y2-y1]
20
+
21
+ def box_to_point(box):
22
+ return [
23
+ [box[0],box[1]],
24
+ [box[0]+box[2],box[1]],
25
+ [box[0]+box[2],box[1]+box[3]],
26
+ [box[0],box[1]+box[3]]
27
+ ]
28
+
29
+ def plus_point(base_pt,add_pt):
30
+ return [base_pt[0]+add_pt[0],base_pt[1]+add_pt[1]]
31
+
32
+ def box_to_xy(box):
33
+ return [box[0],box[1],box[2]+box[0],box[3]+box[1]]
34
+
35
+ def to_int_points(points):
36
+ int_points=[]
37
+ for point in points:
38
+ int_points.append([int(point[0]),int(point[1])])
39
+ return int_points
glibvision/glandmark_utils.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+
4
+ #simple single version
5
+ def bbox_to_glandmarks(file_name,bbox,points = None):
6
+ base,ext = os.path.splitext(file_name)
7
+ glandmark = {"image":{
8
+ "boxes":[{
9
+ "left":int(bbox[0]),"top":int(bbox[1]),"width":int(bbox[2]),"height":int(bbox[3])
10
+ }],
11
+ "file":file_name,
12
+ "id":int(base)
13
+ # width,height ignore here
14
+ }}
15
+ if points is not None:
16
+ parts=[
17
+ ]
18
+ for point in points:
19
+ parts.append({"x":int(point[0]),"y":int(point[1])})
20
+ glandmark["image"]["boxes"][0]["parts"] = parts
21
+ return glandmark
22
+
23
+ #technically this is not g-landmark/dlib ,
24
+ def convert_to_landmark_group_json(points):
25
+ if len(points)!=68:
26
+ print(f"points must be 68 but {len(points)}")
27
+ return None
28
+ new_points=list(points)
29
+
30
+ result = [ # possible multi person ,just possible any func support multi person
31
+
32
+ { # index start 0 but index-number start 1
33
+ "chin":new_points[0:17],
34
+ "left_eyebrow":new_points[17:22],
35
+ "right_eyebrow":new_points[22:27],
36
+ "nose_bridge":new_points[27:31],
37
+ "nose_tip":new_points[31:36],
38
+ "left_eye":new_points[36:42],
39
+ "right_eye":new_points[42:48],
40
+
41
+ # lip points customized structure
42
+ # MIT licensed face_recognition
43
+ # https://github.com/ageitgey/face_recognition
44
+ "top_lip":new_points[48:55]+[new_points[64]]+[new_points[63]]+[new_points[62]]+[new_points[61]]+[new_points[60]],
45
+ "bottom_lip":new_points[54:60]+[new_points[48]]+[new_points[60]]+[new_points[67]]+[new_points[66]]+[new_points[65]]+[new_points[64]],
46
+ }
47
+ ]
48
+ return result
glibvision/numpy_utils.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+
4
+ def apply_binary_mask_to_color(base_image,color,mask):
5
+ """
6
+ 二値マスクを使用して、画像の一部を別の画像にコピーする。
7
+
8
+ Args:
9
+ base_image (np.ndarray): コピー先の画像。
10
+ paste_image (np.ndarray): コピー元の画像。
11
+ mask (np.ndarray): 二値マスク画像。
12
+
13
+ Returns:
14
+ np.ndarray: マスクを適用した画像。
15
+
16
+ """
17
+ # TODO check all shape
18
+ #print_numpy(base_image)
19
+ #print_numpy(paste_image)
20
+ #print_numpy(mask)
21
+ if mask.ndim == 2:
22
+ condition = mask == 255
23
+ else:
24
+ condition = mask[:,:,0] == 255
25
+
26
+ base_image[condition] = color
27
+ return base_image
28
+
29
+ def apply_binary_mask_to_image(base_image,paste_image,mask):
30
+ """
31
+ 二値マスクを使用して、画像の一部を別の画像にコピーする。
32
+
33
+ Args:
34
+ base_image (np.ndarray): コピー先の画像。
35
+ paste_image (np.ndarray): コピー元の画像。
36
+ mask (np.ndarray): 二値マスク画像。
37
+
38
+ Returns:
39
+ np.ndarray: マスクを適用した画像。
40
+
41
+ """
42
+ # TODO check all shape
43
+ #print_numpy(base_image)
44
+ #print_numpy(paste_image)
45
+ #print_numpy(mask)
46
+ if mask.ndim == 2:
47
+ condition = mask == 255
48
+ else:
49
+ condition = mask[:,:,0] == 255
50
+
51
+ base_image[condition] = paste_image[condition]
52
+ return base_image
53
+
54
+ def pil_to_numpy(image):
55
+ return np.array(image, dtype=np.uint8)
56
+
57
+ def extruce_points(points,index,ratio=1.5):
58
+ """
59
+ indexのポイントをratio倍だけ、点群の中心から、外側に膨らます。
60
+ """
61
+ center_point = np.mean(points, axis=0)
62
+ if index < 0 or index > len(points):
63
+ raise ValueError(f"index must be range(0,{len(points)} but value = {index})")
64
+ point1 =points[index]
65
+ print(f"center = {center_point}")
66
+ vec_to_center = point1 - center_point
67
+ return vec_to_center*ratio + center_point
68
+
69
+
70
+ def bulge_polygon(points, bulge_factor=0.1,isClosed=True):
71
+ """
72
+ ポリゴンの辺の中間に点を追加し、外側に膨らませる
73
+ ndarrayを返すので注意
74
+ """
75
+ # 入力 points を NumPy 配列に変換
76
+ points = np.array(points)
77
+
78
+ # ポリゴン全体の重心を求める
79
+ center_point = np.mean(points, axis=0)
80
+ #print(f"center = {center_point}")
81
+ new_points = []
82
+ num_points = len(points)
83
+ for i in range(num_points):
84
+ if i == num_points -1 and not isClosed:
85
+ break
86
+ p1 = points[i]
87
+ #print(f"p{i} = {p1}")
88
+ # 重心から頂点へのベクトル
89
+ #vec_to_center = p1 - center_point
90
+
91
+ # 辺のベクトルを求める
92
+ mid_diff = points[(i + 1) % num_points] - p1
93
+ mid = p1+(mid_diff/2)
94
+
95
+ #print(f"mid = {mid}")
96
+ out_vec = mid - center_point
97
+
98
+ # 重心からのベクトルに bulge_vec を加算
99
+ new_point = mid + out_vec * bulge_factor
100
+
101
+ new_points.append(p1)
102
+ new_points.append(new_point.astype(np.int32))
103
+
104
+ return np.array(new_points)
105
+
106
+
107
+ # image.shape rgb are (1024,1024,3) use 1024,1024 as 2-dimensional
108
+ def create_2d_image(shape):
109
+ grayscale_image = np.zeros(shape[:2], dtype=np.uint8)
110
+ return grayscale_image
glibvision/pil_utils.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image,ImageDraw
2
+ from .draw_utils import box_to_xy,to_int_points,box_to_point
3
+ #ver-2024-11-18
4
+ def create_color_image(width, height, color=(255,255,255)):
5
+ if color == None:
6
+ color = (0,0,0)
7
+
8
+ if len(color )== 3:
9
+ mode ="RGB"
10
+ elif len(color )== 4:
11
+ mode ="RGBA"
12
+
13
+ img = Image.new(mode, (width, height), color)
14
+ return img
15
+
16
+ def fill_points(image,points,color=(255,255,255)):
17
+ return draw_points(image,points,fill=color)
18
+
19
+ def draw_points(image,points,outline=None,fill=None,width=1):
20
+ draw = ImageDraw.Draw(image)
21
+ int_points = [(int(x), int(y)) for x, y in points]
22
+ draw.polygon(int_points, outline=outline,fill=fill,width=width)
23
+ return image
24
+
25
+ def draw_box(image,box,outline=None,fill=None):
26
+ points = to_int_points(box_to_point(box))
27
+ return draw_points(image,points,outline,fill)
28
+
29
+ def from_numpy(numpy_array):
30
+ return Image.fromarray(numpy_array)
gradio_utils.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import os
4
+ import time
5
+ import io
6
+ import hashlib
7
+
8
+ def clear_old_files(dir="files",passed_time=60*60):
9
+ try:
10
+ files = os.listdir(dir)
11
+ current_time = time.time()
12
+ for file in files:
13
+ file_path = os.path.join(dir,file)
14
+
15
+ ctime = os.stat(file_path).st_ctime
16
+ diff = current_time - ctime
17
+ #print(f"ctime={ctime},current_time={current_time},passed_time={passed_time},diff={diff}")
18
+ if diff > passed_time:
19
+ os.remove(file_path)
20
+ except:
21
+ print("maybe still gallery using error")
22
+
23
+ def get_buffer_id(buffer):
24
+ hash_object = hashlib.sha256(buffer.getvalue())
25
+ hex_dig = hash_object.hexdigest()
26
+ unique_id = hex_dig[:32]
27
+ return unique_id
28
+
29
+ def get_image_id(image):
30
+ buffer = io.BytesIO()
31
+ image.save(buffer, format='PNG')
32
+ return get_buffer_id(buffer)
33
+
34
+ def save_image(image,extension="jpg",dir_name="files"):
35
+ id = get_image_id(image)
36
+ os.makedirs(dir_name,exist_ok=True)
37
+ file_path = f"{dir_name}/{id}.{extension}"
38
+
39
+ image.save(file_path)
40
+ return file_path
41
+
42
+ def save_buffer(buffer,extension="webp",dir_name="files"):
43
+ id = get_buffer_id(buffer)
44
+ os.makedirs(dir_name,exist_ok=True)
45
+ file_path = f"{dir_name}/{id}.{extension}"
46
+
47
+ with open(file_path,"wb") as f:
48
+ f.write(buffer.getvalue())
49
+ return file_path
50
+
51
+ def write_file(file_path,text):
52
+ with open(file_path, 'w', encoding='utf-8') as f:
53
+ f.write(text)
54
+
55
+ def read_file(file_path):
56
+ """read the text of target file
57
+ """
58
+ with open(file_path, 'r', encoding='utf-8') as f:
59
+ content = f.read()
60
+ return content
mediapipe_transform.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import mp_triangles
4
+ import time
5
+ from PIL import Image
6
+
7
+ from glibvision.cv2_utils import blend_rgb_images,pil_to_bgr_image,fill_points,crop,paste
8
+ from mp_utils import get_pixel_cordinate_list,extract_landmark,get_pixel_cordinate,get_normalized_landmarks,sort_triangles_by_depth,get_landmark_bbox
9
+
10
+ import numba as nb
11
+ @nb.jit(nopython=True, parallel=True)
12
+ def blend_rgb_images_numba(image1, image2, mask):
13
+ height, width, _ = image1.shape
14
+ result = np.empty((height, width, 3), dtype=np.float32)
15
+
16
+ for i in nb.prange(height):
17
+ for j in range(width):
18
+ alpha = mask[i, j] / 255.0
19
+ for k in range(3):
20
+ result[i, j, k] = (1 - alpha) * image1[i, j, k] + alpha * image2[i, j, k]
21
+
22
+ return result.astype(np.uint8)
23
+
24
+ @nb.jit(nopython=True, parallel=True)
25
+ def blend_rgba_images_numba(image1, image2, mask):
26
+ assert image1.shape[2] == image2.shape[2] , f"Input images must be same image1 = {image1.shape[2]} image2 ={image2.shape[2]}"
27
+ channel = image1.shape[2]
28
+ height, width, _ = image1.shape
29
+ result = np.empty((height, width, channel), dtype=np.float32)
30
+
31
+ for i in nb.prange(height):
32
+ for j in range(width):
33
+ alpha = mask[i, j] / 255.0
34
+ for k in range(channel):
35
+ result[i, j, k] = (1 - alpha) * image1[i, j, k] + alpha * image2[i, j, k]
36
+
37
+ return result.astype(np.uint8)
38
+
39
+
40
+ """
41
+ https://stackoverflow.com/questions/6946653/copying-triangular-image-region-with-pil
42
+ This topic give me a idea
43
+ """
44
+ """
45
+ bug some hide value make white
46
+ """
47
+ debug_affinn = False
48
+ min_affin_plus = 0.1
49
+ def apply_affine_transformation_to_triangle(src_tri, dst_tri, src_img, dst_img):
50
+
51
+ src_tri_np = np.float32(src_tri)
52
+ dst_tri_np = np.float32(dst_tri)
53
+
54
+ assert src_tri_np.shape == (3, 2), f"src_tri_np の形状が不正 {src_tri_np.shape}"
55
+ assert dst_tri_np.shape == (3, 2), f"dst_tri_np の形状が不正 {dst_tri_np.shape}"
56
+
57
+
58
+ #trying avoid same value,or M will broken
59
+ if (src_tri_np[0] == src_tri_np[1]).all():
60
+ src_tri_np[0]+=min_affin_plus
61
+ if (src_tri_np[0] == src_tri_np[2]).all():
62
+ src_tri_np[0]+=min_affin_plus
63
+ if (src_tri_np[1] == src_tri_np[2]).all():
64
+ src_tri_np[1]+=min_affin_plus
65
+ if (src_tri_np[1] == src_tri_np[0]).all():
66
+ src_tri_np[1]+=min_affin_plus
67
+
68
+ if (src_tri_np[1] == src_tri_np[0]).all() or (src_tri_np[1] == src_tri_np[2]).all() or (src_tri_np[2] == src_tri_np[0]).all():
69
+ print("same will white noise happen")
70
+ # 透視変換行列の計算
71
+ M = cv2.getAffineTransform(src_tri_np, dst_tri_np)
72
+ # 画像のサイズ
73
+ h_src, w_src = src_img.shape[:2]
74
+ h_dst, w_dst = dst_img.shape[:2]
75
+
76
+ # 元画像から三角形領域を切り抜くマスク生成
77
+ #src_mask = np.zeros((h_src, w_src), dtype=np.uint8)
78
+ #cv2.fillPoly(src_mask, [np.int32(src_tri)], 255)
79
+
80
+ # Not 元画像の三角形領域のみをマスクで抽出
81
+ src_triangle = src_img #cv2.bitwise_and(src_img, src_img, mask=src_mask)
82
+
83
+ # 変換行列を使って元画像の三角形領域を目標画像のサイズへ変換
84
+
85
+
86
+ transformed = cv2.warpAffine(src_triangle, M, (w_dst, h_dst))
87
+ if debug_affinn:
88
+ cv2.imwrite('affin_src.jpg', src_triangle)
89
+ cv2.imwrite('affin_transformed.jpg', transformed)
90
+
91
+ #print(f"dst_img={dst_img.shape}")
92
+ #print(f"transformed={transformed.shape}")
93
+ # 変換後のマスクの生成
94
+ dst_mask = np.zeros((h_dst, w_dst), dtype=np.uint8)
95
+ cv2.fillPoly(dst_mask, [np.int32(dst_tri)], 255)
96
+
97
+ # 目標画像のマスク領域をクリアするためにデストのインバートマスクを作成
98
+ #dst_mask_inv = cv2.bitwise_not(dst_mask)
99
+
100
+ # 目標画像のマスク部分をクリア
101
+ #dst_background = cv2.bitwise_and(dst_img, dst_img, mask=dst_mask_inv)
102
+
103
+ # 変換された元画像の三角形部分と目標画像の背景部分を合成
104
+ #dst_img = cv2.add(dst_background, transformed)
105
+ #s = time.time()
106
+ #dst_img = blend_rgb_images(dst_img,transformed,dst_mask)
107
+
108
+ use_blend_rgb = False
109
+ if use_blend_rgb:
110
+ if src_img.shape[2] == 3:
111
+ dst_img = blend_rgb_images_numba(dst_img,transformed,dst_mask)
112
+ else:
113
+ dst_img = blend_rgba_images_numba(dst_img,transformed,dst_mask)
114
+ else:
115
+ dst_mask_inv = cv2.bitwise_not(dst_mask)
116
+ transformed = cv2.bitwise_and(transformed, transformed, mask=dst_mask)
117
+ dst_img = cv2.bitwise_and(dst_img, dst_img, mask=dst_mask_inv)
118
+ dst_img = cv2.add(dst_img, transformed)
119
+
120
+ # TODO add rgb mode
121
+
122
+
123
+ #print(f"blend {time.time() -s}")
124
+ if debug_affinn:
125
+ cv2.imwrite('affin_transformed_masked.jpg', transformed)
126
+ cv2.imwrite('affin_dst_mask.jpg', dst_mask)
127
+ return dst_img
128
+
129
+
130
+
131
+ from skimage.exposure import match_histograms
132
+ def color_match(base_image,cropped_image,color_match_format="RGB"):
133
+ reference = np.array(base_image.convert(color_match_format))
134
+ target =np.array(cropped_image.convert(color_match_format))
135
+ matched = match_histograms(target, reference,channel_axis=-1)
136
+
137
+ return Image.fromarray(matched,mode=color_match_format)
138
+
139
+ def process_landmark_transform(image,transform_target_image,
140
+ innner_mouth,innner_eyes,
141
+ color_matching=False,transparent_background=False,add_align_mouth=False,add_align_eyes=False,blur_size=0):
142
+ image_h,image_w = image.shape[:2]
143
+ align_h,align_w = transform_target_image.shape[:2]
144
+
145
+ mp_image,image_face_landmarker_result = extract_landmark(image)
146
+ image_larndmarks=image_face_landmarker_result.face_landmarks
147
+ image_bbox = get_landmark_bbox(image_larndmarks,image_w,image_h,16,16)
148
+
149
+ mp_image,align_face_landmarker_result = extract_landmark(transform_target_image)
150
+ align_larndmarks=align_face_landmarker_result.face_landmarks
151
+ align_bbox = get_landmark_bbox(align_larndmarks,align_w,align_h,16,16)
152
+
153
+ if color_matching:
154
+ image_cropped = crop(image,image_bbox)
155
+ target_cropped = crop(transform_target_image,align_bbox)
156
+ matched = match_histograms(image_cropped, target_cropped,channel_axis=-1)
157
+ paste(image,matched,image_bbox[0],image_bbox[1])
158
+
159
+
160
+ landmark_points = get_normalized_landmarks(align_larndmarks)
161
+
162
+ mesh_triangle_indices = mp_triangles.mesh_triangle_indices.copy()#using directly sometime share
163
+
164
+ #always mix for blur
165
+ mesh_triangle_indices += mp_triangles.INNER_MOUTH
166
+
167
+ mesh_triangle_indices += mp_triangles.INNER_LEFT_EYES + mp_triangles.INNER_RIGHT_EYES
168
+ #print(mesh_triangle_indices)
169
+ sort_triangles_by_depth(landmark_points,mesh_triangle_indices)
170
+
171
+ #mesh_triangle_indices = mp_triangles.contour_to_triangles(True,draw_updown_contour) + mp_triangles.contour_to_triangles(False,draw_updown_contour)+ mp_triangles.mesh_triangle_indices
172
+
173
+
174
+ triangle_size = len(mesh_triangle_indices)
175
+ print(f"triangle_size = {triangle_size},time ={0.1*triangle_size}")
176
+ s = time.time()
177
+
178
+ need_transparent_way = transparent_background == True or blur_size > 0
179
+ if need_transparent_way:# convert Alpha
180
+ transparent_image = np.zeros_like(cv2.cvtColor(transform_target_image, cv2.COLOR_BGR2BGRA))
181
+ h, w = transparent_image.shape[:2]
182
+ cv2.rectangle(transparent_image, (0, 0), (w, h), (0,0,0,0), -1)
183
+
184
+ applied_image = transparent_image
185
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA)
186
+
187
+ else:
188
+ applied_image = transform_target_image
189
+
190
+ for i in range(0,triangle_size):#
191
+ triangle_indices = mesh_triangle_indices[i]
192
+
193
+
194
+ image_points = get_pixel_cordinate_list(image_larndmarks,triangle_indices,image_w,image_h)
195
+
196
+ align_points = get_pixel_cordinate_list(align_larndmarks,triangle_indices,align_w,align_h)
197
+ #print(image_points)
198
+ #print(align_points)
199
+ #fill_points(image,image_points,thickness=3,fill_color=(0,0,0,0))
200
+ #s = time.time()
201
+ #print(f"applied_image={applied_image.shape}")
202
+ applied_image=apply_affine_transformation_to_triangle(image_points,align_points,image,applied_image)
203
+
204
+ print(f"take time {time.time()-s}")
205
+ if need_transparent_way:
206
+ blur_radius = blur_size
207
+ if blur_radius!=0 and blur_radius%2 == 0:
208
+ blur_radius+=1
209
+
210
+ b, g, r,a = cv2.split(applied_image)
211
+ applied_image = cv2.merge([b,g,r])
212
+ mask = a.copy()
213
+ dilate = blur_radius
214
+ kernel = np.ones((dilate, dilate), np.uint8)
215
+ mask = cv2.erode(mask, kernel, iterations=1)
216
+
217
+ if blur_radius>0:
218
+ blurred_image = cv2.GaussianBlur(mask, (blur_radius, blur_radius), 0) #should be odd
219
+ else:
220
+ blurred_image = mask
221
+
222
+ if transparent_background:
223
+ #transform_target_image = np.zeros_like(cv2.cvtColor(transform_target_image, cv2.COLOR_BGR2BGRA))
224
+ transform_target_image=cv2.cvtColor(transform_target_image, cv2.COLOR_BGR2BGRA)
225
+ applied_image = cv2.merge([b,g,r,blurred_image])
226
+ else:
227
+ applied_image = blend_rgb_images(transform_target_image,applied_image,blurred_image)
228
+
229
+ # after mix
230
+ if not innner_mouth or not innner_eyes or (transparent_background and (add_align_mouth or add_align_eyes)):
231
+
232
+ import mp_constants
233
+ dst_mask = np.zeros((align_h,align_w), dtype=np.uint8)
234
+ if not innner_mouth or (transparent_background and add_align_mouth):
235
+ mouth_cordinates = get_pixel_cordinate_list(align_larndmarks,mp_constants.LINE_INNER_MOUTH,align_h,align_w)
236
+ cv2.fillPoly(dst_mask, [np.int32(mouth_cordinates)], 255)
237
+
238
+ if (transparent_background and not add_align_mouth):
239
+ cv2.fillPoly(transform_target_image, [np.int32(mouth_cordinates)], [0,0,0,0])
240
+ if not innner_eyes or (transparent_background and add_align_eyes):
241
+
242
+ left_eyes_cordinates = get_pixel_cordinate_list(align_larndmarks,mp_constants.LINE_LEFT_INNER_EYES,align_h,align_w)
243
+
244
+ cv2.fillPoly(dst_mask, [np.int32(left_eyes_cordinates)], 255)
245
+
246
+ right_eyes_cordinates = get_pixel_cordinate_list(align_larndmarks,mp_constants.LINE_RIGHT_INNER_EYES,align_h,align_w)
247
+ cv2.fillPoly(dst_mask, [np.int32(right_eyes_cordinates)], 255)
248
+
249
+ if (transparent_background and not add_align_eyes):
250
+ cv2.fillPoly(transform_target_image, [np.int32(left_eyes_cordinates)], [0,0,0,0])
251
+ cv2.fillPoly(transform_target_image, [np.int32(right_eyes_cordinates)], [0,0,0,0])
252
+ #cv2.imwrite("deb_transform_target_image.jpg",transform_target_image)
253
+ #cv2.imwrite("deb_dst_mask.jpg",dst_mask)
254
+ #cv2.imwrite("deb_applied_image.jpg",applied_image)
255
+ applied_image = blend_rgba_images_numba(applied_image,transform_target_image,dst_mask)
256
+
257
+ return applied_image
258
+
259
+
260
+
261
+ def process_landmark_transform_pil(pil_image,pil_align_target_image,
262
+ innner_mouth,innner_eyes,
263
+ color_matching=False,transparent_background=False,add_align_mouth=False,add_align_eyes=False,blur_size=0):
264
+ image = pil_to_bgr_image(pil_image)
265
+ align_target_image = pil_to_bgr_image(pil_align_target_image)
266
+ cv_result = process_landmark_transform(image,align_target_image,innner_mouth,innner_eyes,color_matching,transparent_background,add_align_mouth,add_align_eyes,blur_size)
267
+ if transparent_background:
268
+ return Image.fromarray(cv2.cvtColor(cv_result, cv2.COLOR_BGRA2RGBA))
269
+ else:
270
+ return Image.fromarray(cv2.cvtColor(cv_result, cv2.COLOR_BGR2RGB))
271
+
272
+ if __name__ == "__main__":
273
+ #image = Image.open('examples/00002062.jpg')
274
+ #align_target = Image.open('examples/02316230.jpg')
275
+ image = cv2.imread('examples/02316230.jpg') # 元画像
276
+ align_target = cv2.imread('examples/00003245_00.jpg') # 目標画像
277
+ result_img = process_landmark_transform(image,align_target)
278
+
279
+ cv2.imshow('Transformed Image', result_img)
280
+ cv2.waitKey(0)
281
+ cv2.destroyAllWindows()
282
+
283
+ cv2.imwrite('align.png', result_img)
mp_constants.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ver 2024-11-21
2
+ # contour
3
+ POINT_LEFT_HEAD_OUTER_EX = 251 #on side face almost same as full
4
+ POINT_LEFT_HEAD_OUTER = 301
5
+ POINT_LEFT_EYE_OUTER_EX = 356
6
+ POINT_LEFT_EYE_OUTER = 264
7
+ POINT_LEFT_MOUTH_OUTER_EX = 288
8
+ POINT_LEFT_MOUTH_OUTER = 435
9
+ POINT_LEFT_CHIN_OUTER = 379
10
+ POINT_RIGHT_HEAD_OUTER_EX = 21
11
+ POINT_RIGHT_HEAD_OUTER = 71
12
+ POINT_RIGHT_EYE_OUTER_EX = 127
13
+ POINT_RIGHT_EYE_OUTER = 34
14
+ POINT_RIGHT_MOUTH_OUTER_EX = 58
15
+ POINT_RIGHT_MOUTH_OUTER = 215
16
+ POINT_RIGHT_CHIN_OUTER = 150
17
+ POINT_CHIN_BOTTOM = 152
18
+
19
+ POINT_FOREHEAD_TOP = 10
20
+
21
+ POINT_UPPER_LIP_CENTER_BOTTOM=13
22
+ POINT_LOWER_LIP_CENTER_TOP=14
23
+ POINT_LOWER_LIP_CENTER_BOTTOM=17
24
+ POINT_NOSE_CENTER_MIDDLE=5
25
+
26
+ LINE_RIGHT_CONTOUR_OUTER_EYE_TO_CHIN =[127,234,93,132,58,172,136,150,149,176,148,152]
27
+ LINE_RIGHT_CONTOUR_EYE_TO_CHIN = [34,227,137,177,215,138,135,169,170,140,171,175]
28
+ LINE_RIGHT_CONTOUR_INNER_EYE_TO_CHIN =[143,116,123,147,213,192,214,210,211,32,208,199]
29
+
30
+
31
+ LINE_RIGHT_CONTOUR_0 = [152,175,199]
32
+ LINE_RIGHT_CONTOUR_1 = [148,171,208]
33
+ LINE_RIGHT_CONTOUR_2 = [176,140,32]
34
+ LINE_RIGHT_CONTOUR_3 = [149,170,211]
35
+ LINE_RIGHT_CONTOUR_4 = [150,169,210]
36
+ LINE_RIGHT_CONTOUR_5 = [136,135,214]
37
+ LINE_RIGHT_CONTOUR_6 = [172,138,192]
38
+ LINE_RIGHT_CONTOUR_7 = [58,215,213]
39
+ LINE_RIGHT_CONTOUR_8 = [132,177,147]
40
+ LINE_RIGHT_CONTOUR_9 = [93,137,123]
41
+ LINE_RIGHT_CONTOUR_10 = [234,227,116]
42
+ LINE_RIGHT_CONTOUR_11 = [127,34,143]
43
+ #point68
44
+ LINE_RIGHT_CONTOUR_12 = [162,139,156]
45
+ LINE_RIGHT_CONTOUR_13 = [21,71,70]
46
+ LINE_RIGHT_CONTOUR_14 = [54,68,63]
47
+ LINE_RIGHT_CONTOUR_15 = [103,104,105]
48
+ LINE_RIGHT_CONTOUR_16 = [67,69,66]
49
+ LINE_RIGHT_CONTOUR_17 = [109,108,107]
50
+ LINE_RIGHT_CONTOUR_18 = [10,151,9]
51
+
52
+ LANDMARK_68_CONTOUR_1 = LINE_RIGHT_CONTOUR_11
53
+ LANDMARK_68_CONTOUR_2_PART1 = LINE_RIGHT_CONTOUR_10
54
+ LANDMARK_68_CONTOUR_2_PART2 = LINE_RIGHT_CONTOUR_9
55
+ LANDMARK_68_CONTOUR_3 = LINE_RIGHT_CONTOUR_8
56
+ LANDMARK_68_CONTOUR_4 = LINE_RIGHT_CONTOUR_7
57
+ LANDMARK_68_CONTOUR_5 = LINE_RIGHT_CONTOUR_6
58
+ LANDMARK_68_CONTOUR_6_PART1 = LINE_RIGHT_CONTOUR_5
59
+ LANDMARK_68_CONTOUR_6_PART2 = LINE_RIGHT_CONTOUR_4
60
+
61
+ LANDMARK_68_CONTOUR_7 = LINE_RIGHT_CONTOUR_3
62
+ LANDMARK_68_CONTOUR_8_PART1 = LINE_RIGHT_CONTOUR_2
63
+ LANDMARK_68_CONTOUR_8_PART2 = LINE_RIGHT_CONTOUR_1
64
+ LANDMARK_68_CONTOUR_9 = LINE_RIGHT_CONTOUR_0
65
+
66
+
67
+ LINE_LEFT_CONTOUR_1 = [377,396,428]
68
+ LINE_LEFT_CONTOUR_2 = [400,369,262]
69
+ LINE_LEFT_CONTOUR_3 = [378,395,431]
70
+ LINE_LEFT_CONTOUR_4 = [379,394,430]
71
+ LINE_LEFT_CONTOUR_5 = [365,364,434]
72
+ LINE_LEFT_CONTOUR_6 = [397,367,416]
73
+ LINE_LEFT_CONTOUR_7 = [288,435,433]
74
+ LINE_LEFT_CONTOUR_8 = [361,401,376]
75
+ LINE_LEFT_CONTOUR_9 = [323,366,352]
76
+ LINE_LEFT_CONTOUR_10 = [454,447,345]
77
+ LINE_LEFT_CONTOUR_11 = [356,264,372]
78
+ #point68
79
+ LINE_LEFT_CONTOUR_12 = [389,368,383]
80
+ LINE_LEFT_CONTOUR_13 = [251,301,300]
81
+ LINE_LEFT_CONTOUR_14 = [284,298,293]
82
+ LINE_LEFT_CONTOUR_15 = [332,333,334]
83
+ LINE_LEFT_CONTOUR_16 = [297,299,296]
84
+ LINE_LEFT_CONTOUR_17 = [338,337,336]
85
+
86
+ LANDMARK_68_CONTOUR_10 = LINE_LEFT_CONTOUR_1
87
+ LANDMARK_68_CONTOUR_11_PART1 = LINE_LEFT_CONTOUR_2
88
+ LANDMARK_68_CONTOUR_11_PART2 = LINE_LEFT_CONTOUR_3
89
+ LANDMARK_68_CONTOUR_12 = LINE_LEFT_CONTOUR_4
90
+ LANDMARK_68_CONTOUR_13 = LINE_LEFT_CONTOUR_5
91
+ LANDMARK_68_CONTOUR_14 = LINE_LEFT_CONTOUR_6
92
+ LANDMARK_68_CONTOUR_15_PART1 = LINE_LEFT_CONTOUR_7
93
+ LANDMARK_68_CONTOUR_15_PART2 = LINE_LEFT_CONTOUR_8
94
+
95
+ LANDMARK_68_CONTOUR_16 = LINE_LEFT_CONTOUR_9
96
+ LANDMARK_68_CONTOUR_17_PART1 = LINE_LEFT_CONTOUR_10
97
+ LANDMARK_68_CONTOUR_17_PART2 = LINE_LEFT_CONTOUR_11
98
+
99
+ LANDMARK_68_RIGHT_EYEBROW_18 = [70,46] #upper,lower
100
+ LANDMARK_68_RIGHT_EYEBROW_19 = [63,53]
101
+ LANDMARK_68_RIGHT_EYEBROW_20 = [105,52]
102
+ LANDMARK_68_RIGHT_EYEBROW_21 = [66,65]
103
+ LANDMARK_68_RIGHT_EYEBROW_22 = [107,55]
104
+
105
+ LANDMARK_68_LEFT_EYEBROW_23 = [336,285] #upper,lower
106
+ LANDMARK_68_LEFT_EYEBROW_24 = [296,295]
107
+ LANDMARK_68_LEFT_EYEBROW_25 = [334,282]
108
+ LANDMARK_68_LEFT_EYEBROW_26 = [293,283]
109
+ LANDMARK_68_LEFT_EYEBROW_27 = [300,276]
110
+
111
+ POINT_NOSE_0 = 8
112
+ POINT_NOSE_1 = 168
113
+ POINT_NOSE_2 = 6
114
+ POINT_NOSE_3 = 197
115
+ POINT_NOSE_4 = 195
116
+ POINT_NOSE_5 = 5
117
+ POINT_NOSE_6 = 4
118
+ POINT_NOSE_7 = 19
119
+ POINT_NOSE_8 = 94
120
+ POINT_NOSE_9 = 2
121
+
122
+ #side
123
+ POINT_NOSE_10 = 98
124
+ POINT_NOSE_11 = 97
125
+ POINT_NOSE_12 = 326
126
+ POINT_NOSE_13 = 327
127
+
128
+ LANDMARK_68_VERTICAL_NOSE_28 =[8,168]
129
+ LANDMARK_68_VERTICAL_NOSE_29 = [6]
130
+ LANDMARK_68_VERTICAL_NOSE_30=[197,195]
131
+ LANDMARK_68_VERTICAL_NOSE_31=[5,4]
132
+
133
+ LANDMARK_68_HORIZONTAL_NOSE_32 =[POINT_NOSE_10]
134
+ LANDMARK_68_HORIZONTAL_NOSE_33 = [POINT_NOSE_11]
135
+ LANDMARK_68_HORIZONTAL_NOSE_34=[POINT_NOSE_9]
136
+ LANDMARK_68_HORIZONTAL_NOSE_35=[POINT_NOSE_12]
137
+ LANDMARK_68_HORIZONTAL_NOSE_36=[POINT_NOSE_13]
138
+
139
+
140
+ LINE_VERTICAL_NOSE = [POINT_NOSE_0,POINT_NOSE_1,POINT_NOSE_2,POINT_NOSE_3,POINT_NOSE_4,POINT_NOSE_5,POINT_NOSE_6,POINT_NOSE_7,POINT_NOSE_8,POINT_NOSE_9]
141
+ LINE_HORIZONTAL_NOSE =[POINT_NOSE_10,POINT_NOSE_11,POINT_NOSE_9,POINT_NOSE_12,POINT_NOSE_13]
142
+
143
+ ### EYES
144
+ POINT_RIGHT_UPPER_INNER_EYE_1 = 33
145
+ POINT_RIGHT_UPPER_INNER_EYE_2 = 246
146
+ POINT_RIGHT_UPPER_INNER_EYE_3 = 161
147
+ POINT_RIGHT_UPPER_INNER_EYE_4 = 160
148
+ POINT_RIGHT_UPPER_INNER_EYE_5 = 159
149
+ POINT_RIGHT_UPPER_INNER_EYE_6 = 158
150
+ POINT_RIGHT_UPPER_INNER_EYE_7 = 157
151
+ POINT_RIGHT_UPPER_INNER_EYE_8 = 173
152
+ POINT_RIGHT_UPPER_INNER_EYE_9 = 133
153
+
154
+ LINE_RIGHT_UPPER_INNER_EYE=[POINT_RIGHT_UPPER_INNER_EYE_1,POINT_RIGHT_UPPER_INNER_EYE_2,POINT_RIGHT_UPPER_INNER_EYE_3,POINT_RIGHT_UPPER_INNER_EYE_4,POINT_RIGHT_UPPER_INNER_EYE_5,POINT_RIGHT_UPPER_INNER_EYE_6,POINT_RIGHT_UPPER_INNER_EYE_7,POINT_RIGHT_UPPER_INNER_EYE_8,POINT_RIGHT_UPPER_INNER_EYE_9]
155
+
156
+ POINT_RIGHT_LOWER_INNER_EYE_1 = 155
157
+ POINT_RIGHT_LOWER_INNER_EYE_2 = 154
158
+ POINT_RIGHT_LOWER_INNER_EYE_3 = 153
159
+ POINT_RIGHT_LOWER_INNER_EYE_4 = 145
160
+ POINT_RIGHT_LOWER_INNER_EYE_5 = 144
161
+ POINT_RIGHT_LOWER_INNER_EYE_6 = 163
162
+ POINT_RIGHT_LOWER_INNER_EYE_7 = 7
163
+
164
+ LINE_RIGHT_LOWER_INNER_EYE=[POINT_RIGHT_UPPER_INNER_EYE_9,POINT_RIGHT_LOWER_INNER_EYE_1,POINT_RIGHT_LOWER_INNER_EYE_2,POINT_RIGHT_LOWER_INNER_EYE_3,POINT_RIGHT_LOWER_INNER_EYE_4,POINT_RIGHT_LOWER_INNER_EYE_5,POINT_RIGHT_LOWER_INNER_EYE_6,POINT_RIGHT_LOWER_INNER_EYE_7,POINT_RIGHT_UPPER_INNER_EYE_1]
165
+
166
+
167
+ POINT_RIGHT_UPPER_OUTER_EYE_1 = 130
168
+ POINT_RIGHT_UPPER_OUTER_EYE_2 = 247
169
+ POINT_RIGHT_UPPER_OUTER_EYE_3 = 30
170
+ POINT_RIGHT_UPPER_OUTER_EYE_4 = 29
171
+ POINT_RIGHT_UPPER_OUTER_EYE_5 = 27
172
+ POINT_RIGHT_UPPER_OUTER_EYE_6 = 28
173
+ POINT_RIGHT_UPPER_OUTER_EYE_7 = 56
174
+ POINT_RIGHT_UPPER_OUTER_EYE_8 = 190
175
+ POINT_RIGHT_UPPER_OUTER_EYE_9 = 243
176
+
177
+ LINE_RIGHT_UPPER_OUTER_EYE=[POINT_RIGHT_UPPER_OUTER_EYE_1,POINT_RIGHT_UPPER_OUTER_EYE_2,POINT_RIGHT_UPPER_OUTER_EYE_3,POINT_RIGHT_UPPER_OUTER_EYE_4,POINT_RIGHT_UPPER_OUTER_EYE_5,POINT_RIGHT_UPPER_OUTER_EYE_6,POINT_RIGHT_UPPER_OUTER_EYE_7,POINT_RIGHT_UPPER_OUTER_EYE_8,POINT_RIGHT_UPPER_OUTER_EYE_9]
178
+
179
+ LINE_RIGHT_UPPER_MIXED_EYE =[#firs eye1 and eye2 is intesionaly for moveup
180
+ [POINT_RIGHT_UPPER_INNER_EYE_1,POINT_RIGHT_UPPER_OUTER_EYE_2], [POINT_RIGHT_UPPER_INNER_EYE_2,POINT_RIGHT_UPPER_OUTER_EYE_2], [POINT_RIGHT_UPPER_INNER_EYE_3,POINT_RIGHT_UPPER_OUTER_EYE_3], [POINT_RIGHT_UPPER_INNER_EYE_4,POINT_RIGHT_UPPER_OUTER_EYE_4], [POINT_RIGHT_UPPER_INNER_EYE_5,POINT_RIGHT_UPPER_OUTER_EYE_5], [POINT_RIGHT_UPPER_INNER_EYE_6,POINT_RIGHT_UPPER_OUTER_EYE_6]
181
+ ,[POINT_RIGHT_UPPER_INNER_EYE_8],[POINT_RIGHT_UPPER_INNER_EYE_8,POINT_RIGHT_UPPER_INNER_EYE_9] #I'm not sure need this one or not POINT_RIGHT_LOWER_INNER_EYE_1
182
+ ]
183
+
184
+ LINE_RIGHT_UPPER_MIXED_EYE2 =[#firs eye1 and eye2 is intesionaly for moveup
185
+ [POINT_RIGHT_UPPER_INNER_EYE_1,POINT_RIGHT_UPPER_INNER_EYE_1,POINT_RIGHT_UPPER_OUTER_EYE_2],
186
+ [POINT_RIGHT_UPPER_INNER_EYE_2,POINT_RIGHT_UPPER_INNER_EYE_2,POINT_RIGHT_UPPER_OUTER_EYE_2],
187
+ [POINT_RIGHT_UPPER_INNER_EYE_3,POINT_RIGHT_UPPER_INNER_EYE_3,POINT_RIGHT_UPPER_OUTER_EYE_3],
188
+ [POINT_RIGHT_UPPER_INNER_EYE_4,POINT_RIGHT_UPPER_INNER_EYE_4,POINT_RIGHT_UPPER_OUTER_EYE_4],
189
+ [POINT_RIGHT_UPPER_INNER_EYE_5,POINT_RIGHT_UPPER_INNER_EYE_5,POINT_RIGHT_UPPER_OUTER_EYE_5],
190
+ [POINT_RIGHT_UPPER_INNER_EYE_6,POINT_RIGHT_UPPER_INNER_EYE_6,POINT_RIGHT_UPPER_OUTER_EYE_6]
191
+ ,[POINT_RIGHT_UPPER_INNER_EYE_8],
192
+ [POINT_RIGHT_UPPER_INNER_EYE_8,POINT_RIGHT_UPPER_INNER_EYE_9] #I'm not sure need this one or not POINT_RIGHT_LOWER_INNER_EYE_1
193
+ ]
194
+
195
+ # LEFT AND RIGHT IS DIFF
196
+ LINE_RIGHT_EYES_WHITE = LINE_RIGHT_UPPER_INNER_EYE[1:-1] + LINE_RIGHT_LOWER_INNER_EYE[2:-1]
197
+ LINE_RIGHT_INNER_EYES = LINE_RIGHT_UPPER_INNER_EYE + LINE_RIGHT_LOWER_INNER_EYE[1:-1]
198
+
199
+ POINT_RIGHT_LOWER_OUTER_EYE_1 = 112
200
+ POINT_RIGHT_LOWER_OUTER_EYE_2 = 26
201
+ POINT_RIGHT_LOWER_OUTER_EYE_3 = 22
202
+ POINT_RIGHT_LOWER_OUTER_EYE_4 = 23
203
+ POINT_RIGHT_LOWER_OUTER_EYE_5 = 24
204
+ POINT_RIGHT_LOWER_OUTER_EYE_6 = 110
205
+ POINT_RIGHT_LOWER_OUTER_EYE_7 = 25
206
+
207
+ LINE_RIGHT_LOWER_OUTER_EYE=[POINT_RIGHT_UPPER_OUTER_EYE_9,POINT_RIGHT_LOWER_OUTER_EYE_1,POINT_RIGHT_LOWER_OUTER_EYE_2,POINT_RIGHT_LOWER_OUTER_EYE_3,POINT_RIGHT_LOWER_OUTER_EYE_4,POINT_RIGHT_LOWER_OUTER_EYE_5,POINT_RIGHT_LOWER_OUTER_EYE_6,POINT_RIGHT_LOWER_OUTER_EYE_7,POINT_RIGHT_UPPER_OUTER_EYE_1]
208
+
209
+ LINE_RIGHT_LOWER_MIXED_EYE =[
210
+ [POINT_RIGHT_UPPER_INNER_EYE_8,POINT_RIGHT_UPPER_INNER_EYE_9,POINT_RIGHT_LOWER_INNER_EYE_1]
211
+ ,[POINT_RIGHT_LOWER_INNER_EYE_2]
212
+ ,POINT_RIGHT_LOWER_INNER_EYE_3,POINT_RIGHT_LOWER_INNER_EYE_4,POINT_RIGHT_LOWER_INNER_EYE_5,POINT_RIGHT_LOWER_INNER_EYE_6,POINT_RIGHT_LOWER_INNER_EYE_7
213
+ ,[POINT_RIGHT_UPPER_INNER_EYE_1,POINT_RIGHT_UPPER_OUTER_EYE_2] #combine 1 and 2 for move up
214
+ ]
215
+
216
+
217
+ POINT_LEFT_UPPER_INNER_EYE_1 = 362
218
+ POINT_LEFT_UPPER_INNER_EYE_2 = 398
219
+ POINT_LEFT_UPPER_INNER_EYE_3 = 384
220
+ POINT_LEFT_UPPER_INNER_EYE_4 = 385
221
+ POINT_LEFT_UPPER_INNER_EYE_5 = 386
222
+ POINT_LEFT_UPPER_INNER_EYE_6 = 387
223
+ POINT_LEFT_UPPER_INNER_EYE_7 = 388
224
+ POINT_LEFT_UPPER_INNER_EYE_8 = 466
225
+ POINT_LEFT_UPPER_INNER_EYE_9 = 263
226
+
227
+ LINE_LEFT_UPPER_INNER_EYE=[POINT_LEFT_UPPER_INNER_EYE_1,POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_INNER_EYE_4,POINT_LEFT_UPPER_INNER_EYE_5,POINT_LEFT_UPPER_INNER_EYE_6,POINT_LEFT_UPPER_INNER_EYE_7,POINT_LEFT_UPPER_INNER_EYE_8,POINT_LEFT_UPPER_INNER_EYE_9]
228
+ #TODO what is this?
229
+ #LINE_LEFT_UPPER_INNER_EYE=[POINT_LEFT_UPPER_INNER_EYE_1,POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_INNER_EYE_4,POINT_LEFT_UPPER_INNER_EYE_5,POINT_LEFT_UPPER_INNER_EYE_6,POINT_LEFT_UPPER_INNER_EYE_7,POINT_LEFT_UPPER_INNER_EYE_8,POINT_LEFT_UPPER_INNER_EYE_9]
230
+
231
+
232
+
233
+ POINT_LEFT_LOWER_INNER_EYE_1 = 249
234
+ POINT_LEFT_LOWER_INNER_EYE_2 = 390
235
+ POINT_LEFT_LOWER_INNER_EYE_3 = 373
236
+ POINT_LEFT_LOWER_INNER_EYE_4 = 374
237
+ POINT_LEFT_LOWER_INNER_EYE_5 = 380
238
+ POINT_LEFT_LOWER_INNER_EYE_6 = 381
239
+ POINT_LEFT_LOWER_INNER_EYE_7 = 382
240
+
241
+
242
+ LINE_LEFT_LOWER_INNER_EYE=[POINT_LEFT_UPPER_INNER_EYE_9,POINT_LEFT_LOWER_INNER_EYE_1,POINT_LEFT_LOWER_INNER_EYE_2,POINT_LEFT_LOWER_INNER_EYE_3,POINT_LEFT_LOWER_INNER_EYE_4,POINT_LEFT_LOWER_INNER_EYE_5,POINT_LEFT_LOWER_INNER_EYE_6,POINT_LEFT_LOWER_INNER_EYE_7,POINT_LEFT_UPPER_INNER_EYE_1]
243
+
244
+ #outer
245
+
246
+ POINT_LEFT_UPPER_OUTER_EYE_1 = 463
247
+ POINT_LEFT_UPPER_OUTER_EYE_2 = 414
248
+ POINT_LEFT_UPPER_OUTER_EYE_3 = 286
249
+ POINT_LEFT_UPPER_OUTER_EYE_4 = 258
250
+ POINT_LEFT_UPPER_OUTER_EYE_5 = 257
251
+ POINT_LEFT_UPPER_OUTER_EYE_6 = 259
252
+ POINT_LEFT_UPPER_OUTER_EYE_7 = 260
253
+ POINT_LEFT_UPPER_OUTER_EYE_8 = 467
254
+ POINT_LEFT_UPPER_OUTER_EYE_9 = 359
255
+
256
+ LINE_LEFT_UPPER_OUTER_EYE=[POINT_LEFT_UPPER_OUTER_EYE_1,POINT_LEFT_UPPER_OUTER_EYE_2,POINT_LEFT_UPPER_OUTER_EYE_3,POINT_LEFT_UPPER_OUTER_EYE_4,POINT_LEFT_UPPER_OUTER_EYE_5,POINT_LEFT_UPPER_OUTER_EYE_6,POINT_LEFT_UPPER_OUTER_EYE_7,POINT_LEFT_UPPER_OUTER_EYE_8,POINT_LEFT_UPPER_OUTER_EYE_9]
257
+
258
+
259
+ POINT_LEFT_LOWER_OUTER_EYE_1 = 255
260
+ POINT_LEFT_LOWER_OUTER_EYE_2 = 339
261
+ POINT_LEFT_LOWER_OUTER_EYE_3 = 254
262
+ POINT_LEFT_LOWER_OUTER_EYE_4 = 253
263
+ POINT_LEFT_LOWER_OUTER_EYE_5 = 252
264
+ POINT_LEFT_LOWER_OUTER_EYE_6 = 256
265
+ POINT_LEFT_LOWER_OUTER_EYE_7 = 341
266
+
267
+ LINE_LEFT_LOWER_OUTER_EYE=[POINT_LEFT_UPPER_OUTER_EYE_9,POINT_LEFT_LOWER_OUTER_EYE_1,POINT_LEFT_LOWER_OUTER_EYE_2,POINT_LEFT_LOWER_OUTER_EYE_3,POINT_LEFT_LOWER_OUTER_EYE_4,POINT_LEFT_LOWER_OUTER_EYE_5,POINT_LEFT_LOWER_OUTER_EYE_6,POINT_LEFT_LOWER_OUTER_EYE_7,POINT_LEFT_UPPER_OUTER_EYE_1]
268
+
269
+ LINE_LEFT_UPPER_MIXED_EYE =[#firs eye1 and eye2 is intesionaly for moveup
270
+ [POINT_LEFT_UPPER_INNER_EYE_1,POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_LOWER_INNER_EYE_7],
271
+ [POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_UPPER_OUTER_EYE_2], [POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_OUTER_EYE_3], [POINT_LEFT_UPPER_INNER_EYE_4,POINT_LEFT_UPPER_OUTER_EYE_4], [POINT_LEFT_UPPER_INNER_EYE_5,POINT_LEFT_UPPER_OUTER_EYE_5], [POINT_LEFT_UPPER_INNER_EYE_6,POINT_LEFT_UPPER_OUTER_EYE_6]
272
+ ,[POINT_LEFT_UPPER_INNER_EYE_8],[POINT_LEFT_UPPER_OUTER_EYE_8,POINT_LEFT_UPPER_INNER_EYE_9]
273
+ ]
274
+
275
+ LINE_LEFT_UPPER_MIXED_EYE2 =[#firs eye1 and eye2 is intesionaly for moveup
276
+ [POINT_LEFT_UPPER_INNER_EYE_1,POINT_LEFT_UPPER_INNER_EYE_1,POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_LOWER_INNER_EYE_7],
277
+ [POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_UPPER_OUTER_EYE_2],
278
+ [POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_INNER_EYE_3,POINT_LEFT_UPPER_OUTER_EYE_3],
279
+ [POINT_LEFT_UPPER_INNER_EYE_4,POINT_LEFT_UPPER_INNER_EYE_4,POINT_LEFT_UPPER_OUTER_EYE_4],
280
+ [POINT_LEFT_UPPER_INNER_EYE_5,POINT_LEFT_UPPER_INNER_EYE_5,POINT_LEFT_UPPER_OUTER_EYE_5],
281
+ [POINT_LEFT_UPPER_INNER_EYE_6,POINT_LEFT_UPPER_INNER_EYE_6,POINT_LEFT_UPPER_OUTER_EYE_6]
282
+ ,[POINT_LEFT_UPPER_INNER_EYE_8],
283
+ [POINT_LEFT_UPPER_OUTER_EYE_8,POINT_LEFT_UPPER_INNER_EYE_9]
284
+ ]
285
+
286
+ LINE_LEFT_LOWER_MIXED_EYE =[
287
+ [POINT_LEFT_UPPER_OUTER_EYE_8,POINT_LEFT_UPPER_INNER_EYE_9]
288
+ ,[POINT_LEFT_LOWER_INNER_EYE_2]
289
+ ,POINT_LEFT_LOWER_INNER_EYE_3,POINT_LEFT_LOWER_INNER_EYE_4,POINT_LEFT_LOWER_INNER_EYE_5,POINT_LEFT_LOWER_INNER_EYE_6,POINT_LEFT_LOWER_INNER_EYE_7
290
+ , [POINT_LEFT_UPPER_INNER_EYE_1,POINT_LEFT_UPPER_INNER_EYE_2,POINT_LEFT_LOWER_INNER_EYE_7] #combine 1 and 2 for move up
291
+ ]
292
+
293
+ LINE_LEFT_EYES_WHITE = LINE_LEFT_UPPER_INNER_EYE[1:-1] + LINE_LEFT_LOWER_INNER_EYE[1:-2]
294
+ LINE_LEFT_INNER_EYES = LINE_LEFT_UPPER_INNER_EYE + LINE_LEFT_LOWER_INNER_EYE[1:-1]
295
+
296
+
297
+ #LIP Seems broken_
298
+ LINE_RIGHT_UPPER_OUTER_LIP=[
299
+ 61,185,40,39,37,0
300
+ ]
301
+ LINE_LEFT_UPPER_OUTER_LIP=[
302
+ 0,267,269,270,409,291
303
+ ]
304
+
305
+
306
+ LINE_LOWER_OUTER_LIP=[291,#upper
307
+ 375,321,405,314,17,84,181,91,146
308
+ ,61 #upper
309
+ ]
310
+
311
+ LINE_UPPER_INNER_LIP=[
312
+ 61,185,40,39,37,0,267,269,270,409,291
313
+ ]
314
+
315
+ LINE_LOWER_INNER_LIP=[291,#upper
316
+ 375,321,405,314,17,84,181,91,146
317
+ ,61 #upper
318
+ ]
319
+
320
+ LINE_INNER_MOUTH =[
321
+ 78,191,80,81,82,13,
322
+ 312,311,310,415,308,
323
+ 324,318,402,317,14,
324
+ 87,178,88,95
325
+ ]
326
+
327
+ LANDMARK_68_UPPER_OUTER_LIP_49 =[61]
328
+ LANDMARK_68_UPPER_OUTER_LIP_50 =[40,39]
329
+ LANDMARK_68_UPPER_OUTER_LIP_51 =[37]
330
+ LANDMARK_68_UPPER_OUTER_LIP_52 =[0]
331
+ LANDMARK_68_UPPER_OUTER_LIP_53 =[267]
332
+ LANDMARK_68_UPPER_OUTER_LIP_54 =[270,269]
333
+ LANDMARK_68_UPPER_OUTER_LIP_55 =[291]
334
+
335
+ LANDMARK_68_LOWER_OUTER_LIP_56 =[375,321]
336
+ LANDMARK_68_LOWER_OUTER_LIP_57 =[405,314]
337
+ LANDMARK_68_LOWER_OUTER_LIP_58 =[17]
338
+ LANDMARK_68_LOWER_OUTER_LIP_59 =[84,181]
339
+ LANDMARK_68_LOWER_OUTER_LIP_60 =[146,91]
340
+
341
+ LANDMARK_68_UPPER_INNER_LIP_61 =[78]
342
+ LANDMARK_68_UPPER_INNER_LIP_62 =[81]
343
+ LANDMARK_68_UPPER_INNER_LIP_63 =[13]
344
+ LANDMARK_68_UPPER_INNER_LIP_64 =[311]
345
+ LANDMARK_68_UPPER_INNER_LIP_65 =[308]
346
+
347
+ LANDMARK_68_LOWER_INNER_LIP_66 =[402]
348
+ LANDMARK_68_LOWER_INNER_LIP_67 =[14]
349
+ LANDMARK_68_LOWER_INNER_LIP_68 =[178]
350
+
351
+
352
+
353
+ POINT_LEFT_PUPIL = 473
354
+ LINE_LEFT_IRIS = [474,475,476,477]
355
+ POINT_RIGHT_PUPIL = 468
356
+ LINE_RIGHT_IRIS = [469,470,471,472]
mp_triangles.py ADDED
@@ -0,0 +1,1003 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ I don't know the license,I'll made script if i have spare time.
3
+ see https://stackoverflow.com/questions/69858216/mediapipe-facemesh-vertices-mapping
4
+ so I wrote a simple program to generate the vertice tuples from it. The result is in the given json string. You're welcome to copy it if it helps.
5
+ '''
6
+
7
+ #2024-12-01 add hole-triangles
8
+
9
+ INNER_MOUTH =[
10
+ [78,191,95],
11
+ [191,95,80],
12
+ [80,88,95],
13
+ [80,81,88],
14
+ [88,81,178],
15
+ [81,178,82],
16
+ [178,82,87],
17
+ [82,87,13],
18
+ [87,14,13],
19
+ [13,312,317],
20
+ [14,317,13],
21
+ [312,402,311],
22
+ [317,402,312],
23
+ [311,402,318],
24
+ [311,318,310],
25
+ [310,324,318],
26
+ [310,324,415],
27
+ [308,415,324]
28
+ ]
29
+
30
+ INNER_LEFT_EYES=[
31
+ [33,246,7],
32
+
33
+ [246,7,163],
34
+ [246,163,161],
35
+ [161,163,144],
36
+ [161,144,160],
37
+ [160,144,145],
38
+ [160,145,159],
39
+ [159,145,153],
40
+ [159,153,158],
41
+ [158,153,154],
42
+ [158,154,157],
43
+ [157,154,155],
44
+ [157,155,173],
45
+
46
+ [173,155,133]
47
+ ]
48
+ INNER_RIGHT_EYES=[
49
+ [362,398,382],
50
+
51
+ [382,398,384],
52
+ [382,384,381],
53
+ [381,384,385],
54
+ [381,385,380],
55
+ [380,385,386],
56
+ [380,386,374],
57
+ [374,386,387],
58
+ [374,387,373],
59
+ [373,387,388],
60
+ [373,388,390],
61
+ [390,388,466],
62
+ [390,249,466],
63
+
64
+ [466,263,249]
65
+ ]
66
+
67
+ RIGHT_CONTOURS = [
68
+ [152, 175, 199], # LINE_RIGHT_CONTOUR_0
69
+ [148, 171, 208], # LINE_RIGHT_CONTOUR_1
70
+ [176, 140, 32], # LINE_RIGHT_CONTOUR_2
71
+ [149, 170, 211], # LINE_RIGHT_CONTOUR_3
72
+ [150, 169, 210], # LINE_RIGHT_CONTOUR_4
73
+ [136, 135, 214], # LINE_RIGHT_CONTOUR_5
74
+ [172, 138, 192], # LINE_RIGHT_CONTOUR_6
75
+ [58, 215, 213], # LINE_RIGHT_CONTOUR_7
76
+ [132, 177, 147], # LINE_RIGHT_CONTOUR_8
77
+ [93, 137, 123], # LINE_RIGHT_CONTOUR_9
78
+ [234, 227, 116], # LINE_RIGHT_CONTOUR_10
79
+ [127, 34, 143], # LINE_RIGHT_CONTOUR_11
80
+ [162, 139, 156], # LINE_RIGHT_CONTOUR_12
81
+ [21, 71, 70], # LINE_RIGHT_CONTOUR_13
82
+ [54, 68, 63], # LINE_RIGHT_CONTOUR_14
83
+ [103, 104, 105], # LINE_RIGHT_CONTOUR_15
84
+ [67, 69, 66], # LINE_RIGHT_CONTOUR_16
85
+ [109, 108, 107], # LINE_RIGHT_CONTOUR_17
86
+ [10, 151, 9] # LINE_RIGHT_CONTOUR_18
87
+ ]
88
+
89
+ LEFT_CONTOURS = [
90
+ [377, 396, 428], # LINE_LEFT_CONTOUR_1
91
+ [400, 369, 262], # LINE_LEFT_CONTOUR_2
92
+ [378, 395, 431], # LINE_LEFT_CONTOUR_3
93
+ [379, 394, 430], # LINE_LEFT_CONTOUR_4
94
+ [365, 364, 434], # LINE_LEFT_CONTOUR_5
95
+ [397, 367, 416], # LINE_LEFT_CONTOUR_6
96
+ [288, 435, 433], # LINE_LEFT_CONTOUR_7
97
+ [361, 401, 376], # LINE_LEFT_CONTOUR_8
98
+ [323, 366, 352], # LINE_LEFT_CONTOUR_9
99
+ [454, 447, 345], # LINE_LEFT_CONTOUR_10
100
+ [356, 264, 372], # LINE_LEFT_CONTOUR_11
101
+ [389, 368, 383], # LINE_LEFT_CONTOUR_12
102
+ [251, 301, 300], # LINE_LEFT_CONTOUR_13
103
+ [284, 298, 293], # LINE_LEFT_CONTOUR_14
104
+ [332, 333, 334], # LINE_LEFT_CONTOUR_15
105
+ [297, 299, 296], # LINE_LEFT_CONTOUR_16
106
+ [338, 337, 336] # LINE_LEFT_CONTOUR_17
107
+ ]
108
+
109
+ def contour_to_triangles(is_right=True,down_up=True):
110
+ triangles = []
111
+ if is_right:
112
+ if down_up:
113
+ contours = RIGHT_CONTOURS
114
+ else:
115
+ contours = RIGHT_CONTOURS[::-1]
116
+ else:
117
+ if down_up:
118
+ contours = LEFT_CONTOURS
119
+ else:
120
+ contours = LEFT_CONTOURS[::-1]
121
+
122
+ sorted_mesh_triangle_indices = []
123
+ for triangle in mesh_triangle_indices:
124
+ sorted_mesh_triangle_indices.append(sorted(triangle))
125
+
126
+ # no way to know how triangle made even in future.
127
+ for i in range(len(contours)-1):
128
+ first_line = contours[i]
129
+ second_line = contours[i+1]
130
+ #outer
131
+ triangles.append([first_line[0],first_line[1],second_line[0]])
132
+ triangles.append([second_line[0],second_line[1],first_line[1]])
133
+ triangles.append([first_line[0],first_line[1],second_line[1]])
134
+ triangles.append([second_line[0],second_line[1],first_line[0]])
135
+
136
+ #inner
137
+ triangles.append([first_line[1],first_line[2],second_line[1]])
138
+ triangles.append([second_line[1],second_line[2],first_line[2]])
139
+ triangles.append([first_line[1],first_line[2],second_line[2]])
140
+ triangles.append([second_line[1],second_line[2],first_line[1]])
141
+
142
+ exist_triangles = []
143
+ for triangle in triangles:
144
+ sorted_triangle = sorted(triangle)
145
+ if sorted_triangle in sorted_mesh_triangle_indices:
146
+ exist_triangles.append(triangle)
147
+ return exist_triangles
148
+
149
+
150
+ mesh_triangle_indices=[
151
+ [127, 34, 139],
152
+ [ 11, 0, 37],
153
+ [232, 231, 120],
154
+ [ 72, 37, 39],
155
+ [128, 121, 47],
156
+ [232, 121, 128],
157
+ [104, 69, 67],
158
+ [175, 171, 148],
159
+ [118, 50, 101],
160
+ [ 73, 39, 40],
161
+ [ 9, 151, 108],
162
+ [ 48, 115, 131],
163
+ [194, 204, 211],
164
+ [ 74, 40, 185],
165
+ [ 80, 42, 183],
166
+ [ 40, 92, 186],
167
+ [230, 229, 118],
168
+ [202, 212, 214],
169
+ [ 83, 18, 17],
170
+ [ 76, 61, 146],
171
+ [160, 29, 30],
172
+ [ 56, 157, 173],
173
+ [106, 204, 194],
174
+ [135, 214, 192],
175
+ [203, 165, 98],
176
+ [ 21, 71, 68],
177
+ [ 51, 45, 4],
178
+ [144, 24, 23],
179
+ [ 77, 146, 91],
180
+ [205, 50, 187],
181
+ [201, 200, 18],
182
+ [ 91, 106, 182],
183
+ [ 90, 91, 181],
184
+ [ 85, 84, 17],
185
+ [206, 203, 36],
186
+ [148, 171, 140],
187
+ [ 92, 40, 39],
188
+ [193, 189, 244],
189
+ [159, 158, 28],
190
+ [247, 246, 161],
191
+ [236, 3, 196],
192
+ [ 54, 68, 104],
193
+ [193, 168, 8],
194
+ [117, 228, 31],
195
+ [189, 193, 55],
196
+ [ 98, 97, 99],
197
+ [126, 47, 100],
198
+ [166, 79, 218],
199
+ [155, 154, 26],
200
+ [209, 49, 131],
201
+ [135, 136, 150],
202
+ [ 47, 126, 217],
203
+ [223, 52, 53],
204
+ [ 45, 51, 134],
205
+ [211, 170, 140],
206
+ [ 67, 69, 108],
207
+ [ 43, 106, 91],
208
+ [230, 119, 120],
209
+ [226, 130, 247],
210
+ [ 63, 53, 52],
211
+ [238, 20, 242],
212
+ [ 46, 70, 156],
213
+ [ 78, 62, 96],
214
+ [ 46, 53, 63],
215
+ [143, 34, 227],
216
+ [123, 117, 111],
217
+ [ 44, 125, 19],
218
+ [236, 134, 51],
219
+ [216, 206, 205],
220
+ [154, 153, 22],
221
+ [ 39, 37, 167],
222
+ [200, 201, 208],
223
+ [ 36, 142, 100],
224
+ [ 57, 212, 202],
225
+ [ 20, 60, 99],
226
+ [ 28, 158, 157],
227
+ [ 35, 226, 113],
228
+ [160, 159, 27],
229
+ [204, 202, 210],
230
+ [113, 225, 46],
231
+ [ 43, 202, 204],
232
+ [ 62, 76, 77],
233
+ [137, 123, 116],
234
+ [ 41, 38, 72],
235
+ [203, 129, 142],
236
+ [ 64, 98, 240],
237
+ [ 49, 102, 64],
238
+ [ 41, 73, 74],
239
+ [212, 216, 207],
240
+ [ 42, 74, 184],
241
+ [169, 170, 211],
242
+ [170, 149, 176],
243
+ [105, 66, 69],
244
+ [122, 6, 168],
245
+ [123, 147, 187],
246
+ [ 96, 77, 90],
247
+ [ 65, 55, 107],
248
+ [ 89, 90, 180],
249
+ [101, 100, 120],
250
+ [ 63, 105, 104],
251
+ [ 93, 137, 227],
252
+ [ 15, 86, 85],
253
+ [129, 102, 49],
254
+ [ 14, 87, 86],
255
+ [ 55, 8, 9],
256
+ [100, 47, 121],
257
+ [145, 23, 22],
258
+ [ 88, 89, 179],
259
+ [ 6, 122, 196],
260
+ [ 88, 95, 96],
261
+ [138, 172, 136],
262
+ [215, 58, 172],
263
+ [115, 48, 219],
264
+ [ 42, 80, 81],
265
+ [195, 3, 51],
266
+ [ 43, 146, 61],
267
+ [171, 175, 199],
268
+ [ 81, 82, 38],
269
+ [ 53, 46, 225],
270
+ [144, 163, 110],
271
+ [ 52, 65, 66],
272
+ [229, 228, 117],
273
+ [ 34, 127, 234],
274
+ [107, 108, 69],
275
+ [109, 108, 151],
276
+ [ 48, 64, 235],
277
+ [ 62, 78, 191],
278
+ [129, 209, 126],
279
+ [111, 35, 143],
280
+ [117, 123, 50],
281
+ [222, 65, 52],
282
+ [ 19, 125, 141],
283
+ [221, 55, 65],
284
+ [ 3, 195, 197],
285
+ [ 25, 7, 33],
286
+ [220, 237, 44],
287
+ [ 70, 71, 139],
288
+ [122, 193, 245],
289
+ [247, 130, 33],
290
+ [ 71, 21, 162],
291
+ [170, 169, 150],
292
+ [188, 174, 196],
293
+ [216, 186, 92],
294
+ [ 2, 97, 167],
295
+ [141, 125, 241],
296
+ [164, 167, 37],
297
+ [ 72, 38, 12],
298
+ [ 38, 82, 13],
299
+ [ 63, 68, 71],
300
+ [226, 35, 111],
301
+ [101, 50, 205],
302
+ [206, 92, 165],
303
+ [209, 198, 217],
304
+ [165, 167, 97],
305
+ [220, 115, 218],
306
+ [133, 112, 243],
307
+ [239, 238, 241],
308
+ [214, 135, 169],
309
+ [190, 173, 133],
310
+ [171, 208, 32],
311
+ [125, 44, 237],
312
+ [ 86, 87, 178],
313
+ [ 85, 86, 179],
314
+ [ 84, 85, 180],
315
+ [ 83, 84, 181],
316
+ [201, 83, 182],
317
+ [137, 93, 132],
318
+ [ 76, 62, 183],
319
+ [ 61, 76, 184],
320
+ [ 57, 61, 185],
321
+ [212, 57, 186],
322
+ [214, 207, 187],
323
+ [ 34, 143, 156],
324
+ [ 79, 239, 237],
325
+ [123, 137, 177],
326
+ [ 44, 1, 4],
327
+ [201, 194, 32],
328
+ [ 64, 102, 129],
329
+ [213, 215, 138],
330
+ [ 59, 166, 219],
331
+ [242, 99, 97],
332
+ [ 2, 94, 141],
333
+ [ 75, 59, 235],
334
+ [ 24, 110, 228],
335
+ [ 25, 130, 226],
336
+ [ 23, 24, 229],
337
+ [ 22, 23, 230],
338
+ [ 26, 22, 231],
339
+ [112, 26, 232],
340
+ [189, 190, 243],
341
+ [221, 56, 190],
342
+ [ 28, 56, 221],
343
+ [ 27, 28, 222],
344
+ [ 29, 27, 223],
345
+ [ 30, 29, 224],
346
+ [247, 30, 225],
347
+ [238, 79, 20],
348
+ [166, 59, 75],
349
+ [ 60, 75, 240],
350
+ [147, 177, 215],
351
+ [ 20, 79, 166],
352
+ [187, 147, 213],
353
+ [112, 233, 244],
354
+ [233, 128, 245],
355
+ [128, 114, 188],
356
+ [114, 217, 174],
357
+ [131, 115, 220],
358
+ [217, 198, 236],
359
+ [198, 131, 134],
360
+ [177, 132, 58],
361
+ [143, 35, 124],
362
+ [110, 163, 7],
363
+ [228, 110, 25],
364
+ [356, 389, 368],
365
+ [ 11, 302, 267],
366
+ [452, 350, 349],
367
+ [302, 303, 269],
368
+ [357, 343, 277],
369
+ [452, 453, 357],
370
+ [333, 332, 297],
371
+ [175, 152, 377],
372
+ [347, 348, 330],
373
+ [303, 304, 270],
374
+ [ 9, 336, 337],
375
+ [278, 279, 360],
376
+ [418, 262, 431],
377
+ [304, 408, 409],
378
+ [310, 415, 407],
379
+ [270, 409, 410],
380
+ [450, 348, 347],
381
+ [422, 430, 434],
382
+ [313, 314, 17],
383
+ [306, 307, 375],
384
+ [387, 388, 260],
385
+ [286, 414, 398],
386
+ [335, 406, 418],
387
+ [364, 367, 416],
388
+ [423, 358, 327],
389
+ [251, 284, 298],
390
+ [281, 5, 4],
391
+ [373, 374, 253],
392
+ [307, 320, 321],
393
+ [425, 427, 411],
394
+ [421, 313, 18],
395
+ [321, 405, 406],
396
+ [320, 404, 405],
397
+ [315, 16, 17],
398
+ [426, 425, 266],
399
+ [377, 400, 369],
400
+ [322, 391, 269],
401
+ [417, 465, 464],
402
+ [386, 257, 258],
403
+ [466, 260, 388],
404
+ [456, 399, 419],
405
+ [284, 332, 333],
406
+ [417, 285, 8],
407
+ [346, 340, 261],
408
+ [413, 441, 285],
409
+ [327, 460, 328],
410
+ [355, 371, 329],
411
+ [392, 439, 438],
412
+ [382, 341, 256],
413
+ [429, 420, 360],
414
+ [364, 394, 379],
415
+ [277, 343, 437],
416
+ [443, 444, 283],
417
+ [275, 440, 363],
418
+ [431, 262, 369],
419
+ [297, 338, 337],
420
+ [273, 375, 321],
421
+ [450, 451, 349],
422
+ [446, 342, 467],
423
+ [293, 334, 282],
424
+ [458, 461, 462],
425
+ [276, 353, 383],
426
+ [308, 324, 325],
427
+ [276, 300, 293],
428
+ [372, 345, 447],
429
+ [352, 345, 340],
430
+ [274, 1, 19],
431
+ [456, 248, 281],
432
+ [436, 427, 425],
433
+ [381, 256, 252],
434
+ [269, 391, 393],
435
+ [200, 199, 428],
436
+ [266, 330, 329],
437
+ [287, 273, 422],
438
+ [250, 462, 328],
439
+ [258, 286, 384],
440
+ [265, 353, 342],
441
+ [387, 259, 257],
442
+ [424, 431, 430],
443
+ [342, 353, 276],
444
+ [273, 335, 424],
445
+ [292, 325, 307],
446
+ [366, 447, 345],
447
+ [271, 303, 302],
448
+ [423, 266, 371],
449
+ [294, 455, 460],
450
+ [279, 278, 294],
451
+ [271, 272, 304],
452
+ [432, 434, 427],
453
+ [272, 407, 408],
454
+ [394, 430, 431],
455
+ [395, 369, 400],
456
+ [334, 333, 299],
457
+ [351, 417, 168],
458
+ [352, 280, 411],
459
+ [325, 319, 320],
460
+ [295, 296, 336],
461
+ [319, 403, 404],
462
+ [330, 348, 349],
463
+ [293, 298, 333],
464
+ [323, 454, 447],
465
+ [ 15, 16, 315],
466
+ [358, 429, 279],
467
+ [ 14, 15, 316],
468
+ [285, 336, 9],
469
+ [329, 349, 350],
470
+ [374, 380, 252],
471
+ [318, 402, 403],
472
+ [ 6, 197, 419],
473
+ [318, 319, 325],
474
+ [367, 364, 365],
475
+ [435, 367, 397],
476
+ [344, 438, 439],
477
+ [272, 271, 311],
478
+ [195, 5, 281],
479
+ [273, 287, 291],
480
+ [396, 428, 199],
481
+ [311, 271, 268],
482
+ [283, 444, 445],
483
+ [373, 254, 339],
484
+ [282, 334, 296],
485
+ [449, 347, 346],
486
+ [264, 447, 454],
487
+ [336, 296, 299],
488
+ [338, 10, 151],
489
+ [278, 439, 455],
490
+ [292, 407, 415],
491
+ [358, 371, 355],
492
+ [340, 345, 372],
493
+ [346, 347, 280],
494
+ [442, 443, 282],
495
+ [ 19, 94, 370],
496
+ [441, 442, 295],
497
+ [248, 419, 197],
498
+ [263, 255, 359],
499
+ [440, 275, 274],
500
+ [300, 383, 368],
501
+ [351, 412, 465],
502
+ [263, 467, 466],
503
+ [301, 368, 389],
504
+ [395, 378, 379],
505
+ [412, 351, 419],
506
+ [436, 426, 322],
507
+ [ 2, 164, 393],
508
+ [370, 462, 461],
509
+ [164, 0, 267],
510
+ [302, 11, 12],
511
+ [268, 12, 13],
512
+ [293, 300, 301],
513
+ [446, 261, 340],
514
+ [330, 266, 425],
515
+ [426, 423, 391],
516
+ [429, 355, 437],
517
+ [391, 327, 326],
518
+ [440, 457, 438],
519
+ [341, 382, 362],
520
+ [459, 457, 461],
521
+ [434, 430, 394],
522
+ [414, 463, 362],
523
+ [396, 369, 262],
524
+ [354, 461, 457],
525
+ [316, 403, 402],
526
+ [315, 404, 403],
527
+ [314, 405, 404],
528
+ [313, 406, 405],
529
+ [421, 418, 406],
530
+ [366, 401, 361],
531
+ [306, 408, 407],
532
+ [291, 409, 408],
533
+ [287, 410, 409],
534
+ [432, 436, 410],
535
+ [434, 416, 411],
536
+ [264, 368, 383],
537
+ [309, 438, 457],
538
+ [352, 376, 401],
539
+ [274, 275, 4],
540
+ [421, 428, 262],
541
+ [294, 327, 358],
542
+ [433, 416, 367],
543
+ [289, 455, 439],
544
+ [462, 370, 326],
545
+ [ 2, 326, 370],
546
+ [305, 460, 455],
547
+ [254, 449, 448],
548
+ [255, 261, 446],
549
+ [253, 450, 449],
550
+ [252, 451, 450],
551
+ [256, 452, 451],
552
+ [341, 453, 452],
553
+ [413, 464, 463],
554
+ [441, 413, 414],
555
+ [258, 442, 441],
556
+ [257, 443, 442],
557
+ [259, 444, 443],
558
+ [260, 445, 444],
559
+ [467, 342, 445],
560
+ [459, 458, 250],
561
+ [289, 392, 290],
562
+ [290, 328, 460],
563
+ [376, 433, 435],
564
+ [250, 290, 392],
565
+ [411, 416, 433],
566
+ [341, 463, 464],
567
+ [453, 464, 465],
568
+ [357, 465, 412],
569
+ [343, 412, 399],
570
+ [360, 363, 440],
571
+ [437, 399, 456],
572
+ [420, 456, 363],
573
+ [401, 435, 288],
574
+ [372, 383, 353],
575
+ [339, 255, 249],
576
+ [448, 261, 255],
577
+ [133, 243, 190],
578
+ [133, 155, 112],
579
+ [ 33, 246, 247],
580
+ [ 33, 130, 25],
581
+ [398, 384, 286],
582
+ [362, 398, 414],
583
+ [362, 463, 341],
584
+ [263, 359, 467],
585
+ [263, 249, 255],
586
+ [466, 467, 260],
587
+ [ 75, 60, 166],
588
+ [238, 239, 79],
589
+ [162, 127, 139],
590
+ [ 72, 11, 37],
591
+ [121, 232, 120],
592
+ [ 73, 72, 39],
593
+ [114, 128, 47],
594
+ [233, 232, 128],
595
+ [103, 104, 67],
596
+ [152, 175, 148],
597
+ [119, 118, 101],
598
+ [ 74, 73, 40],
599
+ [107, 9, 108],
600
+ [ 49, 48, 131],
601
+ [ 32, 194, 211],
602
+ [184, 74, 185],
603
+ [191, 80, 183],
604
+ [185, 40, 186],
605
+ [119, 230, 118],
606
+ [210, 202, 214],
607
+ [ 84, 83, 17],
608
+ [ 77, 76, 146],
609
+ [161, 160, 30],
610
+ [190, 56, 173],
611
+ [182, 106, 194],
612
+ [138, 135, 192],
613
+ [129, 203, 98],
614
+ [ 54, 21, 68],
615
+ [ 5, 51, 4],
616
+ [145, 144, 23],
617
+ [ 90, 77, 91],
618
+ [207, 205, 187],
619
+ [ 83, 201, 18],
620
+ [181, 91, 182],
621
+ [180, 90, 181],
622
+ [ 16, 85, 17],
623
+ [205, 206, 36],
624
+ [176, 148, 140],
625
+ [165, 92, 39],
626
+ [245, 193, 244],
627
+ [ 27, 159, 28],
628
+ [ 30, 247, 161],
629
+ [174, 236, 196],
630
+ [103, 54, 104],
631
+ [ 55, 193, 8],
632
+ [111, 117, 31],
633
+ [221, 189, 55],
634
+ [240, 98, 99],
635
+ [142, 126, 100],
636
+ [219, 166, 218],
637
+ [112, 155, 26],
638
+ [198, 209, 131],
639
+ [169, 135, 150],
640
+ [114, 47, 217],
641
+ [224, 223, 53],
642
+ [220, 45, 134],
643
+ [ 32, 211, 140],
644
+ [109, 67, 108],
645
+ [146, 43, 91],
646
+ [231, 230, 120],
647
+ [113, 226, 247],
648
+ [105, 63, 52],
649
+ [241, 238, 242],
650
+ [124, 46, 156],
651
+ [ 95, 78, 96],
652
+ [ 70, 46, 63],
653
+ [116, 143, 227],
654
+ [116, 123, 111],
655
+ [ 1, 44, 19],
656
+ [ 3, 236, 51],
657
+ [207, 216, 205],
658
+ [ 26, 154, 22],
659
+ [165, 39, 167],
660
+ [199, 200, 208],
661
+ [101, 36, 100],
662
+ [ 43, 57, 202],
663
+ [242, 20, 99],
664
+ [ 56, 28, 157],
665
+ [124, 35, 113],
666
+ [ 29, 160, 27],
667
+ [211, 204, 210],
668
+ [124, 113, 46],
669
+ [106, 43, 204],
670
+ [ 96, 62, 77],
671
+ [227, 137, 116],
672
+ [ 73, 41, 72],
673
+ [ 36, 203, 142],
674
+ [235, 64, 240],
675
+ [ 48, 49, 64],
676
+ [ 42, 41, 74],
677
+ [214, 212, 207],
678
+ [183, 42, 184],
679
+ [210, 169, 211],
680
+ [140, 170, 176],
681
+ [104, 105, 69],
682
+ [193, 122, 168],
683
+ [ 50, 123, 187],
684
+ [ 89, 96, 90],
685
+ [ 66, 65, 107],
686
+ [179, 89, 180],
687
+ [119, 101, 120],
688
+ [ 68, 63, 104],
689
+ [234, 93, 227],
690
+ [ 16, 15, 85],
691
+ [209, 129, 49],
692
+ [ 15, 14, 86],
693
+ [107, 55, 9],
694
+ [120, 100, 121],
695
+ [153, 145, 22],
696
+ [178, 88, 179],
697
+ [197, 6, 196],
698
+ [ 89, 88, 96],
699
+ [135, 138, 136],
700
+ [138, 215, 172],
701
+ [218, 115, 219],
702
+ [ 41, 42, 81],
703
+ [ 5, 195, 51],
704
+ [ 57, 43, 61],
705
+ [208, 171, 199],
706
+ [ 41, 81, 38],
707
+ [224, 53, 225],
708
+ [ 24, 144, 110],
709
+ [105, 52, 66],
710
+ [118, 229, 117],
711
+ [227, 34, 234],
712
+ [ 66, 107, 69],
713
+ [ 10, 109, 151],
714
+ [219, 48, 235],
715
+ [183, 62, 191],
716
+ [142, 129, 126],
717
+ [116, 111, 143],
718
+ [118, 117, 50],
719
+ [223, 222, 52],
720
+ [ 94, 19, 141],
721
+ [222, 221, 65],
722
+ [196, 3, 197],
723
+ [ 45, 220, 44],
724
+ [156, 70, 139],
725
+ [188, 122, 245],
726
+ [139, 71, 162],
727
+ [149, 170, 150],
728
+ [122, 188, 196],
729
+ [206, 216, 92],
730
+ [164, 2, 167],
731
+ [242, 141, 241],
732
+ [ 0, 164, 37],
733
+ [ 11, 72, 12],
734
+ [ 12, 38, 13],
735
+ [ 70, 63, 71],
736
+ [ 31, 226, 111],
737
+ [ 36, 101, 205],
738
+ [203, 206, 165],
739
+ [126, 209, 217],
740
+ [ 98, 165, 97],
741
+ [237, 220, 218],
742
+ [237, 239, 241],
743
+ [210, 214, 169],
744
+ [140, 171, 32],
745
+ [241, 125, 237],
746
+ [179, 86, 178],
747
+ [180, 85, 179],
748
+ [181, 84, 180],
749
+ [182, 83, 181],
750
+ [194, 201, 182],
751
+ [177, 137, 132],
752
+ [184, 76, 183],
753
+ [185, 61, 184],
754
+ [186, 57, 185],
755
+ [216, 212, 186],
756
+ [192, 214, 187],
757
+ [139, 34, 156],
758
+ [218, 79, 237],
759
+ [147, 123, 177],
760
+ [ 45, 44, 4],
761
+ [208, 201, 32],
762
+ [ 98, 64, 129],
763
+ [192, 213, 138],
764
+ [235, 59, 219],
765
+ [141, 242, 97],
766
+ [ 97, 2, 141],
767
+ [240, 75, 235],
768
+ [229, 24, 228],
769
+ [ 31, 25, 226],
770
+ [230, 23, 229],
771
+ [231, 22, 230],
772
+ [232, 26, 231],
773
+ [233, 112, 232],
774
+ [244, 189, 243],
775
+ [189, 221, 190],
776
+ [222, 28, 221],
777
+ [223, 27, 222],
778
+ [224, 29, 223],
779
+ [225, 30, 224],
780
+ [113, 247, 225],
781
+ [ 99, 60, 240],
782
+ [213, 147, 215],
783
+ [ 60, 20, 166],
784
+ [192, 187, 213],
785
+ [243, 112, 244],
786
+ [244, 233, 245],
787
+ [245, 128, 188],
788
+ [188, 114, 174],
789
+ [134, 131, 220],
790
+ [174, 217, 236],
791
+ [236, 198, 134],
792
+ [215, 177, 58],
793
+ [156, 143, 124],
794
+ [ 25, 110, 7],
795
+ [ 31, 228, 25],
796
+ [264, 356, 368],
797
+ [ 0, 11, 267],
798
+ [451, 452, 349],
799
+ [267, 302, 269],
800
+ [350, 357, 277],
801
+ [350, 452, 357],
802
+ [299, 333, 297],
803
+ [396, 175, 377],
804
+ [280, 347, 330],
805
+ [269, 303, 270],
806
+ [151, 9, 337],
807
+ [344, 278, 360],
808
+ [424, 418, 431],
809
+ [270, 304, 409],
810
+ [272, 310, 407],
811
+ [322, 270, 410],
812
+ [449, 450, 347],
813
+ [432, 422, 434],
814
+ [ 18, 313, 17],
815
+ [291, 306, 375],
816
+ [259, 387, 260],
817
+ [424, 335, 418],
818
+ [434, 364, 416],
819
+ [391, 423, 327],
820
+ [301, 251, 298],
821
+ [275, 281, 4],
822
+ [254, 373, 253],
823
+ [375, 307, 321],
824
+ [280, 425, 411],
825
+ [200, 421, 18],
826
+ [335, 321, 406],
827
+ [321, 320, 405],
828
+ [314, 315, 17],
829
+ [423, 426, 266],
830
+ [396, 377, 369],
831
+ [270, 322, 269],
832
+ [413, 417, 464],
833
+ [385, 386, 258],
834
+ [248, 456, 419],
835
+ [298, 284, 333],
836
+ [168, 417, 8],
837
+ [448, 346, 261],
838
+ [417, 413, 285],
839
+ [326, 327, 328],
840
+ [277, 355, 329],
841
+ [309, 392, 438],
842
+ [381, 382, 256],
843
+ [279, 429, 360],
844
+ [365, 364, 379],
845
+ [355, 277, 437],
846
+ [282, 443, 283],
847
+ [281, 275, 363],
848
+ [395, 431, 369],
849
+ [299, 297, 337],
850
+ [335, 273, 321],
851
+ [348, 450, 349],
852
+ [359, 446, 467],
853
+ [283, 293, 282],
854
+ [250, 458, 462],
855
+ [300, 276, 383],
856
+ [292, 308, 325],
857
+ [283, 276, 293],
858
+ [264, 372, 447],
859
+ [346, 352, 340],
860
+ [354, 274, 19],
861
+ [363, 456, 281],
862
+ [426, 436, 425],
863
+ [380, 381, 252],
864
+ [267, 269, 393],
865
+ [421, 200, 428],
866
+ [371, 266, 329],
867
+ [432, 287, 422],
868
+ [290, 250, 328],
869
+ [385, 258, 384],
870
+ [446, 265, 342],
871
+ [386, 387, 257],
872
+ [422, 424, 430],
873
+ [445, 342, 276],
874
+ [422, 273, 424],
875
+ [306, 292, 307],
876
+ [352, 366, 345],
877
+ [268, 271, 302],
878
+ [358, 423, 371],
879
+ [327, 294, 460],
880
+ [331, 279, 294],
881
+ [303, 271, 304],
882
+ [436, 432, 427],
883
+ [304, 272, 408],
884
+ [395, 394, 431],
885
+ [378, 395, 400],
886
+ [296, 334, 299],
887
+ [ 6, 351, 168],
888
+ [376, 352, 411],
889
+ [307, 325, 320],
890
+ [285, 295, 336],
891
+ [320, 319, 404],
892
+ [329, 330, 349],
893
+ [334, 293, 333],
894
+ [366, 323, 447],
895
+ [316, 15, 315],
896
+ [331, 358, 279],
897
+ [317, 14, 316],
898
+ [ 8, 285, 9],
899
+ [277, 329, 350],
900
+ [253, 374, 252],
901
+ [319, 318, 403],
902
+ [351, 6, 419],
903
+ [324, 318, 325],
904
+ [397, 367, 365],
905
+ [288, 435, 397],
906
+ [278, 344, 439],
907
+ [310, 272, 311],
908
+ [248, 195, 281],
909
+ [375, 273, 291],
910
+ [175, 396, 199],
911
+ [312, 311, 268],
912
+ [276, 283, 445],
913
+ [390, 373, 339],
914
+ [295, 282, 296],
915
+ [448, 449, 346],
916
+ [356, 264, 454],
917
+ [337, 336, 299],
918
+ [337, 338, 151],
919
+ [294, 278, 455],
920
+ [308, 292, 415],
921
+ [429, 358, 355],
922
+ [265, 340, 372],
923
+ [352, 346, 280],
924
+ [295, 442, 282],
925
+ [354, 19, 370],
926
+ [285, 441, 295],
927
+ [195, 248, 197],
928
+ [457, 440, 274],
929
+ [301, 300, 368],
930
+ [417, 351, 465],
931
+ [251, 301, 389],
932
+ [394, 395, 379],
933
+ [399, 412, 419],
934
+ [410, 436, 322],
935
+ [326, 2, 393],
936
+ [354, 370, 461],
937
+ [393, 164, 267],
938
+ [268, 302, 12],
939
+ [312, 268, 13],
940
+ [298, 293, 301],
941
+ [265, 446, 340],
942
+ [280, 330, 425],
943
+ [322, 426, 391],
944
+ [420, 429, 437],
945
+ [393, 391, 326],
946
+ [344, 440, 438],
947
+ [458, 459, 461],
948
+ [364, 434, 394],
949
+ [428, 396, 262],
950
+ [274, 354, 457],
951
+ [317, 316, 402],
952
+ [316, 315, 403],
953
+ [315, 314, 404],
954
+ [314, 313, 405],
955
+ [313, 421, 406],
956
+ [323, 366, 361],
957
+ [292, 306, 407],
958
+ [306, 291, 408],
959
+ [291, 287, 409],
960
+ [287, 432, 410],
961
+ [427, 434, 411],
962
+ [372, 264, 383],
963
+ [459, 309, 457],
964
+ [366, 352, 401],
965
+ [ 1, 274, 4],
966
+ [418, 421, 262],
967
+ [331, 294, 358],
968
+ [435, 433, 367],
969
+ [392, 289, 439],
970
+ [328, 462, 326],
971
+ [ 94, 2, 370],
972
+ [289, 305, 455],
973
+ [339, 254, 448],
974
+ [359, 255, 446],
975
+ [254, 253, 449],
976
+ [253, 252, 450],
977
+ [252, 256, 451],
978
+ [256, 341, 452],
979
+ [414, 413, 463],
980
+ [286, 441, 414],
981
+ [286, 258, 441],
982
+ [258, 257, 442],
983
+ [257, 259, 443],
984
+ [259, 260, 444],
985
+ [260, 467, 445],
986
+ [309, 459, 250],
987
+ [305, 289, 290],
988
+ [305, 290, 460],
989
+ [401, 376, 435],
990
+ [309, 250, 392],
991
+ [376, 411, 433],
992
+ [453, 341, 464],
993
+ [357, 453, 465],
994
+ [343, 357, 412],
995
+ [437, 343, 399],
996
+ [344, 360, 440],
997
+ [420, 437, 456],
998
+ [360, 420, 363],
999
+ [361, 401, 288],
1000
+ [265, 372, 353],
1001
+ [390, 339, 249],
1002
+ [339, 448, 255]
1003
+ ]
mp_utils.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+
3
+ import mediapipe as mp
4
+ from mediapipe.tasks import python
5
+ from mediapipe.tasks.python import vision
6
+ from mediapipe.framework.formats import landmark_pb2
7
+ from mediapipe import solutions
8
+ import numpy as np
9
+
10
+ # 2024-11-27 -extract_landmark :add args
11
+ # add get_pixel_xyz
12
+ # 2024-11-28 add get_normalized_xyz
13
+ # 2024-11-30 add get_normalized_landmarks,sort_triangles_by_depth,get_range_all,get_bbox
14
+ def calculate_distance(p1, p2):
15
+ """
16
+
17
+ """
18
+ return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)
19
+
20
+ def get_range_all():
21
+ return range(0,468)
22
+
23
+ def get_landmark_bbox(face_landmarks_list,w=1024,h=1024,margin_hw=0,margin_hh=0):
24
+ return get_bbox(face_landmarks_list,get_range_all(),w,h,margin_hw,margin_hh)
25
+ def get_bbox(face_landmarks_list,indices,w=1024,h=1024,margin_hw=0,margin_hh=0):
26
+ x1=w
27
+ y1=h
28
+ x2=0
29
+ y2=0
30
+ for index in indices:
31
+ x=min(w,max(0,(face_landmarks_list[0][index].x*w)))
32
+ y=min(h,max(0,(face_landmarks_list[0][index].y*h)))
33
+ if x<x1:
34
+ x1=x
35
+
36
+ if y<y1:
37
+ y1=y
38
+
39
+ if x>x2:
40
+ x2=x
41
+ if y>y2:
42
+ y2=y
43
+ return [max(0,int(x1)-margin_hw),max(0,int(y1)-margin_hh),min(w,int(x2-x1)+margin_hw),min(h,int(y2-y1)+margin_hh)]
44
+
45
+ def to_int_points(points):
46
+ ints=[]
47
+ for pt in points:
48
+ #print(pt)
49
+ value = [int(pt[0]),int(pt[1])]
50
+ #print(value)
51
+ ints.append(value)
52
+ return ints
53
+
54
+ debug = False
55
+ def divide_line_to_points(points,divided): # return divided + 1
56
+ total_length = 0
57
+ line_length_list = []
58
+ for i in range(len(points)-1):
59
+ pt_length = calculate_distance(points[i],points[i+1])
60
+ total_length += pt_length
61
+ line_length_list.append(pt_length)
62
+
63
+ splited_length = total_length/divided
64
+
65
+ def get_new_point(index,lerp):
66
+ pt1 = points[index]
67
+ pt2 = points[index+1]
68
+ diff = [pt2[0] - pt1[0], pt2[1]-pt1[1]]
69
+ new_point = [pt1[0]+diff[0]*lerp,pt1[1]+diff[1]*lerp]
70
+ if debug:
71
+ print(f"pt1 ={pt1} pt2 ={pt2} diff={diff} new_point={new_point}")
72
+
73
+ return new_point
74
+
75
+ if debug:
76
+ print(f"{total_length} splitted = {splited_length} line-length-list = {len(line_length_list)}")
77
+ splited_points=[points[0]]
78
+ for i in range(1,divided):
79
+ need_length = splited_length*i
80
+ if debug:
81
+ print(f"{i} need length = {need_length}")
82
+ current_length = 0
83
+ for j in range(len(line_length_list)):
84
+ line_length = line_length_list[j]
85
+ current_length+=line_length
86
+ if current_length>need_length:
87
+ if debug:
88
+ print(f"over need length index = {j} current={current_length}")
89
+ diff = current_length - need_length
90
+
91
+ lerp_point = 1.0 - (diff/line_length)
92
+ if debug:
93
+ print(f"over = {diff} lerp ={lerp_point}")
94
+ new_point = get_new_point(j,lerp_point)
95
+
96
+ splited_points.append(new_point)
97
+ break
98
+
99
+ splited_points.append(points[-1]) # last one
100
+ splited_points=to_int_points(splited_points)
101
+
102
+ if debug:
103
+ print(f"sp={len(splited_points)}")
104
+ return splited_points
105
+
106
+
107
+
108
+ def expand_bbox(bbox,left=5,top=5,right=5,bottom=5):
109
+ left_pixel = bbox[2]*(float(left)/100)
110
+ top_pixel = bbox[3]*(float(top)/100)
111
+ right_pixel = bbox[2]*(float(right)/100)
112
+ bottom_pixel = bbox[3]*(float(bottom)/100)
113
+ new_box = list(bbox)
114
+ new_box[0] -=left_pixel
115
+ new_box[1] -=top_pixel
116
+ new_box[2] +=left_pixel+right_pixel
117
+ new_box[3] +=top_pixel+bottom_pixel
118
+ return new_box
119
+
120
+ #normalized value index see mp_constants
121
+ def get_normalized_cordinate(face_landmarks_list,index):
122
+ x=face_landmarks_list[0][index].x
123
+ y=face_landmarks_list[0][index].y
124
+ return x,y
125
+
126
+ def get_normalized_xyz(face_landmarks_list,index):
127
+ x=face_landmarks_list[0][index].x
128
+ y=face_landmarks_list[0][index].y
129
+ z=face_landmarks_list[0][index].z
130
+ return x,y,z
131
+
132
+ def get_normalized_landmarks(face_landmarks_list):
133
+ return [get_normalized_xyz(face_landmarks_list,i) for i in range(0,468)]
134
+
135
+ def sort_triangles_by_depth(landmark_points,mesh_triangle_indices):
136
+ assert len(landmark_points) == 468
137
+ mesh_triangle_indices.sort(key=lambda triangle: sum(landmark_points[index][2] for index in triangle) / len(triangle)
138
+ ,reverse=True)
139
+ # z is normalized
140
+ def get_pixel_xyz(face_landmarks_list,landmark,width,height):
141
+ point = get_normalized_cordinate(face_landmarks_list,landmark)
142
+ z = y=face_landmarks_list[0][landmark].z
143
+ return int(point[0]*width),int(point[1]*height),z
144
+
145
+ def get_pixel_cordinate(face_landmarks_list,landmark,width,height):
146
+ point = get_normalized_cordinate(face_landmarks_list,landmark)
147
+ return int(point[0]*width),int(point[1]*height)
148
+
149
+ def get_pixel_cordinate_list(face_landmarks_list,indices,width,height):
150
+ cordinates = []
151
+ for index in indices:
152
+ cordinates.append(get_pixel_cordinate(face_landmarks_list,index,width,height))
153
+ return cordinates
154
+
155
+ def extract_landmark(image_data,model_path="face_landmarker.task",min_face_detection_confidence=0, min_face_presence_confidence=0,output_facial_transformation_matrixes=False):
156
+ BaseOptions = mp.tasks.BaseOptions
157
+ FaceLandmarker = mp.tasks.vision.FaceLandmarker
158
+ FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions
159
+ VisionRunningMode = mp.tasks.vision.RunningMode
160
+
161
+ options = FaceLandmarkerOptions(
162
+ base_options=BaseOptions(model_asset_path=model_path),
163
+ running_mode=VisionRunningMode.IMAGE
164
+ ,min_face_detection_confidence=min_face_detection_confidence, min_face_presence_confidence=min_face_presence_confidence,
165
+ output_facial_transformation_matrixes=output_facial_transformation_matrixes
166
+ )
167
+
168
+ with FaceLandmarker.create_from_options(options) as landmarker:
169
+ if isinstance(image_data,str):
170
+ mp_image = mp.Image.create_from_file(image_data)
171
+ else:
172
+ mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(image_data))
173
+ face_landmarker_result = landmarker.detect(mp_image)
174
+ return mp_image,face_landmarker_result
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ numpy
2
+ torch
3
+ spaces
4
+ mediapipe
5
+ numba