Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -74,7 +74,7 @@ def load_beard_model():
|
|
| 74 |
return beard_model
|
| 75 |
|
| 76 |
# ====================== MASKS ======================
|
| 77 |
-
@timed("Hair + Exclude Mask")
|
| 78 |
def get_hair_and_exclude_masks(pil_image: Image.Image):
|
| 79 |
load_face_parser()
|
| 80 |
orig_w, orig_h = pil_image.size
|
|
@@ -132,11 +132,20 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
|
|
| 132 |
mustache = cv2.resize(mustache, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 133 |
mustache = np.maximum(mustache - exclude, 0)
|
| 134 |
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
|
| 138 |
@timed("Beard Mask")
|
| 139 |
-
def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray):
|
| 140 |
model = load_beard_model()
|
| 141 |
orig_w, orig_h = pil_image.size
|
| 142 |
img_small = pil_image.resize((128, 128), Image.BILINEAR)
|
|
@@ -160,46 +169,43 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray):
|
|
| 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))
|
| 164 |
|
| 165 |
# Remove protected areas
|
| 166 |
mask = np.maximum(mask - exclude_mask, 0)
|
| 167 |
|
| 168 |
-
if mask.sum() > 25:
|
| 169 |
-
#
|
| 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 |
-
#
|
| 176 |
kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13))
|
| 177 |
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_close, iterations=3)
|
| 178 |
|
| 179 |
-
#
|
| 180 |
kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 181 |
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_open, iterations=1)
|
| 182 |
|
| 183 |
-
#
|
| 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:
|
| 189 |
-
epsilon = 0.008 * cv2.arcLength(cnt, True)
|
| 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
|
| 204 |
|
| 205 |
|
|
@@ -270,11 +276,13 @@ def process_face_whitening(input_image: Image.Image):
|
|
| 270 |
target -= 1
|
| 271 |
img_resized = orig.resize((target, target), Image.BILINEAR)
|
| 272 |
|
| 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)
|
|
|
|
| 74 |
return beard_model
|
| 75 |
|
| 76 |
# ====================== MASKS ======================
|
| 77 |
+
@timed("Hair + Exclude + Lip Mask")
|
| 78 |
def get_hair_and_exclude_masks(pil_image: Image.Image):
|
| 79 |
load_face_parser()
|
| 80 |
orig_w, orig_h = pil_image.size
|
|
|
|
| 132 |
mustache = cv2.resize(mustache, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 133 |
mustache = np.maximum(mustache - exclude, 0)
|
| 134 |
|
| 135 |
+
# ====================== LIP MASK (HARD PROTECTION) ======================
|
| 136 |
+
lip_mask = np.zeros((128, 128), dtype=np.float32)
|
| 137 |
+
lip_mask = np.maximum(lip_mask, (probs[10].numpy() > 0.35).astype(np.float32)) # mouth
|
| 138 |
+
lip_mask = np.maximum(lip_mask, (probs[11].numpy() > 0.35).astype(np.float32)) # upper lip
|
| 139 |
+
lip_mask = np.maximum(lip_mask, (probs[12].numpy() > 0.35).astype(np.float32)) # lower lip
|
| 140 |
+
lip_mask = cv2.dilate(lip_mask, kernel, iterations=2) # expand slightly to cover edges
|
| 141 |
+
lip_mask = cv2.resize(lip_mask, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST) # keep binary
|
| 142 |
+
lip_mask = (lip_mask > 0.5).astype(np.float32)
|
| 143 |
+
|
| 144 |
+
return hair, exclude, mustache, lip_mask
|
| 145 |
|
| 146 |
|
| 147 |
@timed("Beard Mask")
|
| 148 |
+
def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_mask: np.ndarray):
|
| 149 |
model = load_beard_model()
|
| 150 |
orig_w, orig_h = pil_image.size
|
| 151 |
img_small = pil_image.resize((128, 128), Image.BILINEAR)
|
|
|
|
| 169 |
if int(cls) == 0: # beard class
|
| 170 |
m = results[0].masks.data[i].cpu().numpy()
|
| 171 |
m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 172 |
+
mask = np.maximum(mask, (m > 0.32).astype(np.float32))
|
| 173 |
|
| 174 |
# Remove protected areas
|
| 175 |
mask = np.maximum(mask - exclude_mask, 0)
|
| 176 |
|
| 177 |
+
if mask.sum() > 25:
|
| 178 |
+
# Aggressive erosion
|
|
|
|
|
|
|
| 179 |
kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
|
| 180 |
mask = cv2.erode(mask, kernel_erode, iterations=2)
|
| 181 |
|
| 182 |
+
# Strong close
|
| 183 |
kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13))
|
| 184 |
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_close, iterations=3)
|
| 185 |
|
| 186 |
+
# Clean noise
|
| 187 |
kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 188 |
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_open, iterations=1)
|
| 189 |
|
| 190 |
+
# Contour smoothing
|
| 191 |
contours, _ = cv2.findContours((mask > 0.1).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
| 192 |
if contours:
|
| 193 |
smooth_mask = np.zeros_like(mask, dtype=np.float32)
|
| 194 |
for cnt in contours:
|
| 195 |
+
if cv2.contourArea(cnt) > 50:
|
| 196 |
+
epsilon = 0.008 * cv2.arcLength(cnt, True)
|
| 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 |
mask = cv2.GaussianBlur(mask, (9, 9), 2.0)
|
|
|
|
|
|
|
| 202 |
mask = (mask > 0.28).astype(np.float32)
|
|
|
|
|
|
|
| 203 |
mask = cv2.erode(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=1)
|
| 204 |
|
| 205 |
+
# ====================== FINAL LIP PROTECTION ======================
|
| 206 |
+
# Force lips to zero after all processing
|
| 207 |
+
mask[lip_mask > 0] = 0
|
| 208 |
+
|
| 209 |
return mask
|
| 210 |
|
| 211 |
|
|
|
|
| 276 |
target -= 1
|
| 277 |
img_resized = orig.resize((target, target), Image.BILINEAR)
|
| 278 |
|
| 279 |
+
hair_mask, exclude_mask, mustache_mask, lip_mask = get_hair_and_exclude_masks(img_resized)
|
| 280 |
+
beard_mask = get_beard_mask_fast(img_resized, exclude_mask, lip_mask)
|
| 281 |
|
| 282 |
+
# Combine with mustache, but lips already zeroed in beard_mask
|
| 283 |
beard_mask = np.maximum(beard_mask, mustache_mask * 0.88)
|
| 284 |
+
# Reapply lip protection in case mustache added lip pixels
|
| 285 |
+
beard_mask[lip_mask > 0] = 0
|
| 286 |
|
| 287 |
final_resized = apply_strong_grey_hair(img_resized, hair_mask, beard_mask)
|
| 288 |
final_img = final_resized.resize((ow, oh), Image.LANCZOS)
|