Scrappy-Doo commited on
Commit
8a9cfc4
·
verified ·
1 Parent(s): 7181c0a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -0
app.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import copy
4
+ import spaces
5
+ import gradio as gr
6
+ import insightface
7
+ import onnxruntime
8
+ import numpy as np
9
+ from PIL import Image
10
+ from typing import List, Union
11
+
12
+ # ─── CONFIGURACIÓN ─────────────────────────────────────────
13
+ MODEL_PATH = "./inswapper_128.onnx"
14
+ DET_SIZE = (320, 320)
15
+
16
+ # ─── CARGA DE MODELOS A NIVEL DE MÓDULO ────────────────────
17
+ # ZeroGPU: los modelos se cargan en 'cuda' aquí.
18
+ # Fuera de @spaces.GPU usa emulación CUDA; dentro, GPU real.
19
+
20
+ # Face swapper (ONNX - GPU)
21
+ face_swapper = insightface.model_zoo.get_model(MODEL_PATH)
22
+
23
+ # Face analyser (InsightFace - CPU para evitar conflictos ONNX CUDA en ZeroGPU)
24
+ face_analyser = insightface.app.FaceAnalysis(
25
+ name="buffalo_l",
26
+ root="./checkpoints",
27
+ providers=["CPUExecutionProvider"]
28
+ )
29
+ face_analyser.prepare(ctx_id=0, det_size=DET_SIZE)
30
+
31
+ # ─── FUNCIONES AUXILIARES ──────────────────────────────────
32
+ def get_many_faces(frame: np.ndarray):
33
+ """Obtiene caras ordenadas de izquierda a derecha"""
34
+ try:
35
+ face = face_analyser.get(frame)
36
+ return sorted(face, key=lambda x: x.bbox[0])
37
+ except (IndexError, TypeError):
38
+ return None
39
+
40
+ def swap_face(source_faces, target_faces, source_index, target_index, temp_frame):
41
+ """Pega la cara fuente en la imagen objetivo"""
42
+ source_face = source_faces[source_index]
43
+ target_face = target_faces[target_index]
44
+ return face_swapper.get(temp_frame, target_face, source_face, paste_back=True)
45
+
46
+ # ─── FUNCIÓN GPU (decorada para ZeroGPU) ───────────────────
47
+ @spaces.GPU(duration=90)
48
+ def process_image(source_img: Image.Image, target_img: Image.Image):
49
+ """
50
+ Pipeline principal de face swapping.
51
+ Corre en GPU Zero con duración de 90s (detección CPU + swap GPU).
52
+ """
53
+ if source_img is None or target_img is None:
54
+ return None, "Faltan imágenes. Sube ambas."
55
+
56
+ # Convertir a BGR para OpenCV
57
+ target_cv = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
58
+ source_cv = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR)
59
+
60
+ # Detectar caras (CPU - más estable en ZeroGPU)
61
+ target_faces = get_many_faces(target_cv)
62
+ source_faces = get_many_faces(source_cv)
63
+
64
+ if target_faces is None or len(target_faces) == 0:
65
+ return None, "No se detectaron caras en la imagen objetivo"
66
+
67
+ if source_faces is None or len(source_faces) == 0:
68
+ return None, "No se detectaron caras en la imagen fuente"
69
+
70
+ num_target = len(target_faces)
71
+ num_source = len(source_faces)
72
+
73
+ temp_frame = copy.deepcopy(target_cv)
74
+
75
+ # Lógica de reemplazo
76
+ if num_source == 1:
77
+ # Una cara fuente -> reemplazar todas las caras objetivo
78
+ for i in range(num_target):
79
+ temp_frame = swap_face(source_faces, target_faces, 0, i, temp_frame)
80
+ else:
81
+ # Múltiples caras fuente -> mapeo 1 a 1
82
+ iterations = min(num_source, num_target)
83
+ for i in range(iterations):
84
+ temp_frame = swap_face(source_faces, target_faces, i, i, temp_frame)
85
+
86
+ # Convertir de vuelta a RGB
87
+ result_img = Image.fromarray(cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB))
88
+ return result_img, f"✅ Swap completado: {num_target} cara(s) reemplazada(s)"
89
+
90
+ # ─── INTERFAZ GRADIO ───────────────────────────────────────
91
+ with gr.Blocks(title="Swapperface - Face Swap", theme=gr.themes.Soft()) as demo:
92
+ gr.Markdown("""
93
+ # 🎭 Swapperface
94
+ ### Face Swapper con InsightFace + ZeroGPU
95
+ Sube una imagen fuente (cara a copiar) y una imagen objetivo (donde pegar la cara).
96
+ """)
97
+
98
+ with gr.Row():
99
+ with gr.Column():
100
+ source_input = gr.Image(
101
+ label="Imagen Fuente (Cara a copiar)",
102
+ type="pil",
103
+ image_mode="RGB",
104
+ height=400
105
+ )
106
+ with gr.Column():
107
+ target_input = gr.Image(
108
+ label="Imagen Objetivo (Donde pegar)",
109
+ type="pil",
110
+ image_mode="RGB",
111
+ height=400
112
+ )
113
+
114
+ swap_btn = gr.Button("🔄 Realizar Face Swap", variant="primary")
115
+
116
+ with gr.Row():
117
+ output_image = gr.Image(label="Resultado", type="pil", height=400)
118
+ output_text = gr.Textbox(label="Estado", interactive=False)
119
+
120
+ # Ejemplos (cache desactivado para ZeroGPU - no hay GPU en startup)
121
+ gr.Examples(
122
+ examples=[
123
+ ["./examples/source1.jpg", "./examples/target1.jpg"],
124
+ ["./examples/source2.jpg", "./examples/target2.jpg"],
125
+ ],
126
+ inputs=[source_input, target_input],
127
+ outputs=[output_image, output_text],
128
+ fn=process_image,
129
+ cache_examples=False,
130
+ )
131
+
132
+ swap_btn.click(
133
+ fn=process_image,
134
+ inputs=[source_input, target_input],
135
+ outputs=[output_image, output_text]
136
+ )
137
+
138
+ gr.Markdown("""
139
+ ---
140
+ ⚡ **ZeroGPU**: La inferencia corre en GPU dinámica. Puede haber cola si hay muchos usuarios.
141
+ """)
142
+
143
+ if __name__ == "__main__":
144
+ demo.queue().launch()