Spaces:
Running
Running
init
Browse files- .gitattributes +1 -0
- .gitignore +2 -0
- README.md +1 -1
- app.py +133 -0
- demo_footer.html +3 -0
- demo_header.html +17 -0
- demo_tools.html +10 -0
- examples/00002062.jpg +0 -0
- examples/00002062.webp +0 -0
- examples/00002200.jpg +0 -0
- examples/00002200.webp +0 -0
- examples/00003245_00.jpg +0 -0
- examples/00003245_00.webp +0 -0
- examples/00005259.jpg +0 -0
- examples/00005259.webp +0 -0
- examples/00018022.jpg +0 -0
- examples/00018022.webp +0 -0
- examples/00039259.jpg +0 -0
- examples/00039259.webp +0 -0
- examples/00100265.jpg +0 -0
- examples/00100265.webp +0 -0
- examples/00824006.jpg +0 -0
- examples/00824006.webp +0 -0
- examples/00824008.jpg +0 -0
- examples/00824008.webp +0 -0
- examples/00825000.jpg +0 -0
- examples/00825000.webp +0 -0
- examples/00826007.jpg +0 -0
- examples/00826007.webp +0 -0
- examples/00827009.jpg +0 -0
- examples/00827009.webp +0 -0
- examples/00828003.jpg +0 -0
- examples/02316230.jpg +0 -0
- examples/02316230.webp +0 -0
- examples/_00039259.webp +0 -0
- examples/img-above.jpg +0 -0
- examples/img-above.webp +0 -0
- face_landmarker.task +3 -0
- face_landmarker.task.txt +8 -0
- face_mesh3d.py +49 -0
- face_mesh_rotation.py +354 -0
- glibvision/common_utils.py +112 -0
- glibvision/cv2_utils.py +139 -0
- glibvision/draw_utils.py +42 -0
- glibvision/glandmark_utils.py +48 -0
- glibvision/numpy_utils.py +110 -0
- glibvision/pil_utils.py +38 -0
- gradio_utils.py +68 -0
- mp_triangles.py +1014 -0
- mp_utils.py +140 -0
.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
|