CheckSim commited on
Commit
f2e40d1
·
verified ·
1 Parent(s): cb45f57

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +66 -98
  2. requirements.txt +0 -1
app.py CHANGED
@@ -9,7 +9,6 @@ from diffusers import StableDiffusionXLInpaintPipeline
9
  from insightface.app import FaceAnalysis
10
  import insightface
11
  from huggingface_hub import hf_hub_download, snapshot_download
12
- import rembg
13
 
14
  # ==============================================================================
15
  # 1. SETUP DELL'AMBIENTE E DOWNLOAD MODELLI
@@ -22,13 +21,18 @@ try:
22
  except Exception as e:
23
  print(f"Errore download AntelopeV2: {e}")
24
 
 
 
 
 
25
  # Inizializza l'analizzatore di volti (InsightFace)
26
  print("Inizializzazione FaceAnalysis...")
27
  face_app = FaceAnalysis(name='antelopev2', root='./', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
28
  face_app.prepare(ctx_id=0, det_size=(640, 640))
29
 
30
- # Inizializza sessione Rembg
31
- rembg_session = rembg.new_session()
 
32
 
33
  # Caricamento Pipeline Inpainting SDXL
34
  print("Caricamento pipeline SDXL Inpaint...")
@@ -42,6 +46,11 @@ pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
42
  use_safetensors=True
43
  )
44
 
 
 
 
 
 
45
  pipe.watermark = None
46
 
47
  # ==============================================================================
