CheckSim commited on
Commit
91d18ca
·
verified ·
1 Parent(s): 4ba0021

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -32
app.py CHANGED
@@ -42,7 +42,6 @@ pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
42
  use_safetensors=True
43
  )
44
 
45
- # Rimuovi il limitatore di sicurezza se crea problemi (opzionale)
46
  pipe.watermark = None
47
 
48
  # ==============================================================================
@@ -77,7 +76,7 @@ STYLES = {
77
  # ==============================================================================
78
 
79
  def extract_head_crop(image_pil, face_bbox):
80
- """Ritaglia un'area abbondante attorno al volto per catturare capelli e collo."""
81
  x1, y1, x2, y2 = face_bbox
82
  w = x2 - x1
83
  h = y2 - y1
@@ -88,66 +87,101 @@ def extract_head_crop(image_pil, face_bbox):
88
  crop_x1 = max(0, int(cx - crop_size/1.5))
89
  crop_y1 = max(0, int(cy - crop_size/1.2)) # Più spazio sopra per i capelli
90
  crop_x2 = min(image_pil.width, int(cx + crop_size/1.5))
91
- crop_y2 = min(image_pil.height, int(cy + crop_size/2.5)) # Meno spazio sotto per evitare troppe spalle
 
 
 
92
 
93
  return image_pil.crop((crop_x1, crop_y1, crop_x2, crop_y2))
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  @spaces.GPU(duration=60)
96
  def generate_avatar(user_image, selected_style):
97
  if user_image is None:
98
  raise gr.Error("Per favore, carica un'immagine del volto.")
99
 
100
- # 1. Analisi del volto utente
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
102
  user_faces = face_app.get(cv_user_image)
103
  if len(user_faces) == 0:
104
  raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
105
-
106
  user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
107
 
108
- # 2. Ritaglio e rimozione sfondo (Head Extraction)
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  print("Estrazione della testa con Rembg...")
110
  cropped_user = extract_head_crop(user_image, user_face.bbox)
111
  head_rgba = rembg.remove(cropped_user, session=rembg_session)
 
112
 
113
- # 3. Analisi della testa scontornata per trovare le nuove coordinate del volto
114
  head_rgb_cv = cv2.cvtColor(np.array(head_rgba.convert("RGB")), cv2.COLOR_RGB2BGR)
115
  head_faces = face_app.get(head_rgb_cv)
116
  if len(head_faces) == 0:
117
- # Fallback se non trova il volto dopo il ritaglio
118
- head_face = user_face
119
  else:
120
- head_face = sorted(head_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
121
 
122
- H_bbox = head_face.bbox
123
  H_w = H_bbox[2] - H_bbox[0]
124
  H_h = H_bbox[3] - H_bbox[1]
125
  H_cx = H_bbox[0] + H_w/2
126
  H_cy = H_bbox[1] + H_h/2
127
-
128
- # 4. Caricamento e analisi del Template
129
- style_config = STYLES[selected_style]
130
- template_path = style_config["template"]
131
-
132
- if not os.path.exists(template_path):
133
- raise gr.Error(f"Errore: il file di template {template_path} non esiste.")
134
-
135
- template_pil = Image.open(template_path).convert("RGB")
136
- cv_template_image = cv2.cvtColor(np.array(template_pil), cv2.COLOR_RGB2BGR)
137
-
138
- template_faces = face_app.get(cv_template_image)
139
- if len(template_faces) == 0:
140
- raise gr.Error("Impossibile trovare un volto nel template di base.")
141
- template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
142
 
143
  T_bbox = template_face.bbox
144
  T_w = T_bbox[2] - T_bbox[0]
145
  T_cx = T_bbox[0] + T_w/2
146
  T_cy = T_bbox[1] + (T_bbox[3] - T_bbox[1])/2
147
 
148
- # 5. Compositing (Incolla la testa sul template)
149
  print("Compositing della testa sul template...")
150
- # Scaliamo la testa in modo che la larghezza del volto coincida (con un piccolo margine in più per coprire bene)
151
  scale = (T_w / H_w) * 1.05
152
  new_size = (int(head_rgba.width * scale), int(head_rgba.height * scale))
153
  scaled_head = head_rgba.resize(new_size, Image.LANCZOS)
@@ -161,14 +195,13 @@ def generate_avatar(user_image, selected_style):
161
  composited_pil = template_pil.copy()
162
  composited_pil.paste(scaled_head, (paste_x, paste_y), scaled_head)
163
 
164
- # 6. Creazione della Maschera per l'Inpainting
165
  mask_pil = Image.new("L", composited_pil.size, 0)
166
  mask_pil.paste(scaled_head.split()[3], (paste_x, paste_y))
167
- # Espandi leggermente la maschera per coprire bene i bordi e sfumala
168
  mask_pil = mask_pil.filter(ImageFilter.MaxFilter(7))
169
  mask_pil = mask_pil.filter(ImageFilter.GaussianBlur(15))
170
 
171
- # 7. Stylization tramite Inpainting
172
  repo_id = style_config["repo_id"]
173
 
174
  if repo_id:
@@ -186,7 +219,7 @@ def generate_avatar(user_image, selected_style):
186
  negative_prompt=style_config["negative_prompt"],
187
  image=composited_pil,
188
  mask_image=mask_pil,
189
- strength=0.45, # Aumentato leggermente per permettere all'IA di ridisegnare capelli e occhiali nello stile
190
  guidance_scale=8.0,
191
  num_inference_steps=35,
192
  ).images[0]
 
42
  use_safetensors=True
43
  )
