vitordigitizing commited on
Commit
028b4f4
·
verified ·
1 Parent(s): 863556d

Update faceless_processing.py

Browse files
Files changed (1) hide show
  1. faceless_processing.py +115 -13
faceless_processing.py CHANGED
@@ -202,6 +202,12 @@ class LineartConfig:
202
  # AJUSTE FINO: Remove 50% do topo do oval (desenha apenas a mandíbula inferior)
203
  face_oval_skip_top_ratio: float = 0.50
204
 
 
 
 
 
 
 
205
 
206
  def _nms_faces(faces: list, iou_thresh: float) -> list:
207
  if not faces:
@@ -501,6 +507,54 @@ def _line_strength_from_detector(pil_lineart: Image.Image, target_hw: Tuple[int,
501
  return strength.astype(np.uint8)
502
 
503
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
505
  h, w = rgb.shape[:2]
506
  detector = get_lineart_detector()
@@ -534,19 +588,19 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
534
  edges = cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel_clean, iterations=1)
535
 
536
  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
537
- filtered = np.zeros_like(edges)
538
-
539
  min_area = getattr(cfg, 'lineart_min_area', 40.0)
 
 
540
 
541
- for cnt in contours:
542
- perimeter = cv2.arcLength(cnt, False)
543
- area = cv2.contourArea(cnt)
544
-
545
- # BARREIRA EXTREMA: Elimina pontos isolados e traços curtos
546
- if perimeter < cfg.lineart_min_perimeter or area < min_area:
547
- continue
548
-
549
- cv2.drawContours(filtered, [cnt], -1, 255, cfg.interior_line_thickness)
550
 
551
  kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
552
  filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
@@ -575,19 +629,34 @@ def _build_interior_edges_canny(rgb: np.ndarray, subject_mask: np.ndarray, cfg:
575
  edges = cv2.bitwise_and(edges, eroded_mask)
576
 
577
  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
578
- filtered = np.zeros_like(edges)
 
579
  for cnt in contours:
580
  perimeter = cv2.arcLength(cnt, False)
581
  if perimeter < cfg.min_contour_perimeter:
582
  continue
583
  eps = 0.0012 * perimeter
584
  smooth = cv2.approxPolyDP(cnt, eps, False)
585
- cv2.drawContours(filtered, [smooth], -1, 255, cfg.interior_line_thickness)
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
587
  filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
588
  return filtered
589
 
590
 
 
591
  _LANDMARKER_LOCK = threading.Lock()
592
  _LANDMARKER_CACHE = None
593
  _LANDMARKER_NUM_FACES = None
@@ -748,6 +817,7 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
748
  return eyebrows_layer
749
 
750
 
 
751
  def make_faceless_lineart(img: Image.Image, cfg: LineartConfig = None) -> Image.Image:
752
  cfg = cfg or LineartConfig()
753
 
@@ -781,6 +851,38 @@ def make_faceless_lineart(img: Image.Image, cfg: LineartConfig = None) -> Image.
781
  faces = detect_faces(rgb, cfg)
782
  print(f"[lineart] Detected {len(faces)} face(s)")
783
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  silhouette = build_silhouette(subject_mask, cfg)
785
  interior = build_interior_edges(rgb, subject_mask, cfg)
786
 
 
202
  # AJUSTE FINO: Remove 50% do topo do oval (desenha apenas a mandíbula inferior)
203
  face_oval_skip_top_ratio: float = 0.50
204
 
205
+ # --- Parâmetros de limpeza e bordabilidade ---
206
+ min_line_distance: int = 8
207
+ line_overlap_threshold: float = 0.35
208
+ custom_tuned_for_embroidery: bool = False
209
+
210
+
211
 
212
  def _nms_faces(faces: list, iou_thresh: float) -> list:
213
  if not faces:
 
507
  return strength.astype(np.uint8)
508
 
509
 
510
+ def _filter_dense_contours(contours: list, min_dist_px: int, overlap_threshold: float, min_perimeter: float, thickness: int, shape: Tuple[int, int], min_area: float = 0.0) -> np.ndarray:
511
+ """
512
+ Filters contours to ensure that no two lines are too close to each other,
513
+ which makes the lineart highly suitable for embroidery (prevents dense/overlapping stitches).
514
+ """
515
+ sorted_cnts = sorted(contours, key=lambda c: cv2.arcLength(c, False), reverse=True)
516
+
517
+ filtered_mask = np.zeros(shape, dtype=np.uint8)
518
+ dilated_mask = np.zeros(shape, dtype=np.uint8)
519
+
520
+ if min_dist_px > 0:
521
+ kernel_dist = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (min_dist_px * 2 + 1, min_dist_px * 2 + 1))
522
+ else:
523
+ kernel_dist = None
524
+
525
+ for cnt in sorted_cnts:
526
+ perimeter = cv2.arcLength(cnt, False)
527
+ # contourArea requires at least 3 points
528
+ area = cv2.contourArea(cnt) if len(cnt) >= 3 else 0.0
529
+
530
+ if perimeter < min_perimeter:
531
+ continue
532
+ if min_area > 0 and area < min_area:
533
+ continue
534
+
535
+ cnt_mask = np.zeros(shape, dtype=np.uint8)
536
+ cv2.drawContours(cnt_mask, [cnt], -1, 255, thickness)
537
+
538
+ if kernel_dist is not None and np.any(dilated_mask > 0):
539
+ intersection = cv2.bitwise_and(cnt_mask, dilated_mask)
540
+ num_intersect = cv2.countNonZero(intersection)
541
+ num_total = cv2.countNonZero(cnt_mask)
542
+
543
+ if num_total > 0 and (num_intersect / num_total) > overlap_threshold:
544
+ # Too close to existing lines
545
+ continue
546
+
547
+ cv2.drawContours(filtered_mask, [cnt], -1, 255, thickness)
548
+
549
+ if kernel_dist is not None:
550
+ cnt_draw = np.zeros(shape, dtype=np.uint8)
551
+ cv2.drawContours(cnt_draw, [cnt], -1, 255, thickness)
552
+ cnt_dilated = cv2.dilate(cnt_draw, kernel_dist, iterations=1)
553
+ dilated_mask = cv2.bitwise_or(dilated_mask, cnt_dilated)
554
+
555
+ return filtered_mask
556
+
557
+
558
  def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
559
  h, w = rgb.shape[:2]
560
  detector = get_lineart_detector()
 
588
  edges = cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel_clean, iterations=1)
