Spaces:
Sleeping
Sleeping
Update faceless_processing.py
Browse files- faceless_processing.py +19 -8
faceless_processing.py
CHANGED
|
@@ -61,14 +61,15 @@ class LineartConfig:
|
|
| 61 |
silhouette_line_thickness: int = 4 # outer contour stroke width (px); match interior for uniform look
|
| 62 |
|
| 63 |
# Edge detection
|
| 64 |
-
canny_low: int =
|
| 65 |
canny_high: int = 110
|
| 66 |
bilateral_d: int = 11
|
| 67 |
bilateral_sigma_color: int = 80
|
| 68 |
bilateral_sigma_space: int = 80
|
| 69 |
gaussian_ksize: int = 9
|
| 70 |
-
min_contour_perimeter: float =
|
| 71 |
interior_line_thickness: int = 3 # thickness of interior detail lines (>=1)
|
|
|
|
| 72 |
|
| 73 |
# Face detection
|
| 74 |
face_scale_factor: float = 1.05
|
|
@@ -188,23 +189,33 @@ def build_face_erase_mask(faces: list, shape: Tuple[int, int], cfg: LineartConfi
|
|
| 188 |
|
| 189 |
|
| 190 |
def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
|
| 191 |
-
"""Detect structural body lines
|
| 192 |
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
| 193 |
blurred = cv2.GaussianBlur(gray, (cfg.gaussian_ksize, cfg.gaussian_ksize), 0)
|
| 194 |
blurred = cv2.bilateralFilter(blurred, cfg.bilateral_d, cfg.bilateral_sigma_color, cfg.bilateral_sigma_space)
|
| 195 |
edges = cv2.Canny(blurred, cfg.canny_low, cfg.canny_high)
|
| 196 |
edges = cv2.bitwise_and(edges, subject_mask)
|
| 197 |
|
| 198 |
-
#
|
| 199 |
contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
| 200 |
-
|
| 201 |
for cnt in contours:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
if cv2.arcLength(cnt, False) > cfg.min_contour_perimeter:
|
| 203 |
cv2.drawContours(filtered, [cnt], -1, 255, cfg.interior_line_thickness)
|
| 204 |
|
| 205 |
-
# Mild dilation for uniform, solid strokes
|
| 206 |
-
kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
|
| 207 |
-
filtered = cv2.dilate(filtered, kernel_dilate, iterations=1)
|
| 208 |
return filtered
|
| 209 |
|
| 210 |
|
|
|
|
| 61 |
silhouette_line_thickness: int = 4 # outer contour stroke width (px); match interior for uniform look
|
| 62 |
|
| 63 |
# Edge detection
|
| 64 |
+
canny_low: int = 25 # lower = more body detail captured
|
| 65 |
canny_high: int = 110
|
| 66 |
bilateral_d: int = 11
|
| 67 |
bilateral_sigma_color: int = 80
|
| 68 |
bilateral_sigma_space: int = 80
|
| 69 |
gaussian_ksize: int = 9
|
| 70 |
+
min_contour_perimeter: float = 30.0 # filters out short noisy lines
|
| 71 |
interior_line_thickness: int = 3 # thickness of interior detail lines (>=1)
|
| 72 |
+
cluster_merge_radius: int = 5 # radius (px) to merge nearby parallel lines into one
|
| 73 |
|
| 74 |
# Face detection
|
| 75 |
face_scale_factor: float = 1.05
|
|
|
|
| 189 |
|
| 190 |
|
| 191 |
def build_interior_edges(rgb: np.ndarray, subject_mask: np.ndarray, cfg: LineartConfig) -> np.ndarray:
|
| 192 |
+
"""Detect structural body lines, capturing more detail then summarising clustered strokes."""
|
| 193 |
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
| 194 |
blurred = cv2.GaussianBlur(gray, (cfg.gaussian_ksize, cfg.gaussian_ksize), 0)
|
| 195 |
blurred = cv2.bilateralFilter(blurred, cfg.bilateral_d, cfg.bilateral_sigma_color, cfg.bilateral_sigma_space)
|
| 196 |
edges = cv2.Canny(blurred, cfg.canny_low, cfg.canny_high)
|
| 197 |
edges = cv2.bitwise_and(edges, subject_mask)
|
| 198 |
|
| 199 |
+
# Step 1: draw all edges that pass the length filter
|
| 200 |
contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
| 201 |
+
raw = np.zeros_like(edges)
|
| 202 |
for cnt in contours:
|
| 203 |
+
if cv2.arcLength(cnt, False) > cfg.min_contour_perimeter:
|
| 204 |
+
cv2.drawContours(raw, [cnt], -1, 255, 1)
|
| 205 |
+
|
| 206 |
+
# Step 2: merge nearby parallel lines into single strokes
|
| 207 |
+
r = cfg.cluster_merge_radius
|
| 208 |
+
k_merge = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (r * 2 + 1, r * 2 + 1))
|
| 209 |
+
merged = cv2.morphologyEx(raw, cv2.MORPH_CLOSE, k_merge) # join close lines
|
| 210 |
+
merged = cv2.erode(merged, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (r, r)), iterations=1) # thin back
|
| 211 |
+
|
| 212 |
+
# Step 3: redraw surviving contours with final thickness for clean uniform strokes
|
| 213 |
+
contours2, _ = cv2.findContours(merged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
| 214 |
+
filtered = np.zeros_like(edges)
|
| 215 |
+
for cnt in contours2:
|
| 216 |
if cv2.arcLength(cnt, False) > cfg.min_contour_perimeter:
|
| 217 |
cv2.drawContours(filtered, [cnt], -1, 255, cfg.interior_line_thickness)
|
| 218 |
|
|
|
|
|
|
|
|
|
|
| 219 |
return filtered
|
| 220 |
|
| 221 |
|