@@ -75,47 +84,33 @@ STYLES = {
75
  # 3. FUNZIONI CORE DELL'APPLICAZIONE
76
  # ==============================================================================
77
 
78
- def extract_head_crop(image_pil, face_bbox):
79
- """Ritaglia l'area della testa, fermandosi alla base del collo per escludere le spalle."""
80
  x1, y1, x2, y2 = face_bbox
81
  w = x2 - x1
82
  h = y2 - y1
83
 
84
  cx, cy = x1 + w/2, y1 + h/2
85
- crop_size = max(w, h) * 3
86
-
87
- crop_x1 = max(0, int(cx - crop_size/1.5))
88
- crop_y1 = max(0, int(cy - crop_size/1.2)) # Più spazio sopra per i capelli
89
- crop_x2 = min(image_pil.width, int(cx + crop_size/1.5))
90
 
91
- # Taglio ancora più drastico: fermiamoci poco sotto il mento per escludere a priori qualsiasi spalla
92
- crop_y2 = min(image_pil.height, int(y2 + h * 0.35))
93
-
94
- return image_pil.crop((crop_x1, crop_y1, crop_x2, crop_y2))
95
-
96
- def cut_bottom(image_rgba, cut_height_ratio=0.20):
97
- """Rende completamente trasparente la base dell'immagine (Hard Cut) per eliminare il collo inferiore."""
98
- width, height = image_rgba.size
99
- cut_height = int(height * cut_height_ratio)
100
- if cut_height == 0:
101
- return image_rgba
102
-
103
- r, g, b, a = image_rgba.split()
104
- alpha_np = np.array(a)
105
 
106
- # Taglio netto: zero alpha nella parte inferiore
107
- alpha_np[-cut_height:, :] = 0
108
 
109
- a = Image.fromarray(alpha_np)
110
- image_rgba.putalpha(a)
111
- return image_rgba
112
 
113
  @spaces.GPU(duration=60)
114
  def generate_avatar(user_image, selected_style):
115
  if user_image is None:
116
  raise gr.Error("Per favore, carica un'immagine del volto.")
117
 
118
- # 1. Caricamento e analisi del Template (lo facciamo prima per sapere dove guarda)
119
  style_config = STYLES[selected_style]
120
  template_path = style_config["template"]
121
 
@@ -130,91 +125,49 @@ def generate_avatar(user_image, selected_style):
130
  raise gr.Error("Impossibile trovare un volto nel template di base.")
131
  template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
132
 
133
- # 2. Analisi iniziale del volto utente per controllare l'orientamento
134
  cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
135
  user_faces = face_app.get(cv_user_image)
136
  if len(user_faces) == 0:
137
  raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
138
  user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
139
 
140
- # 3. Controllo Yaw (Orientamento Destra/Sinistra)
141
- user_yaw = user_face.pose[1]
142
- template_yaw = template_face.pose[1]
143
 
144
- # Se guardano in direzioni opposte, ribaltiamo il selfie
145
- if (user_yaw * template_yaw) < 0:
146
- print("Orientamenti opposti! Eseguo specchio (FLIP_LEFT_RIGHT) del selfie.")
147
- user_image = user_image.transpose(Image.FLIP_LEFT_RIGHT)
148
- # Rianalizziamo il volto ribaltato per avere le nuove coordinate perfette
149
- cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
150
- user_faces = face_app.get(cv_user_image)
151
- user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
152
-
153
- # 4. Ritaglio, rimozione sfondo e Hard Cut del collo
154
- print("Estrazione della testa con Rembg...")
155
- cropped_user = extract_head_crop(user_image, user_face.bbox)
156
- head_rgba = rembg.remove(cropped_user, session=rembg_session)
157
- head_rgba = cut_bottom(head_rgba, cut_height_ratio=0.20) # Taglio netto trasparente
158
-
159
- # 5. Analisi della testa scontornata per trovare le nuove coordinate del volto
160
- head_rgb_cv = cv2.cvtColor(np.array(head_rgba.convert("RGB")), cv2.COLOR_RGB2BGR)
161
- head_faces = face_app.get(head_rgb_cv)
162
- if len(head_faces) == 0:
163
- head_face_crop = user_face
164
- else:
165
- head_face_crop = sorted(head_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
166
-
167
- H_bbox = head_face_crop.bbox
168
- H_w = H_bbox[2] - H_bbox[0]
169
- H_h = H_bbox[3] - H_bbox[1]
170
- H_cx = H_bbox[0] + H_w/2
171
- H_cy = H_bbox[1] + H_h/2
172
-
173
- T_bbox = template_face.bbox
174
- T_w = T_bbox[2] - T_bbox[0]
175
- T_cx = T_bbox[0] + T_w/2
176
- T_cy = T_bbox[1] + (T_bbox[3] - T_bbox[1])/2
177
-
178
- # 6. Compositing (Incolla la testa sul template)
179
- print("Compositing della testa sul template...")
180
- scale = (T_w / H_w) * 1.05
181
- new_size = (int(head_rgba.width * scale), int(head_rgba.height * scale))
182
- scaled_head = head_rgba.resize(new_size, Image.LANCZOS)
183
-
184
- new_H_cx = H_cx * scale
185
- new_H_cy = H_cy * scale
186
-
187
- paste_x = int(T_cx - new_H_cx)
188
- paste_y = int(T_cy - new_H_cy)
189
-
190
- composited_pil = template_pil.copy()
191
- composited_pil.paste(scaled_head, (paste_x, paste_y), scaled_head)
192
-
193
- # 7. Creazione della Maschera per l'Inpainting
194
- mask_pil = Image.new("L", composited_pil.size, 0)
195
- mask_pil.paste(scaled_head.split()[3], (paste_x, paste_y))
196
- mask_pil = mask_pil.filter(ImageFilter.MaxFilter(7))
197
- mask_pil = mask_pil.filter(ImageFilter.GaussianBlur(15))
198
-
199
- # 8. Stylization tramite Inpainting
200
  repo_id = style_config["repo_id"]
201
-
202
  if repo_id:
203
  try:
204
- print(f"Scaricamento/Caricamento LoRA {repo_id}...")
205
  pipe.load_lora_weights(repo_id, weight_name=style_config["weight_name"])
206
  except Exception as e:
207
- print(f"Errore caricamento LoRA da HF: {e}")
208
 
209
  pipe.to(device)
210
 
211
- print("Inizio Inpainting per fondere stile e testa...")
212
- stylized_image = pipe(
 
213
  prompt=style_config["prompt"],
214
  negative_prompt=style_config["negative_prompt"],
215
- image=composited_pil,
216
  mask_image=mask_pil,
217
- strength=0.45,
 
218
  guidance_scale=8.0,
219
  num_inference_steps=35,
220
  ).images[0]
@@ -225,7 +178,22 @@ def generate_avatar(user_image, selected_style):
225
  except:
226
  pass
227
 
228
- return stylized_image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
  # ==============================================================================
231
  # 4. INTERFACCIA GRADIO (FRONTEND)
 
9
  from insightface.app import FaceAnalysis
10
  import insightface
11
  from huggingface_hub import hf_hub_download, snapshot_download
 
12
 
13
  # ==============================================================================
14
  # 1. SETUP DELL'AMBIENTE E DOWNLOAD MODELLI
 
21
  except Exception as e:
22
  print(f"Errore download AntelopeV2: {e}")
23
 
24
+ if not os.path.exists("./models/inswapper_128.onnx"):
25
+ print("Download Inswapper...")
26
+ hf_hub_download(repo_id="ezioruan/inswapper_128.onnx", filename="inswapper_128.onnx", local_dir="./models")
27
+
28
  # Inizializza l'analizzatore di volti (InsightFace)
29
  print("Inizializzazione FaceAnalysis...")
30
  face_app = FaceAnalysis(name='antelopev2', root='./', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
31
  face_app.prepare(ctx_id=0, det_size=(640, 640))
32
 
33
+ # Inizializza Inswapper (per fissare l'identità alla fine)
34
+ print("Inizializzazione Inswapper...")
35
+ swapper = insightface.model_zoo.get_model('./models/inswapper_128.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
36
 
37
  # Caricamento Pipeline Inpainting SDXL
38
  print("Caricamento pipeline SDXL Inpaint...")
 
46
  use_safetensors=True
47
  )
48
 
49
+ # Aggiunta IP-Adapter per l'analisi semantica (Capelli, Occhiali, Barba)
50
+ print("Caricamento IP-Adapter per SDXL...")
51
+ pipe.load_ip_adapter("h94/IP-Adapter", subfolder="sdxl_models", weight_name="ip-adapter_sdxl.bin")
52
+ pipe.set_ip_adapter_scale(0.7) # Bilanciamento tra fedeltà al selfie e stile del prompt
53
+
54
  pipe.watermark = None
55
 
56
  # ==============================================================================
 
84
  # 3. FUNZIONI CORE DELL'APPLICAZIONE
85
  # ==============================================================================
86
 
87
+ def create_head_mask(image_pil, face_bbox):
88
+ """Crea una maschera che copre l'intera testa (inclusi capelli e barba) del template."""
89
  x1, y1, x2, y2 = face_bbox
90
  w = x2 - x1
91
  h = y2 - y1
92
 
93
  cx, cy = x1 + w/2, y1 + h/2
94
+ mask = Image.new("L", image_pil.size, 0)
95
+ draw = ImageDraw.Draw(mask)
 
 
 
96
 
97
+ # Area ovale molto abbondante per coprire capelli in alto e barba in basso
98
+ mask_x1 = max(0, cx - w*1.1)
99
+ mask_x2 = min(image_pil.width, cx + w*1.1)
100
+ mask_y1 = max(0, cy - h*1.4) # Su per i capelli/cappelli
101
+ mask_y2 = min(image_pil.height, cy + h*0.8) # Giù per la barba
 
 
 
 
 
 
 
 
 
102
 
103
+ draw.ellipse((mask_x1, mask_y1, mask_x2, mask_y2), fill=255)
 
104
 
105
+ mask = mask.filter(ImageFilter.GaussianBlur(15))
106
+ return mask
 
107
 
108
  @spaces.GPU(duration=60)
109
  def generate_avatar(user_image, selected_style):
110
  if user_image is None:
111
  raise gr.Error("Per favore, carica un'immagine del volto.")
112
 
113
+ # 1. Caricamento e analisi del Template
114
  style_config = STYLES[selected_style]
115
  template_path = style_config["template"]
116
 
 
125
  raise gr.Error("Impossibile trovare un volto nel template di base.")
126
  template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
127
 
128
+ # 2. Analisi del volto utente
129
  cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
130
  user_faces = face_app.get(cv_user_image)
131
  if len(user_faces) == 0:
132
  raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
133
  user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
134
 
135
+ # 3. Creazione maschera per rigenerazione della testa
136
+ print("Creazione maschera per svuotare la testa del template...")
137
+ mask_pil = create_head_mask(template_pil, template_face.bbox)
138
 
139
+ # Ritaglio del selfie (per dare all'IP-Adapter solo la testa e ignorare lo sfondo della stanza)
140
+ x1, y1, x2, y2 = user_face.bbox
141
+ w = x2 - x1
142
+ h = y2 - y1
143
+ cx, cy = x1 + w/2, y1 + h/2
144
+ crop_size = max(w, h) * 2.5
145
+ ip_x1 = max(0, int(cx - crop_size/2))
146
+ ip_y1 = max(0, int(cy - crop_size/1.5))
147
+ ip_x2 = min(user_image.width, int(cx + crop_size/2))
148
+ ip_y2 = min(user_image.height, int(cy + crop_size/2))
149
+ ip_image = user_image.crop((ip_x1, ip_y1, ip_x2, ip_y2))
150
+
151
+ # 4. Rigenerazione Semantica (SDXL + IP-Adapter)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  repo_id = style_config["repo_id"]
 
153
  if repo_id:
154
  try:
155
+ print(f"Caricamento LoRA {repo_id}...")
156
  pipe.load_lora_weights(repo_id, weight_name=style_config["weight_name"])
157
  except Exception as e:
158
+ print(f"Errore caricamento LoRA: {e}")
159
 
160
  pipe.to(device)
161
 
162
+ print("Inizio Generazione Semantica della Testa (SDXL + IP-Adapter)...")
163
+ # Usa una strength quasi al 100% per ignorare i pixel della testa originale sotto la maschera
164
+ generated_image = pipe(
165
  prompt=style_config["prompt"],
166
  negative_prompt=style_config["negative_prompt"],
167
+ image=template_pil,
168
  mask_image=mask_pil,
169
+ ip_adapter_image=ip_image,
170
+ strength=0.99,
171
  guidance_scale=8.0,
172
  num_inference_steps=35,
173
  ).images[0]
 
178
  except:
179
  pass
180
 
181
+ # 5. Iniezione dell'Identità pura (FaceSwap)
182
+ print("Iniezione dell'identità tramite Inswapper...")
183
+ cv_generated = cv2.cvtColor(np.array(generated_image), cv2.COLOR_RGB2BGR)
184
+ generated_faces = face_app.get(cv_generated)
185
+
186
+ if len(generated_faces) > 0:
187
+ # Trova la faccia appena generata dall'IA
188
+ gen_face = sorted(generated_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
189
+ # Incolla ESATTAMENTE gli occhi, naso e bocca originali dell'utente sopra la testa generata
190
+ cv_final = swapper.get(cv_generated, gen_face, user_face, paste_back=True)
191
+ final_image = Image.fromarray(cv2.cvtColor(cv_final, cv2.COLOR_BGR2RGB))
192
+ else:
193
+ print("Attenzione: SDXL non ha generato un volto rilevabile. Restituisco l'immagine senza Inswapper.")
194
+ final_image = generated_image
195
+
196
+ return final_image
197
 
198
  # ==============================================================================
199
  # 4. INTERFACCIA GRADIO (FRONTEND)
requirements.txt CHANGED
@@ -9,4 +9,3 @@ onnxruntime
9
  opencv-python
10
  huggingface-hub
11
  peft
12
- rembg
 
9
  opencv-python
10
  huggingface-hub
11
  peft