NihalGazi commited on
Commit
08a3d08
·
verified ·
1 Parent(s): 9f57dd2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +22 -227
app.py CHANGED
@@ -1,239 +1,34 @@
1
  import gradio as gr
2
- import cv2
3
- import numpy as np
4
- import mediapipe as mp
5
- import time
6
- import tempfile
7
- import os
8
 
9
- # --- MediaPipe Initialization ---
10
- try:
11
- mp_face_mesh = mp.solutions.face_mesh
12
- face_mesh = mp_face_mesh.FaceMesh(
13
- static_image_mode=True,
14
- max_num_faces=1,
15
- refine_landmarks=True,
16
- min_detection_confidence=0.4,
17
- min_tracking_confidence=0.4
18
- )
19
- print("MediaPipe Face Mesh initialized successfully.")
20
- except (ImportError, AttributeError):
21
- print("Error: Could not initialize MediaPipe Face Mesh. Is mediapipe installed correctly?")
22
- face_mesh = None
23
-
24
- # --- Helper Functions ---
25
-
26
- def get_face_mask_box(img, feather_pct, padding_pct):
27
- h, w = img.shape[:2]
28
- mask = np.zeros((h, w), dtype=np.uint8)
29
- results = face_mesh.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
30
- if not results.multi_face_landmarks:
31
- return None, None
32
- pts = np.array([(int(p.x * w), int(p.y * h)) for p in results.multi_face_landmarks[0].landmark], np.int32)
33
- hull = cv2.convexHull(pts)
34
- cv2.fillConvexPoly(mask, hull, 255)
35
- x, y, bw, bh = cv2.boundingRect(hull)
36
- pad = int(max(bw, bh) * padding_pct)
37
- x_pad = max(x - pad, 0)
38
- y_pad = max(y - pad, 0)
39
- x2 = min(x + bw + pad, w)
40
- y2 = min(y + bh + pad, h)
41
- mask_roi = mask[y_pad:y2, x_pad:x2]
42
- if feather_pct > 0 and mask_roi.size > 0:
43
- k = int(min(mask_roi.shape[0], mask_roi.shape[1]) * feather_pct)
44
- if k % 2 == 0: k += 1
45
- mask_roi = cv2.GaussianBlur(mask_roi, (k, k), 0)
46
- return mask_roi, (x_pad, y_pad, x2 - x_pad, y2 - y_pad)
47
-
48
- def get_landmarks(img, landmark_step=1):
49
- if img is None or face_mesh is None:
50
- return None
51
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
52
- try:
53
- results = face_mesh.process(img_rgb)
54
- except Exception:
55
- return None
56
- if not results.multi_face_landmarks:
57
- return None
58
- landmarks_mp = results.multi_face_landmarks[0]
59
- h, w, _ = img.shape
60
- pts = np.array([(pt.x * w, pt.y * h) for pt in landmarks_mp.landmark], dtype=np.float32)
61
- landmarks = pts[::landmark_step] if landmark_step > 1 else pts
62
- corners = np.array([[0,0],[w-1,0],[0,h-1],[w-1,h-1]], dtype=np.float32)
63
- return np.vstack((landmarks, corners))
64
-
65
- def calculate_delaunay_triangles(rect, points):
66
- if points is None or len(points) < 3:
67
- return []
68
- points[:,0] = np.clip(points[:,0], rect[0], rect[0]+rect[2]-1)
69
- points[:,1] = np.clip(points[:,1], rect[1], rect[1]+rect[3]-1)
70
- subdiv = cv2.Subdiv2D(rect)
71
- inserted = {}
72
- for i, p in enumerate(points):
73
- key = (int(p[0]), int(p[1]))
74
- if key not in inserted:
75
- try:
76
- subdiv.insert(key)
77
- inserted[key] = i
78
- except cv2.error:
79
- continue
80
- tris = subdiv.getTriangleList()
81
- delaunay = []
82
- for t in tris:
83
- coords = [(int(t[0]), int(t[1])), (int(t[2]), int(t[3])), (int(t[4]), int(t[5]))]
84
- if all(rect[0] <= x < rect[0]+rect[2] and rect[1] <= y < rect[1]+rect[3] for x, y in coords):
85
- idxs = [inserted.get(c) for c in coords]
86
- if all(i is not None for i in idxs) and len(set(idxs)) == 3:
87
- delaunay.append(idxs)
88
- return delaunay
89
-
90
- def warp_triangle(img1, img2, t1, t2):
91
- if len(t1) != 3 or len(t2) != 3: return
92
- r1 = cv2.boundingRect(np.float32([t1]))
93
- r2 = cv2.boundingRect(np.float32([t2]))
94
- if r1[2] <= 0 or r1[3] <= 0 or r2[2] <= 0 or r2[3] <= 0:
95
- return
96
- src = img1[r1[1]:r1[1]+r1[3], r1[0]:r1[0]+r1[2]]
97
- if src.size == 0: return
98
- t1r = [(t1[i][0]-r1[0], t1[i][1]-r1[1]) for i in range(3)]
99
- t2r = [(t2[i][0]-r2[0], t2[i][1]-r2[1]) for i in range(3)]
100
- mask = np.zeros((r2[3], r2[2], 3), dtype=np.float32)
101
- cv2.fillConvexPoly(mask, np.int32(t2r), (1,1,1), 16)
102
- M = cv2.getAffineTransform(np.float32(t1r), np.float32(t2r))
103
- warped = cv2.warpAffine(src, M, (r2[2], r2[3]), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101)
104
- warped = warped.astype(np.float32) * mask
105
- y1, y2 = r2[1], r2[1]+r2[3]
106
- x1, x2 = r2[0], r2[0]+r2[2]
107
- img2[y1:y2, x1:x2] = img2[y1:y2, x1:x2] * (1-mask) + warped
108
-
109
- def morph_faces(img1, img2, alpha, dim, step):
110
- if img1 is None or img2 is None:
111
- return np.zeros((dim, dim, 3), dtype=np.uint8)
112
- a = cv2.resize(img1, (dim, dim)).astype(np.float32) / 255.0
113
- b = cv2.resize(img2, (dim, dim)).astype(np.float32) / 255.0
114
- l1 = get_landmarks((a*255).astype(np.uint8), step)
115
- l2 = get_landmarks((b*255).astype(np.uint8), step)
116
- if l1 is None or l2 is None or l1.shape != l2.shape:
117
- return cv2.addWeighted((a*255).astype(np.uint8), 1-alpha, (b*255).astype(np.uint8), alpha, 0)
118
- m = (1-alpha)*l1 + alpha*l2
119
- tris = calculate_delaunay_triangles((0, 0, dim, dim), m)
120
- if not tris:
121
- return cv2.addWeighted((a*255).astype(np.uint8), 1-alpha, (b*255).astype(np.uint8), alpha, 0)
122
- Wa = np.zeros_like(a)
123
- Wb = np.zeros_like(b)
124
- for ids in tris:
125
- warp_triangle(a, Wa, l1[ids], m[ids])
126
- warp_triangle(b, Wb, l2[ids], m[ids])
127
- out = (1-alpha)*Wa + alpha*Wb
128
- return (out*255).astype(np.uint8)
129
-
130
- def process_video(
131
- video_path, ref_img, trans, res, step, feather_pct, padding_pct,
132
- progress=gr.Progress()
133
- ):
134
- # --- Initialization ---
135
- cap = cv2.VideoCapture(video_path)
136
- fps = cap.get(cv2.CAP_PROP_FPS) or 24
137
- total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
138
- progress(0.0, desc="Initializing")
139
-
140
- # --- Prepare masked reference ---
141
- ref_bgr = cv2.cvtColor(ref_img, cv2.COLOR_RGB2BGR)
142
- mask_ref, ref_box = get_face_mask_box(ref_bgr, feather_pct, padding_pct)
143
- if mask_ref is None:
144
- progress(None) # hide on error
145
- return None, None, None, None
146
- x_r, y_r, w_r, h_r = ref_box
147
- ref_cut = ref_bgr[y_r:y_r+h_r, x_r:x_r+w_r]
148
- mask_ref_norm = mask_ref.astype(np.float32)[..., None] / 255.0
149
- ref_masked = (ref_cut.astype(np.float32) * mask_ref_norm).astype(np.uint8)
150
- ref_morph = cv2.resize(ref_masked, (res, res))
151
- progress(0.1, desc="Reference ready")
152
-
153
- # --- Output setup ---
154
- w_o = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
155
- h_o = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
156
- tmp_vid = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
157
- out_vid = cv2.VideoWriter(tmp_vid, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w_o, h_o))
158
-
159
- first_crop = first_ref = first_mask = first_morphed = None
160
-
161
- # --- Frame-by-frame processing ---
162
- for i in range(total):
163
- ret, frame = cap.read()
164
- if not ret:
165
- break
166
- progress(0.1 + 0.8 * (i / total), desc=f"Processing frame {i+1}/{total}")
167
 
