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 |
|