44
 
 
45
  pipe.watermark = None
46
 
47
  # ==============================================================================
 
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
 
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
+ # Meno spazio sotto: fermiamoci alla base del collo, evitando le spalle
92
+ # y2 è circa il mento, y2 + h*0.5 è la fine del collo
93
+ crop_y2 = min(image_pil.height, int(y2 + h * 0.45))
94
 
95
  return image_pil.crop((crop_x1, crop_y1, crop_x2, crop_y2))
96
 
97
+ def fade_bottom(image_rgba, fade_height_ratio=0.15):
98
+ """Applica una sfumatura trasparente alla base dell'immagine per fondere dolcemente il collo."""
99
+ width, height = image_rgba.size
100
+ fade_height = int(height * fade_height_ratio)
101
+ if fade_height == 0:
102
+ return image_rgba
103
+
104
+ r, g, b, a = image_rgba.split()
105
+ alpha_np = np.array(a)
106
+
107
+ # Crea un gradiente da 1.0 (opaco) a 0.0 (trasparente)
108
+ gradient = np.linspace(1, 0, fade_height)
109
+ gradient = np.tile(gradient[:, None], (1, width))
110
+
111
+ # Applica il gradiente alla parte bassa del canale alpha
112
+ alpha_np[-fade_height:, :] = (alpha_np[-fade_height:, :] * gradient).astype(np.uint8)
113
+
114
+ a = Image.fromarray(alpha_np)
115
+ image_rgba.putalpha(a)
116
+ return image_rgba
117
+
118
  @spaces.GPU(duration=60)
119
  def generate_avatar(user_image, selected_style):
120
  if user_image is None:
121
  raise gr.Error("Per favore, carica un'immagine del volto.")
122
 
123
+ # 1. Caricamento e analisi del Template (lo facciamo prima per sapere dove guarda)
124
+ style_config = STYLES[selected_style]
125
+ template_path = style_config["template"]
126
+
127
+ if not os.path.exists(template_path):
128
+ raise gr.Error(f"Errore: il file di template {template_path} non esiste.")
129
+
130
+ template_pil = Image.open(template_path).convert("RGB")
131
+ cv_template_image = cv2.cvtColor(np.array(template_pil), cv2.COLOR_RGB2BGR)
132
+
133
+ template_faces = face_app.get(cv_template_image)
134
+ if len(template_faces) == 0:
135
+ raise gr.Error("Impossibile trovare un volto nel template di base.")
136
+ template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
137
+
138
+ # 2. Analisi iniziale del volto utente per controllare l'orientamento
139
  cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
