Spaces:
Paused
Paused
| 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) ─────────────────── | |
| 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() |