vitordigitizing commited on
Commit
d60ea0b
·
verified ·
1 Parent(s): 2629ec3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +25 -177
app.py CHANGED
@@ -1,189 +1,37 @@
1
  import spaces
2
  import gradio as gr
3
- from PIL import Image, ImageChops, ImageFilter, ImageOps
4
  import io
5
  import os
6
- import numpy as np
7
- import cv2
8
 
9
- # Initialize rembg session
10
- from rembg import remove, new_session
11
- _SESSION = new_session("isnet-general-use")
12
-
13
- def remove_background(img: Image.Image) -> Image.Image:
14
- rgba = img.convert("RGBA")
15
- mask = remove(rgba, session=_SESSION, only_mask=True)
16
- alpha = mask.convert("L") if isinstance(mask, Image.Image) else Image.open(io.BytesIO(mask)).convert("L")
17
- if alpha.size != rgba.size:
18
- alpha = alpha.resize(rgba.size, Image.Resampling.LANCZOS)
19
- r, g, b, _ = rgba.split()
20
- return Image.merge("RGBA", (r, g, b, alpha))
21
-
22
- def blur_faces_smooth(image: Image.Image) -> tuple[Image.Image, list]:
23
- """
24
- Detects faces using multiple Haar Cascades (frontal, alt, profile) to ensure
25
- all faces in group/couple photos are found, and applies a heavy Gaussian blur.
26
- """
27
- rgba = image.convert("RGBA")
28
- width, height = rgba.size
29
-
30
- # Convert PIL Image to OpenCV grayscale for detection
31
- open_cv_image = np.array(rgba.convert("RGB"))
32
- open_cv_image = open_cv_image[:, :, ::-1].copy() # Convert RGB to BGR
33
- gray = cv2.cvtColor(open_cv_image, cv2.COLOR_BGR2GRAY)
34
-
35
- # Load frontal and profile cascades for maximum coverage
36
- face_cascade_default = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
37
- face_cascade_alt = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_alt2.xml')
38
- profile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_profileface.xml')
39
-
40
- faces_default = face_cascade_default.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=3, minSize=(20, 20))
41
- faces_alt = face_cascade_alt.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=3, minSize=(20, 20))
42
- faces_profile = profile_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=3, minSize=(20, 20))
43
-
44
- # Combine detected faces
45
- faces = []
46
- if len(faces_default) > 0: faces.extend(faces_default)
47
- if len(faces_alt) > 0: faces.extend(faces_alt)
48
- if len(faces_profile) > 0: faces.extend(faces_profile)
49
-
50
- if len(faces) == 0:
51
- # Fallback: soft feathered blur on upper center if no faces detected
52
- mask = Image.new("L", (width, height), 0)
53
- mask_np = np.array(mask)
54
- # generic head ellipse at top center
55
- cx, cy = width // 2, int(height * 0.32)
56
- rx, ry = int(width * 0.25), int(height * 0.18)
57
- cv2.ellipse(mask_np, (cx, cy), (rx, ry), 0, 0, 360, 255, -1)
58
- mask = Image.fromarray(mask_np).filter(ImageFilter.GaussianBlur(radius=20))
59
-
60
- blurred_img = rgba.filter(ImageFilter.GaussianBlur(radius=25))
61
- return Image.composite(blurred_img, rgba, mask), []
62
-
63
- # Create a smooth, feathered mask (0 = original, 255 = blurred)
64
- mask = Image.new("L", (width, height), 0)
65
- mask_np = np.array(mask)
66
-
67
- for (x, y, w, h) in faces:
68
- cx = x + w // 2
69
-
70
- # Shift the blur down to cover eyes/mouth and chin, keeping eyebrows untouched.
71
- # rx covers almost the entire face width (w * 0.48, or 96% of w)
72
- # ry and cy_shifted cover from y + 0.33 * h to y + 0.97 * h, containing all eyes/nose/mouth/cheeks
73
- rx = int(w * 0.48)
74
- ry = int(h * 0.32)
75
- cy_shifted = y + int(h * 0.65)
76
-
77
- cv2.ellipse(mask_np, (cx, cy_shifted), (rx, ry), 0, 0, 360, 255, -1)
78
-
79
- mask = Image.fromarray(mask_np)
80
- # Heavy blur on mask to feather edges completely smoothly
81
- mask = mask.filter(ImageFilter.GaussianBlur(radius=10))
82
-
83
- # Generate the heavily blurred version of the face areas
84
- blurred_img = rgba.filter(ImageFilter.GaussianBlur(radius=35))
85
-
86
- # Composite the blurred face areas onto the original image
87
- return Image.composite(blurred_img, rgba, mask), list(faces)
88
-
89
- def extract_fine_details_cv2(img: Image.Image, detail_profile="balanced") -> Image.Image:
90
- """
91
- Uses Bilateral Filter and Canny Edge Detector to extract thick, clean lines.
92
- """
93
- # Convert PIL Image to OpenCV (BGR)
94
- open_cv_image = np.array(img.convert("RGB"))
95
- open_cv_image = open_cv_image[:, :, ::-1].copy() # Convert RGB to BGR
96
-
97
- # Convert to grayscale
98
- gray = cv2.cvtColor(open_cv_image, cv2.COLOR_BGR2GRAY)
99
-
100
- # Heavy Bilateral filter removes small details and fabric texture while keeping main edges sharp
101
- smoothed = cv2.bilateralFilter(gray, d=9, sigmaColor=75, sigmaSpace=75)
102
-
103
- # Set Canny thresholds based on profile (lowered thresholds to prevent losing main lines)
104
- if detail_profile == "soft":
105
- t1, t2 = 40, 130
106
- elif detail_profile == "balanced":
107
- t1, t2 = 18, 75
108
- else: # raw / detailed
109
- t1, t2 = 10, 50
110
-
111
- # Run Canny edge detector
112
- edges = cv2.Canny(smoothed, t1, t2)
113
-
114
- # Dilate with a 3x3 kernel to make lines thicker and more suitable for embroidery representation
115
- kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
116
- edges_dilated = cv2.dilate(edges, kernel, iterations=1)
117
-
118
- # Convert back to PIL Image (L mode)
119
- return Image.fromarray(edges_dilated).convert("L")
120
-
121
- def generate_alpha_outline_cv2(alpha: Image.Image) -> Image.Image:
122
- """
123
- Generates a clean, thicker boundary outline using morphological gradient.
124
- """
125
- alpha_np = np.array(alpha)
126
-
127
- # Morphological gradient (dilate - erode) with a 3x3 kernel
128
- kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
129
- dilated = cv2.dilate(alpha_np, kernel, iterations=1)
130
- eroded = cv2.erode(alpha_np, kernel, iterations=1)
131
-
132
- outline = cv2.subtract(dilated, eroded)
133
-
134
- # Threshold to guarantee clean binary line
135
- _, outline = cv2.threshold(outline, 127, 255, cv2.THRESH_BINARY)
136
-
137
- # Dilate the outline slightly to match the thicker Canny lines
138
- outline_dilated = cv2.dilate(outline, kernel, iterations=1)
139
-
140
- return Image.fromarray(outline_dilated).convert("L")
141
 