168
- mask_roi, box = get_face_mask_box(frame, feather_pct, padding_pct)
169
- if mask_roi is None:
170
- out_vid.write(frame)
171
- continue
172
- x, y, w, h = box
173
- crop = frame[y:y+h, x:x+w]
174
- crop_resized = cv2.resize(crop, (res, res))
175
- alpha = float(np.clip((trans+1)/2, 0, 1))
176
- mor = morph_faces(crop_resized, ref_morph, alpha, res, step)
177
-
178
- if i == 0:
179
- first_crop = crop_resized.copy()
180
- first_ref = ref_morph.copy()
181
- first_mask = cv2.resize(mask_roi, (res, res), interpolation=cv2.INTER_LINEAR)
182
- first_morphed = mor.copy()
183
-
184
- mor_back = cv2.resize(mor, (w, h))
185
- mask_n = (mask_roi.astype(np.float32)[..., None] / 255.0)
186
- region = frame[y:y+h, x:x+w].astype(np.float32)
187
- blended = region * (1-mask_n) + mor_back.astype(np.float32) * mask_n
188
- frame[y:y+h, x:x+w] = blended.astype(np.uint8)
189
- out_vid.write(frame)
190
-
191
- cap.release()
192
- out_vid.release()
193
-
194
- # --- First-frame outputs ---
195
- if first_morphed is not None and first_mask is not None:
196
- mask_n0 = first_mask.astype(np.float32)[..., None] / 255.0
197
- first_morphed = (first_morphed.astype(np.float32) * mask_n0).astype(np.uint8)
198
- else:
199
- zero = np.zeros((res, res, 3), dtype=np.uint8)
200
- first_crop = first_crop or zero
201
- first_ref = first_ref or ref_morph
202
- first_morphed = zero
203
-
204
- progress(1.0, desc="Done")
205
- return tmp_vid, \
206
- cv2.cvtColor(first_crop, cv2.COLOR_BGR2RGB), \
207
- cv2.cvtColor(first_ref, cv2.COLOR_BGR2RGB), \
208
- cv2.cvtColor(first_morphed, cv2.COLOR_BGR2RGB)
209
-
210
- # --- Gradio App ---
211
- css = """video, img { object-fit: contain !important; }"""
212
  with gr.Blocks(css=css) as iface:
