Spaces:
Running
Running
init
Browse files- .gitattributes +1 -0
- .gitignore +1 -0
- README.md +1 -1
- app.py +130 -0
- demo_footer.html +3 -0
- demo_header.html +13 -0
- demo_tools.html +10 -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
@@ -8,7 +8,7 @@ sdk_version: 5.5.0
|
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
license: mit
|
11 |
-
short_description: face detection 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: face detection with mediapipe-landmaker and output multi-size
|
12 |
---
|
13 |
|
14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import spaces
|
2 |
+
import gradio as gr
|
3 |
+
import subprocess
|
4 |
+
from PIL import Image
|
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 |
+
|
17 |
+
Not Face Detector based
|
18 |
+
https://ai.google.dev/edge/mediapipe/solutions/vision/face_detector
|
19 |
+
|
20 |
+
Bacause this is part of getting-landmark program and need control face edge.
|
21 |
+
So I don't know which one is better.never compare these.
|
22 |
+
'''
|
23 |
+
#@spaces.GPU(duration=120)
|
24 |
+
def process_images(image,no_mesh_draw=False,square_shape=False,progress=gr.Progress(track_tqdm=True)):
|
25 |
+
progress(0, desc="Start Mediapipe")
|
26 |
+
|
27 |
+
boxes,mp_image,face_landmarker_result = mp_box.mediapipe_to_box(image)
|
28 |
+
if no_mesh_draw:
|
29 |
+
annotated_image = image
|
30 |
+
else:
|
31 |
+
annotated_image = mp_box.draw_landmarks_on_image(face_landmarker_result,image)
|
32 |
+
annotation_boxes = []
|
33 |
+
|
34 |
+
jsons ={
|
35 |
+
|
36 |
+
}
|
37 |
+
index = 1
|
38 |
+
|
39 |
+
print(boxes)
|
40 |
+
if square_shape:
|
41 |
+
xy_boxes = boxes[3:]
|
42 |
+
else:
|
43 |
+
xy_boxes = boxes[:3]
|
44 |
+
|
45 |
+
print(len(xy_boxes))
|
46 |
+
for box in xy_boxes:
|
47 |
+
label=f"type-{index}"
|
48 |
+
print(box)
|
49 |
+
print(mp_box.xywh_to_xyxy(box))
|
50 |
+
annotation_boxes.append([mp_box.xywh_to_xyxy(box),label])
|
51 |
+
index+=1
|
52 |
+
|
53 |
+
jsons[label] = boxes[index-1]
|
54 |
+
print(label)
|
55 |
+
|
56 |
+
#print(annotation_boxes)
|
57 |
+
formatted_json = json.dumps(jsons, indent=1)
|
58 |
+
#return image
|
59 |
+
return [annotated_image,annotation_boxes],formatted_json
|
60 |
+
|
61 |
+
|
62 |
+
def read_file(file_path: str) -> str:
|
63 |
+
"""read the text of target file
|
64 |
+
"""
|
65 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
66 |
+
content = f.read()
|
67 |
+
|
68 |
+
return content
|
69 |
+
|
70 |
+
css="""
|
71 |
+
#col-left {
|
72 |
+
margin: 0 auto;
|
73 |
+
max-width: 640px;
|
74 |
+
}
|
75 |
+
#col-right {
|
76 |
+
margin: 0 auto;
|
77 |
+
max-width: 640px;
|
78 |
+
}
|
79 |
+
.grid-container {
|
80 |
+
display: flex;
|
81 |
+
align-items: center;
|
82 |
+
justify-content: center;
|
83 |
+
gap:10px
|
84 |
+
}
|
85 |
+
|
86 |
+
.image {
|
87 |
+
width: 128px;
|
88 |
+
height: 128px;
|
89 |
+
object-fit: cover;
|
90 |
+
}
|
91 |
+
|
92 |
+
.text {
|
93 |
+
font-size: 16px;
|
94 |
+
}
|
95 |
+
"""
|
96 |
+
|
97 |
+
#css=css,
|
98 |
+
|
99 |
+
with gr.Blocks(css=css, elem_id="demo-container") as demo:
|
100 |
+
with gr.Column():
|
101 |
+
gr.HTML(read_file("demo_header.html"))
|
102 |
+
gr.HTML(read_file("demo_tools.html"))
|
103 |
+
with gr.Row():
|
104 |
+
with gr.Column():
|
105 |
+
image = gr.Image(height=800,sources=['upload','clipboard'],image_mode='RGB',elem_id="image_upload", type="pil", label="Upload")
|
106 |
+
with gr.Row(elem_id="prompt-container", equal_height=False):
|
107 |
+
with gr.Row():
|
108 |
+
btn = gr.Button("Face Detect", elem_id="run_button")
|
109 |
+
|
110 |
+
with gr.Accordion(label="Advanced Settings", open=False):
|
111 |
+
with gr.Row( equal_height=True):
|
112 |
+
no_mesh_draw = gr.Checkbox(label="No Mesh Drawing")
|
113 |
+
square_shape = gr.Checkbox(label="square shape")
|
114 |
+
|
115 |
+
with gr.Column():
|
116 |
+
image_out = gr.AnnotatedImage(label="Output", elem_id="output-img")
|
117 |
+
text_out = gr.TextArea(label="JSON-Output")
|
118 |
+
|
119 |
+
|
120 |
+
|
121 |
+
|
122 |
+
btn.click(fn=process_images, inputs=[image,no_mesh_draw], outputs =[image_out,text_out], api_name='infer')
|
123 |
+
gr.Examples(
|
124 |
+
examples =["examples/00004200.jpg"],
|
125 |
+
inputs=[image]
|
126 |
+
)
|
127 |
+
gr.HTML(read_file("demo_footer.html"))
|
128 |
+
|
129 |
+
if __name__ == "__main__":
|
130 |
+
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,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div style="text-align: center;">
|
2 |
+
<h1>
|
3 |
+
Mediapipe Face Detect
|
4 |
+
</h1>
|
5 |
+
<div class="grid-container">
|
6 |
+
<img src="https://akjava.github.io/AIDiagramChatWithVoice-FaceCharacter/webp/128/00191245_09_00002200.webp" alt="Flux.1-schnell-WebP3Frame-TalkingAnimation" class="image">
|
7 |
+
This Space use <a href="https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker">Mediapipe face landmark detection model</a> which licensed under <a href="http://www.apache.org/licenses/LICENSE-2.0">the Apache 2.0 License</a>
|
8 |
+
<p class="text">
|
9 |
+
|
10 |
+
</p>
|
11 |
+
</div>
|
12 |
+
|
13 |
+
</div>
|
demo_tools.html
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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><a href="https://huggingface.co/spaces/Akjava/WebPTalkHead">[WebP-3F-TH]</a>
|
4 |
+
<a href="https://huggingface.co/spaces/Akjava/flux1-schnell-img2img">[Flux1-Img2Img(GPU)]</a>
|
5 |
+
<a href="https://huggingface.co/spaces/Akjava/flux1-schnell-mask-inpaint">[Flux1-Inpaint(GPU)]</a>
|
6 |
+
<a href="https://huggingface.co/spaces/Akjava/OpenCVInpaintCPU">[OpenCV-Inapint]</a>
|
7 |
+
<a href="https://huggingface.co/spaces/Akjava/Simple-Whitebalance-Image">[Whitebalance]</a>
|
8 |
+
<a href="https://huggingface.co/spaces/Akjava/Simple-Mask-Paste-Image">[Paste Image]</a>
|
9 |
+
<a href=" https://huggingface.co/spaces/Akjava/WebP-Resize-Convert">[WebP Resize Convert]</a></p>
|
10 |
+
</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
|