savvy7007 commited on
Commit
f68e959
·
verified ·
1 Parent(s): 7b40715

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -68
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # =========================
2
- # app.py (Enhanced Version with Lip-Sync Optimization)
3
  # =========================
4
  import os
5
 
@@ -228,7 +228,7 @@ def _cv2_to_pil(image):
228
  def _pil_to_cv2(image):
229
  return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
230
 
231
- # Enhanced face blending function with mouth protection
232
  def _blend_faces(original_face, swapped_face, blend_percent, mouth_mask=None):
233
  """Blend between original and swapped faces with optional mouth protection"""
234
  if blend_percent == 100:
@@ -241,27 +241,20 @@ def _blend_faces(original_face, swapped_face, blend_percent, mouth_mask=None):
241
  alpha = blend_percent / 100.0
242
 
243
  if mouth_mask is not None:
244
- # Apply different blending for mouth vs non-mouth regions
245
  if mouth_mask.shape[:2] != original_face.shape[:2]:
246
  mouth_mask = cv2.resize(mouth_mask, (original_face.shape[1], original_face.shape[0]))
247
 
248
- # Stronger blending for non-mouth areas, preserve mouth more
249
- mouth_alpha = min(alpha + 0.2, 1.0) # Less blending in mouth area
250
- blended = np.zeros_like(original_face)
 
251
 
252
- # Mouth area - more preservation of swapped face
253
- blended[mouth_mask > 0] = cv2.addWeighted(
254
- swapped_face[mouth_mask > 0], mouth_alpha,
255
- original_face[mouth_mask > 0], 1 - mouth_alpha, 0
256
- )
257
-
258
- # Non-mouth area - normal blending
259
- blended[mouth_mask == 0] = cv2.addWeighted(
260
- swapped_face[mouth_mask == 0], alpha,
261
- original_face[mouth_mask == 0], 1 - alpha, 0
262
- )
263
 
264
- return blended
265
  else:
266
  # Standard blending if no mouth mask
267
  return cv2.addWeighted(swapped_face, alpha, original_face, 1 - alpha, 0)
@@ -278,7 +271,6 @@ def _create_mouth_mask(face_landmarks, image_shape, strength=80):
278
 
279
  # Mouth landmark indices (approximate for 106-point model)
280
  mouth_indices = list(range(48, 68)) # Lips outline
281
- mouth_inner = list(range(60, 68)) # Inner mouth
282
 
283
  if len(landmarks) < 68:
284
  return None
@@ -291,17 +283,11 @@ def _create_mouth_mask(face_landmarks, image_shape, strength=80):
291
  hull = cv2.convexHull(mouth_points)
292
  cv2.fillPoly(mask, [hull], 255)
293
 
294
- # Add inner mouth with lower strength
295
- inner_points = np.array([landmarks[i] for i in mouth_inner], dtype=np.int32)
296
- if len(inner_points) > 2:
297
- inner_hull = cv2.convexHull(inner_points)
298
- cv2.fillPoly(mask, [inner_hull], 200) # Lower value for inner area
299
-
300
  # Apply Gaussian blur for smooth edges
301
- mask = cv2.GaussianBlur(mask, (15, 15), 0)
302
 
303
  # Adjust based on strength parameter
304
- mask = cv2.addWeighted(mask, strength/100.0, np.zeros_like(mask), 0, 0)
305
 
306
  return mask
307
 
@@ -325,7 +311,7 @@ def _select_face(faces, method, image_shape=None):
325
  return faces[0]
326
 
327
  # -------------------------------------
328
- # Core: Enhanced face swap functions with lip-sync optimization
329
  # -------------------------------------
