CheckSim commited on
Commit
d3dd6f3
·
verified ·
1 Parent(s): aeb1532

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +255 -0
  2. requirements.txt +10 -0
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import math
4
+ import torch
5
+ import numpy as np
6
+ import gradio as gr
7
+ import spaces
8
+ from PIL import Image
9
+
10
+ from huggingface_hub import hf_hub_download, snapshot_download
11
+ from insightface.app import FaceAnalysis
12
+ from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel
13
+ from diffusers.pipelines.controlnet.multicontrolnet import MultiControlNetModel
14
+
15
+ # ==============================================================================
16
+ # 1. SETUP E INIZIALIZZAZIONE GLOBALE
17
+ # ==============================================================================
18
+
19
+ device = "cuda" if torch.cuda.is_available() else "cpu"
20
+ dtype = torch.float16 if torch.cuda.is_available() else torch.float32
21
+
22
+ def download_antelopev2():
23
+ """Scarica automaticamente i modelli di InsightFace se non sono presenti."""
24
+ model_dir = "./models/antelopev2"
25
+ if not os.path.exists(model_dir) or len(os.listdir(model_dir)) < 5:
26
+ print("Scaricamento dei modelli antelopev2 in corso...")
27
+ os.makedirs(model_dir, exist_ok=True)
28
+ # Scarica da un repository pubblico affidabile
29
+ snapshot_download(repo_id="DIAMONIK7777/antelopev2", local_dir=model_dir)
30
+ print("Scaricamento completato.")
31
+ return "./models"
32
+
33
+ model_root = download_antelopev2()
34
+
35
+ app = FaceAnalysis(name='antelopev2', root=model_root, providers=['CPUExecutionProvider'])
36
+ app.prepare(ctx_id=0, det_size=(640, 640))
37
+
38
+ base_model_path = "stabilityai/stable-diffusion-xl-base-1.0"
39
+ instantid_path = "InstantX/InstantID"
40
+ canny_path = "diffusers/controlnet-canny-sdxl-1.0"
41
+
42
+ print("Caricamento ControlNet InstantID e Canny (per il Logo)...")
43
+ # Usiamo MultiControlNet per unire il riconoscimento facciale (InstantID) e la forma del logo (Canny)
44
+ controlnet_identity = ControlNetModel.from_pretrained(instantid_path, torch_dtype=dtype, use_safetensors=True)
45
+ controlnet_logo = ControlNetModel.from_pretrained(canny_path, torch_dtype=dtype, use_safetensors=True)
46
+ controlnet = MultiControlNetModel([controlnet_identity, controlnet_logo])
47
+
48
+ print("Caricamento Pipeline SDXL...")
49
+ pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
50
+ base_model_path,
51
+ controlnet=controlnet,
52
+ torch_dtype=dtype,
53
+ use_safetensors=True
54
+ )
55
+ pipe.to(device)
56
+
57
+ print("Caricamento Face Adapter InstantID...")
58
+ pipe.load_ip_adapter("InstantX/InstantID", subfolder="models", weight_name="ip-adapter.bin")
59
+
60
+
61
+ # ==============================================================================
62
+ # 2. CONFIGURAZIONE STILI (Uso di Modelli Hugging Face Pubblici)
63
+ # ==============================================================================
64
+
65
+ # Invece di file locali, usiamo i repo ID di modelli pubblici gratuiti su Hugging Face
66
+ STYLES = {
67
+ "Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
68
+ "repo_id": "nerijs/pixel-art-xl",
69
+ "weight_name": "pixel-art-xl.safetensors",
70
+ "prompt": "pixelart style, Pixel art portrait of a tech builder, indie hacker typing on a retro mechanical keyboard. He is wearing a jacket with a small green, white, and red square patch on the chest. Cyberpunk desk setup, glowing monitors displaying code. High quality, 8-bit game style.",
71
+ "negative_prompt": "ugly, deformed, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, watermark, text"
72
+ },
73
+ "Stile 2: Il Costruttore Rinascimentale": {
74
+ "repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
75
+ "weight_name": "pytorch_lora_weights.safetensors",
76
+ "prompt": "renaissance oil painting, A masterpiece renaissance oil painting of an Italian genius inventor in the style of Leonardo da Vinci. The man is holding a wooden prototype. He is wearing elegant renaissance dark clothes with a subtle green, white and red enameled pin on the lapel. Architectural blueprints on a rustic wooden table. Chiaroscuro lighting.",
77
+ "negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, watermark, text"
78
+ },
79
+ "Stile 3: L'Architetto del Futuro (Design Geometrico)": {
80
+ "repo_id": "goofyai/flat_illustration_sdxl",
81
+ "weight_name": "flat_illustration_sdxl.safetensors",
82
+ "prompt": "flat vector art, Modern flat vector illustration of a tech architect holding a smartphone. He wears a minimalist grey suit jacket featuring a small geometric green, white, and red enamel badge. Clean geometric lines, industrial design background, minimalistic.",
83
+ "negative_prompt": "ugly, deformed, photorealistic, 3d render, bad anatomy, worst quality, low quality, watermark, text"
84
+ },
85
+ "Stile 4: Genio Creativo (Pop-Art)": {
86
+ "repo_id": None, # SDXL di base gestisce molto bene la pop-art senza LoRA
87
+ "weight_name": None,
88
+ "prompt": "vintage pop art, Comic book style pop-art portrait of a creative maker. Vibrant colors. He wears a trendy jacket adorned with an 'Italian Builders' custom patch in green, white and red. Halftone dots, vintage poster aesthetic.",
89
+ "negative_prompt": "ugly, deformed, photorealistic, bad anatomy, worst quality, low quality, watermark, extra text"
90
+ }
91
+ }
92
+
93
+ # ==============================================================================
94
+ # 3. PIPELINE DI GENERAZIONE
95
+ # ==============================================================================
96
+
97
+ def draw_kps(image_pil, kps, color_list=[(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255)]):
98
+ """Genera keypoints per InstantID."""
99
+ stickwidth = 4
100
+ limbSeq = np.array([[0, 2], [1, 2], [3, 2], [4, 2]])
101
+ kps = np.array(kps)
102
+ w, h = image_pil.size
103
+ out_img = np.zeros([h, w, 3])
104
+
105
+ for i in range(len(limbSeq)):
106
+ index = limbSeq[i]
107
+ color = color_list[index[0]]
108
+ x = kps[index][:, 0]
109
+ y = kps[index][:, 1]
110
+ length = ((x[0] - x[1]) ** 2 + (y[0] - y[1]) ** 2) ** 0.5
111
+ angle = math.degrees(math.atan2(y[0] - y[1], x[0] - x[1]))
112
+ polygon = cv2.ellipse2Poly((int(np.mean(x)), int(np.mean(y))), (int(length / 2), stickwidth), int(angle), 0, 360, 1)
113
+ cv2.fillConvexPoly(out_img, polygon, color)
114
+ out_img = (out_img * 0.6).astype(np.uint8)
115
+
116
+ for idx, kp in enumerate(kps):
117
+ color = color_list[idx]
118
+ x, y = kp
119
+ cv2.circle(out_img, (int(x), int(y)), 10, color, -1)
120
+
121
+ return Image.fromarray(out_img.astype(np.uint8))
122
+
123
+ def process_logo_canny(logo_img):
124
+ """Estrae i bordi (Canny) dal logo per guidare l'AI sulla forma, ignorando i colori originali."""
125
+ logo_cv = cv2.cvtColor(np.array(logo_img), cv2.COLOR_RGB2GRAY)
126
+ edges = cv2.Canny(logo_cv, 100, 200)
127
+ edges_3c = np.stack([edges]*3, axis=-1)
128
+
129
+ # Crea un'immagine vuota della grandezza finale (es. 1024x1024)
130
+ canvas = np.zeros((1024, 1024, 3), dtype=np.uint8)
131
+
132
+ # Ridimensiona il logo per farlo diventare una spilla/toppa (es. 200x200 pixel)
133
+ logo_resized = cv2.resize(edges_3c, (200, 200))
134
+
135
+ # Posiziona il logo in basso a destra (es. sul petto o nell'angolo)
136
+ y_offset = 700
137
+ x_offset = 200
138
+ canvas[y_offset:y_offset+200, x_offset:x_offset+200] = logo_resized
139
+
140
+ return Image.fromarray(canvas)
141
+
142
+ @spaces.GPU(duration=120)
143
+ def generate_avatar(user_image, logo_image, selected_style):
144
+ if user_image is None:
145
+ raise gr.Error("Per favore, carica un'immagine del volto.")
146
+ if logo_image is None:
147
+ raise gr.Error("Per favore, carica l'immagine del logo.")
148
+
149
+ # 1. Analisi del volto
150
+ cv_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
151
+ faces = app.get(cv_image)
152
+ if len(faces) == 0:
153
+ raise gr.Error("Nessun volto rilevato. Riprova con un'altra foto in cui il volto sia ben visibile.")
154
+
155
+ face = sorted(faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
156
+ face_emb = face.normed_embedding
157
+
158
+ # Ridimensioniamo l'immagine utente a 1024x1024 (standard SDXL) prima di estrarre i keypoints
159
+ user_image_resized = user_image.resize((1024, 1024))
160
+ faces_resized = app.get(cv2.cvtColor(np.array(user_image_resized), cv2.COLOR_RGB2BGR))
161
+ if len(faces_resized) > 0:
162
+ face_kps = sorted(faces_resized, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1].kps
163
+ else:
164
+ face_kps = face.kps * (1024 / max(user_image.size)) # Approssimazione se fallisce il resize
165
+
166
+ kps_image = draw_kps(user_image_resized, face_kps)
167
+
168
+ # 2. Elaborazione del Logo
169
+ # Il colore originale viene ignorato, prendiamo solo la sagoma (Canny edges)
170
+ logo_canny_image = process_logo_canny(logo_image)
171
+
172
+ # 3. Gestione LoRA dinamico da Hugging Face
173
+ style_config = STYLES[selected_style]
174
+ repo_id = style_config["repo_id"]
175
+
176
+ if repo_id:
177
+ try:
178
+ print(f"Scaricamento/Caricamento LoRA {repo_id}...")
179
+ pipe.load_lora_weights(repo_id, weight_name=style_config["weight_name"])
180
+ except Exception as e:
181
+ print(f"Errore caricamento LoRA da HF: {e}")
182
+
183
+ # 4. Generazione immagine
184
+ prompt = style_config["prompt"]
185
+ negative_prompt = style_config["negative_prompt"]
186
+
187
+ print("Inizio generazione MultiControlNet...")
188
+ image = pipe(
189
+ prompt=prompt,
190
+ negative_prompt=negative_prompt,
191
+ image_embeds=face_emb,
192
+ # Passiamo le due immagini di controllo: [Volto, Forma del Logo]
193
+ image=[kps_image, logo_canny_image],
194
+ # Bilanciamo l'influenza: 0.8 per il volto, 0.4 per il logo (abbastanza per imprimere la forma ma fuso)
195
+ controlnet_conditioning_scale=[0.8, 0.4],
196
+ ip_adapter_scale=0.8,
197
+ num_inference_steps=30,
198
+ guidance_scale=5.0,
199
+ ).images[0]
200
+
201
+ if repo_id:
202
+ try:
203
+ pipe.unload_lora_weights()
204
+ except:
205
+ pass
206
+
207
+ return image
208
+
209
+ # ==============================================================================
210
+ # 4. INTERFACCIA GRADIO (FRONTEND)
211
+ # ==============================================================================
212
+
213
+ custom_css = """
214
+ .container { max-width: 1000px; margin: auto; padding-top: 2rem; }
215
+ .header-title { text-align: center; color: #1e3a8a; font-family: 'Inter', sans-serif; font-weight: 800; font-size: 2.5rem; margin-bottom: 0.5rem; }
216
+ .header-subtitle { text-align: center; color: #4b5563; font-size: 1.1rem; margin-bottom: 2rem; }
217
+ .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; }
218
+ .generate-btn:hover { transform: scale(1.02); }
219
+ """
220
+
221
+ theme = gr.themes.Soft(primary_hue="blue", secondary_hue="emerald").set(
222
+ button_primary_background_fill="*primary_500",
223
+ button_primary_background_fill_hover="*primary_600",
224
+ )
225
+
226
+ with gr.Blocks(css=custom_css, theme=theme) as demo:
227
+ with gr.Column(elem_classes="container"):
228
+ gr.HTML("<h1 class='header-title'>🇮🇹 Italian Builders Avatar Generator 🇮🇹</h1>")
229
+ gr.HTML("<p class='header-subtitle'>Trasforma il tuo selfie. Il tuo brand, la tua identità.</p>")
230
+
231
+ with gr.Row():
232
+ with gr.Column(scale=1):
233
+ input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie")
234
+ logo_image = gr.Image(type="pil", label="2. Carica il Logo (es. PNG a colori)")
235
+
236
+ with gr.Column(scale=1):
237
+ style_selector = gr.Radio(
238
+ choices=list(STYLES.keys()),
239
+ label="3. Scegli il tuo Stile",
240
+ value=list(STYLES.keys())[0]
241
+ )
242
+ gr.Markdown("<br>")
243
+ generate_button = gr.Button("Genera il tuo Avatar 🚀", elem_classes="generate-btn", variant="primary")
244
+
245
+ with gr.Column(scale=1):
246
+ output_image = gr.Image(label="Il tuo Avatar Builder", interactive=False)
247
+
248
+ generate_button.click(
249
+ fn=generate_avatar,
250
+ inputs=[input_image, logo_image, style_selector],
251
+ outputs=output_image
252
+ )
253
+
254
+ if __name__ == "__main__":
255
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ spaces
3
+ torch
4
+ diffusers>=0.26.0
5
+ transformers
6
+ accelerate
7
+ insightface
8
+ onnxruntime
9
+ opencv-python
10
+ huggingface-hub