CheckSim commited on
Commit
8d3d31d
·
verified ·
1 Parent(s): dcf28c3

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -80
app.py CHANGED
@@ -9,8 +9,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.models.controlnets.multicontrolnet import MultiControlNetModel
14
 
15
  # ==============================================================================
16
  # 1. SETUP E INIZIALIZZAZIONE GLOBALE
@@ -25,72 +25,69 @@ def download_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
- # Insightface aggiunge in automatico "/models/antelopev2" alla root che gli passiamo.
32
- # Quindi la root corretta da passargli è "./", così cercherà in "./models/antelopev2".
33
  return "./"
34
 
35
  model_root = download_antelopev2()
36
 
37
- app = FaceAnalysis(name='antelopev2', root=model_root, providers=['CPUExecutionProvider'])
38
- app.prepare(ctx_id=0, det_size=(640, 640))
39
 
40
- base_model_path = "stabilityai/stable-diffusion-xl-base-1.0"
41
- instantid_path = "InstantX/InstantID"
42
- canny_path = "diffusers/controlnet-canny-sdxl-1.0"
43
 
44
- print("Caricamento ControlNet InstantID e Canny (per il Logo)...")
45
- # Usiamo MultiControlNet per unire il riconoscimento facciale (InstantID) e la forma del logo (Canny)
46
- controlnet_identity = ControlNetModel.from_pretrained(
47
- instantid_path,
48
- subfolder="ControlNetModel",
49
- torch_dtype=dtype,
50
  use_safetensors=True
51
  )
52
- controlnet_logo = ControlNetModel.from_pretrained(canny_path, torch_dtype=dtype, use_safetensors=True)
53
- controlnet = MultiControlNetModel([controlnet_identity, controlnet_logo])
54
 
55
- print("Caricamento Pipeline SDXL...")
56
- pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
57
- base_model_path,
 
58
  controlnet=controlnet,
59
  torch_dtype=dtype,
60
  use_safetensors=True
61
  )
62
- pipe.to(device)
63
 
 
64
  print("Caricamento Face Adapter InstantID...")
65
- pipe.load_ip_adapter("InstantX/InstantID", subfolder="", weight_name="ip-adapter.bin")
 
 
 
66
 
67
 
68
  # ==============================================================================
69
- # 2. CONFIGURAZIONE STILI (Uso di Modelli Hugging Face Pubblici)
70
  # ==============================================================================
71
 
72
- # Invece di file locali, usiamo i repo ID di modelli pubblici gratuiti su Hugging Face
73
  STYLES = {
74
  "Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
75
- "repo_id": "nerijs/pixel-art-xl",
76
  "weight_name": "pixel-art-xl.safetensors",
77
  "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.",
78
  "negative_prompt": "ugly, deformed, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, watermark, text"
79
  },
80
  "Stile 2: Il Costruttore Rinascimentale": {
81
- "repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
82
  "weight_name": "pytorch_lora_weights.safetensors",
83
  "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.",
84
  "negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, watermark, text"
85
  },
86
  "Stile 3: L'Architetto del Futuro (Design Geometrico)": {
87
- "repo_id": "goofyai/flat_illustration_sdxl",
88
  "weight_name": "flat_illustration_sdxl.safetensors",
89
  "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.",
90
  "negative_prompt": "ugly, deformed, photorealistic, 3d render, bad anatomy, worst quality, low quality, watermark, text"
91
  },
