vitordigitizing commited on
Commit
4add979
·
verified ·
1 Parent(s): ea28acc

Update faceless_processing.py

Browse files
Files changed (1) hide show
  1. faceless_processing.py +35 -49
faceless_processing.py CHANGED
@@ -81,21 +81,21 @@ class LineartConfig:
81
  mask_smooth_threshold: int = 128
82
  min_silhouette_area: float = 200.0
83
 
84
- # Silhouette outline — desenha com espessura fina e depois dilata
85
- silhouette_line_thickness: int = 1 # desenho fino, dilatação depois
86
-
87
- # Edge detection — parâmetros ajustados para reter detalhes
88
- canny_low: int = 25 # fallback (fixo)
89
- canny_high: int = 90 # fallback (fixo)
90
- bilateral_d: int = 5 # menos agressivo
91
- bilateral_sigma_color: int = 60 # reduzido
92
- bilateral_sigma_space: int = 60 # reduzido
93
- gaussian_ksize: int = 5 # menor
94
- min_contour_perimeter: float = 20.0 # mais baixo para capturar detalhes
95
  interior_line_thickness: int = 1 # fino, dilata depois
96
 
97
- # Secondary detail pass — ATIVADO para capturar bordas finas
98
- enable_detail_pass: bool = True
99
  detail_canny_low: int = 10
100
  detail_canny_high: int = 40
101
  detail_gaussian_ksize: int = 3
@@ -103,14 +103,15 @@ class LineartConfig:
103
  detail_bilateral_d: int = 3
104
  detail_bilateral_sigma: int = 40
105
 
106
- # Embroidery stitch feasibility — reduzido para preservar detalhes curtos
107
- min_embroidery_stitch_px: int = 15
108
 
109
- # Auto-Canny sigma — mais baixo = mais detalhes
110
- canny_sigma: float = 0.20
111
 
112
- # Aspect ratio filter: 0 = disabled. Mantenha 0 para não perder detalhes finos.
113
- min_aspect_ratio: float = 0
 
114
 
115
  # Face detection
116
  face_scale_factor: float = 1.05
