Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,13 +1,6 @@
|
|
| 1 |
# ============================================================================
|
| 2 |
# ULTIMATE FACE SWAP - 100% QUALITY + HEAD SWAP (WITH HAIR!)
|
| 3 |
-
#
|
| 4 |
-
# UPGRADES:
|
| 5 |
-
# 1. β
CodeFormer instead of GFPGAN (superior quality - 100%!)
|
| 6 |
-
# 2. β
Head swap mode (includes hair, ears, neck!)
|
| 7 |
-
# 3. β
Dual restoration (CodeFormer + GFPGAN together = maximum quality)
|
| 8 |
-
# 4. β
Higher fidelity weight (0.5 β 0.2 for more detail)
|
| 9 |
-
# 5. β
Small previews (400x300)
|
| 10 |
-
# 6. β
FIXED: MoviePy import error
|
| 11 |
# ============================================================================
|
| 12 |
|
| 13 |
print("="*80)
|
|
@@ -19,9 +12,9 @@ import subprocess, sys
|
|
| 19 |
print("\n[1/7] Installing packages...")
|
| 20 |
subprocess.check_call([
|
| 21 |
sys.executable, "-m", "pip", "install", "-q",
|
| 22 |
-
"gradio", "insightface==0.7.3", "onnxruntime
|
| 23 |
-
"opencv-python-headless", "moviepy==
|
| 24 |
-
"gfpgan", "basicsr", "facexlib", "
|
| 25 |
])
|
| 26 |
print("β Installed")
|
| 27 |
|
|
@@ -38,27 +31,21 @@ from insightface.model_zoo import get_model
|
|
| 38 |
try:
|
| 39 |
from moviepy.editor import VideoFileClip, ImageSequenceClip
|
| 40 |
except ImportError:
|
| 41 |
-
|
| 42 |
-
from moviepy import VideoFileClip, ImageSequenceClip
|
| 43 |
-
except ImportError:
|
| 44 |
-
# Final fallback - install older stable version
|
| 45 |
-
print(" Installing moviepy 1.0.3...")
|
| 46 |
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "moviepy==1.0.3"])
|
| 47 |
-
from moviepy.editor import VideoFileClip, ImageSequenceClip
|
| 48 |
|
| 49 |
from tqdm import tqdm
|
| 50 |
print("β Imported")
|
| 51 |
|
| 52 |
# ============================================================================
|
| 53 |
-
# SECTION 1: FACE DETECTION
|
| 54 |
# ============================================================================
|
| 55 |
print("\n[3/7] Loading face detector...")
|
| 56 |
-
face_app = FaceAnalysis(name="buffalo_l")
|
| 57 |
-
face_app.prepare(ctx_id
|
| 58 |
-
print("β Face detector loaded")
|
| 59 |
|
| 60 |
# ============================================================================
|
| 61 |
-
# SECTION 2: INSWAPPER MODEL
|
| 62 |
# ============================================================================
|
| 63 |
print("\n[4/7] Loading INSwapper...")
|
| 64 |
|
|
@@ -75,18 +62,18 @@ try:
|
|
| 75 |
urllib.request.urlretrieve(url, model_path)
|
| 76 |
print(f" β Downloaded ({os.path.getsize(model_path) // 1_000_000}MB)")
|
| 77 |
|
| 78 |
-
swapper = get_model(model_path, download=False, download_zip=False)
|
| 79 |
|
| 80 |
SWAPPER_LOADED = True
|
| 81 |
-
print("β INSwapper loaded")
|
| 82 |
|
| 83 |
except Exception as e:
|
| 84 |
print(f"β INSwapper failed: {e}")
|
| 85 |
|
| 86 |
# ============================================================================
|
| 87 |
-
# SECTION 3: CODEFORMER (
|
| 88 |
# ============================================================================
|
| 89 |
-
print("\n[5/7] Loading CodeFormer
|
| 90 |
|
| 91 |
codeformer_net = None
|
| 92 |
CODEFORMER_LOADED = False
|
|
@@ -96,7 +83,6 @@ try:
|
|
| 96 |
from basicsr.utils.download_util import load_file_from_url
|
| 97 |
from basicsr.utils import imwrite, img2tensor, tensor2img
|
| 98 |
from facexlib.utils.face_restoration_helper import FaceRestoreHelper
|
| 99 |
-
from torchvision.transforms.functional import normalize
|
| 100 |
import torch
|
| 101 |
|
| 102 |
# Download CodeFormer model
|
|
@@ -110,7 +96,6 @@ try:
|
|
| 110 |
print(" β Downloaded")
|
| 111 |
|
| 112 |
# Load CodeFormer network
|
| 113 |
-
from basicsr.utils import get_root_logger
|
| 114 |
from basicsr.archs import build_network
|
| 115 |
|
| 116 |
codeformer_net = build_network({
|
|
@@ -125,7 +110,8 @@ try:
|
|
| 125 |
codeformer_net.load_state_dict(checkpoint['params_ema'])
|
| 126 |
codeformer_net.eval()
|
| 127 |
|
| 128 |
-
|
|
|
|
| 129 |
codeformer_net = codeformer_net.to(device)
|
| 130 |
|
| 131 |
# Face helper for detection and alignment
|
|
@@ -140,7 +126,7 @@ try:
|
|
| 140 |
)
|
| 141 |
|
| 142 |
CODEFORMER_LOADED = True
|
| 143 |
-
print("β CodeFormer loaded
|
| 144 |
|
| 145 |
except Exception as e:
|
| 146 |
print(f"β CodeFormer failed: {e}")
|
|
@@ -149,7 +135,7 @@ except Exception as e:
|
|
| 149 |
# ============================================================================
|
| 150 |
# SECTION 4: GFPGAN (BACKUP/COMPLEMENTARY)
|
| 151 |
# ============================================================================
|
| 152 |
-
print("\n[6/7] Loading GFPGAN
|
| 153 |
|
| 154 |
gfpgan_restorer = None
|
| 155 |
GFPGAN_LOADED = False
|
|
@@ -172,11 +158,12 @@ try:
|
|
| 172 |
upscale=2,
|
| 173 |
arch='clean',
|
| 174 |
channel_multiplier=2,
|
| 175 |
-
bg_upsampler=None
|
|
|
|
| 176 |
)
|
| 177 |
|
| 178 |
GFPGAN_LOADED = True
|
| 179 |
-
print("β GFPGAN loaded (
|
| 180 |
|
| 181 |
except Exception as e:
|
| 182 |
print(f"β GFPGAN unavailable: {e}")
|
|
@@ -230,18 +217,15 @@ def detect_faces_with_preview(image):
|
|
| 230 |
return cv2.cvtColor(preview_small, cv2.COLOR_BGR2RGB), faces
|
| 231 |
|
| 232 |
# ============================================================================
|
| 233 |
-
# CODEFORMER RESTORATION FUNCTION
|
| 234 |
# ============================================================================
|
| 235 |
|
| 236 |
def restore_with_codeformer(face_img, fidelity_weight=0.2):
|
| 237 |
-
"""
|
| 238 |
-
Apply CodeFormer restoration with LOW fidelity (0.2 = more AI detail)
|
| 239 |
-
Lower fidelity = more enhancement, higher quality!
|
| 240 |
-
"""
|
| 241 |
import torch
|
| 242 |
-
from torchvision.transforms
|
| 243 |
|
| 244 |
-
device = '
|
| 245 |
|
| 246 |
# Prepare image
|
| 247 |
face_img = cv2.resize(face_img, (512, 512), interpolation=cv2.INTER_LINEAR)
|
|
@@ -249,7 +233,7 @@ def restore_with_codeformer(face_img, fidelity_weight=0.2):
|
|
| 249 |
face_img = torch.from_numpy(face_img).permute(2, 0, 1).unsqueeze(0).to(device)
|
| 250 |
|
| 251 |
# Normalize
|
| 252 |
-
normalize(face_img, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5)
|
| 253 |
|
| 254 |
# Run CodeFormer
|
| 255 |
with torch.no_grad():
|
|
@@ -262,15 +246,11 @@ def restore_with_codeformer(face_img, fidelity_weight=0.2):
|
|
| 262 |
return output
|
| 263 |
|
| 264 |
# ============================================================================
|
| 265 |
-
# ENHANCED FACE SWAP
|
| 266 |
# ============================================================================
|
| 267 |
|
| 268 |
def swap_face_in_frame(frame, source_face, target_face_idx=None, include_hair=False):
|
| 269 |
-
"""
|
| 270 |
-
Enhanced face swap with:
|
| 271 |
-
- CodeFormer for maximum quality
|
| 272 |
-
- Optional head swap mode (includes hair!)
|
| 273 |
-
"""
|
| 274 |
if not SWAPPER_LOADED:
|
| 275 |
return frame
|
| 276 |
|
|
@@ -289,40 +269,31 @@ def swap_face_in_frame(frame, source_face, target_face_idx=None, include_hair=Fa
|
|
| 289 |
for target_face in target_faces:
|
| 290 |
result = swapper.get(result, target_face, source_face, paste_back=True)
|
| 291 |
|
| 292 |
-
#
|
| 293 |
if CODEFORMER_LOADED and codeformer_net:
|
| 294 |
try:
|
| 295 |
swapped_faces = face_app.get(result)
|
| 296 |
|
| 297 |
for face in swapped_faces:
|
| 298 |
x1, y1, x2, y2 = face.bbox.astype(int)
|
| 299 |
-
|
| 300 |
-
# Expand region for head swap mode
|
| 301 |
h, w = result.shape[:2]
|
| 302 |
|
| 303 |
if include_hair:
|
| 304 |
-
|
| 305 |
-
pad = int(max(x2-x1, y2-y1) * 0.6) # 60% padding
|
| 306 |
else:
|
| 307 |
-
|
| 308 |
-
pad = int(max(x2-x1, y2-y1) * 0.3) # 30% padding
|
| 309 |
|
| 310 |
x1 = max(0, x1 - pad)
|
| 311 |
y1 = max(0, y1 - pad)
|
| 312 |
x2 = min(w, x2 + pad)
|
| 313 |
y2 = min(h, y2 + pad)
|
| 314 |
|
| 315 |
-
# Extract region
|
| 316 |
face_region = result[y1:y2, x1:x2].copy()
|
| 317 |
original_size = (x2-x1, y2-y1)
|
| 318 |
|
| 319 |
-
# Apply CodeFormer with LOW fidelity (0.2 = maximum quality!)
|
| 320 |
restored_face = restore_with_codeformer(face_region, fidelity_weight=0.2)
|
| 321 |
-
|
| 322 |
-
# Resize back
|
| 323 |
restored_face = cv2.resize(restored_face, original_size, interpolation=cv2.INTER_LANCZOS4)
|
| 324 |
|
| 325 |
-
# Optional: Apply GFPGAN too for dual restoration
|
| 326 |
if GFPGAN_LOADED and gfpgan_restorer:
|
| 327 |
try:
|
| 328 |
_, _, restored_face = gfpgan_restorer.enhance(
|
|
@@ -334,16 +305,14 @@ def swap_face_in_frame(frame, source_face, target_face_idx=None, include_hair=Fa
|
|
| 334 |
except:
|
| 335 |
pass
|
| 336 |
|
| 337 |
-
# Put restored face back
|
| 338 |
result[y1:y2, x1:x2] = restored_face
|
| 339 |
|
| 340 |
-
print(" β CodeFormer applied
|
| 341 |
|
| 342 |
except Exception as e:
|
| 343 |
print(f" β CodeFormer error: {e}")
|
| 344 |
|
| 345 |
elif GFPGAN_LOADED and gfpgan_restorer:
|
| 346 |
-
# Fallback to GFPGAN only
|
| 347 |
try:
|
| 348 |
swapped_faces = face_app.get(result)
|
| 349 |
|
|
@@ -368,7 +337,7 @@ def swap_face_in_frame(frame, source_face, target_face_idx=None, include_hair=Fa
|
|
| 368 |
|
| 369 |
result[y1:y2, x1:x2] = restored_face
|
| 370 |
|
| 371 |
-
print(" β GFPGAN applied
|
| 372 |
|
| 373 |
except Exception as e:
|
| 374 |
print(f" β GFPGAN error: {e}")
|
|
@@ -380,7 +349,7 @@ def swap_face_in_frame(frame, source_face, target_face_idx=None, include_hair=Fa
|
|
| 380 |
# ============================================================================
|
| 381 |
|
| 382 |
def process_video(video_path, source_face, target_face_index, include_hair, progress_fn):
|
| 383 |
-
"""Process video with
|
| 384 |
|
| 385 |
if not SWAPPER_LOADED:
|
| 386 |
raise ValueError("INSwapper not loaded!")
|
|
@@ -395,11 +364,6 @@ def process_video(video_path, source_face, target_face_index, include_hair, prog
|
|
| 395 |
else:
|
| 396 |
print("FACE SWAP MODE: Swapping face only")
|
| 397 |
|
| 398 |
-
if CODEFORMER_LOADED:
|
| 399 |
-
print("Using CodeFormer - 100% quality!")
|
| 400 |
-
elif GFPGAN_LOADED:
|
| 401 |
-
print("Using GFPGAN - 90% quality")
|
| 402 |
-
|
| 403 |
processed_frames = []
|
| 404 |
|
| 405 |
for i, frame in enumerate(clip.iter_frames()):
|
|
@@ -528,7 +492,7 @@ def handle_generate(source_choice, target_choice, include_hair, progress=gr.Prog
|
|
| 528 |
|
| 529 |
progress(1.0, desc="Complete!")
|
| 530 |
|
| 531 |
-
status = "β
DONE
|
| 532 |
status += "Applied:\n"
|
| 533 |
status += "β INSwapper face swap\n"
|
| 534 |
if include_hair:
|
|
@@ -537,9 +501,9 @@ def handle_generate(source_choice, target_choice, include_hair, progress=gr.Prog
|
|
| 537 |
status += "β FACE SWAP (face only)\n"
|
| 538 |
|
| 539 |
if CODEFORMER_LOADED:
|
| 540 |
-
status += "β CodeFormer restoration
|
| 541 |
elif GFPGAN_LOADED:
|
| 542 |
-
status += "β GFPGAN restoration
|
| 543 |
|
| 544 |
return result, status
|
| 545 |
|
|
@@ -555,8 +519,8 @@ print("\n[7/7] Building interface...")
|
|
| 555 |
|
| 556 |
with gr.Blocks(theme=gr.themes.Soft(), title="Ultimate Face Swap") as demo:
|
| 557 |
|
| 558 |
-
gr.Markdown("# π₯ ULTIMATE
|
| 559 |
-
gr.Markdown("###
|
| 560 |
|
| 561 |
if SWAPPER_LOADED:
|
| 562 |
gr.Markdown("β
**INSwapper Loaded**")
|
|
@@ -564,77 +528,14 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Ultimate Face Swap") as demo:
|
|
| 564 |
gr.Markdown("β **INSwapper Failed**")
|
| 565 |
|
| 566 |
if CODEFORMER_LOADED:
|
| 567 |
-
gr.Markdown("β
**CodeFormer Active**
|
| 568 |
elif GFPGAN_LOADED:
|
| 569 |
-
gr.Markdown("
|
| 570 |
else:
|
| 571 |
-
gr.Markdown("β οΈ **No restoration**
|
| 572 |
|
| 573 |
with gr.Row():
|
| 574 |
with gr.Column():
|
| 575 |
gr.Markdown("### πΈ Source Image")
|
| 576 |
source_image = gr.Image(type="numpy", label="Upload Source Face")
|
| 577 |
-
source_preview = gr.Image(label
|
| 578 |
-
source_status = gr.Textbox(label="Status", lines=2)
|
| 579 |
-
source_dropdown = gr.Dropdown(label="Select Face")
|
| 580 |
-
|
| 581 |
-
with gr.Column():
|
| 582 |
-
gr.Markdown("### π¬ Target Video")
|
| 583 |
-
target_video = gr.Video(label="Upload Target Video")
|
| 584 |
-
target_preview = gr.Image(label="Detected (1/4 size)", height=300)
|
| 585 |
-
target_status = gr.Textbox(label="Status", lines=2)
|
| 586 |
-
target_dropdown = gr.Dropdown(label="Select Person")
|
| 587 |
-
|
| 588 |
-
gr.Markdown("### π Generate Video")
|
| 589 |
-
|
| 590 |
-
head_swap_checkbox = gr.Checkbox(
|
| 591 |
-
value=False,
|
| 592 |
-
label="π₯ HEAD SWAP MODE (includes hair, ears, neck!)"
|
| 593 |
-
)
|
| 594 |
-
|
| 595 |
-
generate_button = gr.Button(
|
| 596 |
-
"π Generate 100% Quality Video!",
|
| 597 |
-
variant="primary",
|
| 598 |
-
size="lg"
|
| 599 |
-
)
|
| 600 |
-
|
| 601 |
-
generation_status = gr.Textbox(label="Status", lines=6)
|
| 602 |
-
result_video = gr.Video(label="Result")
|
| 603 |
-
|
| 604 |
-
# Events
|
| 605 |
-
source_image.change(
|
| 606 |
-
handle_source_image,
|
| 607 |
-
inputs=[source_image],
|
| 608 |
-
outputs=[source_preview, source_status, source_dropdown]
|
| 609 |
-
)
|
| 610 |
-
|
| 611 |
-
target_video.change(
|
| 612 |
-
handle_target_video,
|
| 613 |
-
inputs=[target_video],
|
| 614 |
-
outputs=[target_preview, target_status, target_dropdown]
|
| 615 |
-
)
|
| 616 |
-
|
| 617 |
-
generate_button.click(
|
| 618 |
-
handle_generate,
|
| 619 |
-
inputs=[source_dropdown, target_dropdown, head_swap_checkbox],
|
| 620 |
-
outputs=[result_video, generation_status]
|
| 621 |
-
)
|
| 622 |
-
|
| 623 |
-
print("β Interface built")
|
| 624 |
-
|
| 625 |
-
print("\n" + "="*80)
|
| 626 |
-
print("LAUNCHING - 100% QUALITY + HEAD SWAP MODE!")
|
| 627 |
-
print("="*80)
|
| 628 |
-
|
| 629 |
-
if SWAPPER_LOADED and CODEFORMER_LOADED:
|
| 630 |
-
print("β
All systems ready - 100% quality + head swap available!")
|
| 631 |
-
elif SWAPPER_LOADED:
|
| 632 |
-
print("β οΈ CodeFormer unavailable - quality limited to 90%")
|
| 633 |
-
else:
|
| 634 |
-
print("β INSwapper failed")
|
| 635 |
-
|
| 636 |
-
demo.queue()
|
| 637 |
-
demo.launch(share=True)
|
| 638 |
-
|
| 639 |
-
print("\nβ
Running!")
|
| 640 |
-
print("="*80)
|
|
|
|
| 1 |
# ============================================================================
|
| 2 |
# ULTIMATE FACE SWAP - 100% QUALITY + HEAD SWAP (WITH HAIR!)
|
| 3 |
+
# Fixed for Hugging Face Spaces deployment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
# ============================================================================
|
| 5 |
|
| 6 |
print("="*80)
|
|
|
|
| 12 |
print("\n[1/7] Installing packages...")
|
| 13 |
subprocess.check_call([
|
| 14 |
sys.executable, "-m", "pip", "install", "-q",
|
| 15 |
+
"gradio==4.44.1", "insightface==0.7.3", "onnxruntime",
|
| 16 |
+
"opencv-python-headless", "moviepy==1.0.3", "numpy", "scipy", "tqdm",
|
| 17 |
+
"gfpgan", "basicsr", "facexlib", "torch", "torchvision"
|
| 18 |
])
|
| 19 |
print("β Installed")
|
| 20 |
|
|
|
|
| 31 |
try:
|
| 32 |
from moviepy.editor import VideoFileClip, ImageSequenceClip
|
| 33 |
except ImportError:
|
| 34 |
+
from moviepy import VideoFileClip, ImageSequenceClip
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
from tqdm import tqdm
|
| 37 |
print("β Imported")
|
| 38 |
|
| 39 |
# ============================================================================
|
| 40 |
+
# SECTION 1: FACE DETECTION (CPU MODE)
|
| 41 |
# ============================================================================
|
| 42 |
print("\n[3/7] Loading face detector...")
|
| 43 |
+
face_app = FaceAnalysis(name="buffalo_l", providers=['CPUExecutionProvider'])
|
| 44 |
+
face_app.prepare(ctx_id=-1, det_size=(640, 640)) # ctx_id=-1 for CPU
|
| 45 |
+
print("β Face detector loaded (CPU mode)")
|
| 46 |
|
| 47 |
# ============================================================================
|
| 48 |
+
# SECTION 2: INSWAPPER MODEL (CPU MODE)
|
| 49 |
# ============================================================================
|
| 50 |
print("\n[4/7] Loading INSwapper...")
|
| 51 |
|
|
|
|
| 62 |
urllib.request.urlretrieve(url, model_path)
|
| 63 |
print(f" β Downloaded ({os.path.getsize(model_path) // 1_000_000}MB)")
|
| 64 |
|
| 65 |
+
swapper = get_model(model_path, download=False, download_zip=False, providers=['CPUExecutionProvider'])
|
| 66 |
|
| 67 |
SWAPPER_LOADED = True
|
| 68 |
+
print("β INSwapper loaded (CPU mode)")
|
| 69 |
|
| 70 |
except Exception as e:
|
| 71 |
print(f"β INSwapper failed: {e}")
|
| 72 |
|
| 73 |
# ============================================================================
|
| 74 |
+
# SECTION 3: CODEFORMER (SIMPLIFIED FOR CPU)
|
| 75 |
# ============================================================================
|
| 76 |
+
print("\n[5/7] Loading CodeFormer...")
|
| 77 |
|
| 78 |
codeformer_net = None
|
| 79 |
CODEFORMER_LOADED = False
|
|
|
|
| 83 |
from basicsr.utils.download_util import load_file_from_url
|
| 84 |
from basicsr.utils import imwrite, img2tensor, tensor2img
|
| 85 |
from facexlib.utils.face_restoration_helper import FaceRestoreHelper
|
|
|
|
| 86 |
import torch
|
| 87 |
|
| 88 |
# Download CodeFormer model
|
|
|
|
| 96 |
print(" β Downloaded")
|
| 97 |
|
| 98 |
# Load CodeFormer network
|
|
|
|
| 99 |
from basicsr.archs import build_network
|
| 100 |
|
| 101 |
codeformer_net = build_network({
|
|
|
|
| 110 |
codeformer_net.load_state_dict(checkpoint['params_ema'])
|
| 111 |
codeformer_net.eval()
|
| 112 |
|
| 113 |
+
# Always use CPU for Spaces
|
| 114 |
+
device = 'cpu'
|
| 115 |
codeformer_net = codeformer_net.to(device)
|
| 116 |
|
| 117 |
# Face helper for detection and alignment
|
|
|
|
| 126 |
)
|
| 127 |
|
| 128 |
CODEFORMER_LOADED = True
|
| 129 |
+
print("β CodeFormer loaded (CPU mode)")
|
| 130 |
|
| 131 |
except Exception as e:
|
| 132 |
print(f"β CodeFormer failed: {e}")
|
|
|
|
| 135 |
# ============================================================================
|
| 136 |
# SECTION 4: GFPGAN (BACKUP/COMPLEMENTARY)
|
| 137 |
# ============================================================================
|
| 138 |
+
print("\n[6/7] Loading GFPGAN...")
|
| 139 |
|
| 140 |
gfpgan_restorer = None
|
| 141 |
GFPGAN_LOADED = False
|
|
|
|
| 158 |
upscale=2,
|
| 159 |
arch='clean',
|
| 160 |
channel_multiplier=2,
|
| 161 |
+
bg_upsampler=None,
|
| 162 |
+
device='cpu' # Force CPU
|
| 163 |
)
|
| 164 |
|
| 165 |
GFPGAN_LOADED = True
|
| 166 |
+
print("β GFPGAN loaded (CPU mode)")
|
| 167 |
|
| 168 |
except Exception as e:
|
| 169 |
print(f"β GFPGAN unavailable: {e}")
|
|
|
|
| 217 |
return cv2.cvtColor(preview_small, cv2.COLOR_BGR2RGB), faces
|
| 218 |
|
| 219 |
# ============================================================================
|
| 220 |
+
# CODEFORMER RESTORATION FUNCTION
|
| 221 |
# ============================================================================
|
| 222 |
|
| 223 |
def restore_with_codeformer(face_img, fidelity_weight=0.2):
|
| 224 |
+
"""Apply CodeFormer restoration"""
|
|
|
|
|
|
|
|
|
|
| 225 |
import torch
|
| 226 |
+
from torchvision.transforms import functional as F
|
| 227 |
|
| 228 |
+
device = 'cpu'
|
| 229 |
|
| 230 |
# Prepare image
|
| 231 |
face_img = cv2.resize(face_img, (512, 512), interpolation=cv2.INTER_LINEAR)
|
|
|
|
| 233 |
face_img = torch.from_numpy(face_img).permute(2, 0, 1).unsqueeze(0).to(device)
|
| 234 |
|
| 235 |
# Normalize
|
| 236 |
+
face_img = F.normalize(face_img, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
|
| 237 |
|
| 238 |
# Run CodeFormer
|
| 239 |
with torch.no_grad():
|
|
|
|
| 246 |
return output
|
| 247 |
|
| 248 |
# ============================================================================
|
| 249 |
+
# ENHANCED FACE SWAP
|
| 250 |
# ============================================================================
|
| 251 |
|
| 252 |
def swap_face_in_frame(frame, source_face, target_face_idx=None, include_hair=False):
|
| 253 |
+
"""Enhanced face swap with restoration"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if not SWAPPER_LOADED:
|
| 255 |
return frame
|
| 256 |
|
|
|
|
| 269 |
for target_face in target_faces:
|
| 270 |
result = swapper.get(result, target_face, source_face, paste_back=True)
|
| 271 |
|
| 272 |
+
# Apply restoration
|
| 273 |
if CODEFORMER_LOADED and codeformer_net:
|
| 274 |
try:
|
| 275 |
swapped_faces = face_app.get(result)
|
| 276 |
|
| 277 |
for face in swapped_faces:
|
| 278 |
x1, y1, x2, y2 = face.bbox.astype(int)
|
|
|
|
|
|
|
| 279 |
h, w = result.shape[:2]
|
| 280 |
|
| 281 |
if include_hair:
|
| 282 |
+
pad = int(max(x2-x1, y2-y1) * 0.6)
|
|
|
|
| 283 |
else:
|
| 284 |
+
pad = int(max(x2-x1, y2-y1) * 0.3)
|
|
|
|
| 285 |
|
| 286 |
x1 = max(0, x1 - pad)
|
| 287 |
y1 = max(0, y1 - pad)
|
| 288 |
x2 = min(w, x2 + pad)
|
| 289 |
y2 = min(h, y2 + pad)
|
| 290 |
|
|
|
|
| 291 |
face_region = result[y1:y2, x1:x2].copy()
|
| 292 |
original_size = (x2-x1, y2-y1)
|
| 293 |
|
|
|
|
| 294 |
restored_face = restore_with_codeformer(face_region, fidelity_weight=0.2)
|
|
|
|
|
|
|
| 295 |
restored_face = cv2.resize(restored_face, original_size, interpolation=cv2.INTER_LANCZOS4)
|
| 296 |
|
|
|
|
| 297 |
if GFPGAN_LOADED and gfpgan_restorer:
|
| 298 |
try:
|
| 299 |
_, _, restored_face = gfpgan_restorer.enhance(
|
|
|
|
| 305 |
except:
|
| 306 |
pass
|
| 307 |
|
|
|
|
| 308 |
result[y1:y2, x1:x2] = restored_face
|
| 309 |
|
| 310 |
+
print(" β CodeFormer applied")
|
| 311 |
|
| 312 |
except Exception as e:
|
| 313 |
print(f" β CodeFormer error: {e}")
|
| 314 |
|
| 315 |
elif GFPGAN_LOADED and gfpgan_restorer:
|
|
|
|
| 316 |
try:
|
| 317 |
swapped_faces = face_app.get(result)
|
| 318 |
|
|
|
|
| 337 |
|
| 338 |
result[y1:y2, x1:x2] = restored_face
|
| 339 |
|
| 340 |
+
print(" β GFPGAN applied")
|
| 341 |
|
| 342 |
except Exception as e:
|
| 343 |
print(f" β GFPGAN error: {e}")
|
|
|
|
| 349 |
# ============================================================================
|
| 350 |
|
| 351 |
def process_video(video_path, source_face, target_face_index, include_hair, progress_fn):
|
| 352 |
+
"""Process video with face swap"""
|
| 353 |
|
| 354 |
if not SWAPPER_LOADED:
|
| 355 |
raise ValueError("INSwapper not loaded!")
|
|
|
|
| 364 |
else:
|
| 365 |
print("FACE SWAP MODE: Swapping face only")
|
| 366 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
processed_frames = []
|
| 368 |
|
| 369 |
for i, frame in enumerate(clip.iter_frames()):
|
|
|
|
| 492 |
|
| 493 |
progress(1.0, desc="Complete!")
|
| 494 |
|
| 495 |
+
status = "β
DONE!\n\n"
|
| 496 |
status += "Applied:\n"
|
| 497 |
status += "β INSwapper face swap\n"
|
| 498 |
if include_hair:
|
|
|
|
| 501 |
status += "β FACE SWAP (face only)\n"
|
| 502 |
|
| 503 |
if CODEFORMER_LOADED:
|
| 504 |
+
status += "β CodeFormer restoration\n"
|
| 505 |
elif GFPGAN_LOADED:
|
| 506 |
+
status += "β GFPGAN restoration\n"
|
| 507 |
|
| 508 |
return result, status
|
| 509 |
|
|
|
|
| 519 |
|
| 520 |
with gr.Blocks(theme=gr.themes.Soft(), title="Ultimate Face Swap") as demo:
|
| 521 |
|
| 522 |
+
gr.Markdown("# π₯ ULTIMATE FACE SWAP + HEAD SWAP!")
|
| 523 |
+
gr.Markdown("### Professional face swapping with enhancement")
|
| 524 |
|
| 525 |
if SWAPPER_LOADED:
|
| 526 |
gr.Markdown("β
**INSwapper Loaded**")
|
|
|
|
| 528 |
gr.Markdown("β **INSwapper Failed**")
|
| 529 |
|
| 530 |
if CODEFORMER_LOADED:
|
| 531 |
+
gr.Markdown("β
**CodeFormer Active**")
|
| 532 |
elif GFPGAN_LOADED:
|
| 533 |
+
gr.Markdown("β
**GFPGAN Active**")
|
| 534 |
else:
|
| 535 |
+
gr.Markdown("β οΈ **No restoration available**")
|
| 536 |
|
| 537 |
with gr.Row():
|
| 538 |
with gr.Column():
|
| 539 |
gr.Markdown("### πΈ Source Image")
|
| 540 |
source_image = gr.Image(type="numpy", label="Upload Source Face")
|
| 541 |
+
source_preview = gr.Image(label
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|