init
Browse files- .gitattributes +1 -0
- .gitignore +1 -0
- README.md +1 -1
- app.py +231 -0
- demo_footer.html +3 -0
- demo_header.html +15 -0
- demo_tools.html +4 -0
- examples/00004200.jpg +0 -0
- face_landmarker.task +3 -0
- face_landmarker.task.txt +8 -0
- mp_box.py +133 -0
- requirements.txt +4 -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 @@
|
|
|
|
|
1 |
+
__pycache__
|
README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
emoji: π
|
4 |
colorFrom: indigo
|
5 |
colorTo: indigo
|
|
|
1 |
---
|
2 |
+
title: mediapipe-face-crop-and-replace
|
3 |
emoji: π
|
4 |
colorFrom: indigo
|
5 |
colorTo: indigo
|
app.py
ADDED
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import spaces
|
2 |
+
import gradio as gr
|
3 |
+
import subprocess
|
4 |
+
from PIL import Image,ImageEnhance,ImageFilter
|
5 |
+
import json
|
6 |
+
|
7 |
+
import mp_box
|
8 |
+
'''
|
9 |
+
Face landmark detection based Face Detection.
|
10 |
+
https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker
|
11 |
+
from model card
|
12 |
+
https://storage.googleapis.com/mediapipe-assets/MediaPipe%20BlazeFace%20Model%20Card%20(Short%20Range).pdf
|
13 |
+
Licensed Apache License, Version 2.0
|
14 |
+
Train with google's dataset(more detail see model card)
|
15 |
+
|
16 |
+
Not Face Detector based
|
17 |
+
https://ai.google.dev/edge/mediapipe/solutions/vision/face_detector
|
18 |
+
|
19 |
+
Bacause this is part of getting-landmark program and need control face edge.
|
20 |
+
So I don't know which one is better.never compare these.
|
21 |
+
'''
|
22 |
+
|
23 |
+
def select_box(boxes,box_type):
|
24 |
+
if box_type == "type-3":
|
25 |
+
box = boxes[2]
|
26 |
+
elif box_type =="type-2":
|
27 |
+
box = boxes[1]
|
28 |
+
elif box_type =="type-1":
|
29 |
+
box = boxes[0]
|
30 |
+
else:#never happen
|
31 |
+
box=[0,0,image.size[0],image.size[1]]
|
32 |
+
box_width = box[2]
|
33 |
+
box_height = box[3]
|
34 |
+
box = mp_box.xywh_to_xyxy(box)
|
35 |
+
return box,box_width,box_height
|
36 |
+
|
37 |
+
def process_images(image,replace_image=None,replace_image_need_crop=False,box_type="type-3",fill_color_mode=False,fill_color="black",custom_color="rgba(255,255,255,1)",image_size=1024,filter_image=False,filter_value="Sharpen",progress=gr.Progress(track_tqdm=True)):
|
38 |
+
if image == None:
|
39 |
+
raise gr.Error("Need Image")
|
40 |
+
|
41 |
+
# choose box
|
42 |
+
boxes,mp_image,face_landmarker_result = mp_box.mediapipe_to_box(image)
|
43 |
+
box,box_width,box_height = select_box(boxes,box_type)
|
44 |
+
|
45 |
+
# replace-mode
|
46 |
+
if replace_image!=None:
|
47 |
+
print("replace mode")
|
48 |
+
|
49 |
+
if replace_image_need_crop:
|
50 |
+
replace_boxes,mp_image,face_landmarker_result = mp_box.mediapipe_to_box(replace_image)
|
51 |
+
replace_box,replace_box_width,replace_box_height = select_box(replace_boxes,box_type)
|
52 |
+
|
53 |
+
if fill_color_mode:
|
54 |
+
if replace_image_need_crop:
|
55 |
+
cropped = replace_image.crop(replace_box)
|
56 |
+
cropped.resize(box_width,box_height)
|
57 |
+
else:
|
58 |
+
cropped = replace_image.crop(box)
|
59 |
+
image.paste(cropped,[box[0],box[1]])
|
60 |
+
return image
|
61 |
+
else:#scale mode
|
62 |
+
if replace_image_need_crop:
|
63 |
+
replace_image = replace_image.crop(replace_box)
|
64 |
+
replace_resized = replace_image.resize((box_width,box_height),Image.Resampling.LANCZOS)
|
65 |
+
image.paste(replace_resized,[box[0],box[1]])
|
66 |
+
return image
|
67 |
+
|
68 |
+
|
69 |
+
# crop-mode
|
70 |
+
if fill_color_mode:
|
71 |
+
# choose color
|
72 |
+
color_map={
|
73 |
+
"black":[0,0,0,1],
|
74 |
+
"white":[255,255,255,1],
|
75 |
+
"red":[255,0,0,1],
|
76 |
+
"brown":[92,33,31,1],
|
77 |
+
"pink":[255,192,203,1],
|
78 |
+
}
|
79 |
+
if fill_color == "custom":
|
80 |
+
color_value = custom_color.strip("rgba()").split(",")
|
81 |
+
color_value[0] = int(float(color_value[0]))
|
82 |
+
color_value[1] = int(float(color_value[1]))
|
83 |
+
color_value[2] = int(float(color_value[2]))
|
84 |
+
else:
|
85 |
+
color_value = color_map[fill_color]
|
86 |
+
|
87 |
+
cropped = image.crop(box)
|
88 |
+
|
89 |
+
img = Image.new('RGBA', image.size, (color_value[0], color_value[1], color_value[2]))
|
90 |
+
img.paste(cropped,[box[0],box[1]])
|
91 |
+
return img
|
92 |
+
else:
|
93 |
+
#scale up mode
|
94 |
+
cropped = image.crop(box)
|
95 |
+
resized = resize_image_by_max_dimension(cropped,image_size)
|
96 |
+
filter_map={
|
97 |
+
"Blur":ImageFilter.BLUR,"Smooth More":ImageFilter.SMOOTH_MORE,"Smooth":ImageFilter.SMOOTH,"Sharpen":ImageFilter.SHARPEN,"Edge Enhance":ImageFilter.EDGE_ENHANCE,"Edge Enhance More":ImageFilter.EDGE_ENHANCE_MORE
|
98 |
+
}
|
99 |
+
if filter_value not in filter_map:
|
100 |
+
raise gr.Error(f"filter {filter_value} not found")
|
101 |
+
if filter_image:
|
102 |
+
|
103 |
+
#resized = resized.filter(ImageFilter.SHARPEN)
|
104 |
+
#Gimp's weak 0.1-0.2?
|
105 |
+
enhancer = ImageEnhance.Sharpness(resized)
|
106 |
+
resized = resized.filter(filter_map[filter_value])
|
107 |
+
#resized = enhancer.enhance(sharpen_value)
|
108 |
+
|
109 |
+
|
110 |
+
return resized
|
111 |
+
|
112 |
+
|
113 |
+
def resize_image_by_max_dimension(image, max_size, resampling=Image.Resampling.BICUBIC):
|
114 |
+
image_width, image_height = image.size
|
115 |
+
|
116 |
+
max_dimension = max(image_width, image_height)
|
117 |
+
|
118 |
+
ratio = max_size / max_dimension
|
119 |
+
|
120 |
+
new_width = int(image_width * ratio)
|
121 |
+
new_height = int(image_height * ratio)
|
122 |
+
|
123 |
+
return image.resize((new_width, new_height), resampling)
|
124 |
+
|
125 |
+
|
126 |
+
def read_file(file_path: str) -> str:
|
127 |
+
"""read the text of target file
|
128 |
+
"""
|
129 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
130 |
+
content = f.read()
|
131 |
+
|
132 |
+
return content
|
133 |
+
|
134 |
+
css="""
|
135 |
+
#col-left {
|
136 |
+
margin: 0 auto;
|
137 |
+
max-width: 640px;
|
138 |
+
}
|
139 |
+
#col-right {
|
140 |
+
margin: 0 auto;
|
141 |
+
max-width: 640px;
|
142 |
+
}
|
143 |
+
.grid-container {
|
144 |
+
display: flex;
|
145 |
+
align-items: center;
|
146 |
+
justify-content: center;
|
147 |
+
gap:10px
|
148 |
+
}
|
149 |
+
|
150 |
+
.image {
|
151 |
+
width: 128px;
|
152 |
+
height: 128px;
|
153 |
+
object-fit: cover;
|
154 |
+
}
|
155 |
+
|
156 |
+
.text {
|
157 |
+
font-size: 16px;
|
158 |
+
}
|
159 |
+
"""
|
160 |
+
|
161 |
+
#css=css,
|
162 |
+
def update_button_label(image):
|
163 |
+
if image == None:
|
164 |
+
return gr.Button(visible=bool(0)),gr.Button(visible=bool(1))
|
165 |
+
else:
|
166 |
+
return gr.Button(visible=bool(1)),gr.Row(visible=bool(0))
|
167 |
+
|
168 |
+
def update_visible(fill_color_mode):
|
169 |
+
if fill_color_mode:
|
170 |
+
return gr.Row(visible=bool(0)),gr.Row(visible=bool(1))
|
171 |
+
else:
|
172 |
+
return gr.Row(visible=bool(1)),gr.Row(visible=bool(0))
|
173 |
+
with gr.Blocks(css=css, elem_id="demo-container") as demo:
|
174 |
+
with gr.Column():
|
175 |
+
gr.HTML(read_file("demo_header.html"))
|
176 |
+
gr.HTML(read_file("demo_tools.html"))
|
177 |
+
with gr.Row():
|
178 |
+
with gr.Column():
|
179 |
+
image = gr.Image(sources=['upload','clipboard'],image_mode='RGB',elem_id="image_upload", type="pil", label="Upload")
|
180 |
+
box_type = gr.Dropdown(label="box-type",value="type-3",choices=["type-1","type-2","type-3"])
|
181 |
+
with gr.Row(elem_id="prompt-container", equal_height=False):
|
182 |
+
with gr.Row():
|
183 |
+
btn1 = gr.Button("Face Crop", elem_id="run_button",variant="primary")
|
184 |
+
btn2 = gr.Button("Face Replace", elem_id="run_button2",variant="primary",visible=False)
|
185 |
+
|
186 |
+
replace_image = gr.Image(sources=['upload','clipboard'],image_mode='RGB',elem_id="replace_upload", type="pil", label="replace image")
|
187 |
+
replace_image_need_crop = gr.Checkbox(label="Replace image need crop",value=False)
|
188 |
+
replace_image.change(update_visible,replace_image,[btn1,btn2])
|
189 |
+
with gr.Accordion(label="Advanced Settings", open=False):
|
190 |
+
fill_color_mode = gr.Checkbox(label="Fill Color Mode",value=False)
|
191 |
+
row1 = gr.Row(equal_height=True)
|
192 |
+
row2 = gr.Row(equal_height=True,visible=False)
|
193 |
+
fill_color_mode.change(update_visible,fill_color_mode,[row1,row2])
|
194 |
+
|
195 |
+
with row1:
|
196 |
+
image_size = gr.Slider(
|
197 |
+
label="Image Size",info = "cropped face size",
|
198 |
+
minimum=8,
|
199 |
+
maximum=2048,
|
200 |
+
step=1,
|
201 |
+
value=1024,
|
202 |
+
interactive=True)
|
203 |
+
|
204 |
+
filter_image = gr.Checkbox(label="Filter image")
|
205 |
+
filter_value = gr.Dropdown(label="Filter",value="Sharpen",choices=["Blur","Smooth More","Smooth","Sharpen","Edge Enhance","Edge Enhance More"])
|
206 |
+
with row2:
|
207 |
+
|
208 |
+
fill_color = gr.Dropdown(label="fill color",choices=["black","white","red","brown","pink","custom"])
|
209 |
+
custom_color = gr.ColorPicker(label="custom color",value="rgba(250, 218, 205, 1)")
|
210 |
+
|
211 |
+
|
212 |
+
with gr.Column():
|
213 |
+
image_out = gr.Image(label="Output", elem_id="output-img")
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
|
218 |
+
|
219 |
+
|
220 |
+
gr.on(
|
221 |
+
[btn1.click,btn2.click],
|
222 |
+
fn=process_images, inputs=[image,replace_image,replace_image_need_crop,box_type,fill_color_mode,fill_color,custom_color,image_size,filter_image,filter_value], outputs =[image_out], api_name='infer'
|
223 |
+
)
|
224 |
+
gr.Examples(
|
225 |
+
examples =["examples/00004200.jpg"],
|
226 |
+
inputs=[image]
|
227 |
+
)
|
228 |
+
gr.HTML(read_file("demo_footer.html"))
|
229 |
+
|
230 |
+
if __name__ == "__main__":
|
231 |
+
demo.launch()
|
demo_footer.html
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
<div>
|
2 |
+
<P> Images are generated with <a href="https://huggingface.co/black-forest-labs/FLUX.1-schnell">FLUX.1-schnell</a> and licensed under <a href="http://www.apache.org/licenses/LICENSE-2.0">the Apache 2.0 License</a>
|
3 |
+
</div>
|
demo_header.html
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div style="text-align: center;">
|
2 |
+
<h1>
|
3 |
+
Mediapipe Face Crop and Replace
|
4 |
+
</h1>
|
5 |
+
<div class="grid-container">
|
6 |
+
<img src="https://akjava.github.io/AIDiagramChatWithVoice-FaceCharacter/webp/128/00191245_09_00002200.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 |
+
|
11 |
+
|
12 |
+
</p>
|
13 |
+
</div>
|
14 |
+
|
15 |
+
</div>
|
demo_tools.html
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div style="text-align: center;">
|
2 |
+
<p><a href="https://huggingface.co/spaces/Akjava/mediapipe-face-detect">Mediapipe Face detector</a></p>
|
3 |
+
<p></p>
|
4 |
+
</div>
|
examples/00004200.jpg
ADDED
![]() |
face_landmarker.task
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:64184e229b263107bc2b804c6625db1341ff2bb731874b0bcc2fe6544e0bc9ff
|
3 |
+
size 3758596
|
face_landmarker.task.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Face landmark detection
|
2 |
+
https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker
|
3 |
+
|
4 |
+
model card page is
|
5 |
+
https://storage.googleapis.com/mediapipe-assets/MediaPipe%20BlazeFace%20Model%20Card%20(Short%20Range).pdf
|
6 |
+
|
7 |
+
license is Apache2.0
|
8 |
+
https://www.apache.org/licenses/LICENSE-2.0.html
|
mp_box.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mediapipe as mp
|
2 |
+
from mediapipe.tasks import python
|
3 |
+
from mediapipe.tasks.python import vision
|
4 |
+
from mediapipe.framework.formats import landmark_pb2
|
5 |
+
from mediapipe import solutions
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
# for X,Y,W,H to x1,y1,x2,y2(Left-top,right-bottom style)
|
9 |
+
def xywh_to_xyxy(box):
|
10 |
+
return [box[0],box[1],box[0]+box[2],box[1]+box[3]]
|
11 |
+
|
12 |
+
def convert_to_box(face_landmarks_list,indices,w=1024,h=1024):
|
13 |
+
x1=w
|
14 |
+
y1=h
|
15 |
+
x2=0
|
16 |
+
y2=0
|
17 |
+
for index in indices:
|
18 |
+
x=min(w,max(0,(face_landmarks_list[0][index].x*w)))
|
19 |
+
y=min(h,max(0,(face_landmarks_list[0][index].y*h)))
|
20 |
+
if x<x1:
|
21 |
+
x1=x
|
22 |
+
|
23 |
+
if y<y1:
|
24 |
+
y1=y
|
25 |
+
|
26 |
+
if x>x2:
|
27 |
+
x2=x
|
28 |
+
if y>y2:
|
29 |
+
y2=y
|
30 |
+
|
31 |
+
|
32 |
+
return [int(x1),int(y1),int(x2-x1),int(y2-y1)]
|
33 |
+
|
34 |
+
|
35 |
+
def box_to_square(bbox):
|
36 |
+
box=list(bbox)
|
37 |
+
if box[2]>box[3]:
|
38 |
+
diff = box[2]-box[3]
|
39 |
+
box[3]+=diff
|
40 |
+
box[1]-=diff/2
|
41 |
+
elif box[3]>box[2]:
|
42 |
+
diff = box[3]-box[2]
|
43 |
+
box[2]+=diff
|
44 |
+
box[0]-=diff/2
|
45 |
+
return box
|
46 |
+
|
47 |
+
|
48 |
+
def face_landmark_result_to_box(face_landmarker_result,width=1024,height=1024):
|
49 |
+
face_landmarks_list = face_landmarker_result.face_landmarks
|
50 |
+
|
51 |
+
|
52 |
+
full_indices = list(range(456))
|
53 |
+
|
54 |
+
MIDDLE_FOREHEAD = 151
|
55 |
+
BOTTOM_CHIN_EX = 152
|
56 |
+
BOTTOM_CHIN = 175
|
57 |
+
CHIN_TO_MIDDLE_FOREHEAD = [200,14,1,6,18,9]
|
58 |
+
MOUTH_BOTTOM = [202,200,422]
|
59 |
+
EYEBROW_CHEEK_LEFT_RIGHT = [46,226,50,1,280,446,276]
|
60 |
+
|
61 |
+
LEFT_HEAD_OUTER_EX = 251 #on side face almost same as full
|
62 |
+
LEFT_HEAD_OUTER = 301
|
63 |
+
LEFT_EYE_OUTER_EX = 356
|
64 |
+
LEFT_EYE_OUTER = 264
|
65 |
+
LEFT_MOUTH_OUTER_EX = 288
|
66 |
+
LEFT_MOUTH_OUTER = 288
|
67 |
+
LEFT_CHIN_OUTER = 435
|
68 |
+
RIGHT_HEAD_OUTER_EX = 21
|
69 |
+
RIGHT_HEAD_OUTER = 71
|
70 |
+
RIGHT_EYE_OUTER_EX = 127
|
71 |
+
RIGHT_EYE_OUTER = 34
|
72 |
+
RIGHT_MOUTH_OUTER_EX = 58
|
73 |
+
RIGHT_MOUTH_OUTER = 215
|
74 |
+
RIGHT_CHIN_OUTER = 150
|
75 |
+
|
76 |
+
# TODO naming line
|
77 |
+
min_indices=CHIN_TO_MIDDLE_FOREHEAD+EYEBROW_CHEEK_LEFT_RIGHT+MOUTH_BOTTOM
|
78 |
+
|
79 |
+
chin_to_brow_indices = [LEFT_CHIN_OUTER,LEFT_MOUTH_OUTER,LEFT_EYE_OUTER,LEFT_HEAD_OUTER,MIDDLE_FOREHEAD,RIGHT_HEAD_OUTER,RIGHT_EYE_OUTER,RIGHT_MOUTH_OUTER,RIGHT_CHIN_OUTER,BOTTOM_CHIN]+min_indices
|
80 |
+
|
81 |
+
box1 = convert_to_box(face_landmarks_list,min_indices,width,height)
|
82 |
+
box2 = convert_to_box(face_landmarks_list,chin_to_brow_indices,width,height)
|
83 |
+
box3 = convert_to_box(face_landmarks_list,full_indices,width,height)
|
84 |
+
#print(box)
|
85 |
+
|
86 |
+
return [box1,box2,box3,box_to_square(box1),box_to_square(box2),box_to_square(box3)]
|
87 |
+
|
88 |
+
|
89 |
+
def draw_landmarks_on_image(detection_result,rgb_image):
|
90 |
+
face_landmarks_list = detection_result.face_landmarks
|
91 |
+
annotated_image = np.copy(rgb_image)
|
92 |
+
|
93 |
+
# Loop through the detected faces to visualize.
|
94 |
+
for idx in range(len(face_landmarks_list)):
|
95 |
+
face_landmarks = face_landmarks_list[idx]
|
96 |
+
|
97 |
+
# Draw the face landmarks.
|
98 |
+
face_landmarks_proto = landmark_pb2.NormalizedLandmarkList()
|
99 |
+
face_landmarks_proto.landmark.extend([
|
100 |
+
landmark_pb2.NormalizedLandmark(x=landmark.x, y=landmark.y, z=landmark.z) for landmark in face_landmarks
|
101 |
+
])
|
102 |
+
|
103 |
+
solutions.drawing_utils.draw_landmarks(
|
104 |
+
image=annotated_image,
|
105 |
+
landmark_list=face_landmarks_proto,
|
106 |
+
connections=mp.solutions.face_mesh.FACEMESH_TESSELATION,
|
107 |
+
landmark_drawing_spec=None,
|
108 |
+
connection_drawing_spec=mp.solutions.drawing_styles
|
109 |
+
.get_default_face_mesh_tesselation_style())
|
110 |
+
|
111 |
+
return annotated_image
|
112 |
+
|
113 |
+
def mediapipe_to_box(image_data,model_path="face_landmarker.task"):
|
114 |
+
BaseOptions = mp.tasks.BaseOptions
|
115 |
+
FaceLandmarker = mp.tasks.vision.FaceLandmarker
|
116 |
+
FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions
|
117 |
+
VisionRunningMode = mp.tasks.vision.RunningMode
|
118 |
+
|
119 |
+
options = FaceLandmarkerOptions(
|
120 |
+
base_options=BaseOptions(model_asset_path=model_path),
|
121 |
+
running_mode=VisionRunningMode.IMAGE
|
122 |
+
,min_face_detection_confidence=0, min_face_presence_confidence=0
|
123 |
+
)
|
124 |
+
|
125 |
+
|
126 |
+
with FaceLandmarker.create_from_options(options) as landmarker:
|
127 |
+
if isinstance(image_data,str):
|
128 |
+
mp_image = mp.Image.create_from_file(image_data)
|
129 |
+
else:
|
130 |
+
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=np.asarray(image_data))
|
131 |
+
face_landmarker_result = landmarker.detect(mp_image)
|
132 |
+
boxes = face_landmark_result_to_box(face_landmarker_result,mp_image.width,mp_image.height)
|
133 |
+
return boxes,mp_image,face_landmarker_result
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
numpy
|
2 |
+
torch
|
3 |
+
spaces
|
4 |
+
mediapipe
|