Spaces:
Running
Running
Upload selfie_filter.py
Browse files- selfie_filter.py +152 -0
selfie_filter.py
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import numpy as np
|
3 |
+
import cv2
|
4 |
+
import landmark_detection
|
5 |
+
import gradio as gr
|
6 |
+
from mtcnn_facedetection import detect_faces
|
7 |
+
|
8 |
+
|
9 |
+
def apply_sunglasses(image, landmarks, sunglasses_img):
|
10 |
+
# If image loading fails or no landmarks, return original image
|
11 |
+
if sunglasses_img is None or not landmarks:
|
12 |
+
return image
|
13 |
+
|
14 |
+
# Create a copy of the image to overlay on
|
15 |
+
result = image.copy()
|
16 |
+
|
17 |
+
# Process each face
|
18 |
+
for face_landmarks in landmarks:
|
19 |
+
# We need at least the eye landmarks
|
20 |
+
if len(face_landmarks) < 5:
|
21 |
+
continue
|
22 |
+
|
23 |
+
# Get eye landmarks
|
24 |
+
left_eye_center = np.mean(face_landmarks[36:42], axis=0).astype(int)
|
25 |
+
right_eye_center = np.mean(face_landmarks[42:48], axis=0).astype(int)
|
26 |
+
|
27 |
+
# Calculate eye distance and angle
|
28 |
+
eye_distance = np.linalg.norm(right_eye_center - left_eye_center)
|
29 |
+
# Negate the angle to correct rotation direction
|
30 |
+
angle = -np.degrees(
|
31 |
+
np.arctan2(
|
32 |
+
right_eye_center[1] - left_eye_center[1],
|
33 |
+
right_eye_center[0] - left_eye_center[0],
|
34 |
+
)
|
35 |
+
)
|
36 |
+
|
37 |
+
# Size for sunglasses based on eye distance
|
38 |
+
width = int(eye_distance * 2.5)
|
39 |
+
height = int(width * sunglasses_img.shape[0] / sunglasses_img.shape[1])
|
40 |
+
|
41 |
+
# Resize sunglasses
|
42 |
+
sunglasses_resized = cv2.resize(sunglasses_img, (width, height))
|
43 |
+
|
44 |
+
# Rotate the sunglasses image
|
45 |
+
center = (width // 2, height // 2)
|
46 |
+
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
|
47 |
+
|
48 |
+
# Calculate new dimensions after rotation
|
49 |
+
cos = np.abs(rotation_matrix[0, 0])
|
50 |
+
sin = np.abs(rotation_matrix[0, 1])
|
51 |
+
new_width = int((height * sin) + (width * cos))
|
52 |
+
new_height = int((height * cos) + (width * sin))
|
53 |
+
|
54 |
+
# Adjust rotation matrix
|
55 |
+
rotation_matrix[0, 2] += (new_width / 2) - center[0]
|
56 |
+
rotation_matrix[1, 2] += (new_height / 2) - center[1]
|
57 |
+
|
58 |
+
# Perform the rotation
|
59 |
+
rotated_glasses = cv2.warpAffine(
|
60 |
+
sunglasses_resized,
|
61 |
+
rotation_matrix,
|
62 |
+
(new_width, new_height),
|
63 |
+
flags=cv2.INTER_LINEAR,
|
64 |
+
borderMode=cv2.BORDER_CONSTANT,
|
65 |
+
borderValue=(0, 0, 0, 0),
|
66 |
+
)
|
67 |
+
|
68 |
+
# Position the sunglasses
|
69 |
+
eye_center = ((left_eye_center + right_eye_center) // 2).astype(int)
|
70 |
+
x = eye_center[0] - new_width // 2
|
71 |
+
y = eye_center[1] - new_height // 2
|
72 |
+
|
73 |
+
# Create ROI for overlay
|
74 |
+
y1, y2 = max(0, y), min(result.shape[0], y + new_height)
|
75 |
+
x1, x2 = max(0, x), min(result.shape[1], x + new_width)
|
76 |
+
|
77 |
+
# ROI in the glasses image
|
78 |
+
g_y1, g_y2 = max(0, -y), max(0, -y) + (y2 - y1)
|
79 |
+
g_x1, g_x2 = max(0, -x), max(0, -x) + (x2 - x1)
|
80 |
+
|
81 |
+
# Check if we have valid regions
|
82 |
+
if g_y2 <= rotated_glasses.shape[0] and g_x2 <= rotated_glasses.shape[1]:
|
83 |
+
roi = result[y1:y2, x1:x2]
|
84 |
+
glasses_roi = rotated_glasses[g_y1:g_y2, g_x1:g_x2]
|
85 |
+
|
86 |
+
# Apply alpha blending
|
87 |
+
if glasses_roi.shape[2] == 4 and roi.shape[:2] == glasses_roi.shape[:2]:
|
88 |
+
alpha = glasses_roi[:, :, 3] / 255.0
|
89 |
+
for c in range(3):
|
90 |
+
roi[:, :, c] = (
|
91 |
+
glasses_roi[:, :, c] * alpha + roi[:, :, c] * (1 - alpha)
|
92 |
+
).astype(np.uint8)
|
93 |
+
result[y1:y2, x1:x2] = roi
|
94 |
+
|
95 |
+
return result
|
96 |
+
|
97 |
+
|
98 |
+
def do_facial_landmark_recognition(
|
99 |
+
image: np.ndarray, face_boxes: list[landmark_detection.BoundingBox]
|
100 |
+
):
|
101 |
+
faces = landmark_detection.get_faces(image, face_boxes)
|
102 |
+
landmarks_batch = landmark_detection.get_landmarks(faces)
|
103 |
+
return landmarks_batch
|
104 |
+
|
105 |
+
|
106 |
+
def do_facial_landmark_recognition_with_mtcnn(image: np.ndarray, sunglasses_img):
|
107 |
+
face_boxes = detect_faces(image)
|
108 |
+
landmarks_batch = do_facial_landmark_recognition(image, face_boxes)
|
109 |
+
return apply_sunglasses(image, landmarks_batch, sunglasses_img)
|
110 |
+
|
111 |
+
|
112 |
+
def process_video(input_path, sunglasses_img):
|
113 |
+
output_path = os.path.join(
|
114 |
+
os.path.dirname(input_path), "output_" + os.path.basename(input_path)
|
115 |
+
)
|
116 |
+
# Open the input video
|
117 |
+
cap = cv2.VideoCapture(input_path)
|
118 |
+
if not cap.isOpened():
|
119 |
+
gr.Error(f"Error opening input video file: {input_path}")
|
120 |
+
return
|
121 |
+
|
122 |
+
# Get video properties
|
123 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
124 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
125 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
126 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
127 |
+
|
128 |
+
# Create VideoWriter object
|
129 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
130 |
+
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
|
131 |
+
|
132 |
+
frame_count = 0
|
133 |
+
|
134 |
+
# Process each frame
|
135 |
+
while cap.isOpened():
|
136 |
+
ret, frame = cap.read()
|
137 |
+
if not ret:
|
138 |
+
break
|
139 |
+
|
140 |
+
# Process the frame
|
141 |
+
processed_frame = do_facial_landmark_recognition_with_mtcnn(
|
142 |
+
frame, sunglasses_img
|
143 |
+
)
|
144 |
+
|
145 |
+
# Write the frame
|
146 |
+
out.write(processed_frame)
|
147 |
+
|
148 |
+
# Release resources
|
149 |
+
cap.release()
|
150 |
+
out.release()
|
151 |
+
gr.Info(f"Video processing complete. Output saved to: {output_path}")
|
152 |
+
return output_path
|