Spaces:
Sleeping
Sleeping
Siyun He
commited on
Commit
·
7aff7cc
1
Parent(s):
e9eb9ec
add face shape detection
Browse files- app.py +39 -21
- requirements.txt +2 -1
app.py
CHANGED
@@ -3,12 +3,17 @@ import cvzone
|
|
3 |
import numpy as np
|
4 |
import os
|
5 |
import gradio as gr
|
|
|
6 |
from datetime import datetime
|
7 |
|
8 |
# Load the YuNet model
|
9 |
model_path = 'face_detection_yunet_2023mar.onnx'
|
10 |
face_detector = cv2.FaceDetectorYN.create(model_path, "", (320, 320))
|
11 |
|
|
|
|
|
|
|
|
|
12 |
# Initialize the glass number
|
13 |
num = 1
|
14 |
overlay = cv2.imread(f'glasses/glass{num}.png', cv2.IMREAD_UNCHANGED)
|
@@ -20,6 +25,19 @@ def count_files_in_directory(directory):
|
|
20 |
file_count += len(files)
|
21 |
return file_count
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
directory_path = 'glasses'
|
24 |
total_glass_num = count_files_in_directory(directory_path)
|
25 |
|
@@ -32,62 +50,59 @@ def change_glasses():
|
|
32 |
overlay = cv2.imread(f'glasses/glass{num}.png', cv2.IMREAD_UNCHANGED)
|
33 |
return overlay
|
34 |
|
35 |
-
# Process frame for overlay
|
36 |
def process_frame(frame):
|
37 |
global overlay
|
38 |
-
# Ensure the frame is writable
|
39 |
frame = np.array(frame, copy=True)
|
40 |
-
|
41 |
height, width = frame.shape[:2]
|
42 |
face_detector.setInputSize((width, height))
|
43 |
_, faces = face_detector.detect(frame)
|
44 |
|
|
|
45 |
if faces is not None:
|
46 |
for face in faces:
|
47 |
x, y, w, h = face[:4].astype(int)
|
48 |
-
face_landmarks = face[4:14].reshape(5, 2).astype(int)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
# Get the nose position
|
51 |
nose_x, nose_y = face_landmarks[2].astype(int)
|
52 |
-
# Left and right eye positions
|
53 |
left_eye_x, left_eye_y = face_landmarks[0].astype(int)
|
54 |
right_eye_x, right_eye_y = face_landmarks[1].astype(int)
|
55 |
|
56 |
-
# Calculate the midpoint between the eyes
|
57 |
eye_center_x = (left_eye_x + right_eye_x) // 2
|
58 |
eye_center_y = (left_eye_y + right_eye_y) // 2
|
59 |
|
60 |
-
# Calculate the angle of rotation
|
61 |
delta_x = right_eye_x - left_eye_x
|
62 |
delta_y = right_eye_y - left_eye_y
|
63 |
angle = np.degrees(np.arctan2(delta_y, delta_x))
|
64 |
-
|
65 |
-
# Negate the angle to rotate in the opposite direction
|
66 |
angle = -angle
|
67 |
|
68 |
-
# Resize the overlay
|
69 |
overlay_resize = cv2.resize(overlay, (int(w * 1.15), int(h * 0.8)))
|
70 |
-
|
71 |
-
# Rotate the overlay
|
72 |
overlay_center = (overlay_resize.shape[1] // 2, overlay_resize.shape[0] // 2)
|
73 |
rotation_matrix = cv2.getRotationMatrix2D(overlay_center, angle, 1.0)
|
74 |
overlay_rotated = cv2.warpAffine(
|
75 |
-
overlay_resize, rotation_matrix,
|
76 |
(overlay_resize.shape[1], overlay_resize.shape[0]),
|
77 |
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0, 0)
|
78 |
)
|
79 |
|
80 |
-
# Calculate the position to center the glasses on the eyes
|
81 |
overlay_x = eye_center_x - overlay_rotated.shape[1] // 2
|
82 |
overlay_y = eye_center_y - overlay_rotated.shape[0] // 2
|
83 |
|
84 |
-
# Overlay the glasses
|
85 |
try:
|
86 |
frame = cvzone.overlayPNG(frame, overlay_rotated, [overlay_x, overlay_y])
|
87 |
except Exception as e:
|
88 |
print(f"Error overlaying glasses: {e}")
|
89 |
-
|
90 |
-
return frame
|
91 |
|
92 |
# Transform function
|
93 |
def transform_cv2(frame, transform):
|
@@ -148,9 +163,9 @@ def save_frame(frame):
|
|
148 |
|
149 |
# Gradio webcam input
|
150 |
def webcam_input(frame, transform):
|
151 |
-
frame = process_frame(frame)
|
152 |
frame = transform_cv2(frame, transform)
|
153 |
-
return frame
|
154 |
|
155 |
# Gradio Interface
|
156 |
with gr.Blocks() as demo:
|
@@ -159,13 +174,16 @@ with gr.Blocks() as demo:
|
|
159 |
transform = gr.Dropdown(choices=["cartoon", "edges", "none"],
|
160 |
value="none", label="Transformation")
|
161 |
input_img = gr.Image(sources=["webcam"], type="numpy", streaming=True)
|
|
|
162 |
next_button = gr.Button("Next Glasses")
|
163 |
-
save_button = gr.Button("Save as a Picture")
|
164 |
-
|
|
|
165 |
with gr.Row():
|
166 |
next_button.click(change_glasses, [], [])
|
167 |
with gr.Row():
|
168 |
save_button.click(save_frame, [input_img], [])
|
|
|
169 |
if __name__ == "__main__":
|
170 |
demo.launch(share=True)
|
171 |
|
|
|
3 |
import numpy as np
|
4 |
import os
|
5 |
import gradio as gr
|
6 |
+
import dlib
|
7 |
from datetime import datetime
|
8 |
|
9 |
# Load the YuNet model
|
10 |
model_path = 'face_detection_yunet_2023mar.onnx'
|
11 |
face_detector = cv2.FaceDetectorYN.create(model_path, "", (320, 320))
|
12 |
|
13 |
+
# Load dlib's shape predictor
|
14 |
+
shape_predictor_path = 'shape_predictor_68_face_landmarks.dat'
|
15 |
+
shape_predictor = dlib.shape_predictor(shape_predictor_path)
|
16 |
+
|
17 |
# Initialize the glass number
|
18 |
num = 1
|
19 |
overlay = cv2.imread(f'glasses/glass{num}.png', cv2.IMREAD_UNCHANGED)
|
|
|
25 |
file_count += len(files)
|
26 |
return file_count
|
27 |
|
28 |
+
# Determine face shape
|
29 |
+
def determine_face_shape(landmarks):
|
30 |
+
# Example logic to determine face shape based on landmarks
|
31 |
+
# This is a simplified version and may need adjustments
|
32 |
+
jaw_width = np.linalg.norm(landmarks[0] - landmarks[16])
|
33 |
+
face_height = np.linalg.norm(landmarks[8] - landmarks[27])
|
34 |
+
if jaw_width / face_height > 1.5:
|
35 |
+
return "Round"
|
36 |
+
elif jaw_width / face_height < 1.2:
|
37 |
+
return "Oval"
|
38 |
+
else:
|
39 |
+
return "Square"
|
40 |
+
|
41 |
directory_path = 'glasses'
|
42 |
total_glass_num = count_files_in_directory(directory_path)
|
43 |
|
|
|
50 |
overlay = cv2.imread(f'glasses/glass{num}.png', cv2.IMREAD_UNCHANGED)
|
51 |
return overlay
|
52 |
|
53 |
+
# Process frame for overlay and face shape detection
|
54 |
def process_frame(frame):
|
55 |
global overlay
|
|
|
56 |
frame = np.array(frame, copy=True)
|
|
|
57 |
height, width = frame.shape[:2]
|
58 |
face_detector.setInputSize((width, height))
|
59 |
_, faces = face_detector.detect(frame)
|
60 |
|
61 |
+
face_shape = "Unknown"
|
62 |
if faces is not None:
|
63 |
for face in faces:
|
64 |
x, y, w, h = face[:4].astype(int)
|
65 |
+
face_landmarks = face[4:14].reshape(5, 2).astype(int)
|
66 |
+
|
67 |
+
# Convert to dlib rectangle
|
68 |
+
dlib_rect = dlib.rectangle(x, y, x + w, y + h)
|
69 |
+
landmarks = shape_predictor(frame, dlib_rect)
|
70 |
+
landmarks = np.array([(p.x, p.y) for p in landmarks.parts()])
|
71 |
+
|
72 |
+
# Determine face shape
|
73 |
+
face_shape = determine_face_shape(landmarks)
|
74 |
|
75 |
# Get the nose position
|
76 |
nose_x, nose_y = face_landmarks[2].astype(int)
|
|
|
77 |
left_eye_x, left_eye_y = face_landmarks[0].astype(int)
|
78 |
right_eye_x, right_eye_y = face_landmarks[1].astype(int)
|
79 |
|
|
|
80 |
eye_center_x = (left_eye_x + right_eye_x) // 2
|
81 |
eye_center_y = (left_eye_y + right_eye_y) // 2
|
82 |
|
|
|
83 |
delta_x = right_eye_x - left_eye_x
|
84 |
delta_y = right_eye_y - left_eye_y
|
85 |
angle = np.degrees(np.arctan2(delta_y, delta_x))
|
|
|
|
|
86 |
angle = -angle
|
87 |
|
|
|
88 |
overlay_resize = cv2.resize(overlay, (int(w * 1.15), int(h * 0.8)))
|
|
|
|
|
89 |
overlay_center = (overlay_resize.shape[1] // 2, overlay_resize.shape[0] // 2)
|
90 |
rotation_matrix = cv2.getRotationMatrix2D(overlay_center, angle, 1.0)
|
91 |
overlay_rotated = cv2.warpAffine(
|
92 |
+
overlay_resize, rotation_matrix,
|
93 |
(overlay_resize.shape[1], overlay_resize.shape[0]),
|
94 |
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0, 0)
|
95 |
)
|
96 |
|
|
|
97 |
overlay_x = eye_center_x - overlay_rotated.shape[1] // 2
|
98 |
overlay_y = eye_center_y - overlay_rotated.shape[0] // 2
|
99 |
|
|
|
100 |
try:
|
101 |
frame = cvzone.overlayPNG(frame, overlay_rotated, [overlay_x, overlay_y])
|
102 |
except Exception as e:
|
103 |
print(f"Error overlaying glasses: {e}")
|
104 |
+
|
105 |
+
return frame, face_shape
|
106 |
|
107 |
# Transform function
|
108 |
def transform_cv2(frame, transform):
|
|
|
163 |
|
164 |
# Gradio webcam input
|
165 |
def webcam_input(frame, transform):
|
166 |
+
frame, face_shape = process_frame(frame)
|
167 |
frame = transform_cv2(frame, transform)
|
168 |
+
return frame, face_shape
|
169 |
|
170 |
# Gradio Interface
|
171 |
with gr.Blocks() as demo:
|
|
|
174 |
transform = gr.Dropdown(choices=["cartoon", "edges", "none"],
|
175 |
value="none", label="Transformation")
|
176 |
input_img = gr.Image(sources=["webcam"], type="numpy", streaming=True)
|
177 |
+
face_shape_output = gr.Textbox(label="Detected Face Shape")
|
178 |
next_button = gr.Button("Next Glasses")
|
179 |
+
save_button = gr.Button("Save as a Picture")
|
180 |
+
|
181 |
+
input_img.stream(webcam_input, [input_img, transform], [input_img, face_shape_output], time_limit=30, stream_every=0.1)
|
182 |
with gr.Row():
|
183 |
next_button.click(change_glasses, [], [])
|
184 |
with gr.Row():
|
185 |
save_button.click(save_frame, [input_img], [])
|
186 |
+
|
187 |
if __name__ == "__main__":
|
188 |
demo.launch(share=True)
|
189 |
|
requirements.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
gradio
|
2 |
cvzone
|
3 |
opencv-python
|
4 |
-
numpy
|
|
|
|
1 |
gradio
|
2 |
cvzone
|
3 |
opencv-python
|
4 |
+
numpy
|
5 |
+
dlib
|