Spaces:
Running on Zero
Running on Zero
Update faceless_processing.py
#1
by vitordigitizing - opened
- faceless_processing.py +24 -59
faceless_processing.py
CHANGED
|
@@ -34,8 +34,6 @@ _LINEART_DETECTOR = None
|
|
| 34 |
_LINEART_AVAILABLE = True
|
| 35 |
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
def get_lineart_detector():
|
| 40 |
"""
|
| 41 |
Lazy-load the controlnet_aux LineartDetector (informative-drawings model
|
|
@@ -105,7 +103,6 @@ _download_assets_globally()
|
|
| 105 |
get_lineart_detector()
|
| 106 |
|
| 107 |
|
| 108 |
-
|
| 109 |
@dataclass
|
| 110 |
class LineartConfig:
|
| 111 |
# Backward compatibility fields
|
|
@@ -137,20 +134,15 @@ class LineartConfig:
|
|
| 137 |
lineart_image_resolution: int = 1024
|
| 138 |
lineart_coarse: bool = True
|
| 139 |
|
| 140 |
-
#
|
| 141 |
-
lineart_threshold: int =
|
| 142 |
|
| 143 |
-
#
|
| 144 |
-
lineart_min_perimeter: float =
|
| 145 |
|
| 146 |
-
# Diminuído para manter os traços mais próximos da borda.
|
| 147 |
interior_erode_size: int = 1
|
| 148 |
-
|
| 149 |
-
# AJUSTE GERAL: Afinado para traços internos mais delicados.
|
| 150 |
interior_line_thickness: int = 2
|
| 151 |
|
| 152 |
-
|
| 153 |
-
|
| 154 |
# ---- Canny fallback parametros ----
|
| 155 |
canny_low: int = 35
|
| 156 |
canny_high: int = 75
|
|
@@ -188,9 +180,7 @@ class LineartConfig:
|
|
| 188 |
mediapipe_num_faces: int = 6
|
| 189 |
|
| 190 |
# --- AJUSTE DE SOBRANCELHA ---
|
| 191 |
-
# Reduzido drasticamente para um traço fino e sutil.
|
| 192 |
eyebrow_thickness: int = 2
|
| 193 |
-
# Zerado (era 1). Isso remove o "borrão" que deixava a sobrancelha grossa demais.
|
| 194 |
eyebrow_dilate: int = 0
|
| 195 |
|
| 196 |
# Face shape
|
|
@@ -199,8 +189,6 @@ class LineartConfig:
|
|
| 199 |
face_oval_skip_top_ratio: float = 0.50
|
| 200 |
|
| 201 |
|
| 202 |
-
|
| 203 |
-
|
| 204 |
def _nms_faces(faces: list, iou_thresh: float) -> list:
|
| 205 |
if not faces:
|
| 206 |
return []
|
|
@@ -264,7 +252,6 @@ def detect_faces(rgb: np.ndarray, cfg: LineartConfig) -> list:
|
|
| 264 |
faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)[:max_faces]
|
| 265 |
return faces
|
| 266 |
|
| 267 |
-
|
| 268 |
|
| 269 |
def build_subject_mask(rgb: np.ndarray, cfg: LineartConfig, alpha_channel: Optional[np.ndarray] = None) -> np.ndarray:
|
| 270 |
if alpha_channel is not None:
|
|
@@ -292,8 +279,6 @@ def _smooth_contour_spline(cnt: np.ndarray, n_points: int = 400) -> Optional[np.
|
|
| 292 |
spline_pts = np.stack([x_new, y_new], axis=1).astype(np.int32)
|
| 293 |
return spline_pts.reshape((-1, 1, 2))
|
| 294 |
except Exception:
|
| 295 |
-
|
| 296 |
-
|
| 297 |
return None
|
| 298 |
|
| 299 |
|
|
@@ -338,32 +323,20 @@ def build_face_erase_mask(faces: list, shape: Tuple[int, int], cfg: LineartConfi
|
|
| 338 |
|
| 339 |
|
| 340 |
def _line_strength_from_detector(pil_lineart: Image.Image, target_hw: Tuple[int, int]) -> np.ndarray:
|
| 341 |
-
"""
|
| 342 |
-
Convert the detector output into a polarity-normalised "line strength" map
|
| 343 |
-
where lines = bright (255) on a black (0) background, resized to target.
|
| 344 |
-
Handles either polarity (black-on-white or white-on-black).
|
| 345 |
-
"""
|
| 346 |
h, w = target_hw
|
| 347 |
gray = np.array(pil_lineart.convert("L"))
|
| 348 |
-
# Determine background from the image corners
|
| 349 |
corners = [gray[0, 0], gray[0, -1], gray[-1, 0], gray[-1, -1]]
|
| 350 |
bg = float(np.median(corners))
|
| 351 |
if bg > 127:
|
| 352 |
-
strength = 255 - gray
|
| 353 |
else:
|
| 354 |
-
strength = gray
|
| 355 |
if strength.shape[:2] != (h, w):
|
| 356 |
strength = cv2.resize(strength, (w, h), interpolation=cv2.INTER_LINEAR)
|
| 357 |
return strength.astype(np.uint8)
|
| 358 |
|
| 359 |
|
| 360 |
-
|
| 361 |
def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
|
| 362 |
-
"""
|
| 363 |
-
Detect structural lines (clothing folds, lapels, collars, cuffs, hair) using
|
| 364 |
-
the neural LineartDetector. Produces continuous, illustrator-style strokes.
|
| 365 |
-
Falls back to the legacy Canny pipeline if controlnet_aux is unavailable.
|
| 366 |
-
"""
|
| 367 |
h, w = rgb.shape[:2]
|
| 368 |
detector = get_lineart_detector()
|
| 369 |
|
|
@@ -379,12 +352,8 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
|
|
| 379 |
)
|
| 380 |
strength = _line_strength_from_detector(pil_out, (h, w))
|
| 381 |
|
| 382 |
-
# Threshold to a binary line map
|
| 383 |
_, edges = cv2.threshold(strength, cfg.lineart_threshold, 255, cv2.THRESH_BINARY)
|
| 384 |
|
| 385 |
-
# Keep lines only inside the subject. Use a SMALL erosion so that
|
| 386 |
-
# face-shape, hairline and jaw lines (which sit close to the
|
| 387 |
-
# silhouette) are preserved instead of being eaten away.
|
| 388 |
erode_size = getattr(cfg, 'interior_erode_size', 3)
|
| 389 |
erode_size = max(1, erode_size)
|
| 390 |
if erode_size % 2 == 0:
|
|
@@ -393,24 +362,35 @@ def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: Lineart
|
|
| 393 |
eroded_mask = cv2.erode(subject_mask, kernel_erode, iterations=1)
|
| 394 |
edges = cv2.bitwise_and(edges, eroded_mask)
|
| 395 |
|
| 396 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
| 398 |
filtered = np.zeros_like(edges)
|
| 399 |
for cnt in contours:
|
| 400 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
continue
|
|
|
|
| 402 |
cv2.drawContours(filtered, [cnt], -1, 255, cfg.interior_line_thickness)
|
| 403 |
|
| 404 |
-
#
|
| 405 |
-
|
| 406 |
filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
|
| 407 |
filtered = cv2.GaussianBlur(filtered, (3, 3), 0)
|
| 408 |
-
|
|
|
|
|
|
|
|
|
|
| 409 |
return filtered
|
| 410 |
except Exception as e:
|
| 411 |
print(f"[AI] Neural lineart failed, falling back to Canny: {e}")
|
| 412 |
|
| 413 |
-
# ---------------- Legacy Canny fallback ----------------
|
| 414 |
return _build_interior_edges_canny(rgb, subject_mask, cfg)
|
| 415 |
|
| 416 |
|
|
@@ -457,8 +437,6 @@ def get_landmarker(model_path, num_faces: int = 6):
|
|
| 457 |
output_face_blendshapes=False,
|
| 458 |
output_facial_transformation_matrixes=False,
|
| 459 |
num_faces=num_faces,
|
| 460 |
-
# Lower confidence thresholds so smiling / tilted / smaller faces
|
| 461 |
-
# are still detected and their eyebrows get drawn.
|
| 462 |
min_face_detection_confidence=0.2,
|
| 463 |
min_face_presence_confidence=0.2,
|
| 464 |
min_tracking_confidence=0.2,
|
|
@@ -486,7 +464,6 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
|
|
| 486 |
|
| 487 |
LEFT_EYEBROW = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46]
|
| 488 |
RIGHT_EYEBROW = [336, 296, 334, 293, 300, 285, 295, 282, 283, 276]
|
| 489 |
-
# MediaPipe face oval (head/jaw outline), ordered around the perimeter.
|
| 490 |
FACE_OVAL = [10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288,
|
| 491 |
397, 365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136,
|
| 492 |
172, 58, 132, 93, 234, 127, 162, 21, 54, 103, 67, 109]
|
|
@@ -505,7 +482,6 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
|
|
| 505 |
return False
|
| 506 |
|
| 507 |
def draw_eyebrow_landmarks(face_lms, origin_x, origin_y, scale_x, scale_y):
|
| 508 |
-
# Eyebrows
|
| 509 |
for indices in [LEFT_EYEBROW, RIGHT_EYEBROW]:
|
| 510 |
pts = []
|
| 511 |
for idx in indices:
|
|
@@ -517,7 +493,6 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
|
|
| 517 |
cv2.polylines(eyebrows_layer, [pts_np], isClosed=False, color=255,
|
| 518 |
thickness=eyebrow_thickness, lineType=cv2.LINE_AA)
|
| 519 |
|
| 520 |
-
# Face oval (head / jaw outline) — gives the "formato do rosto"
|
| 521 |
if draw_oval:
|
| 522 |
oval_pts = []
|
| 523 |
for idx in FACE_OVAL:
|
|
@@ -527,16 +502,12 @@ def draw_eyebrows(rgb: np.ndarray, cfg: LineartConfig, faces: Optional[list] = N
|
|
| 527 |
oval_pts.append([px, py])
|
| 528 |
oval_np = np.array(oval_pts, np.int32)
|
| 529 |
if len(oval_np) >= 4:
|
| 530 |
-
# Optionally drop the top (forehead) points so the outline
|
| 531 |
-
# doesn't cut a hard line across the hair.
|
| 532 |
if oval_skip_top > 0:
|
| 533 |
y_min = oval_np[:, 1].min()
|
| 534 |
y_max = oval_np[:, 1].max()
|
| 535 |
cutoff = y_min + (y_max - y_min) * oval_skip_top
|
| 536 |
kept = oval_np[oval_np[:, 1] >= cutoff]
|
| 537 |
if len(kept) >= 4:
|
| 538 |
-
# open arc (jaw + cheeks), not closed, to leave the
|
| 539 |
-
# crown of the head to the hair/silhouette lines
|
| 540 |
arc = kept.reshape((-1, 1, 2))
|
| 541 |
cv2.polylines(eyebrows_layer, [arc], isClosed=False,
|
| 542 |
color=255, thickness=oval_thickness,
|
|
@@ -620,8 +591,6 @@ def make_faceless_lineart(img: Image.Image, cfg: LineartConfig = None) -> Image.
|
|
| 620 |
rgb = img_np[:, :, :3]
|
| 621 |
h, w = rgb.shape[:2]
|
| 622 |
|
| 623 |
-
# If no alpha was provided, derive a subject mask with rembg for a clean
|
| 624 |
-
# silhouette and to confine interior lines to the subject.
|
| 625 |
if alpha_channel is None and _SESSION_ISNET is not None:
|
| 626 |
try:
|
| 627 |
cut = remove(Image.fromarray(rgb).convert("RGBA"), session=_SESSION_ISNET, only_mask=True)
|
|
@@ -685,9 +654,6 @@ def _has_real_transparency(img: Image.Image) -> bool:
|
|
| 685 |
return total > 0 and (visible / total) >= 0.03
|
| 686 |
|
| 687 |
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
def remove_background_subject(img: Image.Image) -> Image.Image:
|
| 692 |
try:
|
| 693 |
rgba = img.convert("RGBA")
|
|
@@ -711,7 +677,6 @@ def make_faceless_cutout(img: Image.Image) -> Image.Image:
|
|
| 711 |
return remove_background_subject(img)
|
| 712 |
|
| 713 |
|
| 714 |
-
|
| 715 |
def build_faceless_embroidery_assets(img: Image.Image, cfg: Optional[LineartConfig] = None, **kwargs) -> Tuple[Image.Image, Image.Image]:
|
| 716 |
if cfg is None:
|
| 717 |
cfg = LineartConfig()
|
|
@@ -721,4 +686,4 @@ def build_faceless_embroidery_assets(img: Image.Image, cfg: Optional[LineartConf
|
|
| 721 |
cfg.negative_prompt = kwargs["negative_prompt"]
|
| 722 |
lineart = make_faceless_lineart(img, cfg)
|
| 723 |
cutout = make_faceless_cutout(img)
|
| 724 |
-
return lineart, cutout
|
|
|
|
| 34 |
_LINEART_AVAILABLE = True
|
| 35 |
|
| 36 |
|
|
|
|
|
|
|
| 37 |
def get_lineart_detector():
|
| 38 |
"""
|
| 39 |
Lazy-load the controlnet_aux LineartDetector (informative-drawings model
|
|
|
|
| 103 |
get_lineart_detector()
|
| 104 |
|
| 105 |
|
|
|
|
| 106 |
@dataclass
|
| 107 |
class LineartConfig:
|
| 108 |
# Backward compatibility fields
|
|
|
|
| 134 |
lineart_image_resolution: int = 1024
|
| 135 |
lineart_coarse: bool = True
|
| 136 |
|
| 137 |
+
# EQUILÍBRIO: 80 é forte o suficiente para ignorar sombra, mas não apaga o rosto.
|
| 138 |
+
lineart_threshold: int = 80
|
| 139 |
|
| 140 |
+
# EQUILÍBRIO: 60 pixels corta fios curtos, preservando linhas estruturais.
|
| 141 |
+
lineart_min_perimeter: float = 60.0
|
| 142 |
|
|
|
|
| 143 |
interior_erode_size: int = 1
|
|
|
|
|
|
|
| 144 |
interior_line_thickness: int = 2
|
| 145 |
|
|
|
|
|
|
|
| 146 |
# ---- Canny fallback parametros ----
|
| 147 |
canny_low: int = 35
|
| 148 |
canny_high: int = 75
|
|
|
|
| 180 |
mediapipe_num_faces: int = 6
|
| 181 |
|
| 182 |
# --- AJUSTE DE SOBRANCELHA ---
|
|
|
|
| 183 |
eyebrow_thickness: int = 2
|
|
|
|
| 184 |
eyebrow_dilate: int = 0
|
| 185 |
|
| 186 |
# Face shape
|
|
|
|
| 189 |
face_oval_skip_top_ratio: float = 0.50
|
| 190 |
|
| 191 |
|
|
|
|
|
|
|
| 192 |
def _nms_faces(faces: list, iou_thresh: float) -> list:
|
| 193 |
if not faces:
|
| 194 |
return []
|
|
|
|
| 252 |
faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)[:max_faces]
|
| 253 |
return faces
|
| 254 |
|
|
|
|
| 255 |
|
| 256 |
def build_subject_mask(rgb: np.ndarray, cfg: LineartConfig, alpha_channel: Optional[np.ndarray] = None) -> np.ndarray:
|
| 257 |
if alpha_channel is not None:
|
|
|
|
| 279 |
spline_pts = np.stack([x_new, y_new], axis=1).astype(np.int32)
|
| 280 |
return spline_pts.reshape((-1, 1, 2))
|
| 281 |
except Exception:
|
|
|
|
|
|
|
| 282 |
return None
|
| 283 |
|
| 284 |
|
|
|
|
| 323 |
|
| 324 |
|
| 325 |
def _line_strength_from_detector(pil_lineart: Image.Image, target_hw: Tuple[int, int]) -> np.ndarray:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
h, w = target_hw
|
| 327 |
gray = np.array(pil_lineart.convert("L"))
|
|
|
|
| 328 |
corners = [gray[0, 0], gray[0, -1], gray[-1, 0], gray[-1, -1]]
|
| 329 |
bg = float(np.median(corners))
|
| 330 |
if bg > 127:
|
| 331 |
+
strength = 255 - gray
|
| 332 |
else:
|
| 333 |
+
strength = gray
|
| 334 |
if strength.shape[:2] != (h, w):
|
| 335 |
strength = cv2.resize(strength, (w, h), interpolation=cv2.INTER_LINEAR)
|
| 336 |
return strength.astype(np.uint8)
|
| 337 |
|
| 338 |
|
|
|
|
| 339 |
def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
h, w = rgb.shape[:2]
|
| 341 |
detector = get_lineart_detector()
|
| 342 |
|
|
|
|
| 352 |
)
|
| 353 |
strength = _line_strength_from_detector(pil_out, (h, w))
|
| 354 |
|
|
|
|
| 355 |
_, edges = cv2.threshold(strength, cfg.lineart_threshold, 255, cv2.THRESH_BINARY)
|
| 356 |
|
|
|
|
|
|
|
|
|
|
| 357 |
erode_size = getattr(cfg, 'interior_erode_size', 3)
|
| 358 |
erode_size = max(1, erode_size)
|
| 359 |
if erode_size % 2 == 0:
|
|
|
|
| 362 |
eroded_mask = cv2.erode(subject_mask, kernel_erode, iterations=1)
|
| 363 |
edges = cv2.bitwise_and(edges, eroded_mask)
|
| 364 |
|
| 365 |
+
# --- O "ASPIRADOR DE PÓ" MATEMÁTICO ---
|
| 366 |
+
# Remove pontos solitários grossos antes da medição
|
| 367 |
+
kernel_clean = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
|
| 368 |
+
edges = cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel_clean, iterations=1)
|
| 369 |
+
|
| 370 |
contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
| 371 |
filtered = np.zeros_like(edges)
|
| 372 |
for cnt in contours:
|
| 373 |
+
perimeter = cv2.arcLength(cnt, False)
|
| 374 |
+
area = cv2.contourArea(cnt)
|
| 375 |
+
|
| 376 |
+
# Barreira dupla: Remove traços menores que 60px E bolinhas grossas.
|
| 377 |
+
if perimeter < cfg.lineart_min_perimeter or (perimeter < 150 and area < 10.0):
|
| 378 |
continue
|
| 379 |
+
|
| 380 |
cv2.drawContours(filtered, [cnt], -1, 255, cfg.interior_line_thickness)
|
| 381 |
|
| 382 |
+
# Suavização final para cantos realistas de linha de costura
|
| 383 |
+
kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
|
| 384 |
filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
|
| 385 |
filtered = cv2.GaussianBlur(filtered, (3, 3), 0)
|
| 386 |
+
|
| 387 |
+
# CORREÇÃO: cv2.threshold retorna uma tupla (_, imagem_resultante)
|
| 388 |
+
_, filtered = cv2.threshold(filtered, 127, 255, cv2.THRESH_BINARY)
|
| 389 |
+
|
| 390 |
return filtered
|
| 391 |
except Exception as e:
|
| 392 |
print(f"[AI] Neural lineart failed, falling back to Canny: {e}")
|
| 393 |
|
|
|
|
| 394 |
return _build_interior_edges_canny(rgb, subject_mask, cfg)
|
| 395 |
|
| 396 |
|
|
|
|
| 437 |
output_face_blendshapes=False,
|
| 438 |
output_facial_transformation_matrixes=False,
|
| 439 |
num_faces=num_faces,
|
|
|
|
|
|
|
| 440 |
min_face_detection_confidence=0.2,
|
| 441 |
min_face_presence_confidence=0.2,
|
| 442 |
min_tracking_confidence=0.2,
|
|
|
|
| 464 |
|
| 465 |
LEFT_EYEBROW = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46]
|
| 466 |
RIGHT_EYEBROW = [336, 296, 334, 293, 300, 285, 295, 282, 283, 276]
|
|
|
|
| 467 |
FACE_OVAL = [10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288,
|
| 468 |
397, 365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136,
|
| 469 |
172, 58, 132, 93, 234, 127, 162, 21, 54, 103, 67, 109]
|
|
|
|
| 482 |
return False
|
| 483 |
|
| 484 |
def draw_eyebrow_landmarks(face_lms, origin_x, origin_y, scale_x, scale_y):
|
|
|
|
| 485 |
for indices in [LEFT_EYEBROW, RIGHT_EYEBROW]:
|
| 486 |
pts = []
|
| 487 |
for idx in indices:
|
|
|
|
| 493 |
cv2.polylines(eyebrows_layer, [pts_np], isClosed=False, color=255,
|
| 494 |
thickness=eyebrow_thickness, lineType=cv2.LINE_AA)
|
| 495 |
|
|
|
|
| 496 |
if draw_oval:
|
| 497 |
oval_pts = []
|
| 498 |
for idx in FACE_OVAL:
|
|
|
|
| 502 |
oval_pts.append([px, py])
|
| 503 |
oval_np = np.array(oval_pts, np.int32)
|
| 504 |
if len(oval_np) >= 4:
|
|
|
|
|
|
|
| 505 |
if oval_skip_top > 0:
|
| 506 |
y_min = oval_np[:, 1].min()
|
| 507 |
y_max = oval_np[:, 1].max()
|
| 508 |
cutoff = y_min + (y_max - y_min) * oval_skip_top
|
| 509 |
kept = oval_np[oval_np[:, 1] >= cutoff]
|
| 510 |
if len(kept) >= 4:
|
|
|
|
|
|
|
| 511 |
arc = kept.reshape((-1, 1, 2))
|
| 512 |
cv2.polylines(eyebrows_layer, [arc], isClosed=False,
|
| 513 |
color=255, thickness=oval_thickness,
|
|
|
|
| 591 |
rgb = img_np[:, :, :3]
|
| 592 |
h, w = rgb.shape[:2]
|
| 593 |
|
|
|
|
|
|
|
| 594 |
if alpha_channel is None and _SESSION_ISNET is not None:
|
| 595 |
try:
|
| 596 |
cut = remove(Image.fromarray(rgb).convert("RGBA"), session=_SESSION_ISNET, only_mask=True)
|
|
|
|
| 654 |
return total > 0 and (visible / total) >= 0.03
|
| 655 |
|
| 656 |
|
|
|
|
|
|
|
|
|
|
| 657 |
def remove_background_subject(img: Image.Image) -> Image.Image:
|
| 658 |
try:
|
| 659 |
rgba = img.convert("RGBA")
|
|
|
|
| 677 |
return remove_background_subject(img)
|
| 678 |
|
| 679 |
|
|
|
|
| 680 |
def build_faceless_embroidery_assets(img: Image.Image, cfg: Optional[LineartConfig] = None, **kwargs) -> Tuple[Image.Image, Image.Image]:
|
| 681 |
if cfg is None:
|
| 682 |
cfg = LineartConfig()
|
|
|
|
| 686 |
cfg.negative_prompt = kwargs["negative_prompt"]
|
| 687 |
lineart = make_faceless_lineart(img, cfg)
|
| 688 |
cutout = make_faceless_cutout(img)
|
| 689 |
+
return lineart, cutout
|