Akjava commited on
Commit
c204f33
·
1 Parent(s): 97eb94e
This view is limited to 50 files because it contains too many changes.   See raw diff
.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
app.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ import gradio as gr
3
+
4
+
5
+ '''
6
+
7
+ '''
8
+ from gradio_utils import clear_old_files,read_file
9
+ from face_mesh_spinning import process_face_mesh_spinning
10
+ from mp_estimate import mean_std_label,estimate_horizontal,estimate_vertical,estimate_horizontal_points,estimate_vertical_points
11
+
12
+ def process_images(image,draw_type,center_scaleup,animation_direction,
13
+ z_multiply,inner_eyes,inner_mouth,
14
+ progress=gr.Progress(track_tqdm=True)):
15
+
16
+ clear_old_files()
17
+
18
+ if image==None:
19
+ raise gr.Error("need image")
20
+
21
+ result,face_landmarker_result,rotated_points = process_face_mesh_spinning(image,draw_type,center_scaleup,animation_direction,z_multiply,inner_eyes,inner_mouth)
22
+
23
+ return result
24
+
25
+
26
+ css="""
27
+ #col-left {
28
+ margin: 0 auto;
29
+ max-width: 640px;
30
+ }
31
+ #col-right {
32
+ margin: 0 auto;
33
+ max-width: 640px;
34
+ }
35
+ .grid-container {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ gap:10px
40
+ }
41
+
42
+ .image {
43
+ width: 128px;
44
+ height: 128px;
45
+ object-fit: cover;
46
+ }
47
+
48
+ .text {
49
+ font-size: 16px;
50
+ }
51
+ """
52
+
53
+ from glibvision.cv2_utils import pil_to_bgr_image,copy_image
54
+ from mp_utils import extract_landmark,get_pixel_cordinate
55
+ import numpy as np
56
+ # TODO move mp_util
57
+ def extract_landmark_double_check(numpy_image,double_check=True,center_index=4,extract_matrix=True):#4 is nose-tip
58
+ mp_image,face_landmarker_result = extract_landmark(numpy_image,"face_landmarker.task",0,0,extract_matrix)
59
+ h,w = numpy_image.shape[:2]
60
+ second_mp_image,first_landmarker_result = None,None
61
+ numpy_view = mp_image.numpy_view()
62
+ if double_check:
63
+ root_cordinate = get_pixel_cordinate(face_landmarker_result.face_landmarks,center_index,w,h)
64
+ diff_center_x = int(w/2 - root_cordinate[0])
65
+ diff_center_y = int(h/2 - root_cordinate[1])
66
+ base = np.zeros_like(numpy_view)
67
+ copy_image(base,numpy_view,diff_center_x,diff_center_y)
68
+ first_landmarker_result = face_landmarker_result
69
+ second_mp_image,face_landmarker_result = extract_landmark(base,"face_landmarker.task",0,0,extract_matrix)
70
+ return mp_image,face_landmarker_result,second_mp_image,first_landmarker_result
71
+
72
+ #css=css,
73
+
74
+ from scipy.spatial.transform import Rotation as R
75
+ def calculate_angle(image,double_check,ignore_x,order):
76
+ cv2_base_image = pil_to_bgr_image(image)
77
+ mp_image,face_landmarker_result,_,_ = extract_landmark_double_check(cv2_base_image,double_check)
78
+ if len(face_landmarker_result.facial_transformation_matrixes)>0:
79
+ transformation_matrix=face_landmarker_result.facial_transformation_matrixes[0]
80
+
81
+ rotation_matrix, translation_vector = transformation_matrix[:3, :3],transformation_matrix[:3, 3]
82
+
83
+ r = R.from_matrix(rotation_matrix)
84
+ euler_angles = r.as_euler(order, degrees=True)
85
+ label = f"Mediapipe Euler yxz: {euler_angles}"
86
+ if ignore_x:
87
+ euler_angles[1]=0
88
+
89
+ result = [label,0,0,0]
90
+ for i,ch in enumerate(order.lower()):
91
+ if ch == "x":
92
+ result[1] = -euler_angles[i]
93
+ elif ch == "y":
94
+ result[2] = euler_angles[i]
95
+ elif ch == "z":
96
+ result[3] = euler_angles[i]
97
+
98
+ return result
99
+ return label,-euler_angles[1],euler_angles[0],euler_angles[2]
100
+ return "",0,0,0
101
+
102
+ def change_animation(animation):
103
+ if animation:
104
+ return gr.Column(visible=True),gr.Column(visible=False)
105
+ else:
106
+ return gr.Column(visible=False),gr.Column(visible=True)
107
+ with gr.Blocks(css=css, elem_id="demo-container") as demo:
108
+ with gr.Column():
109
+ gr.HTML(read_file("demo_header.html"))
110
+ gr.HTML(read_file("demo_tools.html"))
111
+ with gr.Row():
112
+ with gr.Column():
113
+ image = gr.Image(height=800,sources=['upload','clipboard'],image_mode='RGB',elem_id="image_upload", type="pil", label="Image")
114
+
115
+ with gr.Row(elem_id="prompt-container", equal_height=False):
116
+ with gr.Row():
117
+ btn = gr.Button("Rotate Mesh", elem_id="run_button",variant="primary")
118
+
119
+
120
+
121
+ with gr.Accordion(label="Advanced Settings", open=True):
122
+
123
+ draw_type = gr.Radio(label="Draw type",choices=["Dot","Line","Line+Fill","Image"],value="Line",info="making image animation,take over 60 sec and limited frame only")
124
+ with gr.Row( equal_height=True):
125
+ inner_eyes=gr.Checkbox(label="Inner Eyes",value=True)
126
+ inner_mouth=gr.Checkbox(label="Inner Mouth",value=True)
127
+ with gr.Row( equal_height=True):
128
+
129
+ center_scaleup = gr.Checkbox(label="ScaleUp/Fit",value=True,info="center is nose-tip,Zoomed face usually make small")
130
+ z_multiply = gr.Slider(info="Nose height",
131
+ label="Depth-Multiply",
132
+ minimum=0.1,
133
+ maximum=1.5,
134
+ step=0.01,
135
+ value=0.8)
136
+ animation_column = gr.Column(visible=True)
137
+ with animation_column:
138
+ with gr.Row( equal_height=True):
139
+ animation_direction = gr.Radio(label="Animation Direction",choices=["X","Y","Z"],value="Y")
140
+
141
+
142
+
143
+
144
+
145
+
146
+ with gr.Column():
147
+ result_image = gr.Image(height=760,label="Result", elem_id="output-animation",image_mode='RGBA')
148
+
149
+
150
+
151
+
152
+
153
+ btn.click(fn=process_images, inputs=[image,draw_type,center_scaleup,animation_direction,
154
+ z_multiply,inner_eyes,inner_mouth,
155
+ ],outputs=[result_image,
156
+
157
+ ] ,api_name='infer')
158
+
159
+ example_images = [
160
+ ["examples/02316230.jpg","examples/02316230.webp"],
161
+ ["examples/00003245_00.jpg","examples/00003245_00.webp"],
162
+ ["examples/00827009.jpg","examples/00827009.webp"],
163
+ ["examples/00002062.jpg","examples/00002062.webp"],
164
+ ["examples/00824008.jpg","examples/00824008.webp"],
165
+ ["examples/00825000.jpg","examples/00825000.webp"],
166
+ ["examples/00826007.jpg","examples/00826007.webp"],
167
+ ["examples/00824006.jpg","examples/00824006.webp"],
168
+
169
+ ["examples/00002200.jpg","examples/00002200.webp"],
170
+ ["examples/00005259.jpg","examples/00005259.webp"],
171
+ ["examples/00018022.jpg","examples/00018022.webp"],
172
+ ["examples/img-above.jpg","examples/img-above.webp"],
173
+ ["examples/00100265.jpg","examples/00100265.webp"],
174
+ ["examples/00039259.jpg","examples/00039259.webp"],
175
+
176
+ ]
177
+ example1=gr.Examples(
178
+ examples = example_images,label="Image",
179
+ inputs=[image,result_image],examples_per_page=8
180
+ )
181
+
182
+ gr.HTML(read_file("demo_footer.html"))
183
+
184
+ if __name__ == "__main__":
185
+ 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,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div style="text-align: center;">
2
+ <h1>
3
+ Mediapipe Head 2D-Spinning
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
+ opposite side face quality is low<br>
11
+ <a href ="https://huggingface.co/spaces/Akjava/mediapipe-face-mesh-3d">3D Version</a> is much faster but seems hard to mixout 2d.<br>
12
+ </p>
13
+ </div>
14
+
15
+ </div>
demo_tools.html ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <div style="text-align: center;">
2
+ <p>
3
+ This is part of <a href="https://huggingface.co/collections/Akjava/mediapipe-tools-672ffe8ee7b62763c31b70c7">Mediapipe Tools Collection</a>
4
+
5
+ </p>
6
+ <p></p>
7
+ </div>
examples/00002062.jpg ADDED
examples/00002062.webp ADDED
examples/00002200.jpg ADDED
examples/00002200.webp ADDED
examples/00003245_00.jpg ADDED
examples/00003245_00.webp ADDED
examples/00005259.jpg ADDED
examples/00005259.webp ADDED
examples/00018022.jpg ADDED
examples/00018022.webp ADDED
examples/00039259.jpg ADDED
examples/00039259.webp ADDED
examples/00100265.jpg ADDED
examples/00100265.webp ADDED
examples/00824006.jpg ADDED
examples/00824006.webp ADDED
examples/00824008.jpg ADDED
examples/00824008.webp ADDED
examples/00825000.jpg ADDED
examples/00825000.webp ADDED
examples/00826007.jpg ADDED
examples/00826007.webp ADDED
examples/00827009.jpg ADDED
examples/00827009.webp ADDED
examples/00828003.jpg ADDED
examples/02316230.jpg ADDED
examples/02316230.webp ADDED
examples/_00039259.webp ADDED
examples/img-above.jpg ADDED
examples/img-above.webp 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
face_mesh_spinning.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ from PIL import Image,ImageOps,ImageDraw,ImageFilter
3
+ import json
4
+ import os
5
+ import time
6
+ import io
7
+ from mp_utils import get_pixel_cordinate_list,extract_landmark,get_pixel_cordinate,get_normalized_xyz
8
+ from glibvision.draw_utils import points_to_box,box_to_xy,plus_point,calculate_distance
9
+
10
+ import numpy as np
11
+ from glibvision.pil_utils import fill_points,create_color_image,draw_box
12
+
13
+ import glibvision.pil_utils
14
+
15
+ from gradio_utils import save_image,save_buffer,clear_old_files ,read_file
16
+
17
+
18
+ import math
19
+ import mp_triangles
20
+
21
+
22
+ from glibvision.cv2_utils import create_color_image as cv2_create_color_image,copy_image,pil_to_bgr_image
23
+ import cv2
24
+ #TODO move to CV2
25
+
26
+ # i'm not sure this is fast
27
+ def apply_affine_transformation_to_triangle_add(src_tri, dst_tri, src_img, dst_img):
28
+ src_tri_np = np.float32(src_tri)
29
+ dst_tri_np = np.float32(dst_tri)
30
+
31
+ h_dst, w_dst = dst_img.shape[:2]
32
+
33
+ M = cv2.getAffineTransform(src_tri_np, dst_tri_np)
34
+
35
+ dst_mask = np.zeros((h_dst, w_dst), dtype=np.uint8)
36
+ cv2.fillPoly(dst_mask, [np.int32(dst_tri)], 255)
37
+
38
+ transformed = cv2.warpAffine(src_img, M, (w_dst, h_dst))
39
+
40
+ transformed = transformed * (dst_mask[:, :, np.newaxis] / 255).astype(np.uint8)
41
+ dst_background = dst_img * (1 - (dst_mask[:, :, np.newaxis] / 255)).astype(np.uint8)
42
+ dst_img = transformed + dst_background
43
+
44
+ return dst_img
45
+
46
+ def apply_affine_transformation_to_triangle_add(src_tri, dst_tri, src_img, dst_img):
47
+ src_tri_np = np.float32(src_tri)
48
+ dst_tri_np = np.float32(dst_tri)
49
+
50
+ assert src_tri_np.shape == (3, 2), f"src_tri_np の形状が不正 {src_tri_np.shape}"
51
+ assert dst_tri_np.shape == (3, 2), f"dst_tri_np の形状が不正 {dst_tri_np.shape}"
52
+
53
+
54
+ # 透視変換行列の計算
55
+ M = cv2.getAffineTransform(src_tri_np, dst_tri_np)
56
+
57
+ # 画像のサイズ
58
+ h_src, w_src = src_img.shape[:2]
59
+ h_dst, w_dst = dst_img.shape[:2]
60
+
61
+ # 元画像から三角形領域を切り抜くマスク生成
62
+ #src_mask = np.zeros((h_src, w_src), dtype=np.uint8)
63
+ #cv2.fillPoly(src_mask, [np.int32(src_tri)], 255)
64
+
65
+ # Not 元画像の三角形領域のみをマスクで抽出
66
+ src_triangle = src_img #cv2.bitwise_and(src_img, src_img, mask=src_mask)
67
+
68
+ # 変換行列を使って元画像の三角形領域を目標画像のサイズへ変換
69
+
70
+ transformed = cv2.warpAffine(src_triangle, M, (w_dst, h_dst))
71
+ #print(f"dst_img={dst_img.shape}")
72
+ #print(f"transformed={transformed.shape}")
73
+ # 変換後のマスクの生成
74
+ dst_mask = np.zeros((h_dst, w_dst), dtype=np.uint8)
75
+ cv2.fillPoly(dst_mask, [np.int32(dst_tri)], 255)
76
+ transformed = cv2.bitwise_and(transformed, transformed, mask=dst_mask)
77
+
78
+ # 目標画像のマスク領域をクリアするためにデストのインバートマスクを作成
79
+ dst_mask_inv = cv2.bitwise_not(dst_mask)
80
+
81
+ # 目標画像のマスク部分をクリア
82
+ dst_background = cv2.bitwise_and(dst_img, dst_img, mask=dst_mask_inv)
83
+
84
+ # 変換された元画像の三角形部分と目標画像の背景部分を合成
85
+ dst_img = cv2.add(dst_background, transformed)
86
+
87
+ return dst_img
88
+
89
+ # TODO move PIL
90
+ def process_create_webp(images,duration=100, loop=0,quality=85):
91
+ frames = []
92
+ for image_file in images:
93
+ frames.append(image_file)
94
+
95
+ output_buffer = io.BytesIO()
96
+ frames[0].save(output_buffer,
97
+ save_all=True,
98
+ append_images=frames[1:],
99
+ duration=duration,
100
+ loop=loop,
101
+ format='WebP',
102
+ quality=quality
103
+ )
104
+
105
+ return output_buffer.getvalue()
106
+ # TODO move numpy
107
+ def rotate_point_euler(point, angles,order="xyz"):
108
+ """
109
+ オイラー角を使って3Dポイントを回転させる関数
110
+
111
+ Args:
112
+ point: 回転させる3Dポイント (x, y, z)
113
+ angles: 各軸周りの回転角度 (rx, ry, rz) [ラジアン]
114
+
115
+ Returns:
116
+ 回転後の3Dポイント (x', y', z')
117
+ """
118
+
119
+ rx, ry, rz = angles
120
+ point = np.array(point)
121
+
122
+ # X軸周りの回転
123
+ Rx = np.array([
124
+ [1, 0, 0],
125
+ [0, np.cos(rx), -np.sin(rx)],
126
+ [0, np.sin(rx), np.cos(rx)]
127
+ ])
128
+
129
+ # Y軸周りの回転
130
+ Ry = np.array([
131
+ [np.cos(ry), 0, np.sin(ry)],
132
+ [0, 1, 0],
133
+ [-np.sin(ry), 0, np.cos(ry)]
134
+ ])
135
+
136
+ # Z軸周りの回転
137
+ Rz = np.array([
138
+ [np.cos(rz), -np.sin(rz), 0],
139
+ [np.sin(rz), np.cos(rz), 0],
140
+ [0, 0, 1]
141
+ ])
142
+
143
+ # 回転行列の合成 (Z軸 -> Y軸 -> X軸 の順で回転)
144
+ order = order.lower()
145
+ if order == "xyz":
146
+ R = Rx @ Ry @ Rz
147
+ elif order == "xzy":
148
+ R = Rx @ Rz @ Ry
149
+ elif order == "yxz":
150
+ R = Ry @ Rx @ Rz
151
+ elif order == "yzx":
152
+ R = Ry @ Rz @ Rx
153
+ elif order == "zxy":
154
+ R = Rz @ Rx @ Ry
155
+ else:
156
+ R = Rz @ Ry @ Rx
157
+
158
+
159
+
160
+ # 回転後のポイントを計算
161
+ rotated_point = R @ point
162
+
163
+ return rotated_point
164
+
165
+
166
+ def process_face_mesh_spinning(image,draw_type,center_scaleup,animation_direction,z_multiply=0.8,inner_eyes=False,inner_mouth=False):
167
+ animation = True
168
+ offset_x = 0
169
+ offset_y = 0
170
+ # use when center_scaleup is True,scale is 0.45(half-size:0.5-margin/ nosetip-to-top or nosetip-to-bottom
171
+ scale_up = 1.0
172
+
173
+ face_landmarker_result = None
174
+
175
+
176
+ if image == None:#app stop support none image,if mode still image,make problem
177
+ # Box for no Image Case
178
+ image_width = 512
179
+ image_height = 512
180
+ #image = create_color_image(image_width,image_height,(0,0,0))
181
+ points = [(-0.25,-0.25,0),(0.25,-0.25,0),
182
+ (0.25,0.25,0),(-0.25,0.25,0)
183
+ ]
184
+ normalized_center_point = [0.5,0.5]
185
+ else:
186
+ image_width = image.width
187
+ image_height = image.height
188
+
189
+
190
+
191
+
192
+ mp_image,face_landmarker_result = extract_landmark(image,"face_landmarker.task",0,0,True)
193
+
194
+ def rotate_image():
195
+ return None,face_landmarker_result,None
196
+
197
+ #return rotate_image()
198
+ # cordinate eyes
199
+ # cordinate all
200
+ landmark_points = [get_normalized_xyz(face_landmarker_result.face_landmarks,i) for i in range(0,468)]
201
+ # do centering
202
+ normalized_center_point = landmark_points[4]
203
+ normalized_top_point = landmark_points[10]
204
+ normalized_bottom_point = landmark_points[152]
205
+
206
+
207
+ offset_x = normalized_center_point[0]
208
+ offset_y = normalized_center_point[1]
209
+ offset_z = normalized_center_point[2]
210
+
211
+ #need aspect?
212
+ points = [[point[0]-offset_x,point[1]-offset_y,point[2]*z_multiply] for point in landmark_points]
213
+
214
+
215
+ # split xy-cordinate and z-depth
216
+ def split_points_xy_z(points,width,height,center_x,center_y):
217
+ xys = []
218
+ zs = []
219
+ for point in points:
220
+ xys.append(
221
+ [
222
+ point[0]*width*scale_up+center_x,
223
+ point[1]*height*scale_up+center_y
224
+ ]
225
+ )
226
+ zs.append(point[2])
227
+ return xys,zs
228
+
229
+ def draw_grid_in_center(draw,cx,cy,grid_size,grid_color,width=1,draw_horizontal=True,draw_vertical=True):
230
+ w = image.width
231
+ h = image.height
232
+ x_minus_divide = cx//grid_size
233
+ x_plus_divide = (w -cx)//grid_size
234
+ y_minus_divide = cy//grid_size
235
+ y_plus_divide = (h -cx)//grid_size
236
+ for i in range(-x_minus_divide,x_plus_divide+1):
237
+ draw.line([(cx+i*grid_size,0),(cx+i*grid_size,h)],fill=grid_color,width=width)
238
+ for i in range(-y_minus_divide,y_plus_divide+1):
239
+ draw.line([(0,cy+i*grid_size),(w,cy+i*grid_size)],fill=grid_color,width=width)
240
+
241
+ def draw_grid(image,cx=512,cy=512,first_color=(255,0,0)):
242
+ w = image.width
243
+ h = image.height
244
+ second_grid_size=100
245
+ second_color = (128,128,128)
246
+ draw = ImageDraw.Draw(image)
247
+ draw_grid_in_center(draw,cx,cy,20,(100,100,100))
248
+ draw_grid_in_center(draw,cx,cy,100,(192,192,192))
249
+
250
+
251
+
252
+ draw.line([(cx,0),(cx,image.height)],fill=first_color)
253
+ draw.line([(0,cy),(image.width,cy)],fill=first_color)
254
+
255
+ def create_triangle_image(points,width,height,center_x,center_y,line_color=(255,255,255),fill_color=None):
256
+
257
+ cordinates,angled_depth = split_points_xy_z(points,width,height,center_x,center_y)
258
+
259
+ img = create_color_image(width,height,(0,0,0))
260
+ draw = ImageDraw.Draw(img)
261
+ triangles = mp_triangles.get_triangles_copy(True,inner_eyes,inner_eyes,inner_mouth)
262
+
263
+ triangles.sort(key=lambda triangle: sum(angled_depth[index] for index in triangle) / len(triangle)
264
+ ,reverse=True)
265
+ for triangle in triangles:
266
+ triangle_cordinates = [cordinates[index] for index in triangle]
267
+ glibvision.pil_utils.image_draw_points(draw,triangle_cordinates,line_color,fill_color)
268
+
269
+
270
+ return img
271
+
272
+ def create_texture_image(image,origin_points,angled_points,width,height,center_x,center_y,line_color=(255,255,255),fill_color=None):
273
+ cv2_image = pil_to_bgr_image(image)
274
+ #print(f"shape={cv2_image.shape}")
275
+ #cv2.imwrite("tmp.jpg",cv2_image)
276
+ original_cordinates = []
277
+ cordinates,angled_depth = split_points_xy_z(angled_points,width,height,center_x,center_y)
278
+ # original point need offset
279
+ for point in origin_points:
280
+ original_cordinates.append(
281
+ [
282
+ (point[0]+offset_x)*width,
283
+ (point[1]+offset_y)*height
284
+ ]
285
+ )
286
+ if cv2_image.shape[2]==3:
287
+ cv2_bg_img = cv2_create_color_image(cv2_image,(0,0,0))
288
+ else:
289
+ cv2_bg_img = cv2_create_color_image(cv2_image,(0,0,0,0))
290
+
291
+ triangles = mp_triangles.get_triangles_copy(True,inner_eyes,inner_eyes,inner_mouth)
292
+
293
+ triangles.sort(key=lambda triangle: sum(angled_depth[index] for index in triangle) / len(triangle)
294
+ ,reverse=True)
295
+
296
+ for triangle in triangles:
297
+ triangle_cordinates = [cordinates[index] for index in triangle]
298
+ origin_triangle_cordinates = [original_cordinates[index] for index in triangle]
299
+
300
+ cv2_bg_img=apply_affine_transformation_to_triangle_add(origin_triangle_cordinates,triangle_cordinates,cv2_image,cv2_bg_img)
301
+
302
+ img= Image.fromarray(cv2.cvtColor(cv2_bg_img, cv2.COLOR_RGBA2BGRA))
303
+
304
+ return img
305
+
306
+ def create_point_image(points,width,height,center_x,center_y):
307
+ cordinates,_ = split_points_xy_z(points,width,height,center_x,center_y)
308
+ img = create_color_image(width,height,(0,0,0))
309
+ glibvision.pil_utils.draw_points(img,cordinates,None,None,3,(255,0,0),3)
310
+
311
+ return img
312
+
313
+ def angled_points(points,angles,order="xyz"):
314
+ angled_cordinates = []
315
+ for point in points:
316
+ rotated_np_point = rotate_point_euler(point,angles,order)
317
+ angled_cordinates.append(
318
+ [
319
+ rotated_np_point[0],
320
+ rotated_np_point[1],rotated_np_point[2]
321
+ ]
322
+ )
323
+ return angled_cordinates
324
+
325
+
326
+ frames = []
327
+
328
+
329
+ #frames.append(create_point_image(points))
330
+ frame_duration=100
331
+ start_angle=0
332
+ end_angle=360
333
+ step_angle=10
334
+
335
+ if draw_type == "Image":
336
+ start_angle=-90
337
+ end_angle=90
338
+ step_angle=30
339
+
340
+ if not animation:
341
+ start_angle=0
342
+ end_angle=0
343
+ step_angle=360
344
+ if image == None:
345
+ draw_type="Dot"
346
+
347
+
348
+ if center_scaleup and image!=None:
349
+ top_distance = calculate_distance(normalized_center_point,normalized_top_point)
350
+ bottom_distance = calculate_distance(normalized_center_point,normalized_bottom_point)
351
+ distance = top_distance if top_distance>bottom_distance else bottom_distance
352
+ #small_size = image_width if image_width<image_height else image_height
353
+
354
+ scale_up = 0.45 / distance #half - margin
355
+ if image_height>image_width:
356
+ scale_up *= image_width/image_height
357
+ #print(scale_up)
358
+ face_center_x = int(0.5* image_width)#half
359
+ face_center_y = int(0.5* image_height)
360
+ else:
361
+ scale_up = 1.0
362
+ face_center_x = int(normalized_center_point[0]* image_width)
363
+ face_center_y = int(normalized_center_point[1]* image_height)
364
+
365
+
366
+
367
+ rotated_points = None
368
+
369
+ if animation:
370
+ for i in range(start_angle,end_angle,step_angle):
371
+ if animation_direction == "X":
372
+ angles = [math.radians(i),0,0]
373
+ elif animation_direction == "Y":
374
+ angles = [0,math.radians(i),0]
375
+ else:
376
+ angles = [0,0,math.radians(i)]
377
+
378
+ if draw_type == "Dot":
379
+ frames.append(create_point_image(angled_points(points,angles),image_width,image_height,face_center_x,face_center_y))
380
+ elif draw_type == "Line":
381
+ frames.append(create_triangle_image(angled_points(points,angles),image_width,image_height,face_center_x,face_center_y))
382
+ elif draw_type == "Line+Fill":
383
+ frames.append(create_triangle_image(angled_points(points,angles),image_width,image_height,face_center_x,face_center_y,(128,128,128),(200,200,200)))
384
+ elif draw_type == "Image":
385
+ frame_duration=500
386
+ frames.append(create_texture_image(image,points,angled_points(points,angles),image_width,image_height,face_center_x,face_center_y))
387
+ webp = process_create_webp(frames,frame_duration)
388
+ path = save_buffer(webp)
389
+
390
+
391
+
392
+
393
+ return path,face_landmarker_result,rotated_points
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,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ #RGB Image use np.array(image, dtype=np.uint8)
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
147
+
148
+ def copy_image(img1: np.ndarray, img2: np.ndarray, x: int, y: int) -> None:
149
+ # チャネル数と次元数のチェック
150
+ if img1.ndim != 3 or img2.ndim != 3:
151
+ raise ValueError("Both img1 and img2 must be 3-dimensional arrays.")
152
+ elif img1.shape[2] != img2.shape[2]:
153
+ raise ValueError(f"img1 and img2 must have the same number of channels. img1 has {img1.shape[2]} channels, but img2 has {img2.shape[1]} channels.")
154
+
155
+ # Type check
156
+ if not isinstance(img1, np.ndarray) or not isinstance(img2, np.ndarray):
157
+ raise TypeError("img1 and img2 must be NumPy arrays.")
158
+
159
+ if x>=0:
160
+ offset_x=0
161
+ w = min(img1.shape[1]-x,img2.shape[1])
162
+ else:
163
+ w = min(img1.shape[1],img2.shape[1]+x)
164
+ offset_x=int(-x)
165
+ x = 0
166
+
167
+ if y>=0:
168
+ h = min(img1.shape[0]-y,img2.shape[0])
169
+ offset_y=0
170
+ else:
171
+ h = min(img1.shape[0]-y,img2.shape[0]+y)
172
+ offset_y=int(-y)
173
+ y = 0
174
+ x=int(x)
175
+ y=int(y)
176
+ h=int(h)
177
+ w=int(w)
178
+
179
+
180
+ print(f"img1 {img1.shape} img2{img2.shape} x={x} y={y} w={w} h={h}")
181
+ # Paste the overlapping part
182
+ img1[y:y+h, x:x+w] = img2[offset_y:h+offset_y, offset_x:w+offset_x]
glibvision/draw_utils.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DrawUtils
2
+ # not PIL,CV2,Numpy drawing method
3
+ import math
4
+ # 2024-11-29 add calculate_distance
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
40
+
41
+ def calculate_distance(xy, xy2):
42
+ return math.sqrt((xy2[0] - xy[0])**2 + (xy2[1] - xy[1])**2)
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/mediapipe_utils.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from .numpy_utils import rotate_point_euler
3
+ # mediapipe utils work with other glibvisions
4
+
5
+ def rotate_points(points,angles,order="xyz",is_degree=False):
6
+ if is_degree:
7
+ angles = [math.radians(float(value)) for value in angles]
8
+ rotated_cordinates = []
9
+ for point in points:
10
+ rotated_np_point = rotate_point_euler(point,angles,order)
11
+ rotated_cordinates.append(
12
+ [
13
+ rotated_np_point[0],
14
+ rotated_np_point[1],rotated_np_point[2]
15
+ ]
16
+ )
17
+ return rotated_cordinates
glibvision/numpy_utils.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ #2024-12-03 rotate_point_euler
4
+ #2024-12-04 load_data
5
+ def load_data(filepath):
6
+ """
7
+ カンマ区切りのテキストファイルからデータをNumPy配列に読み込みます。
8
+
9
+ Args:
10
+ filepath: データファイルのパス
11
+
12
+ Returns:
13
+ NumPy配列: 読み込まれたデータ。エラーが発生した場合はNone。
14
+ """
15
+ try:
16
+ data = np.loadtxt(filepath, delimiter=",")
17
+ return data
18
+ except (FileNotFoundError, ValueError) as e:
19
+ print(f"Error loading data: {e}")
20
+ return None
21
+ def rotate_point_euler(point, angles,order="xyz",is_degree=False):
22
+
23
+ """
24
+ オイラー角を使って3Dポイントを回転させる関数
25
+
26
+ Args:
27
+ point: 回転させる3Dポイント (x, y, z)
28
+ angles: 各軸周りの回転角度 (rx, ry, rz) [ラジアン]
29
+
30
+ Returns:
31
+ 回転後の3Dポイント (x', y', z')
32
+ """
33
+ if is_degree:
34
+ angles = [np.deg2rad(value) for value in angles]
35
+ rx, ry, rz = angles
36
+ point = np.array(point)
37
+
38
+ # X軸周りの回転
39
+ Rx = np.array([
40
+ [1, 0, 0],
41
+ [0, np.cos(rx), -np.sin(rx)],
42
+ [0, np.sin(rx), np.cos(rx)]
43
+ ])
44
+
45
+ # Y軸周りの回転
46
+ Ry = np.array([
47
+ [np.cos(ry), 0, np.sin(ry)],
48
+ [0, 1, 0],
49
+ [-np.sin(ry), 0, np.cos(ry)]
50
+ ])
51
+
52
+ # Z軸周りの回転
53
+ Rz = np.array([
54
+ [np.cos(rz), -np.sin(rz), 0],
55
+ [np.sin(rz), np.cos(rz), 0],
56
+ [0, 0, 1]
57
+ ])
58
+
59
+ # 回転行列の合成 (Z軸 -> Y軸 -> X軸 の順で回転)
60
+ order = order.lower()
61
+ if order == "xyz":
62
+ R = Rx @ Ry @ Rz
63
+ elif order == "xzy":
64
+ R = Rx @ Rz @ Ry
65
+ elif order == "yxz":
66
+ R = Ry @ Rx @ Rz
67
+ elif order == "yzx":
68
+ R = Ry @ Rz @ Rx
69
+ elif order == "zxy":
70
+ R = Rz @ Rx @ Ry
71
+ else:#zyx
72
+ R = Rz @ Ry @ Rx
73
+
74
+
75
+
76
+ # 回転後のポイントを計算
77
+ rotated_point = R @ point
78
+
79
+ return rotated_point
80
+
81
+ def apply_binary_mask_to_color(base_image,color,mask):
82
+ """
83
+ 二値マスクを使用して、画像の一部を別の画像にコピーする。
84
+
85
+ Args:
86
+ base_image (np.ndarray): コピー先の画像。
87
+ paste_image (np.ndarray): コピー元の画像。
88
+ mask (np.ndarray): 二値マスク画像。
89
+
90
+ Returns:
91
+ np.ndarray: マスクを適用した画像。
92
+
93
+ """
94
+ # TODO check all shape
95
+ #print_numpy(base_image)
96
+ #print_numpy(paste_image)
97
+ #print_numpy(mask)
98
+ if mask.ndim == 2:
99
+ condition = mask == 255
100
+ else:
101
+ condition = mask[:,:,0] == 255
102
+
103
+ base_image[condition] = color
104
+ return base_image
105
+
106
+ def apply_binary_mask_to_image(base_image,paste_image,mask):
107
+ """
108
+ 二値マスクを使用して、画像の一部を別の画像にコピーする。
109
+
110
+ Args:
111
+ base_image (np.ndarray): コピー先の画像。
112
+ paste_image (np.ndarray): コピー元の画像。
113
+ mask (np.ndarray): 二値マスク画像。
114
+
115
+ Returns:
116
+ np.ndarray: マスクを適用した画像。
117
+
118
+ """
119
+ # TODO check all shape
120
+ #print_numpy(base_image)
121
+ #print_numpy(paste_image)
122
+ #print_numpy(mask)
123
+ if mask.ndim == 2:
124
+ condition = mask == 255
125
+ else:
126
+ condition = mask[:,:,0] == 255
127
+
128
+ base_image[condition] = paste_image[condition]
129
+ return base_image
130
+
131
+ def pil_to_numpy(image):
132
+ return np.array(image, dtype=np.uint8)
133
+
134
+ def extruce_points(points,index,ratio=1.5):
135
+ """
136
+ indexのポイントをratio倍だけ、点群の中心から、外側に膨らます。
137
+ """
138
+ center_point = np.mean(points, axis=0)
139
+ if index < 0 or index > len(points):
140
+ raise ValueError(f"index must be range(0,{len(points)} but value = {index})")
141
+ point1 =points[index]
142
+ print(f"center = {center_point}")
143
+ vec_to_center = point1 - center_point
144
+ return vec_to_center*ratio + center_point
145
+
146
+
147
+ def bulge_polygon(points, bulge_factor=0.1,isClosed=True):
148
+ """
149
+ ポリゴンの辺の中間に点を追加し、外側に膨らませる
150
+ ndarrayを返すので注意
151
+ """
152
+ # 入力 points を NumPy 配列に変換
153
+ points = np.array(points)
154
+
155
+ # ポリゴン全体の重心を求める
156
+ center_point = np.mean(points, axis=0)
157
+ #print(f"center = {center_point}")
158
+ new_points = []
159
+ num_points = len(points)
160
+ for i in range(num_points):
161
+ if i == num_points -1 and not isClosed:
162
+ break
163
+ p1 = points[i]
164
+ #print(f"p{i} = {p1}")
165
+ # 重心から頂点へのベクトル
166
+ #vec_to_center = p1 - center_point
167
+
168
+ # 辺のベクトルを求める
169
+ mid_diff = points[(i + 1) % num_points] - p1
170
+ mid = p1+(mid_diff/2)
171
+
172
+ #print(f"mid = {mid}")
173
+ out_vec = mid - center_point
174
+
175
+ # 重心からのベクトルに bulge_vec を加算
176
+ new_point = mid + out_vec * bulge_factor
177
+
178
+ new_points.append(p1)
179
+ new_points.append(new_point.astype(np.int32))
180
+
181
+ return np.array(new_points)
182
+
183
+
184
+ # image.shape rgb are (1024,1024,3) use 1024,1024 as 2-dimensional
185
+ def create_2d_image(shape):
186
+ grayscale_image = np.zeros(shape[:2], dtype=np.uint8)
187
+ return grayscale_image
glibvision/pil_utils.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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,plot_color=None,plot_size=3):
20
+ draw = ImageDraw.Draw(image)
21
+ image_draw_points(draw,points,outline,fill,width,plot_color,plot_size)
22
+ return image
23
+
24
+ def image_draw_points(draw,points,outline=None,fill=None,width=1,plot_color=None,plot_size=3):
25
+ int_points = [(int(x), int(y)) for x, y in points]
26
+ if outline is not None or fill is not None:
27
+ draw.polygon(int_points, outline=outline,fill=fill,width=width)
28
+ if plot_color!=None:
29
+ print(int_points,plot_size,plot_color)
30
+ for point in int_points:
31
+ draw.circle(point,plot_size,fill=plot_color)
32
+
33
+ def draw_box(image,box,outline=None,fill=None):
34
+ points = to_int_points(box_to_point(box))
35
+ return draw_points(image,points,outline,fill)
36
+
37
+ def from_numpy(numpy_array):
38
+ return Image.fromarray(numpy_array)
gradio_utils.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import os
4
+ import time
5
+ import io
6
+ import hashlib
7
+
8
+ #2024-11-28 support bytes get_buffer_id,save_buffer
9
+ def clear_old_files(dir="files",passed_time=60*60):
10
+ try:
11
+ files = os.listdir(dir)
12
+ current_time = time.time()
13
+ for file in files:
14
+ file_path = os.path.join(dir,file)
15
+
16
+ ctime = os.stat(file_path).st_ctime
17
+ diff = current_time - ctime
18
+ #print(f"ctime={ctime},current_time={current_time},passed_time={passed_time},diff={diff}")
19
+ if diff > passed_time:
20
+ os.remove(file_path)
21
+ except:
22
+ print("maybe still gallery using error")
23
+
24
+ def get_buffer_id(buffer,length=32):
25
+ if isinstance(buffer,bytes):
26
+ value = buffer
27
+ else:
28
+ value=buffer.getvalue()
29
+ hash_object = hashlib.sha256(value)
30
+ hex_dig = hash_object.hexdigest()
31
+ unique_id = hex_dig[:length]
32
+ return unique_id
33
+
34
+ def get_image_id(image):
35
+ buffer = io.BytesIO()
36
+ image.save(buffer, format='PNG')
37
+ return get_buffer_id(buffer)
38
+
39
+ def save_image(image,extension="jpg",dir_name="files"):
40
+ id = get_image_id(image)
41
+ os.makedirs(dir_name,exist_ok=True)
42
+ file_path = f"{dir_name}/{id}.{extension}"
43
+
44
+ image.save(file_path)
45
+ return file_path
46
+
47
+ def save_buffer(buffer,extension="webp",dir_name="files"):
48
+ id = get_buffer_id(buffer)
49
+ os.makedirs(dir_name,exist_ok=True)
50
+ file_path = f"{dir_name}/{id}.{extension}"
51
+
52
+ with open(file_path,"wb") as f:
53
+ if isinstance(buffer,bytes):
54
+ f.write(buffer)
55
+ else:
56
+ f.write(buffer.getvalue())
57
+ return file_path
58
+
59
+ def write_file(file_path,text):
60
+ with open(file_path, 'w', encoding='utf-8') as f:
61
+ f.write(text)
62
+
63
+ def read_file(file_path):
64
+ """read the text of target file
65
+ """
66
+ with open(file_path, 'r', encoding='utf-8') as f:
67
+ content = f.read()
68
+ return content
mp_estimate.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #2024-12-04 add forehead_chin_points_pair,estimate_rotatios
2
+ #formart is first,second,middle
3
+ #2024-12-05 deg to rad
4
+ #2024-12-06 get_feature_ratios_cordinate
5
+ #2024-12-08 create_detail_labels
6
+ horizontal_points_pair = [
7
+ [
8
+ "inner-eye",133,362,6
9
+ ],
10
+ [
11
+ "outer-eye",33,263,168
12
+ ],
13
+ [
14
+ "mouth",61,291,13
15
+ ],
16
+ [
17
+ "eyeblow",105,334,9
18
+ ],[
19
+ "nose",98,327,2
20
+ ],[
21
+ "contour",143,372,6
22
+ ],
23
+ [
24
+ "chin",32,262,200
25
+ ], [
26
+ "cheek",123,352,5
27
+ ], [
28
+ "cheek2",192,416,0
29
+ ], [
30
+ "nose1",129,358,1
31
+ ], [
32
+ "nose2",47,277,195
33
+ ], [
34
+ "cheek3",206,426,2
35
+ ], [
36
+ "cheek4",101,330,5
37
+ ], [
38
+ "cheek5",153,380,6
39
+ ]
40
+ ]
41
+ def angle_between_points_and_x_axis(A, B):
42
+ """
43
+ 2点A, Bを結ぶ線分とx軸の正方向との角度を計算する
44
+
45
+ Args:
46
+ A: A点の座標 (x, y) のタプルまたはNumPy配列
47
+ B: B点の座標 (x, y) のタプルまたはNumPy配列
48
+
49
+ Returns:
50
+ 角度(ラジアン)
51
+ """
52
+ x = B[0] - A[0]
53
+ y = B[1] - A[1]
54
+ return np.arctan2(y, x)
55
+
56
+ vertical_points_pair=[
57
+ ["forehead-chin",8,1,199]
58
+ ]
59
+ #formart is first,second,third
60
+ feature_ratios_indices=[
61
+ ["forehead",67,69,66],
62
+ ["forehead",10,151,9],
63
+ ["forehead",297,299,296],
64
+ #["forehead-chin",8,1,199],
65
+ #["middle-chin",168,199,2],
66
+ ["middle",168,195,2],
67
+ ["right",153,101,206],
68
+ ["right2",133,47,129],
69
+ ["left",380,330,426],
70
+ ["left2",362,277,358],
71
+ ["right-contour",143,123,192],
72
+ ["left-contour",372,352,416],
73
+ ["nose",4,1,2],
74
+ ]
75
+
76
+ feature_angles_indices =[
77
+ ["forehead1",9,6],
78
+ ["forehead2",69,299],
79
+ ["eyes1",133,362],
80
+ ["eyes2",133,33],
81
+ ["eyes3",362,263],
82
+ ["nose1",6,2],
83
+ ["nose1",98,327],
84
+ ["nose1",2,1],
85
+ ["nose1",1,6],
86
+ ["lip",61,291],
87
+ ["lip",0,17],
88
+ ["jaw",152,199],
89
+ ["jaw",194,418],
90
+ ["cheek",118,214],
91
+ ["cheek",347,434],
92
+ ["contour",389,397],
93
+ ["contour",127,172],
94
+ ]
95
+ def get_feature_angles_cordinate(face_landmarks,angles=feature_angles_indices):
96
+ points = [get_normalized_cordinate(face_landmarks,i) for i in range(468)]
97
+ return get_feature_angles_cordinate_points(points,angles)
98
+
99
+ def get_feature_angles_cordinate_points(points,angles=feature_angles_indices):
100
+ cordinates=[]
101
+ result_angles = []
102
+ for indices in angles:
103
+ points_cordinate = get_points_by_indices(points,indices[1:])#first one is label
104
+ angle_rad =angle_between_points_and_x_axis(points_cordinate[0][:2],points_cordinate[1][:2])
105
+ result_angles.append(angle_rad)
106
+ cordinates.append(points_cordinate)
107
+ return cordinates,result_angles
108
+
109
+ def get_feature_ratios_cordinate(face_landmarks,ratios=feature_ratios_indices):
110
+ points = [get_normalized_cordinate(face_landmarks,i) for i in range(468)]
111
+ return get_feature_angles_cordinate_points(points,ratios)
112
+
113
+ def ratios_cordinates(cordinates):
114
+
115
+ distance_a = calculate_distance(cordinates[0],cordinates[1])
116
+ distance_b = calculate_distance(cordinates[1],cordinates[2])
117
+ if distance_a == 0 or distance_b == 0:
118
+ return 0
119
+ else:
120
+ return distance_a/distance_b
121
+
122
+ def get_feature_ratios_cordinate_points(points,ratios=feature_ratios_indices):
123
+ cordinates=[]
124
+ result_ratios = []
125
+ for indices in ratios:
126
+ points_cordinate = get_points_by_indices(points,indices[1:])#first one is label
127
+ result_ratios.append(ratios_cordinates(points_cordinate))
128
+ cordinates.append(points_cordinate)
129
+ return cordinates,result_ratios
130
+
131
+
132
+ #vertical-format
133
+ forehead_chin_points_pair=[
134
+ [
135
+ "forehead-chin",8,1,199
136
+ ]
137
+ ]
138
+ horizontal_contour_points_pair=[
139
+ [
140
+ "contour",143,6,372
141
+ ]
142
+ ]
143
+ import math
144
+ def calculate_distance(xy, xy2):
145
+ return math.sqrt((xy2[0] - xy[0])**2 + (xy2[1] - xy[1])**2)
146
+
147
+ def create_detail_labels(values,radian=False,pair_data=horizontal_points_pair):
148
+ assert len(values) == len(pair_data)
149
+ lines = []
150
+ for i,value in enumerate(values):
151
+ if radian:
152
+ value=math.degrees(value)
153
+ lines.append(f"{pair_data[i][0]} = {value:.2f}")
154
+ return "\n".join(lines)
155
+
156
+ import numpy as np
157
+ from mp_utils import get_normalized_cordinate
158
+ def estimate_horizontal(face_landmarks,pair_data = horizontal_points_pair):
159
+ points = [get_normalized_cordinate(face_landmarks,i) for i in range(468)]
160
+ return estimate_horizontal_points(points,pair_data)
161
+
162
+ def get_points_by_indices(face_landmark_points,indices):
163
+ points = [face_landmark_points[index] for index in indices]
164
+ return points
165
+
166
+ def normalized_to_pixel(cordinates,width,height):
167
+ pixel_point = [[pt[0]*width,pt[1]*height] for pt in cordinates]
168
+ return pixel_point
169
+
170
+ def estimate_horizontal_points(face_landmark_points,pair_data = horizontal_points_pair):
171
+ z_angles=[]
172
+ y_ratios = []
173
+ cordinates = []
174
+ for compare_point in pair_data:
175
+ points_cordinate = get_points_by_indices(face_landmark_points,compare_point[1:])#first one is label
176
+ cordinates.append(points_cordinate)
177
+ angle_rad =angle_between_points_and_x_axis(points_cordinate[0][:2],points_cordinate[1][:2])
178
+ #angle_deg = np.degrees(angle_rad)
179
+ z_angles.append(angle_rad)
180
+ right_distance = calculate_distance(points_cordinate[0],points_cordinate[2])
181
+ left_distance = calculate_distance(points_cordinate[1],points_cordinate[2])
182
+ y_ratios.append(left_distance/(right_distance+left_distance))
183
+ return z_angles,y_ratios,cordinates,pair_data
184
+
185
+ def estimate_vertical(face_landmarks,pair_data = vertical_points_pair):
186
+ points = [get_normalized_cordinate(face_landmarks,i) for i in range(468)]
187
+ return estimate_vertical_points(points,pair_data)
188
+
189
+
190
+ def estimate_rotations_v2(face_landmarker_result):
191
+ points = get_normalized_landmarks(face_landmarker_result.face_landmarks,True)
192
+ values1_text=estimate_rotations_point(points)
193
+ result3,ratios = get_feature_ratios_cordinate_points(points)
194
+ key_cordinates,angles = get_feature_angles_cordinate_points(points)
195
+ angles_str=[str(angle) for angle in angles]
196
+ ratios_str=[str(ratio) for ratio in ratios]
197
+ return f"{values1_text},{','.join(angles_str)},{','.join(ratios_str)}"
198
+
199
+ from mp_utils import get_normalized_landmarks
200
+ def estimate_rotations(face_landmarker_result):
201
+ points = get_normalized_landmarks(face_landmarker_result.face_landmarks,True)
202
+ return estimate_rotations_point(points)
203
+ def estimate_rotations_point(points):
204
+ z_angles,y_ratios,h_cordinates,_ =estimate_horizontal_points(points)
205
+ z_angle = np.mean(z_angles)
206
+ y_ratio = np.mean(y_ratios)
207
+ _,x_ratios,h_cordinates,_ =estimate_vertical_points(points)
208
+ x_ratio = np.mean(x_ratios)
209
+
210
+ x_angle,_,_,_ =estimate_vertical_points(points,forehead_chin_points_pair)
211
+ x_angle=np.mean(x_angle)
212
+
213
+ length_ratio = estimate_ratio(points)
214
+
215
+ result = f"{x_ratio:.6f},{y_ratio:.6f},{z_angle:.6f},{x_angle:.6f},{length_ratio:.6f}"
216
+ return result
217
+
218
+ def estimate_ratio(face_landmark_points,a_line=forehead_chin_points_pair,b_line=horizontal_contour_points_pair):
219
+ points_cordinate_a = get_points_by_indices(face_landmark_points,a_line[0][1:])#for campatible
220
+ points_cordinate_b = get_points_by_indices(face_landmark_points,b_line[0][1:])
221
+
222
+ distance_a = calculate_distance(points_cordinate_a[0],points_cordinate_a[2])
223
+ distance_b = calculate_distance(points_cordinate_b[0],points_cordinate_b[2])
224
+ if distance_a == 0 or distance_b == 0:
225
+ return 0
226
+ else:
227
+ return distance_a/distance_b
228
+
229
+ def estimate_vertical_points(face_landmarks,pair_data = vertical_points_pair):
230
+ angles = []
231
+ ratios = []
232
+ cordinates = []
233
+ for compare_point in pair_data:
234
+ points_cordinate = get_points_by_indices(face_landmarks,compare_point[1:])#first one is label
235
+ cordinates.append(points_cordinate)
236
+ angle_rad =angle_between_points_and_x_axis(points_cordinate[0][:2],points_cordinate[2][:2])
237
+ #angle_deg = np.degrees(angle_rad)
238
+ angles.append(angle_rad)
239
+ up_distance = calculate_distance(points_cordinate[0],points_cordinate[1])
240
+ down_distance = calculate_distance(points_cordinate[1],points_cordinate[2])
241
+ ratios.append(down_distance/(down_distance+up_distance))
242
+ return angles,ratios,cordinates,pair_data
243
+ def mean_std_label(values,radian=False):
244
+ mean_value = np.mean(values)
245
+ std_value = np.std(values)
246
+ if radian:
247
+ mean_value = math.degrees(mean_value)
248
+ std_value = math.degrees(std_value)
249
+ value_text = f"mean:{mean_value:.3f} std:{std_value:.3f}"
250
+ return value_text
mp_triangles.py ADDED
@@ -0,0 +1,1016 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ #02
9
+
10
+ INNER_MOUTH =[
11
+ [78,191,95],
12
+ [191,95,80],
13
+ [80,88,95],
14
+ [80,81,88],
15
+ [88,81,178],
16
+ [81,178,82],
17
+ [178,82,87],
18
+ [82,87,13],
19
+ [87,14,13],
20
+ [13,312,317],
21
+ [14,317,13],
22
+ [312,402,311],
23
+ [317,402,312],
24
+ [311,402,318],
25
+ [311,318,310],
26
+ [310,324,318],
27
+ [310,324,415],
28
+ [308,415,324]
29
+ ]
30
+
31
+ INNER_LEFT_EYES=[
32
+ [33,246,7],
33
+
34
+ [246,7,163],
35
+ [246,163,161],
36
+ [161,163,144],
37
+ [161,144,160],
38
+ [160,144,145],
39
+ [160,145,159],
40
+ [159,145,153],
41
+ [159,153,158],
42
+ [158,153,154],
43
+ [158,154,157],
44
+ [157,154,155],
45
+ [157,155,173],
46
+
47
+ [173,155,133]
48
+ ]
49
+ INNER_RIGHT_EYES=[
50
+ [362,398,382],
51
+
52
+ [382,398,384],
53
+ [382,384,381],
54
+ [381,384,385],
55
+ [381,385,380],
56
+ [380,385,386],
57
+ [380,386,374],
58
+ [374,386,387],
59
+ [374,387,373],
60
+ [373,387,388],
61
+ [373,388,390],
62
+ [390,388,466],
63
+ [390,249,466],
64
+
65
+ [466,263,249]
66
+ ]
67
+
68
+ RIGHT_CONTOURS = [
69
+ [152, 175, 199], # LINE_RIGHT_CONTOUR_0
70
+ [148, 171, 208], # LINE_RIGHT_CONTOUR_1
71
+ [176, 140, 32], # LINE_RIGHT_CONTOUR_2
72
+ [149, 170, 211], # LINE_RIGHT_CONTOUR_3
73
+ [150, 169, 210], # LINE_RIGHT_CONTOUR_4
74
+ [136, 135, 214], # LINE_RIGHT_CONTOUR_5
75
+ [172, 138, 192], # LINE_RIGHT_CONTOUR_6
76
+ [58, 215, 213], # LINE_RIGHT_CONTOUR_7
77
+ [132, 177, 147], # LINE_RIGHT_CONTOUR_8
78
+ [93, 137, 123], # LINE_RIGHT_CONTOUR_9
79
+ [234, 227, 116], # LINE_RIGHT_CONTOUR_10
80
+ [127, 34, 143], # LINE_RIGHT_CONTOUR_11
81
+ [162, 139, 156], # LINE_RIGHT_CONTOUR_12
82
+ [21, 71, 70], # LINE_RIGHT_CONTOUR_13
83
+ [54, 68, 63], # LINE_RIGHT_CONTOUR_14
84
+ [103, 104, 105], # LINE_RIGHT_CONTOUR_15
85
+ [67, 69, 66], # LINE_RIGHT_CONTOUR_16
86
+ [109, 108, 107], # LINE_RIGHT_CONTOUR_17
87
+ [10, 151, 9] # LINE_RIGHT_CONTOUR_18
88
+ ]
89
+
90
+ LEFT_CONTOURS = [
91
+ [377, 396, 428], # LINE_LEFT_CONTOUR_1
92
+ [400, 369, 262], # LINE_LEFT_CONTOUR_2
93
+ [378, 395, 431], # LINE_LEFT_CONTOUR_3
94
+ [379, 394, 430], # LINE_LEFT_CONTOUR_4
95
+ [365, 364, 434], # LINE_LEFT_CONTOUR_5
96
+ [397, 367, 416], # LINE_LEFT_CONTOUR_6
97
+ [288, 435, 433], # LINE_LEFT_CONTOUR_7
98
+ [361, 401, 376], # LINE_LEFT_CONTOUR_8
99
+ [323, 366, 352], # LINE_LEFT_CONTOUR_9
100
+ [454, 447, 345], # LINE_LEFT_CONTOUR_10
101
+ [356, 264, 372], # LINE_LEFT_CONTOUR_11
102
+ [389, 368, 383], # LINE_LEFT_CONTOUR_12
103
+ [251, 301, 300], # LINE_LEFT_CONTOUR_13
104
+ [284, 298, 293], # LINE_LEFT_CONTOUR_14
105
+ [332, 333, 334], # LINE_LEFT_CONTOUR_15
106
+ [297, 299, 296], # LINE_LEFT_CONTOUR_16
107
+ [338, 337, 336] # LINE_LEFT_CONTOUR_17
108
+ ]
109
+ def get_triangles_copy(base=True,left_eye=False,right_eye=False,mouth=False):
110
+ triangles = []
111
+ if base:
112
+ triangles += mesh_triangle_indices
113
+ if left_eye:
114
+ triangles += INNER_LEFT_EYES
115
+ if right_eye:
116
+ triangles += INNER_RIGHT_EYES
117
+ if mouth:
118
+ triangles += INNER_MOUTH
119
+ return triangles
120
+
121
+
122
+ def contour_to_triangles(is_right=True,down_up=True):
123
+ triangles = []
124
+ if is_right:
125
+ if down_up:
126
+ contours = RIGHT_CONTOURS
127
+ else:
128
+ contours = RIGHT_CONTOURS[::-1]
129
+ else:
130
+ if down_up:
131
+ contours = LEFT_CONTOURS
132
+ else:
133
+ contours = LEFT_CONTOURS[::-1]
134
+
135
+ sorted_mesh_triangle_indices = []
136
+ for triangle in mesh_triangle_indices:
137
+ sorted_mesh_triangle_indices.append(sorted(triangle))
138
+
139
+ # no way to know how triangle made even in future.
140
+ for i in range(len(contours)-1):
141
+ first_line = contours[i]
142
+ second_line = contours[i+1]
143
+ #outer
144
+ triangles.append([first_line[0],first_line[1],second_line[0]])
145
+ triangles.append([second_line[0],second_line[1],first_line[1]])
146
+ triangles.append([first_line[0],first_line[1],second_line[1]])
147
+ triangles.append([second_line[0],second_line[1],first_line[0]])
148
+
149
+ #inner
150
+ triangles.append([first_line[1],first_line[2],second_line[1]])
151
+ triangles.append([second_line[1],second_line[2],first_line[2]])
152
+ triangles.append([first_line[1],first_line[2],second_line[2]])
153
+ triangles.append([second_line[1],second_line[2],first_line[1]])
154
+
155
+ exist_triangles = []
156
+ for triangle in triangles:
157
+ sorted_triangle = sorted(triangle)
158
+ if sorted_triangle in sorted_mesh_triangle_indices:
159
+ exist_triangles.append(triangle)
160
+ return exist_triangles
161
+
162
+
163
+ mesh_triangle_indices=[
164
+ [127, 34, 139],
165
+ [ 11, 0, 37],
166
+ [232, 231, 120],
167
+ [ 72, 37, 39],
168
+ [128, 121, 47],
169
+ [232, 121, 128],
170
+ [104, 69, 67],
171
+ [175, 171, 148],
172
+ [118, 50, 101],
173
+ [ 73, 39, 40],
174
+ [ 9, 151, 108],
175
+ [ 48, 115, 131],
176
+ [194, 204, 211],
177
+ [ 74, 40, 185],
178
+ [ 80, 42, 183],
179
+ [ 40, 92, 186],
180
+ [230, 229, 118],
181
+ [202, 212, 214],
182
+ [ 83, 18, 17],
183
+ [ 76, 61, 146],
184
+ [160, 29, 30],
185
+ [ 56, 157, 173],
186
+ [106, 204, 194],
187
+ [135, 214, 192],
188
+ [203, 165, 98],
189
+ [ 21, 71, 68],
190
+ [ 51, 45, 4],
191
+ [144, 24, 23],
192
+ [ 77, 146, 91],
193
+ [205, 50, 187],
194
+ [201, 200, 18],
195
+ [ 91, 106, 182],
196
+ [ 90, 91, 181],
197
+ [ 85, 84, 17],
198
+ [206, 203, 36],
199
+ [148, 171, 140],
200
+ [ 92, 40, 39],
201
+ [193, 189, 244],
202
+ [159, 158, 28],
203
+ [247, 246, 161],
204
+ [236, 3, 196],
205
+ [ 54, 68, 104],
206
+ [193, 168, 8],
207
+ [117, 228, 31],
208
+ [189, 193, 55],
209
+ [ 98, 97, 99],
210
+ [126, 47, 100],
211
+ [166, 79, 218],
212
+ [155, 154, 26],
213
+ [209, 49, 131],
214
+ [135, 136, 150],
215
+ [ 47, 126, 217],
216
+ [223, 52, 53],
217
+ [ 45, 51, 134],
218
+ [211, 170, 140],
219
+ [ 67, 69, 108],
220
+ [ 43, 106, 91],
221
+ [230, 119, 120],
222
+ [226, 130, 247],
223
+ [ 63, 53, 52],
224
+ [238, 20, 242],
225
+ [ 46, 70, 156],
226
+ [ 78, 62, 96],
227
+ [ 46, 53, 63],
228
+ [143, 34, 227],
229
+ [123, 117, 111],
230
+ [ 44, 125, 19],
231
+ [236, 134, 51],
232
+ [216, 206, 205],
233
+ [154, 153, 22],
234
+ [ 39, 37, 167],
235
+ [200, 201, 208],
236
+ [ 36, 142, 100],
237
+ [ 57, 212, 202],
238
+ [ 20, 60, 99],
239
+ [ 28, 158, 157],
240
+ [ 35, 226, 113],
241
+ [160, 159, 27],
242
+ [204, 202, 210],
243
+ [113, 225, 46],
244
+ [ 43, 202, 204],
245
+ [ 62, 76, 77],
246
+ [137, 123, 116],
247
+ [ 41, 38, 72],
248
+ [203, 129, 142],
249
+ [ 64, 98, 240],
250
+ [ 49, 102, 64],
251
+ [ 41, 73, 74],
252
+ [212, 216, 207],
253
+ [ 42, 74, 184],
254
+ [169, 170, 211],
255
+ [170, 149, 176],
256
+ [105, 66, 69],
257
+ [122, 6, 168],
258
+ [123, 147, 187],
259
+ [ 96, 77, 90],
260
+ [ 65, 55, 107],
261
+ [ 89, 90, 180],
262
+ [101, 100, 120],
263
+ [ 63, 105, 104],
264
+ [ 93, 137, 227],
265
+ [ 15, 86, 85],
266
+ [129, 102, 49],
267
+ [ 14, 87, 86],
268
+ [ 55, 8, 9],
269
+ [100, 47, 121],
270
+ [145, 23, 22],
271
+ [ 88, 89, 179],
272
+ [ 6, 122, 196],
273
+ [ 88, 95, 96],
274
+ [138, 172, 136],
275
+ [215, 58, 172],
276
+ [115, 48, 219],
277
+ [ 42, 80, 81],
278
+ [195, 3, 51],
279
+ [ 43, 146, 61],
280
+ [171, 175, 199],
281
+ [ 81, 82, 38],
282
+ [ 53, 46, 225],
283
+ [144, 163, 110],
284
+ [ 52, 65, 66],
285
+ [229, 228, 117],
286
+ [ 34, 127, 234],
287
+ [107, 108, 69],
288
+ [109, 108, 151],
289
+ [ 48, 64, 235],
290
+ [ 62, 78, 191],
291
+ [129, 209, 126],
292
+ [111, 35, 143],
293
+ [117, 123, 50],
294
+ [222, 65, 52],
295
+ [ 19, 125, 141],
296
+ [221, 55, 65],
297
+ [ 3, 195, 197],
298
+ [ 25, 7, 33],
299
+ [220, 237, 44],
300
+ [ 70, 71, 139],
301
+ [122, 193, 245],
302
+ [247, 130, 33],
303
+ [ 71, 21, 162],
304
+ [170, 169, 150],
305
+ [188, 174, 196],
306
+ [216, 186, 92],
307
+ [ 2, 97, 167],
308
+ [141, 125, 241],
309
+ [164, 167, 37],
310
+ [ 72, 38, 12],
311
+ [ 38, 82, 13],
312
+ [ 63, 68, 71],
313
+ [226, 35, 111],
314
+ [101, 50, 205],
315
+ [206, 92, 165],
316
+ [209, 198, 217],
317
+ [165, 167, 97],
318
+ [220, 115, 218],
319
+ [133, 112, 243],
320
+ [239, 238, 241],
321
+ [214, 135, 169],
322
+ [190, 173, 133],
323
+ [171, 208, 32],
324
+ [125, 44, 237],
325
+ [ 86, 87, 178],
326
+ [ 85, 86, 179],
327
+ [ 84, 85, 180],
328
+ [ 83, 84, 181],
329
+ [201, 83, 182],
330
+ [137, 93, 132],
331
+ [ 76, 62, 183],
332
+ [ 61, 76, 184],
333
+ [ 57, 61, 185],
334
+ [212, 57, 186],
335
+ [214, 207, 187],
336
+ [ 34, 143, 156],
337
+ [ 79, 239, 237],
338
+ [123, 137, 177],
339
+ [ 44, 1, 4],
340
+ [201, 194, 32],
341
+ [ 64, 102, 129],
342
+ [213, 215, 138],
343
+ [ 59, 166, 219],
344
+ [242, 99, 97],
345
+ [ 2, 94, 141],
346
+ [ 75, 59, 235],
347
+ [ 24, 110, 228],
348
+ [ 25, 130, 226],
349
+ [ 23, 24, 229],
350
+ [ 22, 23, 230],
351
+ [ 26, 22, 231],
352
+ [112, 26, 232],
353
+ [189, 190, 243],
354
+ [221, 56, 190],
355
+ [ 28, 56, 221],
356
+ [ 27, 28, 222],
357
+ [ 29, 27, 223],
358
+ [ 30, 29, 224],
359
+ [247, 30, 225],
360
+ [238, 79, 20],
361
+ [166, 59, 75],
362
+ [ 60, 75, 240],
363
+ [147, 177, 215],
364
+ [ 20, 79, 166],
365
+ [187, 147, 213],
366
+ [112, 233, 244],
367
+ [233, 128, 245],
368
+ [128, 114, 188],
369
+ [114, 217, 174],
370
+ [131, 115, 220],
371
+ [217, 198, 236],
372
+ [198, 131, 134],
373
+ [177, 132, 58],
374
+ [143, 35, 124],
375
+ [110, 163, 7],
376
+ [228, 110, 25],
377
+ [356, 389, 368],
378
+ [ 11, 302, 267],
379
+ [452, 350, 349],
380
+ [302, 303, 269],
381
+ [357, 343, 277],
382
+ [452, 453, 357],
383
+ [333, 332, 297],
384
+ [175, 152, 377],
385
+ [347, 348, 330],
386
+ [303, 304, 270],
387
+ [ 9, 336, 337],
388
+ [278, 279, 360],
389
+ [418, 262, 431],
390
+ [304, 408, 409],
391
+ [310, 415, 407],
392
+ [270, 409, 410],
393
+ [450, 348, 347],
394
+ [422, 430, 434],
395
+ [313, 314, 17],
396
+ [306, 307, 375],
397
+ [387, 388, 260],
398
+ [286, 414, 398],
399
+ [335, 406, 418],
400
+ [364, 367, 416],
401
+ [423, 358, 327],
402
+ [251, 284, 298],
403
+ [281, 5, 4],
404
+ [373, 374, 253],
405
+ [307, 320, 321],
406
+ [425, 427, 411],
407
+ [421, 313, 18],
408
+ [321, 405, 406],
409
+ [320, 404, 405],
410
+ [315, 16, 17],
411
+ [426, 425, 266],
412
+ [377, 400, 369],
413
+ [322, 391, 269],
414
+ [417, 465, 464],
415
+ [386, 257, 258],
416
+ [466, 260, 388],
417
+ [456, 399, 419],
418
+ [284, 332, 333],
419
+ [417, 285, 8],
420
+ [346, 340, 261],
421
+ [413, 441, 285],
422
+ [327, 460, 328],
423
+ [355, 371, 329],
424
+ [392, 439, 438],
425
+ [382, 341, 256],
426
+ [429, 420, 360],
427
+ [364, 394, 379],
428
+ [277, 343, 437],
429
+ [443, 444, 283],
430
+ [275, 440, 363],
431
+ [431, 262, 369],
432
+ [297, 338, 337],
433
+ [273, 375, 321],
434
+ [450, 451, 349],
435
+ [446, 342, 467],
436
+ [293, 334, 282],
437
+ [458, 461, 462],
438
+ [276, 353, 383],
439
+ [308, 324, 325],
440
+ [276, 300, 293],
441
+ [372, 345, 447],
442
+ [352, 345, 340],
443
+ [274, 1, 19],
444
+ [456, 248, 281],
445
+ [436, 427, 425],
446
+ [381, 256, 252],
447
+ [269, 391, 393],
448
+ [200, 199, 428],
449
+ [266, 330, 329],
450
+ [287, 273, 422],
451
+ [250, 462, 328],
452
+ [258, 286, 384],
453
+ [265, 353, 342],
454
+ [387, 259, 257],
455
+ [424, 431, 430],
456
+ [342, 353, 276],
457
+ [273, 335, 424],
458
+ [292, 325, 307],
459
+ [366, 447, 345],
460
+ [271, 303, 302],
461
+ [423, 266, 371],
462
+ [294, 455, 460],
463
+ [279, 278, 294],
464
+ [271, 272, 304],
465
+ [432, 434, 427],
466
+ [272, 407, 408],
467
+ [394, 430, 431],
468
+ [395, 369, 400],
469
+ [334, 333, 299],
470
+ [351, 417, 168],
471
+ [352, 280, 411],
472
+ [325, 319, 320],
473
+ [295, 296, 336],
474
+ [319, 403, 404],
475
+ [330, 348, 349],
476
+ [293, 298, 333],
477
+ [323, 454, 447],
478
+ [ 15, 16, 315],
479
+ [358, 429, 279],
480
+ [ 14, 15, 316],
481
+ [285, 336, 9],
482
+ [329, 349, 350],
483
+ [374, 380, 252],
484
+ [318, 402, 403],
485
+ [ 6, 197, 419],
486
+ [318, 319, 325],
487
+ [367, 364, 365],
488
+ [435, 367, 397],
489
+ [344, 438, 439],
490
+ [272, 271, 311],
491
+ [195, 5, 281],
492
+ [273, 287, 291],
493
+ [396, 428, 199],
494
+ [311, 271, 268],
495
+ [283, 444, 445],
496
+ [373, 254, 339],
497
+ [282, 334, 296],
498
+ [449, 347, 346],
499
+ [264, 447, 454],
500
+ [336, 296, 299],
501
+ [338, 10, 151],
502
+ [278, 439, 455],
503
+ [292, 407, 415],
504
+ [358, 371, 355],
505
+ [340, 345, 372],
506
+ [346, 347, 280],
507
+ [442, 443, 282],
508
+ [ 19, 94, 370],
509
+ [441, 442, 295],
510
+ [248, 419, 197],
511
+ [263, 255, 359],
512
+ [440, 275, 274],
513
+ [300, 383, 368],
514
+ [351, 412, 465],
515
+ [263, 467, 466],
516
+ [301, 368, 389],
517
+ [395, 378, 379],
518
+ [412, 351, 419],
519
+ [436, 426, 322],
520
+ [ 2, 164, 393],
521
+ [370, 462, 461],
522
+ [164, 0, 267],
523
+ [302, 11, 12],
524
+ [268, 12, 13],
525
+ [293, 300, 301],
526
+ [446, 261, 340],
527
+ [330, 266, 425],
528
+ [426, 423, 391],
529
+ [429, 355, 437],
530
+ [391, 327, 326],
531
+ [440, 457, 438],
532
+ [341, 382, 362],
533
+ [459, 457, 461],
534
+ [434, 430, 394],
535
+ [414, 463, 362],
536
+ [396, 369, 262],
537
+ [354, 461, 457],
538
+ [316, 403, 402],
539
+ [315, 404, 403],
540
+ [314, 405, 404],
541
+ [313, 406, 405],
542
+ [421, 418, 406],
543
+ [366, 401, 361],
544
+ [306, 408, 407],
545
+ [291, 409, 408],
546
+ [287, 410, 409],
547
+ [432, 436, 410],
548
+ [434, 416, 411],
549
+ [264, 368, 383],
550
+ [309, 438, 457],
551
+ [352, 376, 401],
552
+ [274, 275, 4],
553
+ [421, 428, 262],
554
+ [294, 327, 358],
555
+ [433, 416, 367],
556
+ [289, 455, 439],
557
+ [462, 370, 326],
558
+ [ 2, 326, 370],
559
+ [305, 460, 455],
560
+ [254, 449, 448],
561
+ [255, 261, 446],
562
+ [253, 450, 449],
563
+ [252, 451, 450],
564
+ [256, 452, 451],
565
+ [341, 453, 452],
566
+ [413, 464, 463],
567
+ [441, 413, 414],
568
+ [258, 442, 441],
569
+ [257, 443, 442],
570
+ [259, 444, 443],
571
+ [260, 445, 444],
572
+ [467, 342, 445],
573
+ [459, 458, 250],
574
+ [289, 392, 290],
575
+ [290, 328, 460],
576
+ [376, 433, 435],
577
+ [250, 290, 392],
578
+ [411, 416, 433],
579
+ [341, 463, 464],
580
+ [453, 464, 465],
581
+ [357, 465, 412],
582
+ [343, 412, 399],
583
+ [360, 363, 440],
584
+ [437, 399, 456],
585
+ [420, 456, 363],
586
+ [401, 435, 288],
587
+ [372, 383, 353],
588
+ [339, 255, 249],
589
+ [448, 261, 255],
590
+ [133, 243, 190],
591
+ [133, 155, 112],
592
+ [ 33, 246, 247],
593
+ [ 33, 130, 25],
594
+ [398, 384, 286],
595
+ [362, 398, 414],
596
+ [362, 463, 341],
597
+ [263, 359, 467],
598
+ [263, 249, 255],
599
+ [466, 467, 260],
600
+ [ 75, 60, 166],
601
+ [238, 239, 79],
602
+ [162, 127, 139],
603
+ [ 72, 11, 37],
604
+ [121, 232, 120],
605
+ [ 73, 72, 39],
606
+ [114, 128, 47],
607
+ [233, 232, 128],
608
+ [103, 104, 67],
609
+ [152, 175, 148],
610
+ [119, 118, 101],
611
+ [ 74, 73, 40],
612
+ [107, 9, 108],
613
+ [ 49, 48, 131],
614
+ [ 32, 194, 211],
615
+ [184, 74, 185],
616
+ [191, 80, 183],
617
+ [185, 40, 186],
618
+ [119, 230, 118],
619
+ [210, 202, 214],
620
+ [ 84, 83, 17],
621
+ [ 77, 76, 146],
622
+ [161, 160, 30],
623
+ [190, 56, 173],
624
+ [182, 106, 194],
625
+ [138, 135, 192],
626
+ [129, 203, 98],
627
+ [ 54, 21, 68],
628
+ [ 5, 51, 4],
629
+ [145, 144, 23],
630
+ [ 90, 77, 91],
631
+ [207, 205, 187],
632
+ [ 83, 201, 18],
633
+ [181, 91, 182],
634
+ [180, 90, 181],
635
+ [ 16, 85, 17],
636
+ [205, 206, 36],
637
+ [176, 148, 140],
638
+ [165, 92, 39],
639
+ [245, 193, 244],
640
+ [ 27, 159, 28],
641
+ [ 30, 247, 161],
642
+ [174, 236, 196],
643
+ [103, 54, 104],
644
+ [ 55, 193, 8],
645
+ [111, 117, 31],
646
+ [221, 189, 55],
647
+ [240, 98, 99],
648
+ [142, 126, 100],
649
+ [219, 166, 218],
650
+ [112, 155, 26],
651
+ [198, 209, 131],
652
+ [169, 135, 150],
653
+ [114, 47, 217],
654
+ [224, 223, 53],
655
+ [220, 45, 134],
656
+ [ 32, 211, 140],
657
+ [109, 67, 108],
658
+ [146, 43, 91],
659
+ [231, 230, 120],
660
+ [113, 226, 247],
661
+ [105, 63, 52],
662
+ [241, 238, 242],
663
+ [124, 46, 156],
664
+ [ 95, 78, 96],
665
+ [ 70, 46, 63],
666
+ [116, 143, 227],
667
+ [116, 123, 111],
668
+ [ 1, 44, 19],
669
+ [ 3, 236, 51],
670
+ [207, 216, 205],
671
+ [ 26, 154, 22],
672
+ [165, 39, 167],
673
+ [199, 200, 208],
674
+ [101, 36, 100],
675
+ [ 43, 57, 202],
676
+ [242, 20, 99],
677
+ [ 56, 28, 157],
678
+ [124, 35, 113],
679
+ [ 29, 160, 27],
680
+ [211, 204, 210],
681
+ [124, 113, 46],
682
+ [106, 43, 204],
683
+ [ 96, 62, 77],
684
+ [227, 137, 116],
685
+ [ 73, 41, 72],
686
+ [ 36, 203, 142],
687
+ [235, 64, 240],
688
+ [ 48, 49, 64],
689
+ [ 42, 41, 74],
690
+ [214, 212, 207],
691
+ [183, 42, 184],
692
+ [210, 169, 211],
693
+ [140, 170, 176],
694
+ [104, 105, 69],
695
+ [193, 122, 168],
696
+ [ 50, 123, 187],
697
+ [ 89, 96, 90],
698
+ [ 66, 65, 107],
699
+ [179, 89, 180],
700
+ [119, 101, 120],
701
+ [ 68, 63, 104],
702
+ [234, 93, 227],
703
+ [ 16, 15, 85],
704
+ [209, 129, 49],
705
+ [ 15, 14, 86],
706
+ [107, 55, 9],
707
+ [120, 100, 121],
708
+ [153, 145, 22],
709
+ [178, 88, 179],
710
+ [197, 6, 196],
711
+ [ 89, 88, 96],
712
+ [135, 138, 136],
713
+ [138, 215, 172],
714
+ [218, 115, 219],
715
+ [ 41, 42, 81],
716
+ [ 5, 195, 51],
717
+ [ 57, 43, 61],
718
+ [208, 171, 199],
719
+ [ 41, 81, 38],
720
+ [224, 53, 225],
721
+ [ 24, 144, 110],
722
+ [105, 52, 66],
723
+ [118, 229, 117],
724
+ [227, 34, 234],
725
+ [ 66, 107, 69],
726
+ [ 10, 109, 151],
727
+ [219, 48, 235],
728
+ [183, 62, 191],
729
+ [142, 129, 126],
730
+ [116, 111, 143],
731
+ [118, 117, 50],
732
+ [223, 222, 52],
733
+ [ 94, 19, 141],
734
+ [222, 221, 65],
735
+ [196, 3, 197],
736
+ [ 45, 220, 44],
737
+ [156, 70, 139],
738
+ [188, 122, 245],
739
+ [139, 71, 162],
740
+ [149, 170, 150],
741
+ [122, 188, 196],
742
+ [206, 216, 92],
743
+ [164, 2, 167],
744
+ [242, 141, 241],
745
+ [ 0, 164, 37],
746
+ [ 11, 72, 12],
747
+ [ 12, 38, 13],
748
+ [ 70, 63, 71],
749
+ [ 31, 226, 111],
750
+ [ 36, 101, 205],
751
+ [203, 206, 165],
752
+ [126, 209, 217],
753
+ [ 98, 165, 97],
754
+ [237, 220, 218],
755
+ [237, 239, 241],
756
+ [210, 214, 169],
757
+ [140, 171, 32],
758
+ [241, 125, 237],
759
+ [179, 86, 178],
760
+ [180, 85, 179],
761
+ [181, 84, 180],
762
+ [182, 83, 181],
763
+ [194, 201, 182],
764
+ [177, 137, 132],
765
+ [184, 76, 183],
766
+ [185, 61, 184],
767
+ [186, 57, 185],
768
+ [216, 212, 186],
769
+ [192, 214, 187],
770
+ [139, 34, 156],
771
+ [218, 79, 237],
772
+ [147, 123, 177],
773
+ [ 45, 44, 4],
774
+ [208, 201, 32],
775
+ [ 98, 64, 129],
776
+ [192, 213, 138],
777
+ [235, 59, 219],
778
+ [141, 242, 97],
779
+ [ 97, 2, 141],
780
+ [240, 75, 235],
781
+ [229, 24, 228],
782
+ [ 31, 25, 226],
783
+ [230, 23, 229],
784
+ [231, 22, 230],
785
+ [232, 26, 231],
786
+ [233, 112, 232],
787
+ [244, 189, 243],
788
+ [189, 221, 190],
789
+ [222, 28, 221],
790
+ [223, 27, 222],
791
+ [224, 29, 223],
792
+ [225, 30, 224],
793
+ [113, 247, 225],
794
+ [ 99, 60, 240],
795
+ [213, 147, 215],
796
+ [ 60, 20, 166],
797
+ [192, 187, 213],
798
+ [243, 112, 244],
799
+ [244, 233, 245],
800
+ [245, 128, 188],
801
+ [188, 114, 174],
802
+ [134, 131, 220],
803
+ [174, 217, 236],
804
+ [236, 198, 134],
805
+ [215, 177, 58],
806
+ [156, 143, 124],
807
+ [ 25, 110, 7],
808
+ [ 31, 228, 25],
809
+ [264, 356, 368],
810
+ [ 0, 11, 267],
811
+ [451, 452, 349],
812
+ [267, 302, 269],
813
+ [350, 357, 277],
814
+ [350, 452, 357],
815
+ [299, 333, 297],
816
+ [396, 175, 377],
817
+ [280, 347, 330],
818
+ [269, 303, 270],
819
+ [151, 9, 337],
820
+ [344, 278, 360],
821
+ [424, 418, 431],
822
+ [270, 304, 409],
823
+ [272, 310, 407],
824
+ [322, 270, 410],
825
+ [449, 450, 347],
826
+ [432, 422, 434],
827
+ [ 18, 313, 17],
828
+ [291, 306, 375],
829
+ [259, 387, 260],
830
+ [424, 335, 418],
831
+ [434, 364, 416],
832
+ [391, 423, 327],
833
+ [301, 251, 298],
834
+ [275, 281, 4],
835
+ [254, 373, 253],
836
+ [375, 307, 321],
837
+ [280, 425, 411],
838
+ [200, 421, 18],
839
+ [335, 321, 406],
840
+ [321, 320, 405],
841
+ [314, 315, 17],
842
+ [423, 426, 266],
843
+ [396, 377, 369],
844
+ [270, 322, 269],
845
+ [413, 417, 464],
846
+ [385, 386, 258],
847
+ [248, 456, 419],
848
+ [298, 284, 333],
849
+ [168, 417, 8],
850
+ [448, 346, 261],
851
+ [417, 413, 285],
852
+ [326, 327, 328],
853
+ [277, 355, 329],
854
+ [309, 392, 438],
855
+ [381, 382, 256],
856
+ [279, 429, 360],
857
+ [365, 364, 379],
858
+ [355, 277, 437],
859
+ [282, 443, 283],
860
+ [281, 275, 363],
861
+ [395, 431, 369],
862
+ [299, 297, 337],
863
+ [335, 273, 321],
864
+ [348, 450, 349],
865
+ [359, 446, 467],
866
+ [283, 293, 282],
867
+ [250, 458, 462],
868
+ [300, 276, 383],
869
+ [292, 308, 325],
870
+ [283, 276, 293],
871
+ [264, 372, 447],
872
+ [346, 352, 340],
873
+ [354, 274, 19],
874
+ [363, 456, 281],
875
+ [426, 436, 425],
876
+ [380, 381, 252],
877
+ [267, 269, 393],
878
+ [421, 200, 428],
879
+ [371, 266, 329],
880
+ [432, 287, 422],
881
+ [290, 250, 328],
882
+ [385, 258, 384],
883
+ [446, 265, 342],
884
+ [386, 387, 257],
885
+ [422, 424, 430],
886
+ [445, 342, 276],
887
+ [422, 273, 424],
888
+ [306, 292, 307],
889
+ [352, 366, 345],
890
+ [268, 271, 302],
891
+ [358, 423, 371],
892
+ [327, 294, 460],
893
+ [331, 279, 294],
894
+ [303, 271, 304],
895
+ [436, 432, 427],
896
+ [304, 272, 408],
897
+ [395, 394, 431],
898
+ [378, 395, 400],
899
+ [296, 334, 299],
900
+ [ 6, 351, 168],
901
+ [376, 352, 411],
902
+ [307, 325, 320],
903
+ [285, 295, 336],
904
+ [320, 319, 404],
905
+ [329, 330, 349],
906
+ [334, 293, 333],
907
+ [366, 323, 447],
908
+ [316, 15, 315],
909
+ [331, 358, 279],
910
+ [317, 14, 316],
911
+ [ 8, 285, 9],
912
+ [277, 329, 350],
913
+ [253, 374, 252],
914
+ [319, 318, 403],
915
+ [351, 6, 419],
916
+ [324, 318, 325],
917
+ [397, 367, 365],
918
+ [288, 435, 397],
919
+ [278, 344, 439],
920
+ [310, 272, 311],
921
+ [248, 195, 281],
922
+ [375, 273, 291],
923
+ [175, 396, 199],
924
+ [312, 311, 268],
925
+ [276, 283, 445],
926
+ [390, 373, 339],
927
+ [295, 282, 296],
928
+ [448, 449, 346],
929
+ [356, 264, 454],
930
+ [337, 336, 299],
931
+ [337, 338, 151],
932
+ [294, 278, 455],
933
+ [308, 292, 415],
934
+ [429, 358, 355],
935
+ [265, 340, 372],
936
+ [352, 346, 280],
937
+ [295, 442, 282],
938
+ [354, 19, 370],
939
+ [285, 441, 295],
940
+ [195, 248, 197],
941
+ [457, 440, 274],
942
+ [301, 300, 368],
943
+ [417, 351, 465],
944
+ [251, 301, 389],
945
+ [394, 395, 379],
946
+ [399, 412, 419],
947
+ [410, 436, 322],
948
+ [326, 2, 393],
949
+ [354, 370, 461],
950
+ [393, 164, 267],
951
+ [268, 302, 12],
952
+ [312, 268, 13],
953
+ [298, 293, 301],
954
+ [265, 446, 340],
955
+ [280, 330, 425],
956
+ [322, 426, 391],
957
+ [420, 429, 437],
958
+ [393, 391, 326],
959
+ [344, 440, 438],
960
+ [458, 459, 461],
961
+ [364, 434, 394],
962
+ [428, 396, 262],
963
+ [274, 354, 457],
964
+ [317, 316, 402],
965
+ [316, 315, 403],
966
+ [315, 314, 404],
967
+ [314, 313, 405],
968
+ [313, 421, 406],
969
+ [323, 366, 361],
970
+ [292, 306, 407],
971
+ [306, 291, 408],
972
+ [291, 287, 409],
973
+ [287, 432, 410],
974
+ [427, 434, 411],
975
+ [372, 264, 383],
976
+ [459, 309, 457],
977
+ [366, 352, 401],
978
+ [ 1, 274, 4],
979
+ [418, 421, 262],
980
+ [331, 294, 358],
981
+ [435, 433, 367],
982
+ [392, 289, 439],
983
+ [328, 462, 326],
984
+ [ 94, 2, 370],
985
+ [289, 305, 455],
986
+ [339, 254, 448],
987
+ [359, 255, 446],
988
+ [254, 253, 449],
989
+ [253, 252, 450],
990
+ [252, 256, 451],
991
+ [256, 341, 452],
992
+ [414, 413, 463],
993
+ [286, 441, 414],
994
+ [286, 258, 441],
995
+ [258, 257, 442],
996
+ [257, 259, 443],
997
+ [259, 260, 444],
998
+ [260, 467, 445],
999
+ [309, 459, 250],
1000
+ [305, 289, 290],
1001
+ [305, 290, 460],
1002
+ [401, 376, 435],
1003
+ [309, 250, 392],
1004
+ [376, 411, 433],
1005
+ [453, 341, 464],
1006
+ [357, 453, 465],
1007
+ [343, 357, 412],
1008
+ [437, 343, 399],
1009
+ [344, 360, 440],
1010
+ [420, 437, 456],
1011
+ [360, 420, 363],
1012
+ [361, 401, 288],
1013
+ [265, 372, 353],
1014
+ [390, 339, 249],
1015
+ [339, 448, 255]
1016
+ ]
mp_utils.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
14
+ # 2024-12-04 add get_normalized_landmarks args
15
+ def calculate_distance(p1, p2):
16
+ """
17
+
18
+ """
19
+ return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)
20
+
21
+
22
+
23
+ def to_int_points(points):
24
+ ints=[]
25
+ for pt in points:
26
+ #print(pt)
27
+ value = [int(pt[0]),int(pt[1])]
28
+ #print(value)
29
+ ints.append(value)
30
+ return ints
31
+
32
+ debug = False
33
+ def divide_line_to_points(points,divided): # return divided + 1
34
+ total_length = 0
35
+ line_length_list = []
36
+ for i in range(len(points)-1):
37
+ pt_length = calculate_distance(points[i],points[i+1])
38
+ total_length += pt_length
39
+ line_length_list.append(pt_length)
40
+
41
+ splited_length = total_length/divided
42
+
43
+ def get_new_point(index,lerp):
44
+ pt1 = points[index]
45
+ pt2 = points[index+1]
46
+ diff = [pt2[0] - pt1[0], pt2[1]-pt1[1]]
47
+ new_point = [pt1[0]+diff[0]*lerp,pt1[1]+diff[1]*lerp]
48
+ if debug:
49
+ print(f"pt1 ={pt1} pt2 ={pt2} diff={diff} new_point={new_point}")
50
+
51
+ return new_point
52
+
53
+ if debug:
54
+ print(f"{total_length} splitted = {splited_length} line-length-list = {len(line_length_list)}")
55
+ splited_points=[points[0]]
56
+ for i in range(1,divided):
57
+ need_length = splited_length*i
58
+ if debug:
59
+ print(f"{i} need length = {need_length}")
60
+ current_length = 0
61
+ for j in range(len(line_length_list)):
62
+ line_length = line_length_list[j]
63
+ current_length+=line_length
64
+ if current_length>need_length:
65
+ if debug:
66
+ print(f"over need length index = {j} current={current_length}")
67
+ diff = current_length - need_length
68
+
69
+ lerp_point = 1.0 - (diff/line_length)
70
+ if debug:
71
+ print(f"over = {diff} lerp ={lerp_point}")
72
+ new_point = get_new_point(j,lerp_point)
73
+
74
+ splited_points.append(new_point)
75
+ break
76
+
77
+ splited_points.append(points[-1]) # last one
78
+ splited_points=to_int_points(splited_points)
79
+
80
+ if debug:
81
+ print(f"sp={len(splited_points)}")
82
+ return splited_points
83
+
84
+
85
+
86
+ def expand_bbox(bbox,left=5,top=5,right=5,bottom=5):
87
+ left_pixel = bbox[2]*(float(left)/100)
88
+ top_pixel = bbox[3]*(float(top)/100)
89
+ right_pixel = bbox[2]*(float(right)/100)
90
+ bottom_pixel = bbox[3]*(float(bottom)/100)
91
+ new_box = list(bbox)
92
+ new_box[0] -=left_pixel
93
+ new_box[1] -=top_pixel
94
+ new_box[2] +=left_pixel+right_pixel
95
+ new_box[3] +=top_pixel+bottom_pixel
96
+ return new_box
97
+
98
+ #normalized value index see mp_constants
99
+ def get_normalized_cordinate(face_landmarks_list,index):
100
+ x=face_landmarks_list[0][index].x
101
+ y=face_landmarks_list[0][index].y
102
+ return x,y
103
+
104
+ def get_normalized_xyz(face_landmarks_list,index):
105
+ x=face_landmarks_list[0][index].x
106
+ y=face_landmarks_list[0][index].y
107
+ z=face_landmarks_list[0][index].z
108
+ return x,y,z
109
+
110
+ def get_normalized_landmarks(face_landmarks_list,recentering=False,recentering_index=4,z_multiply=0.8):
111
+ cordinates = [get_normalized_xyz(face_landmarks_list,i) for i in range(0,468)]
112
+ if recentering:
113
+ normalized_center_point = cordinates[recentering_index]
114
+ offset_x = normalized_center_point[0]
115
+ offset_y = normalized_center_point[1]
116
+
117
+ #need aspect?
118
+ cordinates = [[point[0]-offset_x,point[1]-offset_y,point[2]*z_multiply] for point in cordinates]
119
+
120
+ return cordinates
121
+
122
+
123
+
124
+ def sort_triangles_by_depth(landmark_points,mesh_triangle_indices):
125
+ assert len(landmark_points) == 468
126
+ mesh_triangle_indices.sort(key=lambda triangle: sum(landmark_points[index][2] for index in triangle) / len(triangle)
127
+ ,reverse=True)
128
+ # z is normalized
129
+ def get_pixel_xyz(face_landmarks_list,landmark,width,height):
130
+ point = get_normalized_cordinate(face_landmarks_list,landmark)
131
+ z = y=face_landmarks_list[0][landmark].z
132
+ return int(point[0]*width),int(point[1]*height),z
133
+
134
+ def get_pixel_cordinate(face_landmarks_list,landmark,width,height):
135
+ point = get_normalized_cordinate(face_landmarks_list,landmark)
136
+ return int(point[0]*width),int(point[1]*height)
137
+
138
+ def get_pixel_cordinate_list(face_landmarks_list,indices,width,height):
139
+ cordinates = []
140
+ for index in indices:
141
+ cordinates.append(get_pixel_cordinate(face_landmarks_list,index,width,height))
142
+ return cordinates
143
+
144
+ 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):
145
+ BaseOptions = mp.tasks.BaseOptions
146
+ FaceLandmarker = mp.tasks.vision.FaceLandmarker
147
+ FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions
148
+ VisionRunningMode = mp.tasks.vision.RunningMode
149
+
150
+ options = FaceLandmarkerOptions(
151
+ base_options=BaseOptions(model_asset_path=model_path),
152
+ running_mode=VisionRunningMode.IMAGE
153
+ ,min_face_detection_confidence=min_face_detection_confidence, min_face_presence_confidence=min_face_presence_confidence,
154
+ output_facial_transformation_matrixes=output_facial_transformation_matrixes
155
+ )
156
+
157
+ with FaceLandmarker.create_from_options(options) as landmarker:
158
+ if isinstance(image_data,str):
159
+ mp_image = mp.Image.create_from_file(image_data)
160
+ else:
161
+ mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(image_data))
162
+ face_landmarker_result = landmarker.detect(mp_image)
163
+ return mp_image,face_landmarker_result
164
+