CheckSim commited on
Commit
4ba0021
·
verified ·
1 Parent(s): 89dded3

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +71 -52
  2. requirements.txt +1 -0
app.py CHANGED
@@ -9,6 +9,7 @@ from diffusers import StableDiffusionXLInpaintPipeline
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
@@ -26,14 +27,8 @@ print("Inizializzazione FaceAnalysis...")
26
  face_app = FaceAnalysis(name='antelopev2', root='./', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
27
  face_app.prepare(ctx_id=0, det_size=(640, 640))
28
 
29
- # Download e caricamento del modello Inswapper
30
- print("Download e caricamento Inswapper...")
31
- try:
32
- inswapper_path = hf_hub_download(repo_id="ezioruan/inswapper_128.onnx", filename="inswapper_128.onnx")
33
- face_swapper = insightface.model_zoo.get_model(inswapper_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
34
- except Exception as e:
35
- print(f"Errore caricamento Inswapper: {e}")
36
- face_swapper = None
37
 
38
  # Caricamento Pipeline Inpainting SDXL
39
  print("Caricamento pipeline SDXL Inpaint...")
@@ -53,27 +48,26 @@ pipe.watermark = None
53
  # ==============================================================================
54
  # 2. CONFIGURAZIONE STILI E TEMPLATE
55
  # ==============================================================================
56
- # I template sono le immagini pre-generate fornite dall'utente.
57
  STYLES = {
58
  "Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
59
  "template": "esempi/8bit.png",
60
  "repo_id": "nerijs/pixel-art-xl",
61
  "weight_name": "pixel-art-xl.safetensors",
62
- "prompt": "pixelart style, 8-bit game style face, highly detailed, perfect pixel art shading, 8-bit retro aesthetic, matching lighting",
63
  "negative_prompt": "ugly, deformed, lowres, realistic, photographic, 3d render, photo, photorealistic"
64
  },
65
  "Stile 2: Il Costruttore Rinascimentale": {
66
  "template": "esempi/rinascimentale.png",
67
  "repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
68
  "weight_name": "pytorch_lora_weights.safetensors",
69
- "prompt": "renaissance oil painting, oil painting face, elegant brush strokes, chiaroscuro lighting, classic masterpiece, perfectly integrated face",
70
  "negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, pixel art, cartoon"
71
  },
72
  "Stile 3: Genio Creativo (Pop-Art)": {
73
  "template": "esempi/popart.png",
74
  "repo_id": None,
75
  "weight_name": None,
76
- "prompt": "vintage pop art, Comic book style pop-art face, vibrant colors, halftone dots, graphic illustration, bold lines",
77
  "negative_prompt": "ugly, deformed, photorealistic, realistic photography, oil painting, 3d render"
78
  }
79
  }
@@ -82,37 +76,26 @@ STYLES = {
82
  # 3. FUNZIONI CORE DELL'APPLICAZIONE
83
  # ==============================================================================
84
 
85
- def create_face_mask(image, bbox, expansion=0.2, blur_radius=15):
86
- """
87
- Crea una maschera sfumata attorno al volto per l'inpainting.
88
- """
89
- mask = Image.new("L", image.size, 0)
90
- draw = ImageDraw.Draw(mask)
91
-
92
- x1, y1, x2, y2 = bbox
93
  w = x2 - x1
94
  h = y2 - y1
95
 
96
- # Espandi leggermente la bounding box
97
- nx1 = max(0, x1 - w * expansion)
98
- ny1 = max(0, y1 - h * expansion)
99
- nx2 = min(image.width, x2 + w * expansion)
100
- ny2 = min(image.height, y2 + h * (expansion * 1.5)) # Espandi un po' di più verso il basso per il mento
101
 
102
- # Disegna un ovale
103
- draw.ellipse([nx1, ny1, nx2, ny2], fill=255)
 
 
104
 
105
- # Sfuma i bordi per fondere meglio
106
- mask = mask.filter(ImageFilter.GaussianBlur(blur_radius))
107
- return mask
108
 
109
  @spaces.GPU(duration=60)
110
  def generate_avatar(user_image, selected_style):
111
  if user_image is None:
112
  raise gr.Error("Per favore, carica un'immagine del volto.")
113
-
114
- if face_swapper is None:
115
- raise gr.Error("Modello Inswapper non caricato correttamente.")
116
 
117
  # 1. Analisi del volto utente
118
  cv_user_image = cv2.cvtColor(np.array(user_image), cv2.COLOR_RGB2BGR)
@@ -120,10 +103,29 @@ def generate_avatar(user_image, selected_style):
120
  if len(user_faces) == 0:
121
  raise gr.Error("Nessun volto rilevato nella tua foto. Riprova con un selfie più chiaro.")
122
 
123
- # Prendi il volto più grande
124
  user_face = sorted(user_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
125
 
126
- # 2. Caricamento e analisi del Template
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  style_config = STYLES[selected_style]
128
  template_path = style_config["template"]
129
 
@@ -137,19 +139,38 @@ def generate_avatar(user_image, selected_style):
137
  if len(template_faces) == 0:
138
  raise gr.Error("Impossibile trovare un volto nel template di base.")
139
  template_face = sorted(template_faces, key=lambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]))[-1]
140
-
141
- # 3. Swap del Volto (Fotorealistico)
142
- print("Eseguendo Face Swap...")
143
- swapped_cv = face_swapper.get(cv_template_image, template_face, user_face, paste_back=True)
144
- swapped_pil = Image.fromarray(cv2.cvtColor(swapped_cv, cv2.COLOR_BGR2RGB))
145
 
146
- # 4. Creazione Maschera per l'Inpainting
147
- mask_pil = create_face_mask(template_pil, template_face.bbox)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- # 5. Stylization tramite Inpainting
150
  repo_id = style_config["repo_id"]
151
 
152
- # Carica il LoRA dello stile se necessario
153
  if repo_id:
154
  try:
155
  print(f"Scaricamento/Caricamento LoRA {repo_id}...")
@@ -157,21 +178,19 @@ def generate_avatar(user_image, selected_style):
157
  except Exception as e:
158
  print(f"Errore caricamento LoRA da HF: {e}")
159
 
160
- # Sposta la pipeline su GPU per l'inferenza
161
  pipe.to(device)
162
 
163
- print("Inizio Inpainting per fondere lo stile...")
164
  stylized_image = pipe(
165
  prompt=style_config["prompt"],
166
  negative_prompt=style_config["negative_prompt"],
167
- image=swapped_pil,
168
  mask_image=mask_pil,
169
- strength=0.35, # Bassa strength: mantiene forte l'identità, applica solo lo stile superficiale
170
- guidance_scale=7.5,
171
- num_inference_steps=30,
172
  ).images[0]
173
 
174
- # Scarica il LoRA per evitare conflitti al prossimo giro
175
  if repo_id:
176
  try:
177
  pipe.unload_lora_weights()
@@ -204,7 +223,7 @@ with gr.Blocks(css=custom_css, theme=theme) as demo:
204
 
205
  with gr.Row():
206
  with gr.Column(scale=1):
207
- input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie")
208
 
209
  with gr.Column(scale=1):
210
  style_selector = gr.Radio(
 
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
 
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...")
 
48
  # ==============================================================================
49
  # 2. CONFIGURAZIONE STILI E TEMPLATE
50
  # ==============================================================================
 
51
  STYLES = {
52
  "Stile 1: Bit-Builder (8-Bit/Pixel Art)": {
53
  "template": "esempi/8bit.png",
54
  "repo_id": "nerijs/pixel-art-xl",
55
  "weight_name": "pixel-art-xl.safetensors",
56
+ "prompt": "pixelart style, 8-bit game style face and hair, perfect pixel art shading, 8-bit retro aesthetic, matching lighting",
57
  "negative_prompt": "ugly, deformed, lowres, realistic, photographic, 3d render, photo, photorealistic"
58
  },
59
  "Stile 2: Il Costruttore Rinascimentale": {
60
  "template": "esempi/rinascimentale.png",
61
  "repo_id": "davidmoref/sdxl-lora-adapter-renaissance",
62
  "weight_name": "pytorch_lora_weights.safetensors",
63
+ "prompt": "renaissance oil painting, oil painting face and hair, elegant brush strokes, chiaroscuro lighting, classic masterpiece, perfectly integrated head",
64
  "negative_prompt": "ugly, deformed, modern, photography, bad anatomy, worst quality, low quality, pixel art, cartoon"
65
  },
66
  "Stile 3: Genio Creativo (Pop-Art)": {
67
  "template": "esempi/popart.png",
68
  "repo_id": None,
69
  "weight_name": None,
70
+ "prompt": "vintage pop art, Comic book style pop-art face and hair, vibrant colors, halftone dots, graphic illustration, bold lines",
71
  "negative_prompt": "ugly, deformed, photorealistic, realistic photography, oil painting, 3d render"
72
  }
73
  }
 
76
  # 3. FUNZIONI CORE DELL'APPLICAZIONE
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
84
 
85
+ cx, cy = x1 + w/2, y1 + h/2
86
+ crop_size = max(w, h) * 3
 
 
 
87
 
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)
 
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
 
 
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)
154
+
155
+ new_H_cx = H_cx * scale
156
+ new_H_cy = H_cy * scale
157
+
158
+ paste_x = int(T_cx - new_H_cx)
159
+ paste_y = int(T_cy - new_H_cy)
160
+
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:
175
  try:
176
  print(f"Scaricamento/Caricamento LoRA {repo_id}...")
 
178
  except Exception as e:
179
  print(f"Errore caricamento LoRA da HF: {e}")
180
 
 
181
  pipe.to(device)
182
 
183
+ print("Inizio Inpainting per fondere stile e testa...")
184
  stylized_image = pipe(
185
  prompt=style_config["prompt"],
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]
193
 
 
194
  if repo_id:
195
  try:
196
  pipe.unload_lora_weights()
 
223
 
224
  with gr.Row():
225
  with gr.Column(scale=1):
226
+ input_image = gr.Image(type="pil", label="1. Carica il tuo Selfie (Ben illuminato)")
227
 
228
  with gr.Column(scale=1):
229
  style_selector = gr.Radio(
requirements.txt CHANGED
@@ -9,3 +9,4 @@ onnxruntime
9
  opencv-python
10
  huggingface-hub
11
  peft
 
 
9
  opencv-python
10
  huggingface-hub
11
  peft
12
+ rembg