589
 
590
  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
 
 
591
  min_area = getattr(cfg, 'lineart_min_area', 40.0)
592
+ min_dist = getattr(cfg, 'min_line_distance', 8)
593
+ overlap_thresh = getattr(cfg, 'line_overlap_threshold', 0.35)
594
 
595
+ filtered = _filter_dense_contours(
596
+ contours=contours,
597
+ min_dist_px=min_dist,
598
+ overlap_threshold=overlap_thresh,
599
+ min_perimeter=cfg.lineart_min_perimeter,
600
+ thickness=cfg.interior_line_thickness,
601
+ shape=edges.shape,
602
+ min_area=min_area
603
+ )
604
 
605
  kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
606
  filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
 
629
  edges = cv2.bitwise_and(edges, eroded_mask)
630
 
631
  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
632
+
633
+ smoothed_contours = []
634
  for cnt in contours:
635
  perimeter = cv2.arcLength(cnt, False)
636
  if perimeter < cfg.min_contour_perimeter:
637
  continue
638
  eps = 0.0012 * perimeter
639
  smooth = cv2.approxPolyDP(cnt, eps, False)
640
+ smoothed_contours.append(smooth)
641
+
642
+ min_dist = getattr(cfg, 'min_line_distance', 8)
643
+ overlap_thresh = getattr(cfg, 'line_overlap_threshold', 0.35)
644
+
645
+ filtered = _filter_dense_contours(
646
+ contours=smoothed_contours,
647
+ min_dist_px=min_dist,
648
+ overlap_threshold=overlap_thresh,
649
+ min_perimeter=cfg.min_contour_perimeter,
650
+ thickness=cfg.interior_line_thickness,
651
+ shape=edges.shape
652
+ )
653
+
654
  kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
655
  filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
656
  return filtered
657
 
658
 
659
+
660
  _LANDMARKER_LOCK = threading.Lock()
661
  _LANDMARKER_CACHE = None
662
  _LANDMARKER_NUM_FACES = None
 
817
  return eyebrows_layer
818
 
819
 
820
+
821
  def make_faceless_lineart(img: Image.Image, cfg: LineartConfig = None) -> Image.Image:
822
  cfg = cfg or LineartConfig()
823
 
 
851
  faces = detect_faces(rgb, cfg)
852
  print(f"[lineart] Detected {len(faces)} face(s)")
853
 
854
+ # Se NÃO detectou rosto, entra em modo de contingência ("cartoon-clean" automático)
855
+ is_cartoon_fallback = getattr(cfg, 'is_cartoon_fallback', False) or (len(faces) == 0)
856
+
857
+ if is_cartoon_fallback and not getattr(cfg, 'custom_tuned_for_embroidery', False):
858
+ print("[lineart] No face detected or cartoon fallback active. Tuning parameters for clean structured lineart.")
859
+ cfg.erase_faces = False
860
+ cfg.detect_eyebrows = False
861
+ cfg.erase_borders = False
862
+
863
+ # Parâmetros equilibrados para traços nítidos e bordáveis
864
+ cfg.lineart_threshold = 95
865
+ cfg.lineart_min_perimeter = 45.0
866
+ cfg.lineart_min_area = 20.0
867
+ cfg.lineart_morph_open = True
868
+
869
+ cfg.canny_low = 50
870
+ cfg.canny_high = 110
871
+ cfg.min_contour_perimeter = 35.0
872
+
873
+ # Suavização para remover pelos sem perder formas principais
874
+ cfg.bilateral_d = 7
875
+ cfg.bilateral_sigma_color = 90
876
+ cfg.bilateral_sigma_space = 90
877
+ cfg.gaussian_ksize = 5
878
+
879
+ # Filtro de proximidade entre linhas
880
+ cfg.min_line_distance = 8
881
+ cfg.line_overlap_threshold = 0.35
882
+
883
+ cfg.custom_tuned_for_embroidery = True
884
+
885
+ subject_mask = build_subject_mask(rgb, cfg, alpha_channel)
886
  silhouette = build_silhouette(subject_mask, cfg)
887
  interior = build_interior_edges(rgb, subject_mask, cfg)
888