142
  @spaces.GPU
143
  def process_faceless(input_image, prompt="", negative_prompt=""):
144
  if input_image is None:
145
  return None, None
146
-
147
- # Resize for speed and high-res lines consistency
148
- w, h = input_image.size
149
- max_dim = 800
150
- if max(w, h) > max_dim:
151
- if w > h:
152
- new_w = max_dim
153
- new_h = int(h * (max_dim / w))
154
- else:
155
- new_h = max_dim
156
- new_w = int(w * (max_dim / h))
157
- input_image = input_image.resize((new_w, new_h), Image.Resampling.LANCZOS)
158
-
159
- # 1. Remove background
160
- subject = remove_background(input_image)
161
-
162
- # 2. Smoothly blur face regions (keeps eyebrows, removes eyes/mouth)
163
- preprocessed, faces = blur_faces_smooth(subject)
164
-
165
- # 3. Extract line details using OpenCV Canny (thick, clean lines)
166
- details_alpha = extract_fine_details_cv2(preprocessed, detail_profile="balanced")
167
-
168
- # 4. Generate outer silhouette outline
169
- silhouette_alpha = generate_alpha_outline_cv2(subject.getchannel("A"))
170
-
171
- # 5. Combine outline and hair/body lineart details
172
- combined_alpha = ImageChops.lighter(silhouette_alpha, details_alpha)
173
-
174
- # 6. Clean: mask lines with original subject alpha so no lines leak into transparency
175
- subject_alpha = subject.getchannel("A")
176
- combined_alpha = ImageChops.darker(combined_alpha, subject_alpha)
177
-
178
- # Create final transparent lineart image
179
- lineart = Image.new("RGBA", combined_alpha.size, (0, 0, 0, 0))
180
- lineart.putalpha(combined_alpha)
181
-
182
- # Cutout with smoothed alpha channel
183
- cutout = subject.copy()
184
-
185
- return lineart, cutout
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  demo = gr.Interface(
188
  fn=process_faceless,
189
  inputs=[
@@ -196,8 +44,8 @@ demo = gr.Interface(
196
  gr.Image(label="Recorte do Rosto (Sem Fundo)")
197
  ],
198
  title="Faceless Embroidery Studio Generator",
199
- description="Gera contornos e linearts minimalistas (Faceless) para bordados a partir de retratos."
200
  )
201
 
202
  if __name__ == "__main__":
203
- demo.launch()
 
1
  import spaces
2
  import gradio as gr
3
+ from PIL import Image
4
  import io
5
  import os
 
 
6
 
7
+ # Importa a função melhorada do seu módulo (que já está ajustado)
8
+ from faceless_processing import build_faceless_embroidery_assets
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  @spaces.GPU
11
  def process_faceless(input_image, prompt="", negative_prompt=""):
12
  if input_image is None:
13
  return None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # NÃO REDIMENSIONAMOS A IMAGEM – a função interna já lida com tamanhos variados.
16
+ # Mas se quiser limitar por performance, pode redimensionar para no máximo 1024px
17
+ # (ainda mantendo qualidade para detecção facial).
18
+ # Vou deixar sem redimensionamento para máxima precisão.
19
+
20
+ # Gera lineart e cutout usando a função otimizada
21
+ try:
22
+ lineart, cutout = build_faceless_embroidery_assets(
23
+ input_image,
24
+ use_huggingface=False, # Desativa Hugging Face (pode ativar se tiver token)
25
+ prompt=prompt or None,
26
+ negative_prompt=negative_prompt or None,
27
+ )
28
+ return lineart, cutout
29
+ except Exception as e:
30
+ print(f"[ERRO] process_faceless: {e}")
31
+ # Fallback: retorna a imagem original e uma mensagem de erro
32
+ return input_image, None
33
+
34
+ # Interface Gradio
35
  demo = gr.Interface(
36
  fn=process_faceless,
37
  inputs=[
 
44
  gr.Image(label="Recorte do Rosto (Sem Fundo)")
45
  ],
46
  title="Faceless Embroidery Studio Generator",
47
+ description="Gera contornos e linearts minimalistas (Faceless) para bordados a partir de retratos.\n\n**Nota:** O rosto é completamente apagado e apenas a silhueta externa é desenhada."
48
  )
49
 
50
  if __name__ == "__main__":
51
+ demo.launch()