Spaces:
Running on Zero
Running on Zero
File size: 10,750 Bytes
93529a2 f2df896 d3dd6f3 f2df896 d3dd6f3 f2df896 d3dd6f3 f2df896 89dded3 d3dd6f3 f2df896 d3dd6f3 89dded3 f2e40d1 f2df896 89dded3 8d3d31d d3dd6f3 f2e40d1 f2df896 d3dd6f3 f2df896 d3dd6f3 f2df896 d3dd6f3 f2e40d1 f2df896 d3dd6f3 f2df896 d3dd6f3 f2df896 8d3d31d d3dd6f3 4ba0021 f2df896 d3dd6f3 f2df896 8d3d31d d3dd6f3 4ba0021 f2df896 d3dd6f3 f2df896 8d3d31d d3dd6f3 4ba0021 f2df896 d3dd6f3 f2df896 d3dd6f3 f2e40d1 4ba0021 f2df896 4ba0021 f2e40d1 91d18ca f2e40d1 91d18ca f2e40d1 91d18ca f2e40d1 91d18ca f2df896 526ac93 d3dd6f3 f2df896 f2e40d1 91d18ca f2e40d1 f2df896 f2e40d1 4ba0021 f2e40d1 d3dd6f3 f2e40d1 d3dd6f3 f2e40d1 f2df896 f2e40d1 f2df896 f2e40d1 f2df896 f2e40d1 4ba0021 d3dd6f3 8d3d31d d3dd6f3 8d3d31d f2e40d1 d3dd6f3 f2df896 8d3d31d d3dd6f3 4ba0021 f2df896 d3dd6f3 526ac93 d3dd6f3 8d3d31d d3dd6f3 f2df896 8d3d31d d3dd6f3 526ac93 d3dd6f3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | import spaces
import gradio as gr
import torch
import cv2
import numpy as np
import os
from PIL import Image, ImageDraw, ImageFilter
from diffusers import StableDiffusionXLInpaintPipeline
from insightface.app import FaceAnalysis
import insightface
from huggingface_hub import hf_hub_download, snapshot_download
# ==============================================================================
# 1. SETUP DELL'AMBIENTE E DOWNLOAD MODELLI
# ==============================================================================
# Download sicuro e robusto di antelopev2 da Hugging Face per evitare errori di unzip
print("Download modelli AntelopeV2 da Hugging Face...")
try:
snapshot_download("DIAMONIK7777/antelopev2", local_dir="./models/antelopev2")
except Exception as e:
print(f"Errore download AntelopeV2: {e}")
if not os.path.exists("./models/inswapper_128.onnx"):
print("Download Inswapper...")
hf_hub_download(repo_id="ezioruan/inswapper_128.onnx", filename="inswapper_128.onnx", local_dir="./models")
# Inizializza l'analizzatore di volti (InsightFace)
print("Inizializzazione FaceAnalysis...")
face_app = FaceAnalysis(name='antelopev2', root='./', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
face_app.prepare(ctx_id=0, det_size=(640, 640))
# Inizializza Inswapper (per fissare l'identità alla fine)
print("Inizializzazione Inswapper...")
swapper = insightface.model_zoo.get_model('./models/inswapper_128.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
# Caricamento Pipeline Inpainting SDXL
print("Caricamento pipeline SDXL Inpaint...")
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.float16 if torch.cuda.is_available() else "float32"
pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
"diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
torch_dtype=dtype,
variant="fp16" if torch.cuda.is_available() else None,
use_safetensors=True
)
# Aggiunta IP-Adapter per l'analisi semantica (Capelli, Occhiali, Barba)
print("Caricamento IP-Adapter per SDXL...")
pipe.load_ip_adapter("h94/IP-Adapter", subfolder="sdxl_models", weight_name="ip-adapter_sdxl.bin")
pipe.set_ip_adapter_scale(0.7) # Bilanciamento tra fedeltà al selfie e stile del prompt
pipe.watermark = None
# ==============================================================================
# 2. CONFIGURAZIONE STILI E TEMPLATE
# ==============================================================================
STYLES = {
"Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
"template": "esempi/8bit.png",
"repo_id": "nerijs/pixel-art-xl",
"weight_name": "pixel-art-xl.safetensors",
"prompt": "pixelart style, 8-bit game style face and hair, perfect pixel art shading, 8-bit retro aesthetic, matching lighting",
"negative_prompt": "ugly, deformed, lowres, realistic, photographic, 3d render, photo, photorealistic"
},
"Stile 2: Il Costruttore Rinascimentale": {
"template": "esempi/rinascimentale.png",
"repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
"weight_name": "pytorch_lora_weights.safetensors",
"prompt": "renaissance oil painting, oil painting face and hair, elegant brush strokes, chiaroscuro lighting, classic masterpiece, perfectly integrated head",
"negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, pixel art, cartoon"
},
"Stile 3: Genio Creativo (Pop-Art)": {
"template": "esempi/popart.png",
"repo_id": None,
"weight_name": None,
"prompt": "vintage pop art, Comic book style pop-art face and hair, vibrant colors, halftone dots, graphic illustration, bold lines",
"negative_prompt": "ugly, deformed, photorealistic, realistic photography, oil painting, 3d render"
}
}
# ==============================================================================
# 3. FUNZIONI CORE DELL'APPLICAZIONE
# ==============================================================================
def create_head_mask(image_pil, face_bbox):
"""Crea una maschera che copre l'intera testa (inclusi capelli e barba) del template."""
x1, y1, x2, y2 = face_bbox
w = x2 - x1
h = y2 - y1
cx, cy = x1 + w/2, y1 + h/2
mask = Image.new("L", image_pil.size, 0)
draw = ImageDraw.Draw(mask)
# Area ovale molto abbondante per coprire capelli in alto e barba in basso
mask_x1 = max(0, cx - w*1.1)
mask_x2 = min(image_pil.width, cx + w*1.1)
mask_y1 = max(0, cy - h*1.4) # Su per i capelli/cappelli
mask_y2 = min(image_pil.height, cy + h*0.8) # Giù per la barba
draw.ellipse((mask_x1, mask_y1, mask_x2, mask_y2), fill=255)
mask = mask.filter(ImageFilter.GaussianBlur(15))
return mask
@spaces.GPU(duration=60)
def generate_avatar(user_image, selected_style):
if user_image is None:
raise gr.Error("Per favore, carica un'immagine del volto.")
# 1. Caricamento e analisi del Template
style_config = STYLES[selected_style]
template_path = style_config["template"]
if not os.path.exists(template_path):
raise gr.Error(f"Errore: il file di template {template_path} non esiste.")
template_pil = Image.open(template_path).convert("RGB")
cv_template_image = cv2.cvtColor(np.array(template_pil), cv2.COLOR_RGB2BGR)
template_faces = face_app.get(cv_template_image)
if len(template_faces) == 0:
raise gr.Error("Impossibile trovare un volto nel template di base.")
template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
# 2. Analisi del volto utente
cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
user_faces = face_app.get(cv_user_image)
if len(user_faces) == 0:
raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
# 3. Creazione maschera per rigenerazione della testa
print("Creazione maschera per svuotare la testa del template...")
mask_pil = create_head_mask(template_pil, template_face.bbox)
# Ritaglio del selfie (per dare all'IP-Adapter solo la testa e ignorare lo sfondo della stanza)
x1, y1, x2, y2 = user_face.bbox
w = x2 - x1
h = y2 - y1
cx, cy = x1 + w/2, y1 + h/2
crop_size = max(w, h) * 2.5
ip_x1 = max(0, int(cx - crop_size/2))
ip_y1 = max(0, int(cy - crop_size/1.5))
ip_x2 = min(user_image.width, int(cx + crop_size/2))
ip_y2 = min(user_image.height, int(cy + crop_size/2))
ip_image = user_image.crop((ip_x1, ip_y1, ip_x2, ip_y2))
# 4. Rigenerazione Semantica (SDXL + IP-Adapter)
repo_id = style_config["repo_id"]
if repo_id:
try:
print(f"Caricamento LoRA {repo_id}...")
pipe.load_lora_weights(repo_id, weight_name=style_config["weight_name"])
except Exception as e:
print(f"Errore caricamento LoRA: {e}")
pipe.to(device)
print("Inizio Generazione Semantica della Testa (SDXL + IP-Adapter)...")
# Usa una strength quasi al 100% per ignorare i pixel della testa originale sotto la maschera
generated_image = pipe(
prompt=style_config["prompt"],
negative_prompt=style_config["negative_prompt"],
image=template_pil,
mask_image=mask_pil,
ip_adapter_image=ip_image,
strength=0.99,
guidance_scale=8.0,
num_inference_steps=35,
).images[0]
if repo_id:
try:
pipe.unload_lora_weights()
except:
pass
# 5. Iniezione dell'Identità pura (FaceSwap)
print("Iniezione dell'identità tramite Inswapper...")
cv_generated = cv2.cvtColor(np.array(generated_image), cv2.COLOR_RGB2BGR)
generated_faces = face_app.get(cv_generated)
if len(generated_faces) > 0:
# Trova la faccia appena generata dall'IA
gen_face = sorted(generated_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
# Incolla ESATTAMENTE gli occhi, naso e bocca originali dell'utente sopra la testa generata
cv_final = swapper.get(cv_generated, gen_face, user_face, paste_back=True)
final_image = Image.fromarray(cv2.cvtColor(cv_final, cv2.COLOR_BGR2RGB))
else:
print("Attenzione: SDXL non ha generato un volto rilevabile. Restituisco l'immagine senza Inswapper.")
final_image = generated_image
return final_image
# ==============================================================================
# 4. INTERFACCIA GRADIO (FRONTEND)
# ==============================================================================
custom_css = """
.container { max-width: 1000px; margin: auto; padding-top: 2rem; }
.header-title { text-align: center; color: #1e3a8a; font-family: 'Inter', sans-serif; font-weight: 800; font-size: 2.5rem; margin-bottom: 0.5rem; }
.header-subtitle { text-align: center; color: #4b5563; font-size: 1.1rem; margin-bottom: 2rem; }
.generate-btn { background: linear-gradient(90deg, #10b981 0%, #3b82f6 100%) !important; color: white !important; font-weight: bold !important; font-size: 1.2rem !important; border: none !important; transition: transform 0.2s ease; }
.generate-btn:hover { transform: scale(1.02); }
"""
theme = gr.themes.Soft(primary_hue="blue", secondary_hue="emerald").set(
button_primary_background_fill="*primary_500",
button_primary_background_fill_hover="*primary_600",
)
with gr.Blocks(css=custom_css, theme=theme) as demo:
with gr.Column(elem_classes="container"):
gr.HTML("<h1 class='header-title'>🇮🇹 Italian Builders Avatar Generator 🇮🇹</h1>")
gr.HTML("<p class='header-subtitle'>Trasforma il tuo selfie e unisciti alla community.</p>")
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie (Ben illuminato)")
with gr.Column(scale=1):
style_selector = gr.Radio(
choices=list(STYLES.keys()),
label="2. Scegli il tuo Stile",
value=list(STYLES.keys())[0]
)
gr.Markdown("<br>")
generate_button = gr.Button("Genera il tuo Avatar 🚀", elem_classes="generate-btn", variant="primary")
with gr.Column(scale=1):
output_image = gr.Image(label="Il tuo Avatar", interactive=False)
generate_button.click(
fn=generate_avatar,
inputs=[input_image, style_selector],
outputs=output_image
)
if __name__ == "__main__":
demo.launch()
|