Update app.py
Browse files
app.py
CHANGED
|
@@ -1,42 +1,47 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
-
import gradio as gr
|
| 5 |
-
from insightface.app import FaceAnalysis
|
| 6 |
-
from insightface.model_zoo import get_model
|
| 7 |
from PIL import Image
|
|
|
|
| 8 |
import tempfile
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
face_analyzer = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
|
| 12 |
face_analyzer.prepare(ctx_id=0, det_size=(640, 640))
|
| 13 |
|
| 14 |
-
|
| 15 |
-
swapper =
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
def swap_faces(source_img, target_img, face_index_str, blend_alpha):
|
| 40 |
if source_img is None or target_img is None:
|
| 41 |
raise gr.Error("Please upload both source and target images.")
|
| 42 |
|
|
@@ -44,62 +49,81 @@ def swap_faces(source_img, target_img, face_index_str, blend_alpha):
|
|
| 44 |
target_np = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
|
| 45 |
|
| 46 |
source_faces = face_analyzer.get(source_np)
|
| 47 |
-
if len(source_faces) == 0:
|
| 48 |
-
raise gr.Error("No face found in source image.")
|
| 49 |
-
|
| 50 |
target_faces = face_analyzer.get(target_np)
|
|
|
|
|
|
|
|
|
|
| 51 |
if len(target_faces) == 0:
|
| 52 |
-
raise gr.Error("No faces
|
| 53 |
|
| 54 |
face_index = int(face_index_str)
|
| 55 |
if face_index >= len(target_faces):
|
| 56 |
-
raise gr.Error("Face index out of range.")
|
| 57 |
|
| 58 |
-
# Perform face swap
|
| 59 |
try:
|
| 60 |
swapped = swapper.get(target_np, target_faces[face_index], source_faces[0], paste_back=True)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
final_rgb = cv2.cvtColor(blended, cv2.COLOR_BGR2RGB)
|
| 67 |
output_img = Image.fromarray(final_rgb)
|
| 68 |
|
| 69 |
-
# Save temp file for download
|
| 70 |
tmp_file = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
| 71 |
output_img.save(tmp_file.name)
|
| 72 |
-
return output_img, tmp_file.name, "β
|
| 73 |
-
|
| 74 |
except Exception as e:
|
| 75 |
-
raise gr.Error(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
# Gradio UI
|
| 78 |
-
with gr.Blocks(title="
|
| 79 |
-
gr.Markdown("
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
with gr.Row():
|
| 82 |
-
|
| 83 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
blend_slider = gr.Slider(label="Swap Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0)
|
| 89 |
-
swap_button = gr.Button("π Swap Face")
|
| 90 |
|
| 91 |
with gr.Row():
|
| 92 |
-
|
| 93 |
-
|
|
|
|
| 94 |
|
|
|
|
|
|
|
|
|
|
| 95 |
status = gr.Textbox(label="Status", interactive=False)
|
| 96 |
|
| 97 |
-
|
| 98 |
-
outputs=[
|
| 99 |
-
|
| 100 |
-
swap_button.click(swap_faces,
|
| 101 |
-
inputs=[source_image, target_image, face_dropdown, blend_slider],
|
| 102 |
-
outputs=[output_image, download_file, status])
|
| 103 |
|
| 104 |
-
if __name__ ==
|
| 105 |
-
demo.launch()
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
|
| 3 |
+
import gradio as gr
|
| 4 |
import cv2
|
| 5 |
import numpy as np
|
|
|
|
|
|
|
|
|
|
| 6 |
from PIL import Image
|
| 7 |
+
import os
|
| 8 |
import tempfile
|
| 9 |
+
from insightface.app import FaceAnalysis
|
| 10 |
+
from insightface.model_zoo.inswapper import INSwapper
|
| 11 |
+
from skimage.exposure import match_histograms
|
| 12 |
+
import torch
|
| 13 |
+
from basicsr.archs.rrdbnet_arch import RRDBNet
|
| 14 |
+
from realesrgan import RealESRGANer
|
| 15 |
+
from gfpgan import GFPGANer
|
| 16 |
+
|
| 17 |
+
# Setup face analyzer
|
| 18 |
face_analyzer = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
|
| 19 |
face_analyzer.prepare(ctx_id=0, det_size=(640, 640))
|
| 20 |
|
| 21 |
+
# Load face swapper
|
| 22 |
+
swapper = INSwapper("models/inswapper_128.onnx")
|
| 23 |
+
|
| 24 |
+
# Load GFPGAN and Real-ESRGAN
|
| 25 |
+
bg_upsampler = RealESRGANer(
|
| 26 |
+
scale=2,
|
| 27 |
+
model_path='models/RealESRGAN_x2plus.pth',
|
| 28 |
+
model=RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64,
|
| 29 |
+
num_block=23, num_grow_ch=32, scale=2),
|
| 30 |
+
tile=400, tile_pad=10, pre_pad=0, half=False
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
face_enhancer = GFPGANer(
|
| 34 |
+
model_path='models/GFPGANv1.3.pth',
|
| 35 |
+
upscale=2,
|
| 36 |
+
arch='clean',
|
| 37 |
+
channel_multiplier=2,
|
| 38 |
+
bg_upsampler=bg_upsampler
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
def match_histogram(source_face, target_face):
|
| 42 |
+
return match_histograms(source_face, target_face, channel_axis=-1)
|
| 43 |
+
|
| 44 |
+
def swap_faces(source_img, target_img, face_index_str, blend_alpha, enhance_face):
|
|
|
|
|
|
|
| 45 |
if source_img is None or target_img is None:
|
| 46 |
raise gr.Error("Please upload both source and target images.")
|
| 47 |
|
|
|
|
| 49 |
target_np = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
|
| 50 |
|
| 51 |
source_faces = face_analyzer.get(source_np)
|
|
|
|
|
|
|
|
|
|
| 52 |
target_faces = face_analyzer.get(target_np)
|
| 53 |
+
|
| 54 |
+
if len(source_faces) == 0:
|
| 55 |
+
raise gr.Error("No face detected in source image.")
|
| 56 |
if len(target_faces) == 0:
|
| 57 |
+
raise gr.Error("No faces detected in target image.")
|
| 58 |
|
| 59 |
face_index = int(face_index_str)
|
| 60 |
if face_index >= len(target_faces):
|
| 61 |
+
raise gr.Error(f"Face index {face_index} out of range (found {len(target_faces)} faces).")
|
| 62 |
|
|
|
|
| 63 |
try:
|
| 64 |
swapped = swapper.get(target_np, target_faces[face_index], source_faces[0], paste_back=True)
|
| 65 |
+
|
| 66 |
+
box = target_faces[face_index].bbox.astype(int)
|
| 67 |
+
x1, y1, x2, y2 = box
|
| 68 |
+
original_face = target_np[y1:y2, x1:x2]
|
| 69 |
+
swapped_face = swapped[y1:y2, x1:x2]
|
| 70 |
+
matched = match_histogram(swapped_face, original_face)
|
| 71 |
+
swapped[y1:y2, x1:x2] = matched.astype(np.uint8)
|
| 72 |
+
|
| 73 |
+
mask = np.zeros_like(swapped[:, :, 0])
|
| 74 |
+
cv2.rectangle(mask, (x1, y1), (x2, y2), 255, -1)
|
| 75 |
+
mask = cv2.GaussianBlur(mask, (15, 15), 0)
|
| 76 |
+
blended = np.where(mask[..., None] > 0,
|
| 77 |
+
cv2.addWeighted(swapped, blend_alpha, target_np, 1 - blend_alpha, 0),
|
| 78 |
+
target_np)
|
| 79 |
+
|
| 80 |
+
if enhance_face:
|
| 81 |
+
_, _, enhanced = face_enhancer.enhance(blended, has_aligned=False, only_center_face=False, paste_back=True)
|
| 82 |
+
blended = enhanced
|
| 83 |
+
|
| 84 |
final_rgb = cv2.cvtColor(blended, cv2.COLOR_BGR2RGB)
|
| 85 |
output_img = Image.fromarray(final_rgb)
|
| 86 |
|
|
|
|
| 87 |
tmp_file = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
| 88 |
output_img.save(tmp_file.name)
|
| 89 |
+
return output_img, tmp_file.name, "β
Swap & Enhancement Completed!"
|
|
|
|
| 90 |
except Exception as e:
|
| 91 |
+
raise gr.Error(f"Swap failed: {str(e)}")
|
| 92 |
+
|
| 93 |
+
def count_faces(image):
|
| 94 |
+
if image is None:
|
| 95 |
+
return "Please upload a target image."
|
| 96 |
+
img_np = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
| 97 |
+
faces = face_analyzer.get(img_np)
|
| 98 |
+
return f"{len(faces)} face(s) detected in the target image."
|
| 99 |
|
| 100 |
# Gradio UI
|
| 101 |
+
with gr.Blocks(title="π« Face Swap & Enhance App") as demo:
|
| 102 |
+
gr.Markdown("""
|
| 103 |
+
# π Face Swapper + π©βπ§ GFPGAN Enhancer + π§ Real-ESRGAN
|
| 104 |
+
Upload a face and swap it into another photo. Optional face enhancement with GFPGAN!
|
| 105 |
+
""")
|
| 106 |
|
| 107 |
with gr.Row():
|
| 108 |
+
source_input = gr.Image(label="π§ Source Face", type="pil")
|
| 109 |
+
target_input = gr.Image(label="π§ Target Image", type="pil")
|
| 110 |
|
| 111 |
+
detect_button = gr.Button("π Detect Faces in Target")
|
| 112 |
+
face_count = gr.Textbox(label="Detected Faces", interactive=False)
|
| 113 |
+
detect_button.click(count_faces, inputs=target_input, outputs=face_count)
|
|
|
|
|
|
|
| 114 |
|
| 115 |
with gr.Row():
|
| 116 |
+
face_index = gr.Textbox(label="π― Face Index", value="0")
|
| 117 |
+
blend_slider = gr.Slider(label="π« Blend Smoothness", minimum=0, maximum=1, value=0.8, step=0.05)
|
| 118 |
+
enhance_face = gr.Checkbox(label="β¨ Enhance Face with GFPGAN", value=True)
|
| 119 |
|
| 120 |
+
swap_button = gr.Button("π Run Face Swap")
|
| 121 |
+
output_img = gr.Image(label="πΌοΈ Output")
|
| 122 |
+
download_path = gr.File(label="π₯ Download")
|
| 123 |
status = gr.Textbox(label="Status", interactive=False)
|
| 124 |
|
| 125 |
+
swap_button.click(swap_faces, inputs=[source_input, target_input, face_index, blend_slider, enhance_face],
|
| 126 |
+
outputs=[output_img, download_path, status])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
+
if __name__ == '__main__':
|
| 129 |
+
demo.launch()
|