Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,143 +1,110 @@
|
|
1 |
-
import os
|
2 |
-
os.system("pip install cmake")
|
3 |
-
os.system("pip install dlib opencv-python numpy Pillow gradio")
|
4 |
-
|
5 |
import gradio as gr
|
6 |
import cv2
|
7 |
import numpy as np
|
8 |
from PIL import Image
|
9 |
-
import
|
10 |
-
|
11 |
-
#
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
#
|
16 |
-
def
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
def
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
for i in range(3):
|
45 |
-
t1_rect.append(((t1[i][0] - r1[0]),(t1[i][1] - r1[1])))
|
46 |
-
t2_rect.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))
|
47 |
-
t_rect.append(((t[i][0] - r[0]),(t[i][1] - r[1])))
|
48 |
-
|
49 |
-
img1_rect = img1[r1[1]:r1[1]+r1[3], r1[0]:r1[0]+r1[2]]
|
50 |
-
img2_rect = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]]
|
51 |
-
|
52 |
-
warp_img1 = apply_affine_transform(img1_rect, t1_rect, t_rect, (r[2], r[3]))
|
53 |
-
warp_img2 = apply_affine_transform(img2_rect, t2_rect, t_rect, (r[2], r[3]))
|
54 |
-
|
55 |
-
img_rect = (1.0 - alpha) * warp_img1 + alpha * warp_img2
|
56 |
-
|
57 |
-
mask = np.zeros((r[3], r[2], 3), dtype=np.float32)
|
58 |
-
cv2.fillConvexPoly(mask, np.int32(t_rect), (1.0, 1.0, 1.0), 16, 0)
|
59 |
-
img[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] = img[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] * (1 - mask) + img_rect * mask
|
60 |
-
|
61 |
-
def delaunay_triangulation(points, w, h):
|
62 |
-
subdiv = cv2.Subdiv2D((0, 0, w, h))
|
63 |
-
for p in points:
|
64 |
subdiv.insert((p[0], p[1]))
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
return Image.fromarray(np.uint8(morphed))
|
99 |
|
100 |
-
|
101 |
-
def
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
if s_ee > 0.0:
|
112 |
-
ee_img = load_image(ee)
|
113 |
-
result = np.array(morph_faces(result, ee_img, s_ee))
|
114 |
-
if s_ww > 0.0:
|
115 |
-
ww_img = load_image(ww)
|
116 |
-
result = np.array(morph_faces(result, ww_img, s_ww))
|
117 |
-
if s_na > 0.0:
|
118 |
-
na_img = load_image(na)
|
119 |
-
result = np.array(morph_faces(result, na_img, s_na))
|
120 |
-
|
121 |
-
return Image.fromarray(result)
|
122 |
-
|
123 |
-
# --- Gradio UI ---
|
124 |
iface = gr.Interface(
|
125 |
-
fn=
|
126 |
inputs=[
|
127 |
-
gr.Image(label="MM Image (Neutral)"
|
128 |
-
gr.Image(label="AA Image"
|
129 |
-
gr.Image(label="EE Image"
|
130 |
-
gr.Image(label="OO Image"
|
131 |
-
gr.Image(label="WW Image"
|
132 |
-
gr.Image(label="NA Image"
|
133 |
-
|
134 |
-
gr.Slider(0
|
135 |
-
gr.Slider(0
|
136 |
-
gr.Slider(0
|
137 |
-
gr.Slider(0
|
138 |
-
gr.Slider(0.0, 1.0, step=0.05, label="Morph Strength NA"),
|
139 |
],
|
140 |
-
outputs=gr.Image(label="
|
141 |
live=True
|
142 |
)
|
143 |
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import cv2
|
3 |
import numpy as np
|
4 |
from PIL import Image
|
5 |
+
import mediapipe as mp
|
6 |
+
|
7 |
+
# MediaPipe face mesh setup
|
8 |
+
mp_face_mesh = mp.solutions.face_mesh
|
9 |
+
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=True)
|
10 |
+
|
11 |
+
# Get facial landmarks
|
12 |
+
def get_landmarks(image):
|
13 |
+
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
14 |
+
results = face_mesh.process(rgb)
|
15 |
+
h, w, _ = image.shape
|
16 |
+
|
17 |
+
if results.multi_face_landmarks:
|
18 |
+
landmarks = []
|
19 |
+
for pt in results.multi_face_landmarks[0].landmark:
|
20 |
+
x, y = int(pt.x * w), int(pt.y * h)
|
21 |
+
landmarks.append((x, y))
|
22 |
+
return np.array(landmarks, dtype=np.int32)
|
23 |
+
return None
|
24 |
+
|
25 |
+
# Morph images based on landmarks and alpha
|
26 |
+
def morph_images(img1, img2, alpha):
|
27 |
+
lm1 = get_landmarks(img1)
|
28 |
+
lm2 = get_landmarks(img2)
|
29 |
+
|
30 |
+
if lm1 is None or lm2 is None:
|
31 |
+
return Image.fromarray(img1) # fallback
|
32 |
+
|
33 |
+
lm_avg = ((1 - alpha) * lm1 + alpha * lm2).astype(np.int32)
|
34 |
+
|
35 |
+
# Triangulation
|
36 |
+
rect = (0, 0, img1.shape[1], img1.shape[0])
|
37 |
+
subdiv = cv2.Subdiv2D(rect)
|
38 |
+
for p in lm_avg:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
subdiv.insert((p[0], p[1]))
|
40 |
+
triangles = subdiv.getTriangleList().astype(np.int32)
|
41 |
+
|
42 |
+
def get_indices(tri, points):
|
43 |
+
idxs = []
|
44 |
+
for p in tri:
|
45 |
+
for i, pt in enumerate(points):
|
46 |
+
if np.linalg.norm(np.array(p) - np.array(pt)) < 1.0:
|
47 |
+
idxs.append(i)
|
48 |
+
return idxs if len(idxs) == 3 else None
|
49 |
+
|
50 |
+
morphed = np.zeros_like(img1)
|
51 |
+
for tri in triangles:
|
52 |
+
pts = [(tri[0], tri[1]), (tri[2], tri[3]), (tri[4], tri[5])]
|
53 |
+
idxs = get_indices(pts, lm_avg.tolist())
|
54 |
+
if idxs is None: continue
|
55 |
+
|
56 |
+
t1 = np.float32([lm1[i] for i in idxs])
|
57 |
+
t2 = np.float32([lm2[i] for i in idxs])
|
58 |
+
t = np.float32([lm_avg[i] for i in idxs])
|
59 |
+
|
60 |
+
def warp_triangle(src, t_src, t_dst):
|
61 |
+
rect_src = cv2.boundingRect(t_src)
|
62 |
+
rect_dst = cv2.boundingRect(t_dst)
|
63 |
+
|
64 |
+
t_src_offset = np.array([[pt[0]-rect_src[0], pt[1]-rect_src[1]] for pt in t_src], np.float32)
|
65 |
+
t_dst_offset = np.array([[pt[0]-rect_dst[0], pt[1]-rect_dst[1]] for pt in t_dst], np.float32)
|
66 |
+
|
67 |
+
mask = np.zeros((rect_dst[3], rect_dst[2], 3), dtype=np.float32)
|
68 |
+
cv2.fillConvexPoly(mask, np.int32(t_dst_offset), (1.0, 1.0, 1.0), 16, 0)
|
69 |
+
|
70 |
+
src_crop = src[rect_src[1]:rect_src[1]+rect_src[3], rect_src[0]:rect_src[0]+rect_src[2]]
|
71 |
+
warp_mat = cv2.getAffineTransform(t_src_offset, t_dst_offset)
|
72 |
+
dst_crop = cv2.warpAffine(src_crop, warp_mat, (rect_dst[2], rect_dst[3]), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101)
|
73 |
+
morphed[rect_dst[1]:rect_dst[1]+rect_dst[3], rect_dst[0]:rect_dst[0]+rect_dst[2]] *= (1 - mask)
|
74 |
+
morphed[rect_dst[1]:rect_dst[1]+rect_dst[3], rect_dst[0]:rect_dst[0]+rect_dst[2]] += dst_crop * mask
|
75 |
+
|
76 |
+
warp_triangle(img1, t1, t)
|
77 |
+
warp_triangle(img2, t2, t)
|
78 |
|
79 |
return Image.fromarray(np.uint8(morphed))
|
80 |
|
81 |
+
def process(mm_image, aa_image, ee_image, oo_image, ww_image, na_image, slider_aa, slider_oo, slider_ee, slider_ww, slider_na):
|
82 |
+
def load(img): return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
83 |
+
|
84 |
+
img = load(mm_image)
|
85 |
+
for s, phoneme in [(slider_aa, aa_image), (slider_oo, oo_image), (slider_ee, ee_image), (slider_ww, ww_image), (slider_na, na_image)]:
|
86 |
+
if s > 0.0:
|
87 |
+
target = load(phoneme)
|
88 |
+
img = np.array(morph_images(img, target, s))
|
89 |
+
|
90 |
+
return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
|
91 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
iface = gr.Interface(
|
93 |
+
fn=process,
|
94 |
inputs=[
|
95 |
+
gr.Image(label="MM Image (Neutral)"),
|
96 |
+
gr.Image(label="AA Image"),
|
97 |
+
gr.Image(label="EE Image"),
|
98 |
+
gr.Image(label="OO Image"),
|
99 |
+
gr.Image(label="WW Image"),
|
100 |
+
gr.Image(label="NA Image"),
|
101 |
+
gr.Slider(0, 1, 0.05, label="Strength AA"),
|
102 |
+
gr.Slider(0, 1, 0.05, label="Strength OO"),
|
103 |
+
gr.Slider(0, 1, 0.05, label="Strength EE"),
|
104 |
+
gr.Slider(0, 1, 0.05, label="Strength WW"),
|
105 |
+
gr.Slider(0, 1, 0.05, label="Strength NA"),
|
|
|
106 |
],
|
107 |
+
outputs=gr.Image(label="Lipsynced Output"),
|
108 |
live=True
|
109 |
)
|
110 |
|