92
  "Stile 4: Genio Creativo (Pop-Art)": {
93
- "repo_id": None, # SDXL di base gestisce molto bene la pop-art senza LoRA
94
  "weight_name": None,
95
  "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.",
96
  "negative_prompt": "ugly, deformed, photorealistic, bad anatomy, worst quality, low quality, watermark, extra text"
@@ -102,7 +99,7 @@ STYLES = {
102
  # ==============================================================================
103
 
104
  def draw_kps(image_pil, kps, color_list=[(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255)]):
105
- """Genera keypoints per InstantID."""
106
  stickwidth = 4
107
  limbSeq = np.array([[0, 2], [1, 2], [3, 2], [4, 2]])
108
  kps = np.array(kps)
@@ -127,59 +124,71 @@ def draw_kps(image_pil, kps, color_list=[(255,0,0), (0,255,0), (0,0,255), (255,2
127
 
128
  return Image.fromarray(out_img.astype(np.uint8))
129
 
130
- def process_logo_canny(logo_img):
131
- """Estrae i bordi (Canny) dal logo per guidare l'AI sulla forma, ignorando i colori originali."""
132
- logo_cv = cv2.cvtColor(np.array(logo_img), cv2.COLOR_RGB2GRAY)
133
- edges = cv2.Canny(logo_cv, 100, 200)
134
- edges_3c = np.stack([edges]*3, axis=-1)
135
-
136
- # Crea un'immagine vuota della grandezza finale (es. 1024x1024)
137
- canvas = np.zeros((1024, 1024, 3), dtype=np.uint8)
138
-
139
- # Ridimensiona il logo per farlo diventare una spilla/toppa (es. 200x200 pixel)
140
- logo_resized = cv2.resize(edges_3c, (200, 200))
141
-
142
- # Posiziona il logo in basso a destra (es. sul petto o nell'angolo)
143
- y_offset = 700
144
- x_offset = 200
145
- canvas[y_offset:y_offset+200, x_offset:x_offset+200] = logo_resized
146
-
147
- return Image.fromarray(canvas)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  @spaces.GPU(duration=120)
150
  def generate_avatar(user_image, logo_image, selected_style):
151
  if user_image is None:
152
  raise gr.Error("Per favore, carica un'immagine del volto.")
153
- if logo_image is None:
154
- raise gr.Error("Per favore, carica l'immagine del logo.")
155
 
156
  # 1. Analisi del volto
157
  cv_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
158
- faces = app.get(cv_image)
159
  if len(faces) == 0:
160
  raise gr.Error("Nessun volto rilevato. Riprova con un'altra foto in cui il volto sia ben visibile.")
161
-
162
  face = sorted(faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
163
  face_emb = face.normed_embedding
164
-
165
- # Ridimensioniamo l'immagine utente a 1024x1024 (standard SDXL) prima di estrarre i keypoints
 
166
  user_image_resized = user_image.resize((1024, 1024))
167
- faces_resized = app.get(cv2.cvtColor(np.array(user_image_resized), cv2.COLOR_RGB2BGR))
168
  if len(faces_resized) > 0:
169
  face_kps = sorted(faces_resized, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1].kps
170
- else:
171
- face_kps = face.kps * (1024 / max(user_image.size)) # Approssimazione se fallisce il resize
172
-
173
  kps_image = draw_kps(user_image_resized, face_kps)
174
-
175
- # 2. Elaborazione del Logo
176
- # Il colore originale viene ignorato, prendiamo solo la sagoma (Canny edges)
177
- logo_canny_image = process_logo_canny(logo_image)
178
-
179
- # 3. Gestione LoRA dinamico da Hugging Face
180
  style_config = STYLES[selected_style]
181
  repo_id = style_config["repo_id"]
182
-
183
  if repo_id:
184
  try:
185
  print(f"Scaricamento/Caricamento LoRA {repo_id}...")
@@ -187,30 +196,32 @@ def generate_avatar(user_image, logo_image, selected_style):
187
  except Exception as e:
188
  print(f"Errore caricamento LoRA da HF: {e}")
189
 
190
- # 4. Generazione immagine
191
  prompt = style_config["prompt"]
192
  negative_prompt = style_config["negative_prompt"]
193
-
194
- print("Inizio generazione MultiControlNet...")
 
195
  image = pipe(
196
  prompt=prompt,
197
  negative_prompt=negative_prompt,
198
  image_embeds=face_emb,
199
- # Passiamo le due immagini di controllo: [Volto, Forma del Logo]
200
- image=[kps_image, logo_canny_image],
201
- # Bilanciamo l'influenza: 0.8 per il volto, 0.4 per il logo (abbastanza per imprimere la forma ma fuso)
202
- controlnet_conditioning_scale=[0.8, 0.4],
203
- ip_adapter_scale=0.8,
204
  num_inference_steps=30,
205
  guidance_scale=5.0,
206
  ).images[0]
207
-
208
  if repo_id:
209
  try:
210
  pipe.unload_lora_weights()
211
  except:
212
  pass
213
-
 
 
 
 
214
  return image
215
 
216
  # ==============================================================================
@@ -234,12 +245,12 @@ with gr.Blocks(css=custom_css, theme=theme) as demo:
234
  with gr.Column(elem_classes="container"):
235
  gr.HTML("<h1 class='header-title'>🇮🇹 Italian Builders Avatar Generator 🇮🇹</h1>")
236
  gr.HTML("<p class='header-subtitle'>Trasforma il tuo selfie. Il tuo brand, la tua identità.</p>")
237
-
238
  with gr.Row():
239
  with gr.Column(scale=1):
240
  input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie")
241
- logo_image = gr.Image(type="pil", label="2. Carica il Logo (es. PNG a colori)")
242
-
243
  with gr.Column(scale=1):
244
  style_selector = gr.Radio(
245
  choices=list(STYLES.keys()),
@@ -248,10 +259,10 @@ with gr.Blocks(css=custom_css, theme=theme) as demo:
248
  )
249
  gr.Markdown("<br>")
250
  generate_button = gr.Button("Genera il tuo Avatar 🚀", elem_classes="generate-btn", variant="primary")
251
-
252
  with gr.Column(scale=1):
253
  output_image = gr.Image(label="Il tuo Avatar Builder", interactive=False)
254
-
255
  generate_button.click(
256
  fn=generate_avatar,
257
  inputs=[input_image, logo_image, style_selector],
 
9
 
10
  from huggingface_hub import hf_hub_download, snapshot_download
11
  from insightface.app import FaceAnalysis
12
+ from diffusers import ControlNetModel
13
+ from diffusers.pipelines import StableDiffusionXLInstantIDPipeline
14
 
15
  # ==============================================================================
16
  # 1. SETUP E INIZIALIZZAZIONE GLOBALE
 
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
  snapshot_download(repo_id="DIAMONIK7777/antelopev2", local_dir=model_dir)
29
  print("Scaricamento completato.")
 
 
30
  return "./"
31
 
32
  model_root = download_antelopev2()
33
 
34
+ face_app = FaceAnalysis(name='antelopev2', root=model_root, providers=['CPUExecutionProvider'])
35
+ face_app.prepare(ctx_id=0, det_size=(640, 640))
36
 
37
+ # Scarica il file ip-adapter.bin di InstantID
38
+ face_adapter = hf_hub_download(repo_id="InstantX/InstantID", filename="ip-adapter.bin")
 
39
 
40
+ # Carica il ControlNet (IdentityNet) di InstantID
41
+ print("Caricamento ControlNet InstantID...")
42
+ controlnet = ControlNetModel.from_pretrained(
43
+ "InstantX/InstantID",
44
+ subfolder="ControlNetModel",
45
+ torch_dtype=dtype,
46
  use_safetensors=True
47
  )
 
 
48
 
49
+ # Carica la pipeline dedicata InstantID (NON quella generica ControlNet)
50
+ print("Caricamento Pipeline SDXL InstantID...")
51
+ pipe = StableDiffusionXLInstantIDPipeline.from_pretrained(
52
+ "stabilityai/stable-diffusion-xl-base-1.0",
53
  controlnet=controlnet,
54
  torch_dtype=dtype,
55
  use_safetensors=True
56
  )
 
57
 
58
+ # Carica il Face Adapter con il metodo specifico di InstantID
59
  print("Caricamento Face Adapter InstantID...")
60
+ pipe.load_ip_adapter_instantid(face_adapter)
61
+ pipe.to(device)
62
+
63
+ print("Pipeline pronta!")
64
 
65
 
66
  # ==============================================================================
67
+ # 2. CONFIGURAZIONE STILI
68
  # ==============================================================================
69
 
 
70
  STYLES = {
71
  "Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
72
+ "repo_id": "nerijs/pixel-art-xl",
73
  "weight_name": "pixel-art-xl.safetensors",
74
  "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.",
75
  "negative_prompt": "ugly, deformed, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, watermark, text"
76
  },
77
  "Stile 2: Il Costruttore Rinascimentale": {
78
+ "repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
79
  "weight_name": "pytorch_lora_weights.safetensors",
80
  "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.",
81
  "negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, watermark, text"
82
  },
83
  "Stile 3: L'Architetto del Futuro (Design Geometrico)": {
84
+ "repo_id": "goofyai/flat_illustration_sdxl",
85
  "weight_name": "flat_illustration_sdxl.safetensors",
86
  "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.",
87
  "negative_prompt": "ugly, deformed, photorealistic, 3d render, bad anatomy, worst quality, low quality, watermark, text"
88
  },
89
  "Stile 4: Genio Creativo (Pop-Art)": {
90
+ "repo_id": None,
91
  "weight_name": None,
92
  "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.",
93
  "negative_prompt": "ugly, deformed, photorealistic, bad anatomy, worst quality, low quality, watermark, extra text"
 
99
  # ==============================================================================
100
 
101
  def draw_kps(image_pil, kps, color_list=[(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255)]):
102
+ """Genera l'immagine dei keypoints per la ControlNet di InstantID."""
103
  stickwidth = 4
104
  limbSeq = np.array([[0, 2], [1, 2], [3, 2], [4, 2]])
105
  kps = np.array(kps)
 
124
 
125
  return Image.fromarray(out_img.astype(np.uint8))
126
 
127
+ def apply_logo_overlay(generated_image, logo_image, style_name):
128
+ """
129
+ Applica il logo sull'immagine generata come una "spilla" o "toppa",
130
+ adattandone l'aspetto in base allo stile scelto.
131
+ """
132
+ logo = logo_image.copy().convert("RGBA")
133
+
134
+ # Ridimensiona il logo per farlo sembrare una spilla (circa 15% dell'immagine)
135
+ logo_size = int(generated_image.width * 0.15)
136
+ logo = logo.resize((logo_size, logo_size), Image.LANCZOS)
137
+
138
+ # Applica filtri stilistici al logo in base allo stile
139
+ logo_np = np.array(logo)
140
+
141
+ if "Bit-Builder" in style_name:
142
+ # Effetto pixelato: rimpicciolisci e riallarga
143
+ small = cv2.resize(logo_np, (16, 16), interpolation=cv2.INTER_NEAREST)
144
+ logo_np = cv2.resize(small, (logo_size, logo_size), interpolation=cv2.INTER_NEAREST)
145
+ elif "Rinascimentale" in style_name:
146
+ # Effetto dipinto a olio: sfocatura leggera
147
+ logo_np[:, :, :3] = cv2.bilateralFilter(logo_np[:, :, :3], 9, 75, 75)
148
+ elif "Architetto" in style_name:
149
+ # Effetto geometrico: aumento contrasto
150
+ logo_np[:, :, :3] = np.clip(logo_np[:, :, :3].astype(np.float32) * 1.3, 0, 255).astype(np.uint8)
151
+ # Pop-Art: logo invariato (già vivace di suo)
152
+
153
+ logo_styled = Image.fromarray(logo_np)
154
+
155
+ # Posiziona il logo in basso a sinistra (zona petto)
156
+ x_pos = int(generated_image.width * 0.15)
157
+ y_pos = int(generated_image.height * 0.70)
158
+
159
+ # Componi sul risultato finale
160
+ result = generated_image.convert("RGBA")
161
+ result.paste(logo_styled, (x_pos, y_pos), logo_styled)
162
+
163
+ return result.convert("RGB")
164
 
165
  @spaces.GPU(duration=120)
166
  def generate_avatar(user_image, logo_image, selected_style):
167
  if user_image is None:
168
  raise gr.Error("Per favore, carica un'immagine del volto.")
 
 
169
 
170
  # 1. Analisi del volto
171
  cv_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
172
+ faces = face_app.get(cv_image)
173
  if len(faces) == 0:
174
  raise gr.Error("Nessun volto rilevato. Riprova con un'altra foto in cui il volto sia ben visibile.")
175
+
176
  face = sorted(faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
177
  face_emb = face.normed_embedding
178
+ face_kps = face.kps
179
+
180
+ # Ridimensioniamo l'immagine utente a 1024x1024 (standard SDXL)
181
  user_image_resized = user_image.resize((1024, 1024))
182
+ faces_resized = face_app.get(cv2.cvtColor(np.array(user_image_resized), cv2.COLOR_RGB2BGR))
183
  if len(faces_resized) > 0:
184
  face_kps = sorted(faces_resized, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1].kps
185
+
 
 
186
  kps_image = draw_kps(user_image_resized, face_kps)
187
+
188
+ # 2. Gestione LoRA
 
 
 
 
189
  style_config = STYLES[selected_style]
190
  repo_id = style_config["repo_id"]
191
+
192
  if repo_id:
193
  try:
194
  print(f"Scaricamento/Caricamento LoRA {repo_id}...")
 
196
  except Exception as e:
197
  print(f"Errore caricamento LoRA da HF: {e}")
198
 
199
+ # 3. Generazione immagine con InstantID
200
  prompt = style_config["prompt"]
201
  negative_prompt = style_config["negative_prompt"]
202
+
203
+ print("Inizio generazione InstantID...")
204
+ pipe.set_ip_adapter_scale(0.8)
205
  image = pipe(
206
  prompt=prompt,
207
  negative_prompt=negative_prompt,
208
  image_embeds=face_emb,
209
+ image=kps_image,
210
+ controlnet_conditioning_scale=0.8,
 
 
 
211
  num_inference_steps=30,
212
  guidance_scale=5.0,
213
  ).images[0]
214
+
215
  if repo_id:
216
  try:
217
  pipe.unload_lora_weights()
218
  except:
219
  pass
220
+
221
+ # 4. Sovrapposizione del logo stilizzato (se fornito)
222
+ if logo_image is not None:
223
+ image = apply_logo_overlay(image, logo_image, selected_style)
224
+
225
  return image
226
 
227
  # ==============================================================================
 
245
  with gr.Column(elem_classes="container"):
246
  gr.HTML("<h1 class='header-title'>🇮🇹 Italian Builders Avatar Generator 🇮🇹</h1>")
247
  gr.HTML("<p class='header-subtitle'>Trasforma il tuo selfie. Il tuo brand, la tua identità.</p>")
248
+
249
  with gr.Row():
250
  with gr.Column(scale=1):
251
  input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie")
252
+ logo_image = gr.Image(type="pil", label="2. Carica il Logo (opzionale, PNG a colori)")
253
+
254
  with gr.Column(scale=1):
255
  style_selector = gr.Radio(
256
  choices=list(STYLES.keys()),
 
259
  )
260
  gr.Markdown("<br>")
261
  generate_button = gr.Button("Genera il tuo Avatar 🚀", elem_classes="generate-btn", variant="primary")
262
+
263
  with gr.Column(scale=1):
264
  output_image = gr.Image(label="Il tuo Avatar Builder", interactive=False)
265
+
266
  generate_button.click(
267
  fn=generate_avatar,
268
  inputs=[input_image, logo_image, style_selector],