LogicGoInfotechSpaces commited on
Commit
26107c3
·
verified ·
1 Parent(s): 1268fcd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -89
app.py CHANGED
@@ -147,123 +147,116 @@ async def log_faceswap_hit(token: str, status: str = "success"):
147
  # --------------------- Face Swap Pipeline ---------------------
148
  swap_lock = threading.Lock()
149
 
150
- def match_histogram(src_face, tgt_face):
151
- """Match the histogram of src_face to that of tgt_face for color harmony."""
152
- matched = cv2.cvtColor(src_face, cv2.COLOR_BGR2LAB)
153
- target_lab = cv2.cvtColor(tgt_face, cv2.COLOR_BGR2LAB)
154
- for i in range(3):
155
- matched[..., i] = cv2.equalizeHist(matched[..., i])
156
- matched[..., i] = cv2.matchTemplate(matched[..., i], target_lab[..., i], cv2.TM_CCOEFF_NORMED)
157
- return cv2.cvtColor(matched, cv2.COLOR_LAB2BGR)
158
-
159
- def find_mask_center(mask):
160
- """Compute center for seamlessClone using mask moments."""
161
- moments = cv2.moments(mask)
162
- if moments["m00"] != 0:
163
- center_x = int(moments["m10"] / moments["m00"])
164
- center_y = int(moments["m01"] / moments["m00"])
165
- return (center_x, center_y)
166
- else:
167
- # Fallback to geometric center
168
- h, w = mask.shape
169
- return (w // 2, h // 2)
170
-
171
  def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
172
  try:
173
  with swap_lock:
174
- # Cleanup temp and prepare dirs
175
  if os.path.exists(temp_dir):
176
  shutil.rmtree(temp_dir)
177
  os.makedirs(temp_dir, exist_ok=True)
 
178
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
179
- tgt_bgr_full = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
180
 
181
  src_faces = face_analysis_app.get(src_bgr)
182
- tgt_faces = face_analysis_app.get(tgt_bgr_full)
 
183
  if not src_faces or not tgt_faces:
184
  return None, None, "❌ Face not detected in source or target image"
185
 
186
- def expand_bbox(bbox, img_shape, scale=1.6):
187
- ih, iw = img_shape[:2]
 
 
 
 
188
  x1, y1, x2, y2 = map(int, bbox)
189
- w, h = x2 - x1, y2 - y1
190
- cx, cy = x1 + w // 2, y1 + h // 2
191
- new_w, new_h = int(w * scale), int(h * scale)
192
- nx1 = max(0, cx - new_w // 2)
193
- ny1 = max(0, cy - new_h // 2)
194
- nx2 = min(iw, cx + new_w // 2)
195
- ny2 = min(ih, cy + new_h // 2)
196
  return nx1, ny1, nx2, ny2
197
 
198
- src_face0 = src_faces[0]
199
- tgt_face0 = tgt_faces[0]
200
-
201
- # Source crop for precise landmark
202
- s_x1, s_y1, s_x2, s_y2 = expand_bbox(src_face0.bbox, src_bgr.shape, scale=1.4)
203
- src_crop = src_bgr[s_y1:s_y2, s_x1:s_x2]
204
- src_crop_faces = face_analysis_app.get(src_crop)
205
- if src_crop_faces:
206
- src_for_swap = src_crop
207
- src_face_for_swap = src_crop_faces[0]
208
- else:
209
- src_for_swap = src_bgr
210
- src_face_for_swap = src_face0
211
-
212
- # Target crop for even more precision
213
- t_x1, t_y1, t_x2, t_y2 = expand_bbox(tgt_face0.bbox, tgt_bgr_full.shape, scale=1.7)
214
- tgt_crop = tgt_bgr_full[t_y1:t_y2, t_x1:t_x2]
215
- tgt_crop_faces = face_analysis_app.get(tgt_crop)
216
- if tgt_crop_faces:
217
- tgt_for_swap = tgt_crop
218
- tgt_face_for_swap = tgt_crop_faces[0]
219
- # Face swap
220
- swapped_crop = swapper.get(tgt_for_swap, tgt_face_for_swap, src_face_for_swap)
221
- if swapped_crop is None:
222
- return None, None, "❌ Face swap failed on crop"
223
- # Histogram matching for color realism
224
- swapped_crop = match_histogram(swapped_crop, tgt_for_swap)
225
- # Mask for seamless blending
226
- mask = cv2.cvtColor(swapped_crop, cv2.COLOR_BGR2GRAY)
227
- mask = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)[1]
228
- mask = cv2.GaussianBlur(mask, (15, 15), 8)
229
- # Get the center using moments
230
- center = find_mask_center(mask)
231
- # Smoothing step to reduce artifacts
232
- swapped_crop = cv2.bilateralFilter(swapped_crop, 9, 75, 75)
233
- # Seamless clone for perfect embedding
234
- try:
235
- blended = cv2.seamlessClone(swapped_crop, tgt_bgr_full, mask, (t_x1 + center[0], t_y1 + center[1]), cv2.NORMAL_CLONE)
236
- except Exception:
237
- blended = tgt_bgr_full.copy()
238
- h, w = swapped_crop.shape[:2]
239
- blended[t_y1:t_y1+h, t_x1:t_x1+w] = swapped_crop
240
- swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
241
- cv2.imwrite(swapped_path, blended)
242
- else:
243
- # Fallback: swap on full image
244
- swapped_bgr_full = swapper.get(tgt_bgr_full, tgt_face0, src_face0)
245
- if swapped_bgr_full is None:
246
- return None, None, "❌ Face swap failed on full image"
247
- swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
248
- cv2.imwrite(swapped_path, swapped_bgr_full)
249
- # CodeFormer enhancement post-processing as last step
 
 
 
 
 
 
250
  cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
251
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
252
  if result.returncode != 0:
253
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
254
- final_results_dir = os.path.join(temp_dir, "final_results")
255
- final_files = [f for f in os.listdir(final_results_dir) if f.endswith(".png")]
 
256
  if not final_files:
257
- return None, None, "❌ No enhanced image found"
258
- final_path = os.path.join(final_results_dir, final_files[0])
 
259
  final_img = cv2.cvtColor(cv2.imread(final_path), cv2.COLOR_BGR2RGB)
260
  return final_img, final_path, ""
 
261
  except Exception as e:
262
  return None, None, f"❌ Error: {str(e)}"
263
 
264
 
265
 
266
 
 
267
  # --------------------- Gradio ---------------------
268
  with gr.Blocks() as demo:
269
  gr.Markdown("Face Swap")
 
147
  # --------------------- Face Swap Pipeline ---------------------
148
  swap_lock = threading.Lock()
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
151
  try:
152
  with swap_lock:
153
+ # Clean workspace
154
  if os.path.exists(temp_dir):
155
  shutil.rmtree(temp_dir)
156
  os.makedirs(temp_dir, exist_ok=True)
157
+
158
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
159
+ tgt_bgr = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
160
 
161
  src_faces = face_analysis_app.get(src_bgr)
162
+ tgt_faces = face_analysis_app.get(tgt_bgr)
163
+
164
  if not src_faces or not tgt_faces:
165
  return None, None, "❌ Face not detected in source or target image"
166
 
167
+ src_face = src_faces[0]
168
+ tgt_face = tgt_faces[0]
169
+
170
+ # --- Adaptive expansion based on face size ---
171
+ def adaptive_bbox(bbox, shape, factor=1.7):
172
+ h, w = shape[:2]
173
  x1, y1, x2, y2 = map(int, bbox)
174
+ bw, bh = x2 - x1, y2 - y1
175
+ cx, cy = x1 + bw // 2, y1 + bh // 2
176
+ new_w, new_h = int(bw * factor), int(bh * factor)
177
+ nx1, ny1 = max(0, cx - new_w // 2), max(0, cy - new_h // 2)
178
+ nx2, ny2 = min(w, cx + new_w // 2), min(h, cy + new_h // 2)
 
 
179
  return nx1, ny1, nx2, ny2
180
 
181
+ sx1, sy1, sx2, sy2 = adaptive_bbox(src_face.bbox, src_bgr.shape)
182
+ tx1, ty1, tx2, ty2 = adaptive_bbox(tgt_face.bbox, tgt_bgr.shape)
183
+
184
+ src_crop = src_bgr[sy1:sy2, sx1:sx2]
185
+ tgt_crop = tgt_bgr[ty1:ty2, tx1:tx2]
186
+
187
+ src_face_crop = face_analysis_app.get(src_crop)
188
+ tgt_face_crop = face_analysis_app.get(tgt_crop)
189
+ if not src_face_crop or not tgt_face_crop:
190
+ return None, None, "❌ Re-detection failed in cropped region"
191
+
192
+ src_face_refined = src_face_crop[0]
193
+ tgt_face_refined = tgt_face_crop[0]
194
+
195
+ # --- Alignment correction using landmarks (similarity transform) ---
196
+ def align_face(img, src_pts, tgt_pts):
197
+ M, _ = cv2.estimateAffinePartial2D(src_pts, tgt_pts)
198
+ if M is not None:
199
+ aligned = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]), flags=cv2.INTER_LINEAR)
200
+ return aligned
201
+ return img
202
+
203
+ src_pts = src_face_refined.landmark_2d_106[:68].astype(np.float32)
204
+ tgt_pts = tgt_face_refined.landmark_2d_106[:68].astype(np.float32)
205
+ src_aligned = align_face(src_crop, src_pts, tgt_pts)
206
+
207
+ # --- Perform swap on aligned faces ---
208
+ swapped_crop = swapper.get(tgt_crop, tgt_face_refined, src_face_refined)
209
+ if swapped_crop is None:
210
+ return None, None, "❌ Swap failed"
211
+
212
+ # --- Feathered mask for smooth blending ---
213
+ gray = cv2.cvtColor(swapped_crop, cv2.COLOR_BGR2GRAY)
214
+ mask = np.where(gray > 5, 255, 0).astype(np.uint8)
215
+ mask = cv2.GaussianBlur(mask, (41, 41), 15)
216
+
217
+ # --- Tone normalization between faces ---
218
+ def match_tone(src, ref):
219
+ src_mean, src_std = cv2.meanStdDev(src)
220
+ ref_mean, ref_std = cv2.meanStdDev(ref)
221
+ adj = (src - src_mean) * (ref_std / (src_std + 1e-6)) + ref_mean
222
+ return np.clip(adj, 0, 255).astype(np.uint8)
223
+
224
+ swapped_tone = match_tone(swapped_crop, tgt_crop)
225
+
226
+ # --- Seamless composite ---
227
+ center = (tx1 + (tx2 - tx1)//2, ty1 + (ty2 - ty1)//2)
228
+ try:
229
+ blended = cv2.seamlessClone(swapped_tone, tgt_bgr, mask, center, cv2.NORMAL_CLONE)
230
+ except Exception:
231
+ blended = tgt_bgr.copy()
232
+ h, w = swapped_tone.shape[:2]
233
+ blended[ty1:ty1+h, tx1:tx1+w] = swapped_tone
234
+
235
+ swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex}.jpg")
236
+ cv2.imwrite(swapped_path, blended)
237
+
238
+ # --- CodeFormer enhancement ---
239
  cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
240
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
241
  if result.returncode != 0:
242
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
243
+
244
+ final_dir = os.path.join(temp_dir, "final_results")
245
+ final_files = [f for f in os.listdir(final_dir) if f.endswith(".png")]
246
  if not final_files:
247
+ return None, None, "❌ No enhanced output found"
248
+ final_path = os.path.join(final_dir, final_files[0])
249
+
250
  final_img = cv2.cvtColor(cv2.imread(final_path), cv2.COLOR_BGR2RGB)
251
  return final_img, final_path, ""
252
+
253
  except Exception as e:
254
  return None, None, f"❌ Error: {str(e)}"
255
 
256
 
257
 
258
 
259
+
260
  # --------------------- Gradio ---------------------
261
  with gr.Blocks() as demo:
262
  gr.Markdown("Face Swap")