Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -107,30 +107,25 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
|
|
| 107 |
hair = hair * (1 - face_m)
|
| 108 |
hair = cv2.resize(hair, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 109 |
|
| 110 |
-
# ======================
|
| 111 |
exclude = np.zeros((128, 128), dtype=np.float32)
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
exclude = np.maximum(exclude, (probs[
|
| 115 |
-
exclude = np.maximum(exclude, (probs[
|
| 116 |
-
exclude = np.maximum(exclude, (probs[
|
| 117 |
-
|
| 118 |
-
exclude =
|
| 119 |
-
|
| 120 |
-
# Strong dilation for full coverage
|
| 121 |
-
kernel_big = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
|
| 122 |
-
exclude = cv2.dilate(exclude, kernel_big, iterations=3)
|
| 123 |
-
exclude = cv2.GaussianBlur(exclude, (9, 9), 1.8)
|
| 124 |
exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 125 |
|
| 126 |
# ====================== MUSTACHE MASK ======================
|
| 127 |
-
mustache_upper = (probs[11].numpy() > 0.
|
| 128 |
-
mustache_lower = (probs[12].numpy() > 0.
|
| 129 |
mustache = np.maximum(mustache_upper, mustache_lower)
|
| 130 |
|
| 131 |
-
mouth = (probs[10].numpy() > 0.
|
| 132 |
-
mustache = np.maximum(mustache, mouth * 0.
|
| 133 |
-
|
| 134 |
kernel_must = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
|
| 135 |
mustache = cv2.morphologyEx(mustache, cv2.MORPH_CLOSE, kernel_must, iterations=2)
|
| 136 |
mustache = cv2.GaussianBlur(mustache, (5, 5), 1.2)
|
|
@@ -150,12 +145,12 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray):
|
|
| 150 |
results = model.predict(
|
| 151 |
img_array,
|
| 152 |
device=DEVICE.type,
|
| 153 |
-
conf=0.
|
| 154 |
-
iou=0.
|
| 155 |
imgsz=128,
|
| 156 |
half=False,
|
| 157 |
verbose=False,
|
| 158 |
-
max_det=
|
| 159 |
)
|
| 160 |
|
| 161 |
mask = np.zeros((orig_h, orig_w), dtype=np.float32)
|
|
@@ -165,46 +160,44 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray):
|
|
| 165 |
if int(cls) == 0: # beard class
|
| 166 |
m = results[0].masks.data[i].cpu().numpy()
|
| 167 |
m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 168 |
-
mask = np.maximum(mask, (m > 0.
|
| 169 |
|
| 170 |
-
#
|
| 171 |
mask = np.maximum(mask - exclude_mask, 0)
|
| 172 |
|
| 173 |
-
if mask.sum() > 25:
|
| 174 |
-
# ======================
|
| 175 |
|
| 176 |
-
# Aggressive erosion to
|
| 177 |
kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
|
| 178 |
mask = cv2.erode(mask, kernel_erode, iterations=2)
|
| 179 |
|
| 180 |
-
#
|
| 181 |
-
kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (
|
| 182 |
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_close, iterations=3)
|
| 183 |
|
| 184 |
-
# Clean
|
| 185 |
kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 186 |
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_open, iterations=1)
|
| 187 |
|
| 188 |
-
# Contour
|
| 189 |
-
contours, _ = cv2.findContours((mask > 0.
|
| 190 |
-
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
| 191 |
-
|
| 192 |
if contours:
|
| 193 |
smooth_mask = np.zeros_like(mask, dtype=np.float32)
|
| 194 |
for cnt in contours:
|
| 195 |
-
if cv2.contourArea(cnt) >
|
| 196 |
-
epsilon = 0.
|
| 197 |
approx = cv2.approxPolyDP(cnt, epsilon, True)
|
| 198 |
cv2.drawContours(smooth_mask, [approx], -1, 1.0, thickness=cv2.FILLED)
|
| 199 |
mask = smooth_mask
|
| 200 |
|
| 201 |
-
#
|
| 202 |
mask = cv2.GaussianBlur(mask, (9, 9), 2.0)
|
| 203 |
|
| 204 |
-
#
|
| 205 |
-
mask = (mask > 0.
|
| 206 |
|
| 207 |
-
# Final
|
| 208 |
mask = cv2.erode(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=1)
|
| 209 |
|
| 210 |
return mask
|
|
@@ -280,8 +273,8 @@ def process_face_whitening(input_image: Image.Image):
|
|
| 280 |
hair_mask, exclude_mask, mustache_mask = get_hair_and_exclude_masks(img_resized)
|
| 281 |
beard_mask = get_beard_mask_fast(img_resized, exclude_mask)
|
| 282 |
|
| 283 |
-
# Combine
|
| 284 |
-
beard_mask = np.maximum(beard_mask, mustache_mask * 0.
|
| 285 |
|
| 286 |
final_resized = apply_strong_grey_hair(img_resized, hair_mask, beard_mask)
|
| 287 |
final_img = final_resized.resize((ow, oh), Image.LANCZOS)
|
|
|
|
| 107 |
hair = hair * (1 - face_m)
|
| 108 |
hair = cv2.resize(hair, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 109 |
|
| 110 |
+
# ====================== EXCLUDE MASK ======================
|
| 111 |
exclude = np.zeros((128, 128), dtype=np.float32)
|
| 112 |
+
exclude = np.maximum(exclude, (probs[10].numpy() > 0.35).astype(np.float32))
|
| 113 |
+
exclude = np.maximum(exclude, (probs[11].numpy() > 0.35).astype(np.float32))
|
| 114 |
+
exclude = np.maximum(exclude, (probs[12].numpy() > 0.35).astype(np.float32))
|
| 115 |
+
exclude = np.maximum(exclude, (probs[4].numpy() > 0.35).astype(np.float32))
|
| 116 |
+
exclude = np.maximum(exclude, (probs[5].numpy() > 0.35).astype(np.float32))
|
| 117 |
+
|
| 118 |
+
exclude = cv2.dilate(exclude, kernel, iterations=2)
|
| 119 |
+
exclude = cv2.GaussianBlur(exclude, (5, 5), 1.2)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 121 |
|
| 122 |
# ====================== MUSTACHE MASK ======================
|
| 123 |
+
mustache_upper = (probs[11].numpy() > 0.30).astype(np.float32)
|
| 124 |
+
mustache_lower = (probs[12].numpy() > 0.30).astype(np.float32)
|
| 125 |
mustache = np.maximum(mustache_upper, mustache_lower)
|
| 126 |
|
| 127 |
+
mouth = (probs[10].numpy() > 0.30).astype(np.float32)
|
| 128 |
+
mustache = np.maximum(mustache, mouth * 0.6)
|
|
|
|
| 129 |
kernel_must = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
|
| 130 |
mustache = cv2.morphologyEx(mustache, cv2.MORPH_CLOSE, kernel_must, iterations=2)
|
| 131 |
mustache = cv2.GaussianBlur(mustache, (5, 5), 1.2)
|
|
|
|
| 145 |
results = model.predict(
|
| 146 |
img_array,
|
| 147 |
device=DEVICE.type,
|
| 148 |
+
conf=0.20,
|
| 149 |
+
iou=0.45,
|
| 150 |
imgsz=128,
|
| 151 |
half=False,
|
| 152 |
verbose=False,
|
| 153 |
+
max_det=8
|
| 154 |
)
|
| 155 |
|
| 156 |
mask = np.zeros((orig_h, orig_w), dtype=np.float32)
|
|
|
|
| 160 |
if int(cls) == 0: # beard class
|
| 161 |
m = results[0].masks.data[i].cpu().numpy()
|
| 162 |
m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 163 |
+
mask = np.maximum(mask, (m > 0.32).astype(np.float32)) # slightly lower threshold
|
| 164 |
|
| 165 |
+
# Remove protected areas
|
| 166 |
mask = np.maximum(mask - exclude_mask, 0)
|
| 167 |
|
| 168 |
+
if mask.sum() > 25: # lowered a bit
|
| 169 |
+
# ====================== HIGHLY IMPROVED ROUNDING ======================
|
| 170 |
|
| 171 |
+
# 1. Aggressive erosion to kill extra space
|
| 172 |
kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
|
| 173 |
mask = cv2.erode(mask, kernel_erode, iterations=2)
|
| 174 |
|
| 175 |
+
# 2. Strong close with very big kernel for perfect round corners
|
| 176 |
+
kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13))
|
| 177 |
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_close, iterations=3)
|
| 178 |
|
| 179 |
+
# 3. Clean noise
|
| 180 |
kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 181 |
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_open, iterations=1)
|
| 182 |
|
| 183 |
+
# 4. Contour based smoothing (Best for round & tight beard shape)
|
| 184 |
+
contours, _ = cv2.findContours((mask > 0.1).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
|
|
|
|
|
|
| 185 |
if contours:
|
| 186 |
smooth_mask = np.zeros_like(mask, dtype=np.float32)
|
| 187 |
for cnt in contours:
|
| 188 |
+
if cv2.contourArea(cnt) > 50: # ignore very small noise
|
| 189 |
+
epsilon = 0.008 * cv2.arcLength(cnt, True) # lower = more round
|
| 190 |
approx = cv2.approxPolyDP(cnt, epsilon, True)
|
| 191 |
cv2.drawContours(smooth_mask, [approx], -1, 1.0, thickness=cv2.FILLED)
|
| 192 |
mask = smooth_mask
|
| 193 |
|
| 194 |
+
# 5. Final soft blur
|
| 195 |
mask = cv2.GaussianBlur(mask, (9, 9), 2.0)
|
| 196 |
|
| 197 |
+
# 6. Very tight final threshold (no extra space)
|
| 198 |
+
mask = (mask > 0.28).astype(np.float32)
|
| 199 |
|
| 200 |
+
# 7. Final light erosion for extra safety
|
| 201 |
mask = cv2.erode(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=1)
|
| 202 |
|
| 203 |
return mask
|
|
|
|
| 273 |
hair_mask, exclude_mask, mustache_mask = get_hair_and_exclude_masks(img_resized)
|
| 274 |
beard_mask = get_beard_mask_fast(img_resized, exclude_mask)
|
| 275 |
|
| 276 |
+
# Combine with mustache
|
| 277 |
+
beard_mask = np.maximum(beard_mask, mustache_mask * 0.88)
|
| 278 |
|
| 279 |
final_resized = apply_strong_grey_hair(img_resized, hair_mask, beard_mask)
|
| 280 |
final_img = final_resized.resize((ow, oh), Image.LANCZOS)
|