330
  def swap_faces_in_image(
331
  source_image_bgr, target_image_bgr, proc_res, max_faces,
@@ -370,21 +356,22 @@ def swap_faces_in_image(
370
  st.warning("⚠️ No faces detected in the target image.")
371
  return _cv2_to_pil(target_image_bgr)
372
 
373
- # Limit faces to largest N
374
  target_faces = sorted(
375
  target_faces,
376
  key=lambda f: (f.bbox[2]-f.bbox[0])*(f.bbox[3]-f.bbox[1]),
377
  reverse=True
378
- )[:max_faces]
 
379
 
380
  # Swap faces with lip-sync optimization
381
  result_image = target_image_proc.copy()
382
  for tface in target_faces:
383
  try:
384
- # Get face bounding box
385
  x1, y1, x2, y2 = [int(coord) for coord in tface.bbox]
386
- x1, y1 = max(0, x1), max(0, y1)
387
- x2, y2 = min(result_image.shape[1], x2), min(result_image.shape[0], y2)
388
 
389
  # Skip if invalid bbox
390
  if x2 <= x1 or y2 <= y1:
@@ -398,12 +385,11 @@ def swap_faces_in_image(
398
  if lip_sync_enabled and hasattr(tface, 'landmark_2d_106'):
399
  mouth_mask = _create_mouth_mask(tface, face_region.shape, mouth_mask_strength)
400
 
401
- # Perform the swap
402
- swapped_region = swapper.get(result_image, tface, source_face, paste_back=True)
403
- swapped_face = swapped_region[y1:y2, x1:x2]
404
 
405
  # Apply blending with mouth protection
406
- blended_face = _blend_faces(face_region, swapped_face, blend_percent, mouth_mask)
407
  result_image[y1:y2, x1:x2] = blended_face
408
 
409
  except Exception as swap_e:
@@ -508,10 +494,9 @@ def swap_faces_in_video(
508
  except Exception as det_e:
509
  target_faces = []
510
 
511
- # Apply frame consistency
512
- if frame_consistency > 0 and previous_faces and target_faces:
513
- target_faces = _apply_frame_consistency(target_faces, previous_faces, frame_consistency/100.0)
514
-
515
  # Limit faces
516
  if target_faces:
517
  target_faces = sorted(
@@ -519,16 +504,15 @@ def swap_faces_in_video(
519
  key=lambda f: (f.bbox[2]-f.bbox[0])*(f.bbox[3]-f.bbox[1]),
520
  reverse=True
521
  )[:max_faces]
522
- previous_faces = {i: face for i, face in enumerate(target_faces)}
523
 
524
  # Swap faces with lip-sync optimization
525
  result_frame = proc_frame.copy()
526
  for tface in target_faces:
527
  try:
528
- # Get face bounding box
529
  x1, y1, x2, y2 = [int(coord) for coord in tface.bbox]
530
- x1, y1 = max(0, x1), max(0, y1)
531
- x2, y2 = min(result_frame.shape[1], x2), min(result_frame.shape[0], y2)
532
 
533
  # Skip if invalid bbox
534
  if x2 <= x1 or y2 <= y1:
@@ -542,12 +526,11 @@ def swap_faces_in_video(
542
  if lip_sync_enabled and hasattr(tface, 'landmark_2d_106'):
543
  mouth_mask = _create_mouth_mask(tface, face_region.shape, mouth_mask_strength)
544
 
545
- # Perform the swap
546
- swapped_region = swapper.get(result_frame, tface, source_face, paste_back=True)
547
- swapped_face = swapped_region[y1:y2, x1:x2]
548
 
549
  # Apply blending with mouth protection
550
- blended_face = _blend_faces(face_region, swapped_face, blend_percent, mouth_mask)
551
  result_frame[y1:y2, x1:x2] = blended_face
552
 
553
  except Exception as swap_e:
@@ -582,25 +565,6 @@ def swap_faces_in_video(
582
 
583
  return output_path
584
 
585
- def _apply_frame_consistency(current_faces, previous_faces, consistency_strength):
586
- """Maintain consistency between frames for smoother video"""
587
- if not current_faces or not previous_faces:
588
- return current_faces
589
-
590
- consistent_faces = []
591
- for i, current_face in enumerate(current_faces):
592
- if i in previous_faces:
593
- # Blend current face with previous face for consistency
594
- prev_face = previous_faces[i]
595
- # Simple position smoothing (you can add more sophisticated blending)
596
- current_face.bbox = [
597
- prev_face.bbox[j] * consistency_strength + current_face.bbox[j] * (1 - consistency_strength)
598
- for j in range(4)
599
- ]
600
- consistent_faces.append(current_face)
601
-
602
- return consistent_faces
603
-
604
  # -------------------------
605
  # UI: Improved layout
606
  # -------------------------
 
1
  # =========================
2
+ # app.py (Fixed Version - No More Lip Glitches)
3
  # =========================
4
  import os
5
 
 
228
  def _pil_to_cv2(image):
229
  return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
230
 
231
+ # Fixed face blending function
232
  def _blend_faces(original_face, swapped_face, blend_percent, mouth_mask=None):
233
  """Blend between original and swapped faces with optional mouth protection"""
234
  if blend_percent == 100:
 
241
  alpha = blend_percent / 100.0
242
 
243
  if mouth_mask is not None:
244
+ # Ensure mask matches dimensions
245
  if mouth_mask.shape[:2] != original_face.shape[:2]:
246
  mouth_mask = cv2.resize(mouth_mask, (original_face.shape[1], original_face.shape[0]))
247
 
248
+ # Normalize mask to 0-1 range
249
+ mouth_mask_float = mouth_mask.astype(np.float32) / 255.0
250
+ if len(mouth_mask_float.shape) == 2:
251
+ mouth_mask_float = np.repeat(mouth_mask_float[:, :, np.newaxis], 3, axis=2)
252
 
253
+ # Apply blending with mask
254
+ blended = swapped_face * mouth_mask_float + original_face * (1 - mouth_mask_float)
255
+ blended = blended * alpha + original_face * (1 - alpha)
 
 
 
 
 
 
 
 
256
 
257
+ return blended.astype(np.uint8)
258
  else:
259
  # Standard blending if no mouth mask
260
  return cv2.addWeighted(swapped_face, alpha, original_face, 1 - alpha, 0)
 
271
 
272
  # Mouth landmark indices (approximate for 106-point model)
273
  mouth_indices = list(range(48, 68)) # Lips outline
 
274
 
275
  if len(landmarks) < 68:
276
  return None
 
283
  hull = cv2.convexHull(mouth_points)
284
  cv2.fillPoly(mask, [hull], 255)
285
 
 
 
 
 
 
 
286
  # Apply Gaussian blur for smooth edges
287
+ mask = cv2.GaussianBlur(mask, (21, 21), 0)
288
 
289
  # Adjust based on strength parameter
290
+ mask = np.clip(mask * (strength / 100.0), 0, 255).astype(np.uint8)
291
 
292
  return mask
293
 
 
311
  return faces[0]
312
 
313
  # -------------------------------------
314
+ # Core: FIXED face swap functions
315
  # -------------------------------------
316
  def swap_faces_in_image(
317
  source_image_bgr, target_image_bgr, proc_res, max_faces,
 
356
  st.warning("⚠️ No faces detected in the target image.")
357
  return _cv2_to_pil(target_image_bgr)
358
 
359
+ # Limit faces to largest N with quality filtering
360
  target_faces = sorted(
361
  target_faces,
362
  key=lambda f: (f.bbox[2]-f.bbox[0])*(f.bbox[3]-f.bbox[1]),
363
  reverse=True
364
+ )
365
+ target_faces = [f for f in target_faces if f.det_score > 0.5][:max_faces]
366
 
367
  # Swap faces with lip-sync optimization
368
  result_image = target_image_proc.copy()
369
  for tface in target_faces:
370
  try:
371
+ # Get face bounding box with padding
372
  x1, y1, x2, y2 = [int(coord) for coord in tface.bbox]
373
+ x1, y1 = max(0, x1-10), max(0, y1-10) # Add padding
374
+ x2, y2 = min(result_image.shape[1], x2+10), min(result_image.shape[0], y2+10)
375
 
376
  # Skip if invalid bbox
377
  if x2 <= x1 or y2 <= y1:
 
385
  if lip_sync_enabled and hasattr(tface, 'landmark_2d_106'):
386
  mouth_mask = _create_mouth_mask(tface, face_region.shape, mouth_mask_strength)
387
 
388
+ # FIXED: Process only the face region, not the whole image
389
+ swapped_face_region = swapper.get(face_region, tface, source_face, paste_back=False)
 
390
 
391
  # Apply blending with mouth protection
392
+ blended_face = _blend_faces(face_region, swapped_face_region, blend_percent, mouth_mask)
393
  result_image[y1:y2, x1:x2] = blended_face
394
 
395
  except Exception as swap_e:
 
494
  except Exception as det_e:
495
  target_faces = []
496
 
497
+ # Quality filtering
498
+ target_faces = [f for f in target_faces if f.det_score > 0.6]
499
+
 
500
  # Limit faces
501
  if target_faces:
502
  target_faces = sorted(
 
504
  key=lambda f: (f.bbox[2]-f.bbox[0])*(f.bbox[3]-f.bbox[1]),
505
  reverse=True
506
  )[:max_faces]
 
507
 
508
  # Swap faces with lip-sync optimization
509
  result_frame = proc_frame.copy()
510
  for tface in target_faces:
511
  try:
512
+ # Get face bounding box with padding
513
  x1, y1, x2, y2 = [int(coord) for coord in tface.bbox]
514
+ x1, y1 = max(0, x1-15), max(0, y1-15)
515
+ x2, y2 = min(result_frame.shape[1], x2+15), min(result_frame.shape[0], y2+15)
516
 
517
  # Skip if invalid bbox
518
  if x2 <= x1 or y2 <= y1:
 
526
  if lip_sync_enabled and hasattr(tface, 'landmark_2d_106'):
527
  mouth_mask = _create_mouth_mask(tface, face_region.shape, mouth_mask_strength)
528
 
529
+ # FIXED: Process only the face region
530
+ swapped_face_region = swapper.get(face_region, tface, source_face, paste_back=False)
 
531
 
532
  # Apply blending with mouth protection
533
+ blended_face = _blend_faces(face_region, swapped_face_region, blend_percent, mouth_mask)
534
  result_frame[y1:y2, x1:x2] = blended_face
535
 
536
  except Exception as swap_e:
 
565
 
566
  return output_path
567
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  # -------------------------
569
  # UI: Improved layout
570
  # -------------------------