Akjava commited on
Commit
72b443b
·
1 Parent(s): de81262
.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
@@ -8,7 +8,7 @@ sdk_version: 5.7.1
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: create 3d face-mesh from image with mediapipe
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: create 3d-gltf face-mesh from image with mediapipe
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_mesh3d import process_image3d
10
+
11
+
12
+ def process_images(image,smooth_mesh,depto_ratio,inner_eyes,inner_mouth,
13
+ progress=gr.Progress(track_tqdm=True)):
14
+
15
+ clear_old_files()
16
+
17
+ result = process_image3d(image,smooth_mesh,depto_ratio,inner_eyes,inner_mouth)
18
+
19
+ return result
20
+
21
+
22
+ css="""
23
+ #col-left {
24
+ margin: 0 auto;
25
+ max-width: 640px;
26
+ }
27
+ #col-right {
28
+ margin: 0 auto;
29
+ max-width: 640px;
30
+ }
31
+ .grid-container {
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ gap:10px
36
+ }
37
+
38
+ .image {
39
+ width: 128px;
40
+ height: 128px;
41
+ object-fit: cover;
42
+ }
43
+
44
+ .text {
45
+ font-size: 16px;
46
+ }
47
+ """
48
+
49
+ #css=css,
50
+ from glibvision.cv2_utils import pil_to_bgr_image
51
+ from mp_utils import extract_landmark
52
+ from scipy.spatial.transform import Rotation as R
53
+
54
+
55
+ def change_viewer3d_mode(mode):
56
+ if mode=="solid":
57
+ return gr.Model3D(display_mode="solid")
58
+ else:
59
+ print("wireframe")
60
+ return gr.Model3D(display_mode="wireframe")
61
+
62
+ with gr.Blocks(css=css, elem_id="demo-container") as demo:
63
+ with gr.Column():
64
+ gr.HTML(read_file("demo_header.html"))
65
+ gr.HTML(read_file("demo_tools.html"))
66
+ with gr.Row():
67
+ with gr.Column():
68
+ image = gr.Image(height=800,sources=['upload','clipboard'],image_mode='RGB',elem_id="image_upload", type="pil", label="Image")
69
+
70
+ with gr.Row(elem_id="prompt-container", equal_height=False):
71
+ with gr.Row():
72
+ btn = gr.Button("3D Mesh", elem_id="run_button",variant="primary")
73
+
74
+
75
+
76
+ with gr.Accordion(label="Advanced Settings", open=True):
77
+ with gr.Row( equal_height=True):
78
+ inner_eyes=gr.Checkbox(label="Inner Eyes",value=True)
79
+ inner_mouth=gr.Checkbox(label="Inner Mouth",value=True)
80
+ with gr.Row( equal_height=True):
81
+
82
+ smooth_mesh = gr.Checkbox(label="Smooth mesh",value=True,info="smooth or blockly")
83
+ depto_ratio = gr.Slider(
84
+ label="Depth Ratio",info="If you feel nose height strange change this",
85
+ minimum=0.01,
86
+ maximum=1,
87
+ step=0.01,
88
+ value=0.8)
89
+
90
+ animation_column = gr.Column(visible=True)
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+ with gr.Column():#camera_position=(0,0,1.5)
102
+ result_3d = gr.Model3D(height=800,label="Result",display_mode="solid",elem_id="output-3d",value="files/mesh.obj",clear_color=[0.5,0.5,0.5,1])
103
+
104
+ btn.click(fn=process_images, inputs=[image,smooth_mesh,depto_ratio,inner_eyes,inner_mouth
105
+ ],outputs=[result_3d] ,api_name='infer')
106
+
107
+ example_images = [
108
+ ["examples/02316230.jpg"],
109
+ ["examples/00003245_00.jpg"],
110
+ ["examples/00827009.jpg"],
111
+ ["examples/00002062.jpg"],
112
+ ["examples/00824008.jpg"],
113
+ ["examples/00825000.jpg"],
114
+ ["examples/00826007.jpg"],
115
+ ["examples/00824006.jpg"],
116
+ ["examples/00828003.jpg"],
117
+ ["examples/00002200.jpg"],
118
+ ["examples/00005259.jpg"],
119
+ ["examples/00018022.jpg"],
120
+ ["examples/img-above.jpg"],
121
+ ["examples/00100265.jpg"],
122
+ ["examples/00039259.jpg"],
123
+
124
+ ]
125
+ example1=gr.Examples(
126
+ examples = example_images,label="Image",
127
+ inputs=[image],examples_per_page=8
128
+ )
129
+
130
+ gr.HTML(read_file("demo_footer.html"))
131
+
132
+ if __name__ == "__main__":
133
+ 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-Mesh 3D
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 is my first step of 2D image to 3D<br>
11
+ 3D Scene is stil dark,I could not solve the problem yet.<a href="https://huggingface.co/spaces/Akjava/mediapipe-head-2d-spinning">here</a> is 2D vesion<br>
12
+ I'm not familiar with pyvista ,I'll use the downloaded gltf with Blender or Godot.<br>
13
+ If any gltf problems happen let me know.
14
+ </p>
15
+ </div>
16
+
17
+ </div>
demo_tools.html ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <div style="text-align: center;">
2
+ <p>
3
+ <a href="https://huggingface.co/collections/Akjava/mediapipe-tools-672ffe8ee7b62763c31b70c7">Mediapipe Collections</a> Pickups |
4
+ <a href="https://huggingface.co/spaces/Akjava/mediapipe-face-mesh-3d">Face-Mesh 3D</a> |
5
+ <a href="https://huggingface.co/spaces/Akjava/mediapipe-face-mesh-2d">Face-Mesh 2D-Rotation</a> |
6
+ <a href="https://huggingface.co/spaces/Akjava/mediapipe-face-pose-estimation">Face Pose Estimation</a> |
7
+ <a href="https://huggingface.co/spaces/Akjava/mediapipe-face-skin-transform">Face Skin Transform</a>
8
+ </p>
9
+ <p></p>
10
+ </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_mesh3d.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pyvista as pv
2
+ import numpy as np
3
+ import cv2
4
+ from glibvision.cv2_utils import pil_to_bgr_image
5
+
6
+ from mp_utils import get_pixel_cordinate_list,extract_landmark,get_pixel_cordinate,get_normalized_xyz
7
+ import mp_triangles
8
+ def process_image3d(image,smooth_mesh,depto_ratio,inner_eyes,inner_mouth):
9
+
10
+ mp_image,face_landmarker_result = extract_landmark(image)
11
+ landmark_points = [get_normalized_xyz(face_landmarker_result.face_landmarks,i) for i in range(0,468)]#468 0478 is iris
12
+
13
+ aspect_ratio = image.width/image.height
14
+ yup = [#I'm not sure
15
+ # I'm not sure
16
+ ( point[2]*aspect_ratio*depto_ratio,1.0-point[0]*aspect_ratio,1.0 - point[1]) for point in landmark_points
17
+ ]
18
+
19
+ uv = [
20
+ ( point[0],1.0-point[1]) for point in landmark_points
21
+ ]
22
+ # 頂点座標 (x, y, z)
23
+ vertices = np.array(
24
+ yup
25
+ )
26
+
27
+ # 三角形インデックス
28
+
29
+ def flatten_and_interleave(list_of_lists):
30
+ return [([len(item)] + list(item) )for item in list_of_lists]
31
+
32
+ faces = np.array(
33
+ flatten_and_interleave(mp_triangles.get_triangles_copy(True,inner_eyes,inner_eyes,inner_mouth))
34
+ )
35
+
36
+ # PolyDataオブジェクトの作成
37
+ mesh = pv.PolyData(vertices, faces)
38
+ path = "files/mesh.gltf"#TODO uniq file
39
+
40
+ texture = pv.Texture(np.array(image, dtype=np.uint8))
41
+ uv_coords = np.array(uv,dtype="float32")
42
+ mesh.active_texture_coordinates = uv_coords
43
+
44
+ pl = pv.Plotter()
45
+
46
+ pl.add_mesh(mesh,texture=texture,smooth_shading=smooth_mesh)
47
+ pl.export_gltf(path)
48
+
49
+ return path
face_mesh_rotation.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 pil_to_bgr_image
23
+ from glibvision.cv2_utils import create_color_image as cv2_create_color_image
24
+ import cv2
25
+ #TODO move to CV2
26
+
27
+ # i'm not sure this is fast
28
+ def apply_affine_transformation_to_triangle_add(src_tri, dst_tri, src_img, dst_img):
29
+ src_tri_np = np.float32(src_tri)
30
+ dst_tri_np = np.float32(dst_tri)
31
+
32
+ h_dst, w_dst = dst_img.shape[:2]
33
+
34
+ M = cv2.getAffineTransform(src_tri_np, dst_tri_np)
35
+
36
+ dst_mask = np.zeros((h_dst, w_dst), dtype=np.uint8)
37
+ cv2.fillPoly(dst_mask, [np.int32(dst_tri)], 255)
38
+
39
+ transformed = cv2.warpAffine(src_img, M, (w_dst, h_dst))
40
+
41
+ transformed = transformed * (dst_mask[:, :, np.newaxis] / 255).astype(np.uint8)
42
+ dst_background = dst_img * (1 - (dst_mask[:, :, np.newaxis] / 255)).astype(np.uint8)
43
+ dst_img = transformed + dst_background
44
+
45
+ return dst_img
46
+
47
+ def apply_affine_transformation_to_triangle_add(src_tri, dst_tri, src_img, dst_img):
48
+ src_tri_np = np.float32(src_tri)
49
+ dst_tri_np = np.float32(dst_tri)
50
+
51
+ assert src_tri_np.shape == (3, 2), f"src_tri_np の形状が不正 {src_tri_np.shape}"
52
+ assert dst_tri_np.shape == (3, 2), f"dst_tri_np の形状が不正 {dst_tri_np.shape}"
53
+
54
+
55
+ # 透視変換行列の計算
56
+ M = cv2.getAffineTransform(src_tri_np, dst_tri_np)
57
+
58
+ # 画像のサイズ
59
+ h_src, w_src = src_img.shape[:2]
60
+ h_dst, w_dst = dst_img.shape[:2]
61
+
62
+ # 元画像から三角形領域を切り抜くマスク生成
63
+ #src_mask = np.zeros((h_src, w_src), dtype=np.uint8)
64
+ #cv2.fillPoly(src_mask, [np.int32(src_tri)], 255)
65
+
66
+ # Not 元画像の三角形領域のみをマスクで抽出
67
+ src_triangle = src_img #cv2.bitwise_and(src_img, src_img, mask=src_mask)
68
+
69
+ # 変換行列を使って元画像の三角形領域を目標画像のサイズへ変換
70
+
71
+ transformed = cv2.warpAffine(src_triangle, M, (w_dst, h_dst))
72
+ #print(f"dst_img={dst_img.shape}")
73
+ #print(f"transformed={transformed.shape}")
74
+ # 変換後のマスクの生成
75
+ dst_mask = np.zeros((h_dst, w_dst), dtype=np.uint8)
76
+ cv2.fillPoly(dst_mask, [np.int32(dst_tri)], 255)
77
+ transformed = cv2.bitwise_and(transformed, transformed, mask=dst_mask)
78
+
79
+ # 目標画像のマスク領域をクリアするためにデストのインバートマスクを作成
80
+ dst_mask_inv = cv2.bitwise_not(dst_mask)
81
+
82
+ # 目標画像のマスク部分をクリア
83
+ dst_background = cv2.bitwise_and(dst_img, dst_img, mask=dst_mask_inv)
84
+
85
+ # 変換された元画像の三角形部分と目標画像の背景部分を合成
86
+ dst_img = cv2.add(dst_background, transformed)
87
+
88
+ return dst_img
89
+
90
+ # TODO move PIL
91
+ def process_create_webp(images,duration=100, loop=0,quality=85):
92
+ frames = []
93
+ for image_file in images:
94
+ frames.append(image_file)
95
+
96
+ output_buffer = io.BytesIO()
97
+ frames[0].save(output_buffer,
98
+ save_all=True,
99
+ append_images=frames[1:],
100
+ duration=duration,
101
+ loop=loop,
102
+ format='WebP',
103
+ quality=quality
104
+ )
105
+
106
+ return output_buffer.getvalue()
107
+ # TODO move numpy
108
+ def rotate_point_euler(point, angles,order="xyz"):
109
+ """
110
+ オイラー角を使って3Dポイントを回転させる関数
111
+
112
+ Args:
113
+ point: 回転させる3Dポイント (x, y, z)
114
+ angles: 各軸周りの回転角度 (rx, ry, rz) [ラジアン]
115
+
116
+ Returns:
117
+ 回転後の3Dポイント (x', y', z')
118
+ """
119
+
120
+ rx, ry, rz = angles
121
+ point = np.array(point)
122
+
123
+ # X軸周りの回転
124
+ Rx = np.array([
125
+ [1, 0, 0],
126
+ [0, np.cos(rx), -np.sin(rx)],
127
+ [0, np.sin(rx), np.cos(rx)]
128
+ ])
129
+
130
+ # Y軸周りの回転
131
+ Ry = np.array([
132
+ [np.cos(ry), 0, np.sin(ry)],
133
+ [0, 1, 0],
134
+ [-np.sin(ry), 0, np.cos(ry)]
135
+ ])
136
+
137
+ # Z軸周りの回転
138
+ Rz = np.array([
139
+ [np.cos(rz), -np.sin(rz), 0],
140
+ [np.sin(rz), np.cos(rz), 0],
141
+ [0, 0, 1]
142
+ ])
143
+
144
+ # 回転行列の合成 (Z軸 -> Y軸 -> X軸 の順で回転)
145
+ order = order.lower()
146
+ if order == "xyz":
147
+ R = Rx @ Ry @ Rz
148
+ elif order == "xzy":
149
+ R = Rx @ Rz @ Ry
150
+ elif order == "yxz":
151
+ R = Ry @ Rx @ Rz
152
+ elif order == "yzx":
153
+ R = Ry @ Rz @ Rx
154
+ elif order == "zxy":
155
+ R = Rz @ Rx @ Ry
156
+ else:
157
+ R = Rz @ Ry @ Rx
158
+
159
+
160
+
161
+ # 回転後のポイントを計算
162
+ rotated_point = R @ point
163
+
164
+ return rotated_point
165
+
166
+
167
+ def process_face_mesh_rotation(image,draw_type,animation,center_scaleup,animation_direction,rotation_order,euler_x,euler_y,euler_z):
168
+
169
+ offset_x = 0
170
+ offset_y = 0
171
+ scale_up = 1.0
172
+
173
+ if image == None:
174
+ # Box for no Image Case
175
+ image_width = 512
176
+ image_height = 512
177
+ #image = create_color_image(image_width,image_height,(0,0,0))
178
+ points = [(-0.25,-0.25,0),(0.25,-0.25,0),
179
+ (0.25,0.25,0),(-0.25,0.25,0)
180
+ ]
181
+ normalized_center_point = [0.5,0.5]
182
+ else:
183
+ image_width = image.width
184
+ image_height = image.height
185
+ mp_image,face_landmarker_result = extract_landmark(image)
186
+ # cordinate eyes
187
+ # cordinate all
188
+ landmark_points = [get_normalized_xyz(face_landmarker_result.face_landmarks,i) for i in range(0,468)]
189
+ # do centering
190
+ normalized_center_point = landmark_points[4]
191
+ normalized_top_point = landmark_points[10]
192
+ normalized_bottom_point = landmark_points[152]
193
+
194
+
195
+ offset_x = normalized_center_point[0]
196
+ offset_y = normalized_center_point[1]
197
+
198
+ points = [[point[0]-offset_x,point[1]-offset_y,point[2]] for point in landmark_points]
199
+
200
+
201
+ # split xy-cordinate and z-depth
202
+ def split_points_xy_z(points,width,height,center_x,center_y):
203
+ xys = []
204
+ zs = []
205
+ for point in points:
206
+ xys.append(
207
+ [
208
+ point[0]*width*scale_up+center_x,
209
+ point[1]*height*scale_up+center_y
210
+ ]
211
+ )
212
+ zs.append(point[2])
213
+ return xys,zs
214
+
215
+
216
+ def create_triangle_image(points,width,height,center_x,center_y,line_color=(255,255,255),fill_color=None):
217
+ print(center_x,center_y)
218
+ cordinates,angled_depth = split_points_xy_z(points,width,height,center_x,center_y)
219
+
220
+ img = create_color_image(width,height,(0,0,0))
221
+ draw = ImageDraw.Draw(img)
222
+ triangles = mp_triangles.mesh_triangle_indices
223
+ triangles.sort(key=lambda triangle: sum(angled_depth[index] for index in triangle) / len(triangle)
224
+ ,reverse=True)
225
+ for triangle in triangles:
226
+ triangle_cordinates = [cordinates[index] for index in triangle]
227
+ glibvision.pil_utils.image_draw_points(draw,triangle_cordinates,line_color,fill_color)
228
+ return img
229
+
230
+ def create_texture_image(image,origin_points,angled_points,width,height,center_x,center_y,line_color=(255,255,255),fill_color=None):
231
+ cv2_image = pil_to_bgr_image(image)
232
+ #cv2.imwrite("tmp.jpg",cv2_image)
233
+ original_cordinates = []
234
+ cordinates,angled_depth = split_points_xy_z(angled_points,width,height,center_x,center_y)
235
+ # original point need offset
236
+ for point in origin_points:
237
+ original_cordinates.append(
238
+ [
239
+ (point[0]+offset_x)*width,
240
+ (point[1]+offset_y)*height
241
+ ]
242
+ )
243
+
244
+ cv2_bg_img = cv2_create_color_image(cv2_image,(0,0,0))
245
+
246
+ triangles = mp_triangles.mesh_triangle_indices
247
+ triangles.sort(key=lambda triangle: sum(angled_depth[index] for index in triangle) / len(triangle)
248
+ ,reverse=True)
249
+
250
+ for triangle in triangles:
251
+ triangle_cordinates = [cordinates[index] for index in triangle]
252
+ origin_triangle_cordinates = [original_cordinates[index] for index in triangle]
253
+
254
+ cv2_bg_img=apply_affine_transformation_to_triangle_add(origin_triangle_cordinates,triangle_cordinates,cv2_image,cv2_bg_img)
255
+
256
+ return Image.fromarray(cv2.cvtColor(cv2_bg_img, cv2.COLOR_RGB2BGR))
257
+
258
+ def create_point_image(points,width,height,center_x,center_y):
259
+ cordinates,_ = split_points_xy_z(points,width,height,center_x,center_y)
260
+ img = create_color_image(width,height,(0,0,0))
261
+ glibvision.pil_utils.draw_points(img,cordinates,None,None,3,(255,0,0),3)
262
+
263
+ return img
264
+
265
+ def angled_points(points,angles,order="xyz"):
266
+ angled_cordinates = []
267
+ for point in points:
268
+ rotated_np_point = rotate_point_euler(point,angles,order)
269
+ angled_cordinates.append(
270
+ [
271
+ rotated_np_point[0],
272
+ rotated_np_point[1],rotated_np_point[2]
273
+ ]
274
+ )
275
+ return angled_cordinates
276
+
277
+
278
+ frames = []
279
+
280
+
281
+ #frames.append(create_point_image(points))
282
+ frame_duration=100
283
+ start_angle=0
284
+ end_angle=360
285
+ step_angle=10
286
+
287
+ if draw_type == "Image":
288
+ start_angle=-90
289
+ end_angle=90
290
+ step_angle=30
291
+
292
+ if not animation:
293
+ start_angle=0
294
+ end_angle=0
295
+ step_angle=360
296
+ if image == None:
297
+ draw_type="Dot"
298
+
299
+
300
+ if center_scaleup:
301
+ top_distance = calculate_distance(normalized_center_point,normalized_top_point)
302
+ bottom_distance = calculate_distance(normalized_center_point,normalized_bottom_point)
303
+ distance = top_distance if top_distance>bottom_distance else bottom_distance
304
+ #small_size = image_width if image_width<image_height else image_height
305
+
306
+ scale_up = 0.45 / distance #half - margin
307
+ print(scale_up)
308
+ face_center_x = int(0.5* image_width)#half
309
+ face_center_y = int(0.5* image_height)
310
+ else:
311
+ scale_up = 1.0
312
+ face_center_x = int(normalized_center_point[0]* image_width)
313
+ face_center_y = int(normalized_center_point[1]* image_height)
314
+
315
+
316
+ if animation:
317
+ for i in range(start_angle,end_angle,step_angle):
318
+ if animation_direction == "X":
319
+ angles = [math.radians(i),0,0]
320
+ elif animation_direction == "Y":
321
+ angles = [0,math.radians(i),0]
322
+ else:
323
+ angles = [0,0,math.radians(i)]
324
+
325
+ if draw_type == "Dot":
326
+ frames.append(create_point_image(angled_points(points,angles),image_width,image_height,face_center_x,face_center_y))
327
+ elif draw_type == "Line":
328
+ frames.append(create_triangle_image(angled_points(points,angles),image_width,image_height,face_center_x,face_center_y))
329
+ elif draw_type == "Line+Fill":
330
+ 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)))
331
+ elif draw_type == "Image":
332
+ frame_duration=500
333
+ frames.append(create_texture_image(image,points,angled_points(points,angles),image_width,image_height,face_center_x,face_center_y))
334
+ webp = process_create_webp(frames,frame_duration)
335
+ path = save_buffer(webp)
336
+ else:
337
+ print(rotation_order,euler_x,euler_y,euler_z)
338
+ angles = [math.radians(float(euler_x)),math.radians(float(euler_y)),math.radians(float(euler_z))]
339
+ if draw_type == "Dot":
340
+ result_image = create_point_image(angled_points(points,angles,rotation_order),image_width,image_height,face_center_x,face_center_y)
341
+ path = save_image(result_image)
342
+ elif draw_type == "Line":
343
+ result_image = create_triangle_image(angled_points(points,angles,rotation_order),image_width,image_height,face_center_x,face_center_y)
344
+ path = save_image(result_image)
345
+ elif draw_type == "Line+Fill":
346
+ result_image = create_triangle_image(angled_points(points,angles,rotation_order),image_width,image_height,face_center_x,face_center_y,(128,128,128),(200,200,200))
347
+ path = save_image(result_image)
348
+ elif draw_type == "Image":
349
+ result_image = create_texture_image(image,points,angled_points(points,angles,rotation_order),image_width,image_height,face_center_x,face_center_y)
350
+ path = save_image(result_image)
351
+
352
+
353
+
354
+ return path
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,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+
5
+
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
+ # RGB Image use np.array(image, dtype=np.uint8)
126
+ def pil_to_bgr_image(image):
127
+ np_image = np.array(image, dtype=np.uint8)
128
+ if np_image.shape[2] == 4:
129
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_RGBA2BGRA)
130
+ else:
131
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_RGB2BGR)
132
+ return bgr_img
133
+
134
+ def bgr_to_rgb(np_image):
135
+ if np_image.shape[2] == 4:
136
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_RBGRA2RGBA)
137
+ else:
138
+ bgr_img = cv2.cvtColor(np_image, cv2.COLOR_BGR2RGB)
139
+ return bgr_img
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/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,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_triangles.py ADDED
@@ -0,0 +1,1014 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ #2024-12-02 get_triangles_copy
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
+ def get_triangles_copy(base=True,left_eye=False,right_eye=False,mouth=False):
109
+ triangles = []
110
+ if base:
111
+ triangles += mesh_triangle_indices
112
+ if left_eye:
113
+ triangles += INNER_LEFT_EYES
114
+ if right_eye:
115
+ triangles += INNER_RIGHT_EYES
116
+ if mouth:
117
+ triangles += INNER_MOUTH
118
+ return triangles
119
+
120
+ def contour_to_triangles(is_right=True,down_up=True):
121
+ triangles = []
122
+ if is_right:
123
+ if down_up:
124
+ contours = RIGHT_CONTOURS
125
+ else:
126
+ contours = RIGHT_CONTOURS[::-1]
127
+ else:
128
+ if down_up:
129
+ contours = LEFT_CONTOURS
130
+ else:
131
+ contours = LEFT_CONTOURS[::-1]
132
+
133
+ sorted_mesh_triangle_indices = []
134
+ for triangle in mesh_triangle_indices:
135
+ sorted_mesh_triangle_indices.append(sorted(triangle))
136
+
137
+ # no way to know how triangle made even in future.
138
+ for i in range(len(contours)-1):
139
+ first_line = contours[i]
140
+ second_line = contours[i+1]
141
+ #outer
142
+ triangles.append([first_line[0],first_line[1],second_line[0]])
143
+ triangles.append([second_line[0],second_line[1],first_line[1]])
144
+ triangles.append([first_line[0],first_line[1],second_line[1]])
145
+ triangles.append([second_line[0],second_line[1],first_line[0]])
146
+
147
+ #inner
148
+ triangles.append([first_line[1],first_line[2],second_line[1]])
149
+ triangles.append([second_line[1],second_line[2],first_line[2]])
150
+ triangles.append([first_line[1],first_line[2],second_line[2]])
151
+ triangles.append([second_line[1],second_line[2],first_line[1]])
152
+
153
+ exist_triangles = []
154
+ for triangle in triangles:
155
+ sorted_triangle = sorted(triangle)
156
+ if sorted_triangle in sorted_mesh_triangle_indices:
157
+ exist_triangles.append(triangle)
158
+ return exist_triangles
159
+
160
+
161
+ mesh_triangle_indices=[
162
+ [127, 34, 139],
163
+ [ 11, 0, 37],
164
+ [232, 231, 120],
165
+ [ 72, 37, 39],
166
+ [128, 121, 47],
167
+ [232, 121, 128],
168
+ [104, 69, 67],
169
+ [175, 171, 148],
170
+ [118, 50, 101],
171
+ [ 73, 39, 40],
172
+ [ 9, 151, 108],
173
+ [ 48, 115, 131],
174
+ [194, 204, 211],
175
+ [ 74, 40, 185],
176
+ [ 80, 42, 183],
177
+ [ 40, 92, 186],
178
+ [230, 229, 118],
179
+ [202, 212, 214],
180
+ [ 83, 18, 17],
181
+ [ 76, 61, 146],
182
+ [160, 29, 30],
183
+ [ 56, 157, 173],
184
+ [106, 204, 194],
185
+ [135, 214, 192],
186
+ [203, 165, 98],
187
+ [ 21, 71, 68],
188
+ [ 51, 45, 4],
189
+ [144, 24, 23],
190
+ [ 77, 146, 91],
191
+ [205, 50, 187],
192
+ [201, 200, 18],
193
+ [ 91, 106, 182],
194
+ [ 90, 91, 181],
195
+ [ 85, 84, 17],
196
+ [206, 203, 36],
197
+ [148, 171, 140],
198
+ [ 92, 40, 39],
199
+ [193, 189, 244],
200
+ [159, 158, 28],
201
+ [247, 246, 161],
202
+ [236, 3, 196],
203
+ [ 54, 68, 104],
204
+ [193, 168, 8],
205
+ [117, 228, 31],
206
+ [189, 193, 55],
207
+ [ 98, 97, 99],
208
+ [126, 47, 100],
209
+ [166, 79, 218],
210
+ [155, 154, 26],
211
+ [209, 49, 131],
212
+ [135, 136, 150],
213
+ [ 47, 126, 217],
214
+ [223, 52, 53],
215
+ [ 45, 51, 134],
216
+ [211, 170, 140],
217
+ [ 67, 69, 108],
218
+ [ 43, 106, 91],
219
+ [230, 119, 120],
220
+ [226, 130, 247],
221
+ [ 63, 53, 52],
222
+ [238, 20, 242],
223
+ [ 46, 70, 156],
224
+ [ 78, 62, 96],
225
+ [ 46, 53, 63],
226
+ [143, 34, 227],
227
+ [123, 117, 111],
228
+ [ 44, 125, 19],
229
+ [236, 134, 51],
230
+ [216, 206, 205],
231
+ [154, 153, 22],
232
+ [ 39, 37, 167],
233
+ [200, 201, 208],
234
+ [ 36, 142, 100],
235
+ [ 57, 212, 202],
236
+ [ 20, 60, 99],
237
+ [ 28, 158, 157],
238
+ [ 35, 226, 113],
239
+ [160, 159, 27],
240
+ [204, 202, 210],
241
+ [113, 225, 46],
242
+ [ 43, 202, 204],
243
+ [ 62, 76, 77],
244
+ [137, 123, 116],
245
+ [ 41, 38, 72],
246
+ [203, 129, 142],
247
+ [ 64, 98, 240],
248
+ [ 49, 102, 64],
249
+ [ 41, 73, 74],
250
+ [212, 216, 207],
251
+ [ 42, 74, 184],
252
+ [169, 170, 211],
253
+ [170, 149, 176],
254
+ [105, 66, 69],
255
+ [122, 6, 168],
256
+ [123, 147, 187],
257
+ [ 96, 77, 90],
258
+ [ 65, 55, 107],
259
+ [ 89, 90, 180],
260
+ [101, 100, 120],
261
+ [ 63, 105, 104],
262
+ [ 93, 137, 227],
263
+ [ 15, 86, 85],
264
+ [129, 102, 49],
265
+ [ 14, 87, 86],
266
+ [ 55, 8, 9],
267
+ [100, 47, 121],
268
+ [145, 23, 22],
269
+ [ 88, 89, 179],
270
+ [ 6, 122, 196],
271
+ [ 88, 95, 96],
272
+ [138, 172, 136],
273
+ [215, 58, 172],
274
+ [115, 48, 219],
275
+ [ 42, 80, 81],
276
+ [195, 3, 51],
277
+ [ 43, 146, 61],
278
+ [171, 175, 199],
279
+ [ 81, 82, 38],
280
+ [ 53, 46, 225],
281
+ [144, 163, 110],
282
+ [ 52, 65, 66],
283
+ [229, 228, 117],
284
+ [ 34, 127, 234],
285
+ [107, 108, 69],
286
+ [109, 108, 151],
287
+ [ 48, 64, 235],
288
+ [ 62, 78, 191],
289
+ [129, 209, 126],
290
+ [111, 35, 143],
291
+ [117, 123, 50],
292
+ [222, 65, 52],
293
+ [ 19, 125, 141],
294
+ [221, 55, 65],
295
+ [ 3, 195, 197],
296
+ [ 25, 7, 33],
297
+ [220, 237, 44],
298
+ [ 70, 71, 139],
299
+ [122, 193, 245],
300
+ [247, 130, 33],
301
+ [ 71, 21, 162],
302
+ [170, 169, 150],
303
+ [188, 174, 196],
304
+ [216, 186, 92],
305
+ [ 2, 97, 167],
306
+ [141, 125, 241],
307
+ [164, 167, 37],
308
+ [ 72, 38, 12],
309
+ [ 38, 82, 13],
310
+ [ 63, 68, 71],
311
+ [226, 35, 111],
312
+ [101, 50, 205],
313
+ [206, 92, 165],
314
+ [209, 198, 217],
315
+ [165, 167, 97],
316
+ [220, 115, 218],
317
+ [133, 112, 243],
318
+ [239, 238, 241],
319
+ [214, 135, 169],
320
+ [190, 173, 133],
321
+ [171, 208, 32],
322
+ [125, 44, 237],
323
+ [ 86, 87, 178],
324
+ [ 85, 86, 179],
325
+ [ 84, 85, 180],
326
+ [ 83, 84, 181],
327
+ [201, 83, 182],
328
+ [137, 93, 132],
329
+ [ 76, 62, 183],
330
+ [ 61, 76, 184],
331
+ [ 57, 61, 185],
332
+ [212, 57, 186],
333
+ [214, 207, 187],
334
+ [ 34, 143, 156],
335
+ [ 79, 239, 237],
336
+ [123, 137, 177],
337
+ [ 44, 1, 4],
338
+ [201, 194, 32],
339
+ [ 64, 102, 129],
340
+ [213, 215, 138],
341
+ [ 59, 166, 219],
342
+ [242, 99, 97],
343
+ [ 2, 94, 141],
344
+ [ 75, 59, 235],
345
+ [ 24, 110, 228],
346
+ [ 25, 130, 226],
347
+ [ 23, 24, 229],
348
+ [ 22, 23, 230],
349
+ [ 26, 22, 231],
350
+ [112, 26, 232],
351
+ [189, 190, 243],
352
+ [221, 56, 190],
353
+ [ 28, 56, 221],
354
+ [ 27, 28, 222],
355
+ [ 29, 27, 223],
356
+ [ 30, 29, 224],
357
+ [247, 30, 225],
358
+ [238, 79, 20],
359
+ [166, 59, 75],
360
+ [ 60, 75, 240],
361
+ [147, 177, 215],
362
+ [ 20, 79, 166],
363
+ [187, 147, 213],
364
+ [112, 233, 244],
365
+ [233, 128, 245],
366
+ [128, 114, 188],
367
+ [114, 217, 174],
368
+ [131, 115, 220],
369
+ [217, 198, 236],
370
+ [198, 131, 134],
371
+ [177, 132, 58],
372
+ [143, 35, 124],
373
+ [110, 163, 7],
374
+ [228, 110, 25],
375
+ [356, 389, 368],
376
+ [ 11, 302, 267],
377
+ [452, 350, 349],
378
+ [302, 303, 269],
379
+ [357, 343, 277],
380
+ [452, 453, 357],
381
+ [333, 332, 297],
382
+ [175, 152, 377],
383
+ [347, 348, 330],
384
+ [303, 304, 270],
385
+ [ 9, 336, 337],
386
+ [278, 279, 360],
387
+ [418, 262, 431],
388
+ [304, 408, 409],
389
+ [310, 415, 407],
390
+ [270, 409, 410],
391
+ [450, 348, 347],
392
+ [422, 430, 434],
393
+ [313, 314, 17],
394
+ [306, 307, 375],
395
+ [387, 388, 260],
396
+ [286, 414, 398],
397
+ [335, 406, 418],
398
+ [364, 367, 416],
399
+ [423, 358, 327],
400
+ [251, 284, 298],
401
+ [281, 5, 4],
402
+ [373, 374, 253],
403
+ [307, 320, 321],
404
+ [425, 427, 411],
405
+ [421, 313, 18],
406
+ [321, 405, 406],
407
+ [320, 404, 405],
408
+ [315, 16, 17],
409
+ [426, 425, 266],
410
+ [377, 400, 369],
411
+ [322, 391, 269],
412
+ [417, 465, 464],
413
+ [386, 257, 258],
414
+ [466, 260, 388],
415
+ [456, 399, 419],
416
+ [284, 332, 333],
417
+ [417, 285, 8],
418
+ [346, 340, 261],
419
+ [413, 441, 285],
420
+ [327, 460, 328],
421
+ [355, 371, 329],
422
+ [392, 439, 438],
423
+ [382, 341, 256],
424
+ [429, 420, 360],
425
+ [364, 394, 379],
426
+ [277, 343, 437],
427
+ [443, 444, 283],
428
+ [275, 440, 363],
429
+ [431, 262, 369],
430
+ [297, 338, 337],
431
+ [273, 375, 321],
432
+ [450, 451, 349],
433
+ [446, 342, 467],
434
+ [293, 334, 282],
435
+ [458, 461, 462],
436
+ [276, 353, 383],
437
+ [308, 324, 325],
438
+ [276, 300, 293],
439
+ [372, 345, 447],
440
+ [352, 345, 340],
441
+ [274, 1, 19],
442
+ [456, 248, 281],
443
+ [436, 427, 425],
444
+ [381, 256, 252],
445
+ [269, 391, 393],
446
+ [200, 199, 428],
447
+ [266, 330, 329],
448
+ [287, 273, 422],
449
+ [250, 462, 328],
450
+ [258, 286, 384],
451
+ [265, 353, 342],
452
+ [387, 259, 257],
453
+ [424, 431, 430],
454
+ [342, 353, 276],
455
+ [273, 335, 424],
456
+ [292, 325, 307],
457
+ [366, 447, 345],
458
+ [271, 303, 302],
459
+ [423, 266, 371],
460
+ [294, 455, 460],
461
+ [279, 278, 294],
462
+ [271, 272, 304],
463
+ [432, 434, 427],
464
+ [272, 407, 408],
465
+ [394, 430, 431],
466
+ [395, 369, 400],
467
+ [334, 333, 299],
468
+ [351, 417, 168],
469
+ [352, 280, 411],
470
+ [325, 319, 320],
471
+ [295, 296, 336],
472
+ [319, 403, 404],
473
+ [330, 348, 349],
474
+ [293, 298, 333],
475
+ [323, 454, 447],
476
+ [ 15, 16, 315],
477
+ [358, 429, 279],
478
+ [ 14, 15, 316],
479
+ [285, 336, 9],
480
+ [329, 349, 350],
481
+ [374, 380, 252],
482
+ [318, 402, 403],
483
+ [ 6, 197, 419],
484
+ [318, 319, 325],
485
+ [367, 364, 365],
486
+ [435, 367, 397],
487
+ [344, 438, 439],
488
+ [272, 271, 311],
489
+ [195, 5, 281],
490
+ [273, 287, 291],
491
+ [396, 428, 199],
492
+ [311, 271, 268],
493
+ [283, 444, 445],
494
+ [373, 254, 339],
495
+ [282, 334, 296],
496
+ [449, 347, 346],
497
+ [264, 447, 454],
498
+ [336, 296, 299],
499
+ [338, 10, 151],
500
+ [278, 439, 455],
501
+ [292, 407, 415],
502
+ [358, 371, 355],
503
+ [340, 345, 372],
504
+ [346, 347, 280],
505
+ [442, 443, 282],
506
+ [ 19, 94, 370],
507
+ [441, 442, 295],
508
+ [248, 419, 197],
509
+ [263, 255, 359],
510
+ [440, 275, 274],
511
+ [300, 383, 368],
512
+ [351, 412, 465],
513
+ [263, 467, 466],
514
+ [301, 368, 389],
515
+ [395, 378, 379],
516
+ [412, 351, 419],
517
+ [436, 426, 322],
518
+ [ 2, 164, 393],
519
+ [370, 462, 461],
520
+ [164, 0, 267],
521
+ [302, 11, 12],
522
+ [268, 12, 13],
523
+ [293, 300, 301],
524
+ [446, 261, 340],
525
+ [330, 266, 425],
526
+ [426, 423, 391],
527
+ [429, 355, 437],
528
+ [391, 327, 326],
529
+ [440, 457, 438],
530
+ [341, 382, 362],
531
+ [459, 457, 461],
532
+ [434, 430, 394],
533
+ [414, 463, 362],
534
+ [396, 369, 262],
535
+ [354, 461, 457],
536
+ [316, 403, 402],
537
+ [315, 404, 403],
538
+ [314, 405, 404],
539
+ [313, 406, 405],
540
+ [421, 418, 406],
541
+ [366, 401, 361],
542
+ [306, 408, 407],
543
+ [291, 409, 408],
544
+ [287, 410, 409],
545
+ [432, 436, 410],
546
+ [434, 416, 411],
547
+ [264, 368, 383],
548
+ [309, 438, 457],
549
+ [352, 376, 401],
550
+ [274, 275, 4],
551
+ [421, 428, 262],
552
+ [294, 327, 358],
553
+ [433, 416, 367],
554
+ [289, 455, 439],
555
+ [462, 370, 326],
556
+ [ 2, 326, 370],
557
+ [305, 460, 455],
558
+ [254, 449, 448],
559
+ [255, 261, 446],
560
+ [253, 450, 449],
561
+ [252, 451, 450],
562
+ [256, 452, 451],
563
+ [341, 453, 452],
564
+ [413, 464, 463],
565
+ [441, 413, 414],
566
+ [258, 442, 441],
567
+ [257, 443, 442],
568
+ [259, 444, 443],
569
+ [260, 445, 444],
570
+ [467, 342, 445],
571
+ [459, 458, 250],
572
+ [289, 392, 290],
573
+ [290, 328, 460],
574
+ [376, 433, 435],
575
+ [250, 290, 392],
576
+ [411, 416, 433],
577
+ [341, 463, 464],
578
+ [453, 464, 465],
579
+ [357, 465, 412],
580
+ [343, 412, 399],
581
+ [360, 363, 440],
582
+ [437, 399, 456],
583
+ [420, 456, 363],
584
+ [401, 435, 288],
585
+ [372, 383, 353],
586
+ [339, 255, 249],
587
+ [448, 261, 255],
588
+ [133, 243, 190],
589
+ [133, 155, 112],
590
+ [ 33, 246, 247],
591
+ [ 33, 130, 25],
592
+ [398, 384, 286],
593
+ [362, 398, 414],
594
+ [362, 463, 341],
595
+ [263, 359, 467],
596
+ [263, 249, 255],
597
+ [466, 467, 260],
598
+ [ 75, 60, 166],
599
+ [238, 239, 79],
600
+ [162, 127, 139],
601
+ [ 72, 11, 37],
602
+ [121, 232, 120],
603
+ [ 73, 72, 39],
604
+ [114, 128, 47],
605
+ [233, 232, 128],
606
+ [103, 104, 67],
607
+ [152, 175, 148],
608
+ [119, 118, 101],
609
+ [ 74, 73, 40],
610
+ [107, 9, 108],
611
+ [ 49, 48, 131],
612
+ [ 32, 194, 211],
613
+ [184, 74, 185],
614
+ [191, 80, 183],
615
+ [185, 40, 186],
616
+ [119, 230, 118],
617
+ [210, 202, 214],
618
+ [ 84, 83, 17],
619
+ [ 77, 76, 146],
620
+ [161, 160, 30],
621
+ [190, 56, 173],
622
+ [182, 106, 194],
623
+ [138, 135, 192],
624
+ [129, 203, 98],
625
+ [ 54, 21, 68],
626
+ [ 5, 51, 4],
627
+ [145, 144, 23],
628
+ [ 90, 77, 91],
629
+ [207, 205, 187],
630
+ [ 83, 201, 18],
631
+ [181, 91, 182],
632
+ [180, 90, 181],
633
+ [ 16, 85, 17],
634
+ [205, 206, 36],
635
+ [176, 148, 140],
636
+ [165, 92, 39],
637
+ [245, 193, 244],
638
+ [ 27, 159, 28],
639
+ [ 30, 247, 161],
640
+ [174, 236, 196],
641
+ [103, 54, 104],
642
+ [ 55, 193, 8],
643
+ [111, 117, 31],
644
+ [221, 189, 55],
645
+ [240, 98, 99],
646
+ [142, 126, 100],
647
+ [219, 166, 218],
648
+ [112, 155, 26],
649
+ [198, 209, 131],
650
+ [169, 135, 150],
651
+ [114, 47, 217],
652
+ [224, 223, 53],
653
+ [220, 45, 134],
654
+ [ 32, 211, 140],
655
+ [109, 67, 108],
656
+ [146, 43, 91],
657
+ [231, 230, 120],
658
+ [113, 226, 247],
659
+ [105, 63, 52],
660
+ [241, 238, 242],
661
+ [124, 46, 156],
662
+ [ 95, 78, 96],
663
+ [ 70, 46, 63],
664
+ [116, 143, 227],
665
+ [116, 123, 111],
666
+ [ 1, 44, 19],
667
+ [ 3, 236, 51],
668
+ [207, 216, 205],
669
+ [ 26, 154, 22],
670
+ [165, 39, 167],
671
+ [199, 200, 208],
672
+ [101, 36, 100],
673
+ [ 43, 57, 202],
674
+ [242, 20, 99],
675
+ [ 56, 28, 157],
676
+ [124, 35, 113],
677
+ [ 29, 160, 27],
678
+ [211, 204, 210],
679
+ [124, 113, 46],
680
+ [106, 43, 204],
681
+ [ 96, 62, 77],
682
+ [227, 137, 116],
683
+ [ 73, 41, 72],
684
+ [ 36, 203, 142],
685
+ [235, 64, 240],
686
+ [ 48, 49, 64],
687
+ [ 42, 41, 74],
688
+ [214, 212, 207],
689
+ [183, 42, 184],
690
+ [210, 169, 211],
691
+ [140, 170, 176],
692
+ [104, 105, 69],
693
+ [193, 122, 168],
694
+ [ 50, 123, 187],
695
+ [ 89, 96, 90],
696
+ [ 66, 65, 107],
697
+ [179, 89, 180],
698
+ [119, 101, 120],
699
+ [ 68, 63, 104],
700
+ [234, 93, 227],
701
+ [ 16, 15, 85],
702
+ [209, 129, 49],
703
+ [ 15, 14, 86],
704
+ [107, 55, 9],
705
+ [120, 100, 121],
706
+ [153, 145, 22],
707
+ [178, 88, 179],
708
+ [197, 6, 196],
709
+ [ 89, 88, 96],
710
+ [135, 138, 136],
711
+ [138, 215, 172],
712
+ [218, 115, 219],
713
+ [ 41, 42, 81],
714
+ [ 5, 195, 51],
715
+ [ 57, 43, 61],
716
+ [208, 171, 199],
717
+ [ 41, 81, 38],
718
+ [224, 53, 225],
719
+ [ 24, 144, 110],
720
+ [105, 52, 66],
721
+ [118, 229, 117],
722
+ [227, 34, 234],
723
+ [ 66, 107, 69],
724
+ [ 10, 109, 151],
725
+ [219, 48, 235],
726
+ [183, 62, 191],
727
+ [142, 129, 126],
728
+ [116, 111, 143],
729
+ [118, 117, 50],
730
+ [223, 222, 52],
731
+ [ 94, 19, 141],
732
+ [222, 221, 65],
733
+ [196, 3, 197],
734
+ [ 45, 220, 44],
735
+ [156, 70, 139],
736
+ [188, 122, 245],
737
+ [139, 71, 162],
738
+ [149, 170, 150],
739
+ [122, 188, 196],
740
+ [206, 216, 92],
741
+ [164, 2, 167],
742
+ [242, 141, 241],
743
+ [ 0, 164, 37],
744
+ [ 11, 72, 12],
745
+ [ 12, 38, 13],
746
+ [ 70, 63, 71],
747
+ [ 31, 226, 111],
748
+ [ 36, 101, 205],
749
+ [203, 206, 165],
750
+ [126, 209, 217],
751
+ [ 98, 165, 97],
752
+ [237, 220, 218],
753
+ [237, 239, 241],
754
+ [210, 214, 169],
755
+ [140, 171, 32],
756
+ [241, 125, 237],
757
+ [179, 86, 178],
758
+ [180, 85, 179],
759
+ [181, 84, 180],
760
+ [182, 83, 181],
761
+ [194, 201, 182],
762
+ [177, 137, 132],
763
+ [184, 76, 183],
764
+ [185, 61, 184],
765
+ [186, 57, 185],
766
+ [216, 212, 186],
767
+ [192, 214, 187],
768
+ [139, 34, 156],
769
+ [218, 79, 237],
770
+ [147, 123, 177],
771
+ [ 45, 44, 4],
772
+ [208, 201, 32],
773
+ [ 98, 64, 129],
774
+ [192, 213, 138],
775
+ [235, 59, 219],
776
+ [141, 242, 97],
777
+ [ 97, 2, 141],
778
+ [240, 75, 235],
779
+ [229, 24, 228],
780
+ [ 31, 25, 226],
781
+ [230, 23, 229],
782
+ [231, 22, 230],
783
+ [232, 26, 231],
784
+ [233, 112, 232],
785
+ [244, 189, 243],
786
+ [189, 221, 190],
787
+ [222, 28, 221],
788
+ [223, 27, 222],
789
+ [224, 29, 223],
790
+ [225, 30, 224],
791
+ [113, 247, 225],
792
+ [ 99, 60, 240],
793
+ [213, 147, 215],
794
+ [ 60, 20, 166],
795
+ [192, 187, 213],
796
+ [243, 112, 244],
797
+ [244, 233, 245],
798
+ [245, 128, 188],
799
+ [188, 114, 174],
800
+ [134, 131, 220],
801
+ [174, 217, 236],
802
+ [236, 198, 134],
803
+ [215, 177, 58],
804
+ [156, 143, 124],
805
+ [ 25, 110, 7],
806
+ [ 31, 228, 25],
807
+ [264, 356, 368],
808
+ [ 0, 11, 267],
809
+ [451, 452, 349],
810
+ [267, 302, 269],
811
+ [350, 357, 277],
812
+ [350, 452, 357],
813
+ [299, 333, 297],
814
+ [396, 175, 377],
815
+ [280, 347, 330],
816
+ [269, 303, 270],
817
+ [151, 9, 337],
818
+ [344, 278, 360],
819
+ [424, 418, 431],
820
+ [270, 304, 409],
821
+ [272, 310, 407],
822
+ [322, 270, 410],
823
+ [449, 450, 347],
824
+ [432, 422, 434],
825
+ [ 18, 313, 17],
826
+ [291, 306, 375],
827
+ [259, 387, 260],
828
+ [424, 335, 418],
829
+ [434, 364, 416],
830
+ [391, 423, 327],
831
+ [301, 251, 298],
832
+ [275, 281, 4],
833
+ [254, 373, 253],
834
+ [375, 307, 321],
835
+ [280, 425, 411],
836
+ [200, 421, 18],
837
+ [335, 321, 406],
838
+ [321, 320, 405],
839
+ [314, 315, 17],
840
+ [423, 426, 266],
841
+ [396, 377, 369],
842
+ [270, 322, 269],
843
+ [413, 417, 464],
844
+ [385, 386, 258],
845
+ [248, 456, 419],
846
+ [298, 284, 333],
847
+ [168, 417, 8],
848
+ [448, 346, 261],
849
+ [417, 413, 285],
850
+ [326, 327, 328],
851
+ [277, 355, 329],
852
+ [309, 392, 438],
853
+ [381, 382, 256],
854
+ [279, 429, 360],
855
+ [365, 364, 379],
856
+ [355, 277, 437],
857
+ [282, 443, 283],
858
+ [281, 275, 363],
859
+ [395, 431, 369],
860
+ [299, 297, 337],
861
+ [335, 273, 321],
862
+ [348, 450, 349],
863
+ [359, 446, 467],
864
+ [283, 293, 282],
865
+ [250, 458, 462],
866
+ [300, 276, 383],
867
+ [292, 308, 325],
868
+ [283, 276, 293],
869
+ [264, 372, 447],
870
+ [346, 352, 340],
871
+ [354, 274, 19],
872
+ [363, 456, 281],
873
+ [426, 436, 425],
874
+ [380, 381, 252],
875
+ [267, 269, 393],
876
+ [421, 200, 428],
877
+ [371, 266, 329],
878
+ [432, 287, 422],
879
+ [290, 250, 328],
880
+ [385, 258, 384],
881
+ [446, 265, 342],
882
+ [386, 387, 257],
883
+ [422, 424, 430],
884
+ [445, 342, 276],
885
+ [422, 273, 424],
886
+ [306, 292, 307],
887
+ [352, 366, 345],
888
+ [268, 271, 302],
889
+ [358, 423, 371],
890
+ [327, 294, 460],
891
+ [331, 279, 294],
892
+ [303, 271, 304],
893
+ [436, 432, 427],
894
+ [304, 272, 408],
895
+ [395, 394, 431],
896
+ [378, 395, 400],
897
+ [296, 334, 299],
898
+ [ 6, 351, 168],
899
+ [376, 352, 411],
900
+ [307, 325, 320],
901
+ [285, 295, 336],
902
+ [320, 319, 404],
903
+ [329, 330, 349],
904
+ [334, 293, 333],
905
+ [366, 323, 447],
906
+ [316, 15, 315],
907
+ [331, 358, 279],
908
+ [317, 14, 316],
909
+ [ 8, 285, 9],
910
+ [277, 329, 350],
911
+ [253, 374, 252],
912
+ [319, 318, 403],
913
+ [351, 6, 419],
914
+ [324, 318, 325],
915
+ [397, 367, 365],
916
+ [288, 435, 397],
917
+ [278, 344, 439],
918
+ [310, 272, 311],
919
+ [248, 195, 281],
920
+ [375, 273, 291],
921
+ [175, 396, 199],
922
+ [312, 311, 268],
923
+ [276, 283, 445],
924
+ [390, 373, 339],
925
+ [295, 282, 296],
926
+ [448, 449, 346],
927
+ [356, 264, 454],
928
+ [337, 336, 299],
929
+ [337, 338, 151],
930
+ [294, 278, 455],
931
+ [308, 292, 415],
932
+ [429, 358, 355],
933
+ [265, 340, 372],
934
+ [352, 346, 280],
935
+ [295, 442, 282],
936
+ [354, 19, 370],
937
+ [285, 441, 295],
938
+ [195, 248, 197],
939
+ [457, 440, 274],
940
+ [301, 300, 368],
941
+ [417, 351, 465],
942
+ [251, 301, 389],
943
+ [394, 395, 379],
944
+ [399, 412, 419],
945
+ [410, 436, 322],
946
+ [326, 2, 393],
947
+ [354, 370, 461],
948
+ [393, 164, 267],
949
+ [268, 302, 12],
950
+ [312, 268, 13],
951
+ [298, 293, 301],
952
+ [265, 446, 340],
953
+ [280, 330, 425],
954
+ [322, 426, 391],
955
+ [420, 429, 437],
956
+ [393, 391, 326],
957
+ [344, 440, 438],
958
+ [458, 459, 461],
959
+ [364, 434, 394],
960
+ [428, 396, 262],
961
+ [274, 354, 457],
962
+ [317, 316, 402],
963
+ [316, 315, 403],
964
+ [315, 314, 404],
965
+ [314, 313, 405],
966
+ [313, 421, 406],
967
+ [323, 366, 361],
968
+ [292, 306, 407],
969
+ [306, 291, 408],
970
+ [291, 287, 409],
971
+ [287, 432, 410],
972
+ [427, 434, 411],
973
+ [372, 264, 383],
974
+ [459, 309, 457],
975
+ [366, 352, 401],
976
+ [ 1, 274, 4],
977
+ [418, 421, 262],
978
+ [331, 294, 358],
979
+ [435, 433, 367],
980
+ [392, 289, 439],
981
+ [328, 462, 326],
982
+ [ 94, 2, 370],
983
+ [289, 305, 455],
984
+ [339, 254, 448],
985
+ [359, 255, 446],
986
+ [254, 253, 449],
987
+ [253, 252, 450],
988
+ [252, 256, 451],
989
+ [256, 341, 452],
990
+ [414, 413, 463],
991
+ [286, 441, 414],
992
+ [286, 258, 441],
993
+ [258, 257, 442],
994
+ [257, 259, 443],
995
+ [259, 260, 444],
996
+ [260, 467, 445],
997
+ [309, 459, 250],
998
+ [305, 289, 290],
999
+ [305, 290, 460],
1000
+ [401, 376, 435],
1001
+ [309, 250, 392],
1002
+ [376, 411, 433],
1003
+ [453, 341, 464],
1004
+ [357, 453, 465],
1005
+ [343, 357, 412],
1006
+ [437, 343, 399],
1007
+ [344, 360, 440],
1008
+ [420, 437, 456],
1009
+ [360, 420, 363],
1010
+ [361, 401, 288],
1011
+ [265, 372, 353],
1012
+ [390, 339, 249],
1013
+ [339, 448, 255]
1014
+ ]
mp_utils.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ def calculate_distance(p1, p2):
14
+ """
15
+
16
+ """
17
+ return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)
18
+ def to_int_points(points):
19
+ ints=[]
20
+ for pt in points:
21
+ #print(pt)
22
+ value = [int(pt[0]),int(pt[1])]
23
+ #print(value)
24
+ ints.append(value)
25
+ return ints
26
+
27
+ debug = False
28
+ def divide_line_to_points(points,divided): # return divided + 1
29
+ total_length = 0
30
+ line_length_list = []
31
+ for i in range(len(points)-1):
32
+ pt_length = calculate_distance(points[i],points[i+1])
33
+ total_length += pt_length
34
+ line_length_list.append(pt_length)
35
+
36
+ splited_length = total_length/divided
37
+
38
+ def get_new_point(index,lerp):
39
+ pt1 = points[index]
40
+ pt2 = points[index+1]
41
+ diff = [pt2[0] - pt1[0], pt2[1]-pt1[1]]
42
+ new_point = [pt1[0]+diff[0]*lerp,pt1[1]+diff[1]*lerp]
43
+ if debug:
44
+ print(f"pt1 ={pt1} pt2 ={pt2} diff={diff} new_point={new_point}")
45
+
46
+ return new_point
47
+
48
+ if debug:
49
+ print(f"{total_length} splitted = {splited_length} line-length-list = {len(line_length_list)}")
50
+ splited_points=[points[0]]
51
+ for i in range(1,divided):
52
+ need_length = splited_length*i
53
+ if debug:
54
+ print(f"{i} need length = {need_length}")
55
+ current_length = 0
56
+ for j in range(len(line_length_list)):
57
+ line_length = line_length_list[j]
58
+ current_length+=line_length
59
+ if current_length>need_length:
60
+ if debug:
61
+ print(f"over need length index = {j} current={current_length}")
62
+ diff = current_length - need_length
63
+
64
+ lerp_point = 1.0 - (diff/line_length)
65
+ if debug:
66
+ print(f"over = {diff} lerp ={lerp_point}")
67
+ new_point = get_new_point(j,lerp_point)
68
+
69
+ splited_points.append(new_point)
70
+ break
71
+
72
+ splited_points.append(points[-1]) # last one
73
+ splited_points=to_int_points(splited_points)
74
+
75
+ if debug:
76
+ print(f"sp={len(splited_points)}")
77
+ return splited_points
78
+
79
+
80
+
81
+ def expand_bbox(bbox,left=5,top=5,right=5,bottom=5):
82
+ left_pixel = bbox[2]*(float(left)/100)
83
+ top_pixel = bbox[3]*(float(top)/100)
84
+ right_pixel = bbox[2]*(float(right)/100)
85
+ bottom_pixel = bbox[3]*(float(bottom)/100)
86
+ new_box = list(bbox)
87
+ new_box[0] -=left_pixel
88
+ new_box[1] -=top_pixel
89
+ new_box[2] +=left_pixel+right_pixel
90
+ new_box[3] +=top_pixel+bottom_pixel
91
+ return new_box
92
+
93
+ #normalized value index see mp_constants
94
+ def get_normalized_cordinate(face_landmarks_list,index):
95
+ x=face_landmarks_list[0][index].x
96
+ y=face_landmarks_list[0][index].y
97
+ return x,y
98
+
99
+ def get_normalized_xyz(face_landmarks_list,index):
100
+ x=face_landmarks_list[0][index].x
101
+ y=face_landmarks_list[0][index].y
102
+ z=face_landmarks_list[0][index].z
103
+ return x,y,z
104
+
105
+ # z is normalized
106
+ def get_pixel_xyz(face_landmarks_list,landmark,width,height):
107
+ point = get_normalized_cordinate(face_landmarks_list,landmark)
108
+ z = y=face_landmarks_list[0][landmark].z
109
+ return int(point[0]*width),int(point[1]*height),z
110
+
111
+ def get_pixel_cordinate(face_landmarks_list,landmark,width,height):
112
+ point = get_normalized_cordinate(face_landmarks_list,landmark)
113
+ return int(point[0]*width),int(point[1]*height)
114
+
115
+ def get_pixel_cordinate_list(face_landmarks_list,indices,width,height):
116
+ cordinates = []
117
+ for index in indices:
118
+ cordinates.append(get_pixel_cordinate(face_landmarks_list,index,width,height))
119
+ return cordinates
120
+
121
+ 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):
122
+ BaseOptions = mp.tasks.BaseOptions
123
+ FaceLandmarker = mp.tasks.vision.FaceLandmarker
124
+ FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions
125
+ VisionRunningMode = mp.tasks.vision.RunningMode
126
+
127
+ options = FaceLandmarkerOptions(
128
+ base_options=BaseOptions(model_asset_path=model_path),
129
+ running_mode=VisionRunningMode.IMAGE
130
+ ,min_face_detection_confidence=min_face_detection_confidence, min_face_presence_confidence=min_face_presence_confidence,
131
+ output_facial_transformation_matrixes=output_facial_transformation_matrixes
132
+ )
133
+
134
+ with FaceLandmarker.create_from_options(options) as landmarker:
135
+ if isinstance(image_data,str):
136
+ mp_image = mp.Image.create_from_file(image_data)
137
+ else:
138
+ mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(image_data))
139
+ face_landmarker_result = landmarker.detect(mp_image)
140
+ return mp_image,face_landmarker_result