update sketch input
Browse files
app.py
CHANGED
|
@@ -125,6 +125,92 @@ def apply_parsing_white_mask_to_person_cv2(
|
|
| 125 |
result_bgr = cv2.cvtColor(result_rgb, cv2.COLOR_RGB2BGR)
|
| 126 |
return result_bgr
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
def compute_hw_from_person(person_path: str):
|
| 130 |
img = _imread_or_raise(person_path)
|
|
@@ -402,6 +488,15 @@ def run_one(paths: Paths, prompt: str, steps: int = DEFAULT_STEPS):
|
|
| 402 |
parsing_img = res["images"][0] if res.get("images") else None
|
| 403 |
if parsing_img is None:
|
| 404 |
raise RuntimeError("run_simple_extractor returned no parsing images.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
# -------------------------------------------------
|
| 407 |
# ✅ (2) UI sketch 업로드는 optional
|
|
|
|
| 125 |
result_bgr = cv2.cvtColor(result_rgb, cv2.COLOR_RGB2BGR)
|
| 126 |
return result_bgr
|
| 127 |
|
| 128 |
+
from typing import Optional, Tuple
|
| 129 |
+
import numpy as np
|
| 130 |
+
from PIL import Image
|
| 131 |
+
|
| 132 |
+
def clean_and_smooth_parsing_mask(
|
| 133 |
+
parsing_img: Image.Image,
|
| 134 |
+
*,
|
| 135 |
+
white_threshold: int = 128,
|
| 136 |
+
min_white_area: int = 300,
|
| 137 |
+
close_ksize: int = 7,
|
| 138 |
+
open_ksize: int = 3,
|
| 139 |
+
morph_iters: int = 1,
|
| 140 |
+
blur_ksize: int = 0,
|
| 141 |
+
) -> Image.Image:
|
| 142 |
+
"""
|
| 143 |
+
Clean small white blobs and smooth boundaries on a grayscale (0/255) PIL mask.
|
| 144 |
+
|
| 145 |
+
Args:
|
| 146 |
+
parsing_img: PIL.Image in 'L' mode recommended. White=foreground, Black=background.
|
| 147 |
+
white_threshold: threshold to binarize. >= threshold -> white(255), else black(0).
|
| 148 |
+
min_white_area: remove connected white components smaller than this pixel area.
|
| 149 |
+
close_ksize: kernel size for morphological closing (fill small holes, smooth edges).
|
| 150 |
+
open_ksize: kernel size for morphological opening (remove small spikes/noise).
|
| 151 |
+
morph_iters: number of iterations for close/open.
|
| 152 |
+
blur_ksize: optional gaussian blur kernel size (odd number, e.g. 7). 0 disables blur.
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
A PIL.Image (mode 'L') cleaned + smoothed (values 0 or 255).
|
| 156 |
+
"""
|
| 157 |
+
if not isinstance(parsing_img, Image.Image):
|
| 158 |
+
raise TypeError("parsing_img must be a PIL.Image.Image")
|
| 159 |
+
|
| 160 |
+
# Convert to grayscale
|
| 161 |
+
img_l = parsing_img.convert("L")
|
| 162 |
+
arr = np.array(img_l, dtype=np.uint8)
|
| 163 |
+
|
| 164 |
+
# Binarize -> 0/255
|
| 165 |
+
mask = np.where(arr >= white_threshold, 255, 0).astype(np.uint8)
|
| 166 |
+
|
| 167 |
+
# --- connected components: remove small white regions ---
|
| 168 |
+
try:
|
| 169 |
+
import cv2
|
| 170 |
+
except ImportError as e:
|
| 171 |
+
raise ImportError(
|
| 172 |
+
"This function requires opencv-python (cv2). Install with: pip install opencv-python"
|
| 173 |
+
) from e
|
| 174 |
+
|
| 175 |
+
# Connected components on binary mask
|
| 176 |
+
# Note: cv2.connectedComponentsWithStats expects 0/255 (or 0/1), uint8.
|
| 177 |
+
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)
|
| 178 |
+
|
| 179 |
+
# Keep only components with area >= min_white_area (label 0 is background)
|
| 180 |
+
keep = np.zeros_like(mask)
|
| 181 |
+
for lab in range(1, num_labels):
|
| 182 |
+
area = int(stats[lab, cv2.CC_STAT_AREA])
|
| 183 |
+
if area >= min_white_area:
|
| 184 |
+
keep[labels == lab] = 255
|
| 185 |
+
|
| 186 |
+
mask = keep
|
| 187 |
+
|
| 188 |
+
# --- boundary smoothing via morphology ---
|
| 189 |
+
def _odd_or_one(k: int) -> int:
|
| 190 |
+
k = int(k)
|
| 191 |
+
if k <= 1:
|
| 192 |
+
return 1
|
| 193 |
+
return k if (k % 2 == 1) else (k + 1)
|
| 194 |
+
|
| 195 |
+
close_k = _odd_or_one(close_ksize)
|
| 196 |
+
open_k = _odd_or_one(open_ksize)
|
| 197 |
+
|
| 198 |
+
if close_k > 1:
|
| 199 |
+
k_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (close_k, close_k))
|
| 200 |
+
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, k_close, iterations=int(morph_iters))
|
| 201 |
+
|
| 202 |
+
if open_k > 1:
|
| 203 |
+
k_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (open_k, open_k))
|
| 204 |
+
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, k_open, iterations=int(morph_iters))
|
| 205 |
+
|
| 206 |
+
# --- optional blur for extra smooth boundary (then re-threshold) ---
|
| 207 |
+
if blur_ksize and int(blur_ksize) > 1:
|
| 208 |
+
b = _odd_or_one(int(blur_ksize))
|
| 209 |
+
mask_blur = cv2.GaussianBlur(mask, (b, b), 0)
|
| 210 |
+
mask = np.where(mask_blur >= 128, 255, 0).astype(np.uint8)
|
| 211 |
+
|
| 212 |
+
return Image.fromarray(mask, mode="L")
|
| 213 |
+
|
| 214 |
|
| 215 |
def compute_hw_from_person(person_path: str):
|
| 216 |
img = _imread_or_raise(person_path)
|
|
|
|
| 488 |
parsing_img = res["images"][0] if res.get("images") else None
|
| 489 |
if parsing_img is None:
|
| 490 |
raise RuntimeError("run_simple_extractor returned no parsing images.")
|
| 491 |
+
|
| 492 |
+
parsing_img = clean_and_smooth_parsing_mask(
|
| 493 |
+
parsing_img,
|
| 494 |
+
min_white_area=300, # 작은 흰색 덩어리 제거 강도
|
| 495 |
+
close_ksize=9, # 경계 매끈 + 작은 구멍 메움
|
| 496 |
+
open_ksize=3, # 잔 노이즈 제거
|
| 497 |
+
morph_iters=1,
|
| 498 |
+
blur_ksize=7, # 더 부드럽게 (원치 않으면 0)
|
| 499 |
+
)
|
| 500 |
|
| 501 |
# -------------------------------------------------
|
| 502 |
# ✅ (2) UI sketch 업로드는 optional
|