Spaces:
Sleeping
Sleeping
Update faceless_processing.py
Browse files- faceless_processing.py +67 -7
faceless_processing.py
CHANGED
|
@@ -124,6 +124,7 @@ class LineartConfig:
|
|
| 124 |
rembg_model: str = ""
|
| 125 |
transparency_threshold: float = 0.03
|
| 126 |
detect_eyebrows: bool = True
|
|
|
|
| 127 |
|
| 128 |
# Subject mask
|
| 129 |
white_bg_threshold: int = 245
|
|
@@ -131,19 +132,19 @@ class LineartConfig:
|
|
| 131 |
mask_open_iterations: int = 1
|
| 132 |
mask_smooth_ksize: int = 5
|
| 133 |
mask_smooth_threshold: int = 128
|
| 134 |
-
# Área mínima de silhueta. Valor menor preserva crianças e figuras pequenas.
|
| 135 |
-
min_silhouette_area: float = 80.0
|
| 136 |
-
|
| 137 |
# Silhouette outline
|
| 138 |
silhouette_line_thickness: int = 4
|
| 139 |
|
|
|
|
|
|
|
|
|
|
| 140 |
# --- Neural line-art parameters ---
|
| 141 |
lineart_detect_resolution: int = 1024
|
| 142 |
lineart_image_resolution: int = 1024
|
| 143 |
lineart_coarse: bool = True
|
| 144 |
|
| 145 |
-
#
|
| 146 |
-
lineart_threshold: int =
|
| 147 |
|
| 148 |
# REDUZIDO: Permite traços de corpos menores (crianças em grupos)
|
| 149 |
lineart_min_perimeter: float = 80.0
|
|
@@ -215,6 +216,7 @@ class LineartConfig:
|
|
| 215 |
|
| 216 |
|
| 217 |
|
|
|
|
| 218 |
def _nms_faces(faces: list, iou_thresh: float) -> list:
|
| 219 |
if not faces:
|
| 220 |
return []
|
|
@@ -468,7 +470,7 @@ def build_silhouette(subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray
|
|
| 468 |
cv2.drawContours(silhouette, [approx], -1, 255,
|
| 469 |
cfg.silhouette_line_thickness, lineType=cv2.LINE_AA)
|
| 470 |
return silhouette
|
| 471 |
-
|
| 472 |
|
| 473 |
def build_face_erase_mask(rgb: np.ndarray, faces: list, cfg: LineartConfig) -> np.ndarray:
|
| 474 |
h, w = rgb.shape[:2]
|
|
@@ -699,11 +701,64 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
|
|
| 699 |
return _build_interior_edges_canny(rgb, subject_mask, cfg)
|
| 700 |
|
| 701 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
def _build_interior_edges_canny(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
|
| 703 |
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
| 704 |
blurred = cv2.bilateralFilter(gray, cfg.bilateral_d, cfg.bilateral_sigma_color, cfg.bilateral_sigma_space)
|
| 705 |
blurred = cv2.GaussianBlur(blurred, (cfg.gaussian_ksize, cfg.gaussian_ksize), 0)
|
| 706 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
|
| 708 |
erode_size = max(5, cfg.silhouette_line_thickness * 2 + 3)
|
| 709 |
if erode_size % 2 == 0:
|
|
@@ -740,6 +795,7 @@ def _build_interior_edges_canny(rgb: np.ndarray, subject_mask: np.ndarray, cfg:
|
|
| 740 |
return filtered
|
| 741 |
|
| 742 |
|
|
|
|
| 743 |
_LANDMARKER_LOCK = threading.Lock()
|
| 744 |
_LANDMARKER_CACHE = None
|
| 745 |
_LANDMARKER_NUM_FACES = None
|
|
@@ -902,6 +958,7 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
|
|
| 902 |
return eyebrows_layer
|
| 903 |
|
| 904 |
|
|
|
|
| 905 |
def make_faceless_lineart(img: Image.Image, cfg: LineartConfig = None) -> Image.Image:
|
| 906 |
cfg = cfg or LineartConfig()
|
| 907 |
|
|
@@ -1058,6 +1115,9 @@ def make_faceless_cutout(img: Image.Image) -> Image.Image:
|
|
| 1058 |
|
| 1059 |
|
| 1060 |
|
|
|
|
|
|
|
|
|
|
| 1061 |
def build_faceless_embroidery_assets(img: Image.Image, cfg: Optional[LineartConfig] = None, **kwargs) -> Tuple[Image.Image, Image.Image]:
|
| 1062 |
if cfg is None:
|
| 1063 |
cfg = LineartConfig()
|
|
|
|
| 124 |
rembg_model: str = ""
|
| 125 |
transparency_threshold: float = 0.03
|
| 126 |
detect_eyebrows: bool = True
|
| 127 |
+
is_cartoon: bool = False
|
| 128 |
|
| 129 |
# Subject mask
|
| 130 |
white_bg_threshold: int = 245
|
|
|
|
| 132 |
mask_open_iterations: int = 1
|
| 133 |
mask_smooth_ksize: int = 5
|
| 134 |
mask_smooth_threshold: int = 128
|
|
|
|
|
|
|
|
|
|
| 135 |
# Silhouette outline
|
| 136 |
silhouette_line_thickness: int = 4
|
| 137 |
|
| 138 |
+
# Área mínima de silhueta. Valor menor preserva crianças e figuras pequenas.
|
| 139 |
+
min_silhouette_area: float = 90.0
|
| 140 |
+
|
| 141 |
# --- Neural line-art parameters ---
|
| 142 |
lineart_detect_resolution: int = 1024
|
| 143 |
lineart_image_resolution: int = 1024
|
| 144 |
lineart_coarse: bool = True
|
| 145 |
|
| 146 |
+
# AUMENTADO: Força bruta para ignorar detalhes fracos
|
| 147 |
+
lineart_threshold: int = 115
|
| 148 |
|
| 149 |
# REDUZIDO: Permite traços de corpos menores (crianças em grupos)
|
| 150 |
lineart_min_perimeter: float = 80.0
|
|
|
|
| 216 |
|
| 217 |
|
| 218 |
|
| 219 |
+
|
| 220 |
def _nms_faces(faces: list, iou_thresh: float) -> list:
|
| 221 |
if not faces:
|
| 222 |
return []
|
|
|
|
| 470 |
cv2.drawContours(silhouette, [approx], -1, 255,
|
| 471 |
cfg.silhouette_line_thickness, lineType=cv2.LINE_AA)
|
| 472 |
return silhouette
|
| 473 |
+
|
| 474 |
|
| 475 |
def build_face_erase_mask(rgb: np.ndarray, faces: list, cfg: LineartConfig) -> np.ndarray:
|
| 476 |
h, w = rgb.shape[:2]
|
|
|
|
| 701 |
return _build_interior_edges_canny(rgb, subject_mask, cfg)
|
| 702 |
|
| 703 |
|
| 704 |
+
def _skeletonize(img: np.ndarray) -> np.ndarray:
|
| 705 |
+
"""
|
| 706 |
+
Applies morphological skeletonization to reduce binary lines to 1-pixel wide centerlines.
|
| 707 |
+
"""
|
| 708 |
+
size = np.size(img)
|
| 709 |
+
skel = np.zeros(img.shape, np.uint8)
|
| 710 |
+
element = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
|
| 711 |
+
img_copy = img.copy()
|
| 712 |
+
|
| 713 |
+
for _ in range(120): # safety limit
|
| 714 |
+
eroded = cv2.erode(img_copy, element)
|
| 715 |
+
temp = cv2.dilate(eroded, element)
|
| 716 |
+
temp = cv2.subtract(img_copy, temp)
|
| 717 |
+
skel = cv2.bitwise_or(skel, temp)
|
| 718 |
+
img_copy = eroded
|
| 719 |
+
if cv2.countNonZero(img_copy) == 0:
|
| 720 |
+
break
|
| 721 |
+
|
| 722 |
+
return skel
|
| 723 |
+
|
| 724 |
+
|
| 725 |
def _build_interior_edges_canny(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
|
| 726 |
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
| 727 |
blurred = cv2.bilateralFilter(gray, cfg.bilateral_d, cfg.bilateral_sigma_color, cfg.bilateral_sigma_space)
|
| 728 |
blurred = cv2.GaussianBlur(blurred, (cfg.gaussian_ksize, cfg.gaussian_ksize), 0)
|
| 729 |
+
|
| 730 |
+
if getattr(cfg, 'is_cartoon', False):
|
| 731 |
+
# 1. Binarização adaptativa inteligente para extrair traços cheios (evita contorno duplo!)
|
| 732 |
+
corners = [blurred[0, 0], blurred[0, -1], blurred[-1, 0], blurred[-1, -1]]
|
| 733 |
+
bg = float(np.median(corners))
|
| 734 |
+
if bg > 127: # Fundo claro, traços escuros
|
| 735 |
+
thresh = cv2.adaptiveThreshold(
|
| 736 |
+
blurred, 255,
|
| 737 |
+
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
| 738 |
+
cv2.THRESH_BINARY_INV,
|
| 739 |
+
21, 5
|
| 740 |
+
)
|
| 741 |
+
else: # Fundo escuro, traços claros
|
| 742 |
+
thresh = cv2.adaptiveThreshold(
|
| 743 |
+
blurred, 255,
|
| 744 |
+
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
| 745 |
+
cv2.THRESH_BINARY,
|
| 746 |
+
21, 5
|
| 747 |
+
)
|
| 748 |
+
|
| 749 |
+
# 2. Canny complementar para não perder pequenos detalhes de alto contraste
|
| 750 |
+
canny_edges = cv2.Canny(blurred, cfg.canny_low, cfg.canny_high)
|
| 751 |
+
# Dilata Canny levemente para fundir com os traços do limiar adaptativo
|
| 752 |
+
kernel_c = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
|
| 753 |
+
canny_dilated = cv2.dilate(canny_edges, kernel_c, iterations=1)
|
| 754 |
+
|
| 755 |
+
# Combina ambos
|
| 756 |
+
combined = cv2.bitwise_or(thresh, canny_dilated)
|
| 757 |
+
|
| 758 |
+
# 3. Esqueletização para converter todos os traços cheios em linhas de 1 pixel centralizadas
|
| 759 |
+
edges = _skeletonize(combined)
|
| 760 |
+
else:
|
| 761 |
+
edges = cv2.Canny(blurred, cfg.canny_low, cfg.canny_high)
|
| 762 |
|
| 763 |
erode_size = max(5, cfg.silhouette_line_thickness * 2 + 3)
|
| 764 |
if erode_size % 2 == 0:
|
|
|
|
| 795 |
return filtered
|
| 796 |
|
| 797 |
|
| 798 |
+
|
| 799 |
_LANDMARKER_LOCK = threading.Lock()
|
| 800 |
_LANDMARKER_CACHE = None
|
| 801 |
_LANDMARKER_NUM_FACES = None
|
|
|
|
| 958 |
return eyebrows_layer
|
| 959 |
|
| 960 |
|
| 961 |
+
|
| 962 |
def make_faceless_lineart(img: Image.Image, cfg: LineartConfig = None) -> Image.Image:
|
| 963 |
cfg = cfg or LineartConfig()
|
| 964 |
|
|
|
|
| 1115 |
|
| 1116 |
|
| 1117 |
|
| 1118 |
+
|
| 1119 |
+
|
| 1120 |
+
|
| 1121 |
def build_faceless_embroidery_assets(img: Image.Image, cfg: Optional[LineartConfig] = None, **kwargs) -> Tuple[Image.Image, Image.Image]:
|
| 1122 |
if cfg is None:
|
| 1123 |
cfg = LineartConfig()
|