213
- gr.Markdown("# Morph with Face-Shaped Composite and Padding Percentage")
 
 
 
 
 
 
214
  with gr.Row():
215
- vid = gr.Video(label='Input Video')
216
- ref = gr.Image(type='numpy', label='Reference Image')
 
217
  with gr.Row():
218
- res = gr.Dropdown([256,384,512,768], value=512, label='Resolution')
219
- step = gr.Slider(1,4,value=4,step=1,label='Landmark Sub-sampling')
220
- feather = gr.Slider(0.0,0.5,value=0.1,step=0.01,label='Feather (%)')
221
- padding = gr.Slider(0.0,0.5,value=0.24,step=0.01,label='Padding (%)')
222
- trans = gr.Slider(-1.0,1.0,value=-0.35,step=0.05,label='Transition Level')
223
- btn = gr.Button('Generate Morph 🚀')
224
- out_vid = gr.Video(label='Morphed Video')
225
- out_crop = gr.Image(label='First Frame Crop')
226
- out_ref = gr.Image(label='Masked Reference')
227
- out_morph = gr.Image(label='Masked Morphed First Frame')
228
 
229
  btn.click(
230
  fn=process_video,
231
- inputs=[vid, ref, trans, res, step, feather, padding],
232
- outputs=[out_vid, out_crop, out_ref, out_morph],
233
  show_progress=True
234
  )
235
 
236
- gr.Markdown("---\n*Default values set and feather/padding are now relative percentages.*")
237
-
238
- # Enable queueing so progress updates render
239
- iface.queue().launch(debug=True)
 
1
  import gradio as gr
2
+ from faceflux import process_video
 
 
 
 
 
3
 
4
+ css = """video { object-fit: contain !important; }"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  with gr.Blocks(css=css) as iface:
7
+ gr.Markdown("# Super Fast Face Swap FACEFLUX")
8
+ gr.Markdown(
9
+ "**FACEFLUX**: Ultra-lightweight, CPU-only face swap. "
10
+ "Ideal for small or distant faces; offline & privacy-preserving. "
11
+ "Weakness: large up-close or extreme angles."
12
+ )
13
+
14
  with gr.Row():
15
+ vid = gr.Video(label="Input Video")
16
+ ref = gr.Image(type="numpy", label="Reference Image")
17
+
18
  with gr.Row():
19
+ res = gr.Dropdown([256,384,512,768], value=512, label="Resolution")
20
+ quality = gr.Slider(1,4, value=1, step=1, label="Swap Quality")
21
+ feather = gr.Slider(0.12, 0.24, value=0.12, step=0.01, label="Feather (%)")
22
+ strength = gr.Slider(-0.35, -0.15, value=-0.25, step=0.05, label="Strength")
23
+
24
+ btn = gr.Button("Generate Morph 🚀")
25
+ out_vid = gr.Video(label="Morphed Video")
 
 
 
26
 
27
  btn.click(
28
  fn=process_video,
29
+ inputs=[vid, ref, strength, res, quality, feather, 0.24],
30
+ outputs=[out_vid],
31
  show_progress=True
32
  )
33
 
34
+ iface.queue().launch(debug=True)