@@ -280,22 +281,10 @@ def _auto_canny(image: np.ndarray, sigma: float = 0.33,
280
  v = float(np.median(image))
281
  lower = int(max(0, (1.0 - sigma) * v))
282
  upper = int(min(255, (1.0 + sigma) * v))
283
- print(f"[auto_canny] median={v:.1f} lower={lower} upper={upper}")
284
  return cv2.Canny(image, lower, upper)
285
 
286
 
287
- def _xdog_filter(gray: np.ndarray, sigma: float = 1.4, k: float = 1.6,
288
- gamma: float = 0.98, epsilon: float = 0.02, phi: float = 200) -> np.ndarray:
289
- f = gray.astype(np.float32) / 255.0
290
- g1 = cv2.GaussianBlur(f, (0, 0), sigma)
291
- g2 = cv2.GaussianBlur(f, (0, 0), sigma * k)
292
- dog = g1 - gamma * g2
293
- result = np.where(dog >= epsilon, 1.0, 1.0 + np.tanh(phi * (dog - epsilon)))
294
- result = np.clip(result, 0.0, 1.0)
295
- edges = (1.0 - result) * 255
296
- return edges.astype(np.uint8)
297
-
298
-
299
  def build_face_erase_mask(faces: list, shape: Tuple[int, int], cfg: LineartConfig) -> np.ndarray:
300
  h, w = shape
301
  mask = np.zeros((h, w), dtype=np.uint8)
@@ -312,34 +301,33 @@ def build_face_erase_mask(faces: list, shape: Tuple[int, int], cfg: LineartConfi
312
  def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
313
  gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
314
 
315
- # Suavização mais leve
316
  blurred = cv2.bilateralFilter(gray, cfg.bilateral_d,
317
  cfg.bilateral_sigma_color,
318
  cfg.bilateral_sigma_space)
319
  blurred = cv2.GaussianBlur(blurred, (cfg.gaussian_ksize, cfg.gaussian_ksize), 0)
320
 
321
- # Erosão da máscara menos agressiva
322
  erode_size = max(3, cfg.silhouette_line_thickness + 2)
323
  if erode_size % 2 == 0:
324
  erode_size += 1
325
  kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (erode_size, erode_size))
326
  eroded_mask = cv2.erode(subject_mask, kernel_erode, iterations=1)
327
 
328
- # Detecção principal (Canny adaptativo)
329
  sigma = getattr(cfg, 'canny_sigma', 0.33)
330
  if sigma > 0:
331
  edges = _auto_canny(blurred, sigma, mask=eroded_mask)
332
  else:
333
  edges = cv2.Canny(blurred, cfg.canny_low, cfg.canny_high)
334
 
335
- # Passagem de detalhes (ativada)
336
  if getattr(cfg, 'enable_detail_pass', False):
337
  detail_d = getattr(cfg, 'detail_bilateral_d', 3)
338
  detail_sigma = getattr(cfg, 'detail_bilateral_sigma', 40)
339
  detail_ksize = getattr(cfg, 'detail_gaussian_ksize', 3)
340
  blurred_detail = cv2.bilateralFilter(gray, detail_d, detail_sigma, detail_sigma)
341
  blurred_detail = cv2.GaussianBlur(blurred_detail, (detail_ksize, detail_ksize), 0)
342
- # Para detalhes, usamos Canny fixo com limiares baixos
343
  edges_detail = cv2.Canny(
344
  blurred_detail,
345
  getattr(cfg, 'detail_canny_low', 10),
@@ -351,8 +339,8 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
351
 
352
  # Filtrar contornos
353
  min_perimeter = cfg.min_contour_perimeter
354
- min_stitch = getattr(cfg, 'min_embroidery_stitch_px', 15)
355
- min_aspect = getattr(cfg, 'min_aspect_ratio', 0)
356
 
357
  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
358
  filtered = np.zeros_like(edges)
@@ -363,13 +351,17 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
363
  continue
364
  if min_stitch > 0 and perimeter < min_stitch:
365
  continue
 
 
366
  if min_aspect > 0:
367
  x, y, bw, bh = cv2.boundingRect(cnt)
368
  long_side = max(bw, bh)
369
  short_side = min(bw, bh) + 1
 
370
  if long_side / short_side < min_aspect:
371
  continue
372
- # Suavização do contorno com epsilon pequeno
 
373
  eps = 0.005 * perimeter
374
  smooth = cv2.approxPolyDP(cnt, eps, False)
375
  # Desenha com espessura fina (1) para preservar geometria
@@ -377,7 +369,7 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
377
 
378
  # Dilatação final para engrossar as linhas (mantém a forma)
379
  kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
380
- filtered = cv2.dilate(filtered, kernel_dilate, iterations=2) # engrossa 2px
381
 
382
  return filtered
383
 
@@ -515,7 +507,6 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
515
  except Exception as e:
516
  print(f"[AI] Eyebrow extraction error or library missing: {e}")
517
 
518
- # Dilatação para engrossar as sobrancelhas
519
  if eyebrows_layer.any():
520
  dil = getattr(cfg, 'eyebrow_dilate', 2)
521
  if dil > 0:
@@ -539,7 +530,7 @@ def draw_face_oval(
539
  148, 176, 149, 150, 136, 172, 58, 132, 93, 234
540
  ]
541
 
542
- thickness = getattr(cfg, 'face_oval_thickness', 2) # fino, dilata depois
543
 
544
  try:
545
  import mediapipe as mp
@@ -626,7 +617,6 @@ def draw_face_oval(
626
  except Exception as e:
627
  print(f"[face_oval] Error: {e}")
628
 
629
- # Dilatação para engrossar o oval (consistente com as demais linhas)
630
  if oval_layer.any():
631
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
632
  oval_layer = cv2.dilate(oval_layer, kernel, iterations=2)
@@ -678,11 +668,7 @@ def make_faceless_lineart(
678
  face_oval = draw_face_oval(rgb, cfg, faces=faces)
679
  final_lines = cv2.add(final_lines, face_oval)
680
 
681
- # Dilatação final global para garantir espessura uniforme (opcional)
682
- # Se quiser linhas ainda mais grossas, descomente:
683
- # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
684
- # final_lines = cv2.dilate(final_lines, kernel, iterations=1)
685
-
686
  border_erase_width = max(cfg.silhouette_line_thickness, cfg.interior_line_thickness) + 2
687
  if border_erase_width > 0:
688
  final_lines[0:border_erase_width, :] = 0
 
81
  mask_smooth_threshold: int = 128
82
  min_silhouette_area: float = 200.0
83
 
84
+ # Silhouette outline — desenha fino e dilata depois
85
+ silhouette_line_thickness: int = 1
86
+
87
+ # ----- PARÂMETROS EQUILIBRADOS (nem muito detalhe, nem muito liso) -----
88
+ canny_low: int = 30 # fallback
89
+ canny_high: int = 120 # fallback
90
+ bilateral_d: int = 7 # médio
91
+ bilateral_sigma_color: int = 80 # médio
92
+ bilateral_sigma_space: int = 80 # médio
93
+ gaussian_ksize: int = 7 # médio (mata textura fina, mantém dobras)
94
+ min_contour_perimeter: float = 30.0 # ignora linhas muito curtas (ruído)
95
  interior_line_thickness: int = 1 # fino, dilata depois
96
 
97
+ # ---- Detail pass DESLIGADO (evita excesso de ruído) ----
98
+ enable_detail_pass: bool = False
99
  detail_canny_low: int = 10
100
  detail_canny_high: int = 40
101
  detail_gaussian_ksize: int = 3
 
103
  detail_bilateral_d: int = 3
104
  detail_bilateral_sigma: int = 40
105
 
106
+ # Embroidery stitch feasibility
107
+ min_embroidery_stitch_px: int = 20
108
 
109
+ # Auto-Canny sigma — valor médio (0.25) pega dobras principais e ignora textura
110
+ canny_sigma: float = 0.25
111
 
112
+ # --- FILTRO MÁGICO: elimina bolinhas/rugas (redondas) e mantém linhas (dobras) ---
113
+ # 2.0 = só mantém contornos que são pelo menos 2x mais longos que largos
114
+ min_aspect_ratio: float = 2.0
115
 
116
  # Face detection
117
  face_scale_factor: float = 1.05
 
281
  v = float(np.median(image))
282
  lower = int(max(0, (1.0 - sigma) * v))
283
  upper = int(min(255, (1.0 + sigma) * v))
284
+ # print(f"[auto_canny] median={v:.1f} lower={lower} upper={upper}")
285
  return cv2.Canny(image, lower, upper)
286
 
287
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  def build_face_erase_mask(faces: list, shape: Tuple[int, int], cfg: LineartConfig) -> np.ndarray:
289
  h, w = shape
290
  mask = np.zeros((h, w), dtype=np.uint8)
 
301
  def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
302
  gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
303
 
304
+ # Suavização moderada: mata textura de pele/tecido, mas mantém dobras principais
305
  blurred = cv2.bilateralFilter(gray, cfg.bilateral_d,
306
  cfg.bilateral_sigma_color,
307
  cfg.bilateral_sigma_space)
308
  blurred = cv2.GaussianBlur(blurred, (cfg.gaussian_ksize, cfg.gaussian_ksize), 0)
309
 
310
+ # Erosão da máscara para evitar bordas do fundo
311
  erode_size = max(3, cfg.silhouette_line_thickness + 2)
312
  if erode_size % 2 == 0:
313
  erode_size += 1
314
  kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (erode_size, erode_size))
315
  eroded_mask = cv2.erode(subject_mask, kernel_erode, iterations=1)
316
 
317
+ # Detecção principal com Canny adaptativo (sigma=0.25)
318
  sigma = getattr(cfg, 'canny_sigma', 0.33)
319
  if sigma > 0:
320
  edges = _auto_canny(blurred, sigma, mask=eroded_mask)
321
  else:
322
  edges = cv2.Canny(blurred, cfg.canny_low, cfg.canny_high)
323
 
324
+ # Detail pass DESLIGADO para evitar excesso de ruído
325
  if getattr(cfg, 'enable_detail_pass', False):
326
  detail_d = getattr(cfg, 'detail_bilateral_d', 3)
327
  detail_sigma = getattr(cfg, 'detail_bilateral_sigma', 40)
328
  detail_ksize = getattr(cfg, 'detail_gaussian_ksize', 3)
329
  blurred_detail = cv2.bilateralFilter(gray, detail_d, detail_sigma, detail_sigma)
330
  blurred_detail = cv2.GaussianBlur(blurred_detail, (detail_ksize, detail_ksize), 0)
 
331
  edges_detail = cv2.Canny(
332
  blurred_detail,
333
  getattr(cfg, 'detail_canny_low', 10),
 
339
 
340
  # Filtrar contornos
341
  min_perimeter = cfg.min_contour_perimeter
342
+ min_stitch = getattr(cfg, 'min_embroidery_stitch_px', 20)
343
+ min_aspect = getattr(cfg, 'min_aspect_ratio', 2.0) # FILTRO DE ASPECTO ATIVADO
344
 
345
  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
346
  filtered = np.zeros_like(edges)
 
351
  continue
352
  if min_stitch > 0 and perimeter < min_stitch:
353
  continue
354
+
355
+ # ----- FILTRO MÁGICO: elimina rugas/poros (redondos) e mantém dobras (alongadas) -----
356
  if min_aspect > 0:
357
  x, y, bw, bh = cv2.boundingRect(cnt)
358
  long_side = max(bw, bh)
359
  short_side = min(bw, bh) + 1
360
+ # Se for mais redondo que o limite, descarta
361
  if long_side / short_side < min_aspect:
362
  continue
363
+
364
+ # Suavização do contorno para ficar orgânico
365
  eps = 0.005 * perimeter
366
  smooth = cv2.approxPolyDP(cnt, eps, False)
367
  # Desenha com espessura fina (1) para preservar geometria
 
369
 
370
  # Dilatação final para engrossar as linhas (mantém a forma)
371
  kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
372
+ filtered = cv2.dilate(filtered, kernel_dilate, iterations=2)
373
 
374
  return filtered
375
 
 
507
  except Exception as e:
508
  print(f"[AI] Eyebrow extraction error or library missing: {e}")
509
 
 
510
  if eyebrows_layer.any():
511
  dil = getattr(cfg, 'eyebrow_dilate', 2)
512
  if dil > 0:
 
530
  148, 176, 149, 150, 136, 172, 58, 132, 93, 234
531
  ]
532
 
533
+ thickness = getattr(cfg, 'face_oval_thickness', 2)
534
 
535
  try:
536
  import mediapipe as mp
 
617
  except Exception as e:
618
  print(f"[face_oval] Error: {e}")
619
 
 
620
  if oval_layer.any():
621
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
622
  oval_layer = cv2.dilate(oval_layer, kernel, iterations=2)
 
668
  face_oval = draw_face_oval(rgb, cfg, faces=faces)
669
  final_lines = cv2.add(final_lines, face_oval)
670
 
671
+ # Borda segura
 
 
 
 
672
  border_erase_width = max(cfg.silhouette_line_thickness, cfg.interior_line_thickness) + 2
673
  if border_erase_width > 0:
674
  final_lines[0:border_erase_width, :] = 0