140
  user_faces = face_app.get(cv_user_image)
141
  if len(user_faces) == 0:
142
  raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
 
143
  user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
144
 
145
+ # 3. Controllo Yaw (Orientamento Destra/Sinistra)
146
+ user_yaw = user_face.pose[1]
147
+ template_yaw = template_face.pose[1]
148
+
149
+ # Se guardano in direzioni opposte, ribaltiamo il selfie
150
+ if (user_yaw * template_yaw) < 0:
151
+ print("Orientamenti opposti! Eseguo specchio (FLIP_LEFT_RIGHT) del selfie.")
152
+ user_image = user_image.transpose(Image.FLIP_LEFT_RIGHT)
153
+ # Rianalizziamo il volto ribaltato per avere le nuove coordinate perfette
154
+ cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
155
+ user_faces = face_app.get(cv_user_image)
156
+ user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
157
+
158
+ # 4. Ritaglio, rimozione sfondo e sfumatura collo
159
  print("Estrazione della testa con Rembg...")
160
  cropped_user = extract_head_crop(user_image, user_face.bbox)
161
  head_rgba = rembg.remove(cropped_user, session=rembg_session)
162
+ head_rgba = fade_bottom(head_rgba, fade_height_ratio=0.15) # Sfuma il collo
163
 
164
+ # 5. Analisi della testa scontornata per trovare le nuove coordinate del volto
165
  head_rgb_cv = cv2.cvtColor(np.array(head_rgba.convert("RGB")), cv2.COLOR_RGB2BGR)
166
  head_faces = face_app.get(head_rgb_cv)
167
  if len(head_faces) == 0:
168
+ head_face_crop = user_face
 
169
  else:
170
+ head_face_crop = sorted(head_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
171
 
172
+ H_bbox = head_face_crop.bbox
173
  H_w = H_bbox[2] - H_bbox[0]
174
  H_h = H_bbox[3] - H_bbox[1]
175
  H_cx = H_bbox[0] + H_w/2
176
  H_cy = H_bbox[1] + H_h/2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
  T_bbox = template_face.bbox
179
  T_w = T_bbox[2] - T_bbox[0]
180
  T_cx = T_bbox[0] + T_w/2
181
  T_cy = T_bbox[1] + (T_bbox[3] - T_bbox[1])/2
182
 
183
+ # 6. Compositing (Incolla la testa sul template)
184
  print("Compositing della testa sul template...")
 
185
  scale = (T_w / H_w) * 1.05
186
  new_size = (int(head_rgba.width * scale), int(head_rgba.height * scale))
187
  scaled_head = head_rgba.resize(new_size, Image.LANCZOS)
 
195
  composited_pil = template_pil.copy()
196
  composited_pil.paste(scaled_head, (paste_x, paste_y), scaled_head)
197
 
198
+ # 7. Creazione della Maschera per l'Inpainting
199
  mask_pil = Image.new("L", composited_pil.size, 0)
200
  mask_pil.paste(scaled_head.split()[3], (paste_x, paste_y))
 
201
  mask_pil = mask_pil.filter(ImageFilter.MaxFilter(7))
202
  mask_pil = mask_pil.filter(ImageFilter.GaussianBlur(15))
203
 
204
+ # 8. Stylization tramite Inpainting
205
  repo_id = style_config["repo_id"]
206
 
207
  if repo_id:
 
219
  negative_prompt=style_config["negative_prompt"],
220
  image=composited_pil,
221
  mask_image=mask_pil,
222
+ strength=0.45,
223
  guidance_scale=8.0,
224
  num_inference_steps=35,
225
  ).images[0]