Swapperface / app.py
Scrappy-Doo's picture
Create app.py
8a9cfc4 verified
Raw
History Blame Contribute Delete
5.28 kB
import os
import cv2
import copy
import spaces
import gradio as gr
import insightface
import onnxruntime
import numpy as np
from PIL import Image
from typing import List, Union
# ─── CONFIGURACIÓN ─────────────────────────────────────────
MODEL_PATH = "./inswapper_128.onnx"
DET_SIZE = (320, 320)
# ─── CARGA DE MODELOS A NIVEL DE MÓDULO ────────────────────
# ZeroGPU: los modelos se cargan en 'cuda' aquí.
# Fuera de @spaces.GPU usa emulación CUDA; dentro, GPU real.
# Face swapper (ONNX - GPU)
face_swapper = insightface.model_zoo.get_model(MODEL_PATH)
# Face analyser (InsightFace - CPU para evitar conflictos ONNX CUDA en ZeroGPU)
face_analyser = insightface.app.FaceAnalysis(
name="buffalo_l",
root="./checkpoints",
providers=["CPUExecutionProvider"]
)
face_analyser.prepare(ctx_id=0, det_size=DET_SIZE)
# ─── FUNCIONES AUXILIARES ──────────────────────────────────
def get_many_faces(frame: np.ndarray):
"""Obtiene caras ordenadas de izquierda a derecha"""
try:
face = face_analyser.get(frame)
return sorted(face, key=lambda x: x.bbox[0])
except (IndexError, TypeError):
return None
def swap_face(source_faces, target_faces, source_index, target_index, temp_frame):
"""Pega la cara fuente en la imagen objetivo"""
source_face = source_faces[source_index]
target_face = target_faces[target_index]
return face_swapper.get(temp_frame, target_face, source_face, paste_back=True)
# ─── FUNCIÓN GPU (decorada para ZeroGPU) ───────────────────
@spaces.GPU(duration=90)
def process_image(source_img: Image.Image, target_img: Image.Image):
"""
Pipeline principal de face swapping.
Corre en GPU Zero con duración de 90s (detección CPU + swap GPU).
"""
if source_img is None or target_img is None:
return None, "Faltan imágenes. Sube ambas."
# Convertir a BGR para OpenCV
target_cv = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
source_cv = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR)
# Detectar caras (CPU - más estable en ZeroGPU)
target_faces = get_many_faces(target_cv)
source_faces = get_many_faces(source_cv)
if target_faces is None or len(target_faces) == 0:
return None, "No se detectaron caras en la imagen objetivo"
if source_faces is None or len(source_faces) == 0:
return None, "No se detectaron caras en la imagen fuente"
num_target = len(target_faces)
num_source = len(source_faces)
temp_frame = copy.deepcopy(target_cv)
# Lógica de reemplazo
if num_source == 1:
# Una cara fuente -> reemplazar todas las caras objetivo
for i in range(num_target):
temp_frame = swap_face(source_faces, target_faces, 0, i, temp_frame)
else:
# Múltiples caras fuente -> mapeo 1 a 1
iterations = min(num_source, num_target)
for i in range(iterations):
temp_frame = swap_face(source_faces, target_faces, i, i, temp_frame)
# Convertir de vuelta a RGB
result_img = Image.fromarray(cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB))
return result_img, f"✅ Swap completado: {num_target} cara(s) reemplazada(s)"
# ─── INTERFAZ GRADIO ───────────────────────────────────────
with gr.Blocks(title="Swapperface - Face Swap", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🎭 Swapperface
### Face Swapper con InsightFace + ZeroGPU
Sube una imagen fuente (cara a copiar) y una imagen objetivo (donde pegar la cara).
""")
with gr.Row():
with gr.Column():
source_input = gr.Image(
label="Imagen Fuente (Cara a copiar)",
type="pil",
image_mode="RGB",
height=400
)
with gr.Column():
target_input = gr.Image(
label="Imagen Objetivo (Donde pegar)",
type="pil",
image_mode="RGB",
height=400
)
swap_btn = gr.Button("🔄 Realizar Face Swap", variant="primary")
with gr.Row():
output_image = gr.Image(label="Resultado", type="pil", height=400)
output_text = gr.Textbox(label="Estado", interactive=False)
# Ejemplos (cache desactivado para ZeroGPU - no hay GPU en startup)
gr.Examples(
examples=[
["./examples/source1.jpg", "./examples/target1.jpg"],
["./examples/source2.jpg", "./examples/target2.jpg"],
],
inputs=[source_input, target_input],
outputs=[output_image, output_text],
fn=process_image,
cache_examples=False,
)
swap_btn.click(
fn=process_image,
inputs=[source_input, target_input],
outputs=[output_image, output_text]
)
gr.Markdown("""
---
⚡ **ZeroGPU**: La inferencia corre en GPU dinámica. Puede haber cola si hay muchos usuarios.
""")
if __name__ == "__main__":
demo.queue().launch()