Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -73,10 +73,9 @@ def load_beard_model():
|
|
| 73 |
beard_model = YOLO(BEARD_MODEL_PATH)
|
| 74 |
return beard_model
|
| 75 |
|
| 76 |
-
# ======================
|
| 77 |
@timed("Mustache Mask")
|
| 78 |
def get_mustache_mask(probs, orig_w, orig_h, exclude_mask):
|
| 79 |
-
# Sensitive detection for thin/sparse mustache
|
| 80 |
u_lip = (probs[11].numpy() > 0.13).astype(np.float32)
|
| 81 |
l_lip = (probs[12].numpy() > 0.13).astype(np.float32)
|
| 82 |
mouth = (probs[10].numpy() > 0.18).astype(np.float32)
|
|
@@ -84,7 +83,6 @@ def get_mustache_mask(probs, orig_w, orig_h, exclude_mask):
|
|
| 84 |
mustache = np.maximum(u_lip * 1.15, l_lip)
|
| 85 |
mustache = np.maximum(mustache, mouth * 0.45)
|
| 86 |
|
| 87 |
-
# Morphology for nice mustache shape
|
| 88 |
kernel_h = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
|
| 89 |
kernel_e = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 90 |
|
|
@@ -92,15 +90,12 @@ def get_mustache_mask(probs, orig_w, orig_h, exclude_mask):
|
|
| 92 |
mustache = cv2.morphologyEx(mustache, cv2.MORPH_CLOSE, kernel_h, iterations=2)
|
| 93 |
mustache = cv2.GaussianBlur(mustache, (7, 5), 1.2)
|
| 94 |
|
| 95 |
-
# Small downward shift
|
| 96 |
shift_y = 1
|
| 97 |
M = np.float32([[1, 0, 0], [0, 1, shift_y]])
|
| 98 |
mustache = cv2.warpAffine(mustache, M, (mustache.shape[1], mustache.shape[0]),
|
| 99 |
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
| 100 |
|
| 101 |
mustache = cv2.resize(mustache, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 102 |
-
|
| 103 |
-
# Less aggressive exclude
|
| 104 |
mustache = np.maximum(mustache - exclude_mask * 0.5, 0)
|
| 105 |
mustache = cv2.GaussianBlur(mustache, (5, 5), 1.0)
|
| 106 |
mustache = (mustache > 0.15).astype(np.float32)
|
|
@@ -151,7 +146,7 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
|
|
| 151 |
exclude = cv2.GaussianBlur(exclude, (5, 5), 1.2)
|
| 152 |
exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 153 |
|
| 154 |
-
# Lip mask
|
| 155 |
lip_mask = np.zeros((128, 128), dtype=np.float32)
|
| 156 |
lip_mask = np.maximum(lip_mask, (probs[10].numpy() > 0.42).astype(np.float32))
|
| 157 |
lip_mask = np.maximum(lip_mask, (probs[11].numpy() > 0.42).astype(np.float32))
|
|
@@ -160,7 +155,6 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
|
|
| 160 |
lip_mask = cv2.resize(lip_mask, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
|
| 161 |
lip_mask = (lip_mask > 0.5).astype(np.float32)
|
| 162 |
|
| 163 |
-
# Mustache mask
|
| 164 |
mustache = get_mustache_mask(probs, orig_w, orig_h, exclude)
|
| 165 |
|
| 166 |
return hair, exclude, mustache, lip_mask
|
|
@@ -220,7 +214,7 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_ma
|
|
| 220 |
mask[lip_mask > 0] = 0
|
| 221 |
return mask
|
| 222 |
|
| 223 |
-
# ======================
|
| 224 |
@timed("Color Transfer")
|
| 225 |
def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask: np.ndarray):
|
| 226 |
comb = np.maximum(hair_mask, beard_mask)
|
|
@@ -230,7 +224,7 @@ def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask
|
|
| 230 |
img = np.array(image).astype(np.float32) / 255.0
|
| 231 |
hsv = cv2.cvtColor((img * 255).astype(np.uint8), cv2.COLOR_RGB2HSV).astype(np.float32)
|
| 232 |
|
| 233 |
-
# Hair Grey
|
| 234 |
hsv_hair = hsv.copy()
|
| 235 |
hsv_hair[..., 1] = hsv_hair[..., 1] * (1 - 0.78 * hair_mask)
|
| 236 |
original_v = hsv[..., 2]
|
|
@@ -241,18 +235,15 @@ def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask
|
|
| 241 |
)
|
| 242 |
hair_grey = cv2.cvtColor(hsv_hair.astype(np.uint8), cv2.COLOR_HSV2RGB).astype(np.float32) / 255.0
|
| 243 |
|
| 244 |
-
# Beard + Mustache Grey
|
| 245 |
-
beard_grey = hair_grey.copy()
|
| 246 |
-
|
| 247 |
-
# Apply grey to beard + mustache
|
| 248 |
beard_mask_3ch = np.stack([beard_mask, beard_mask, beard_mask], axis=2)
|
| 249 |
-
final =
|
| 250 |
|
| 251 |
-
#
|
| 252 |
hair_mask_3ch = np.stack([hair_mask, hair_mask, hair_mask], axis=2)
|
| 253 |
final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
|
| 254 |
|
| 255 |
-
# Final
|
| 256 |
comb_3ch = np.stack([comb, comb, comb], axis=2)
|
| 257 |
final = final * comb_3ch + img * (1 - comb_3ch)
|
| 258 |
|
|
@@ -280,10 +271,10 @@ def process_face_whitening(input_image: Image.Image):
|
|
| 280 |
hair_mask, exclude_mask, mustache_mask, lip_mask = get_hair_and_exclude_masks(img_resized)
|
| 281 |
beard_mask = get_beard_mask_fast(img_resized, exclude_mask, lip_mask)
|
| 282 |
|
| 283 |
-
# Strong
|
| 284 |
beard_mask = np.maximum(beard_mask, mustache_mask * 0.98)
|
| 285 |
|
| 286 |
-
# Extra boost for thin
|
| 287 |
weak_mustache = (mustache_mask > 0.18) & (beard_mask < 0.48)
|
| 288 |
beard_mask[weak_mustache] = np.maximum(beard_mask[weak_mustache], 0.75)
|
| 289 |
|
|
|
|
| 73 |
beard_model = YOLO(BEARD_MODEL_PATH)
|
| 74 |
return beard_model
|
| 75 |
|
| 76 |
+
# ====================== MUSTACHE MASK ======================
|
| 77 |
@timed("Mustache Mask")
|
| 78 |
def get_mustache_mask(probs, orig_w, orig_h, exclude_mask):
|
|
|
|
| 79 |
u_lip = (probs[11].numpy() > 0.13).astype(np.float32)
|
| 80 |
l_lip = (probs[12].numpy() > 0.13).astype(np.float32)
|
| 81 |
mouth = (probs[10].numpy() > 0.18).astype(np.float32)
|
|
|
|
| 83 |
mustache = np.maximum(u_lip * 1.15, l_lip)
|
| 84 |
mustache = np.maximum(mustache, mouth * 0.45)
|
| 85 |
|
|
|
|
| 86 |
kernel_h = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
|
| 87 |
kernel_e = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 88 |
|
|
|
|
| 90 |
mustache = cv2.morphologyEx(mustache, cv2.MORPH_CLOSE, kernel_h, iterations=2)
|
| 91 |
mustache = cv2.GaussianBlur(mustache, (7, 5), 1.2)
|
| 92 |
|
|
|
|
| 93 |
shift_y = 1
|
| 94 |
M = np.float32([[1, 0, 0], [0, 1, shift_y]])
|
| 95 |
mustache = cv2.warpAffine(mustache, M, (mustache.shape[1], mustache.shape[0]),
|
| 96 |
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
| 97 |
|
| 98 |
mustache = cv2.resize(mustache, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
|
|
|
|
|
|
| 99 |
mustache = np.maximum(mustache - exclude_mask * 0.5, 0)
|
| 100 |
mustache = cv2.GaussianBlur(mustache, (5, 5), 1.0)
|
| 101 |
mustache = (mustache > 0.15).astype(np.float32)
|
|
|
|
| 146 |
exclude = cv2.GaussianBlur(exclude, (5, 5), 1.2)
|
| 147 |
exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 148 |
|
| 149 |
+
# Lip mask
|
| 150 |
lip_mask = np.zeros((128, 128), dtype=np.float32)
|
| 151 |
lip_mask = np.maximum(lip_mask, (probs[10].numpy() > 0.42).astype(np.float32))
|
| 152 |
lip_mask = np.maximum(lip_mask, (probs[11].numpy() > 0.42).astype(np.float32))
|
|
|
|
| 155 |
lip_mask = cv2.resize(lip_mask, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
|
| 156 |
lip_mask = (lip_mask > 0.5).astype(np.float32)
|
| 157 |
|
|
|
|
| 158 |
mustache = get_mustache_mask(probs, orig_w, orig_h, exclude)
|
| 159 |
|
| 160 |
return hair, exclude, mustache, lip_mask
|
|
|
|
| 214 |
mask[lip_mask > 0] = 0
|
| 215 |
return mask
|
| 216 |
|
| 217 |
+
# ====================== COLOR TRANSFER - BEARD SAME AS HAIR ======================
|
| 218 |
@timed("Color Transfer")
|
| 219 |
def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask: np.ndarray):
|
| 220 |
comb = np.maximum(hair_mask, beard_mask)
|
|
|
|
| 224 |
img = np.array(image).astype(np.float32) / 255.0
|
| 225 |
hsv = cv2.cvtColor((img * 255).astype(np.uint8), cv2.COLOR_RGB2HSV).astype(np.float32)
|
| 226 |
|
| 227 |
+
# === Hair Grey (Base) ===
|
| 228 |
hsv_hair = hsv.copy()
|
| 229 |
hsv_hair[..., 1] = hsv_hair[..., 1] * (1 - 0.78 * hair_mask)
|
| 230 |
original_v = hsv[..., 2]
|
|
|
|
| 235 |
)
|
| 236 |
hair_grey = cv2.cvtColor(hsv_hair.astype(np.uint8), cv2.COLOR_HSV2RGB).astype(np.float32) / 255.0
|
| 237 |
|
| 238 |
+
# === Beard + Mustache ko BILKUL SAME Hair Grey do ===
|
|
|
|
|
|
|
|
|
|
| 239 |
beard_mask_3ch = np.stack([beard_mask, beard_mask, beard_mask], axis=2)
|
| 240 |
+
final = hair_grey * beard_mask_3ch + img * (1 - beard_mask_3ch)
|
| 241 |
|
| 242 |
+
# Hair area (on top)
|
| 243 |
hair_mask_3ch = np.stack([hair_mask, hair_mask, hair_mask], axis=2)
|
| 244 |
final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
|
| 245 |
|
| 246 |
+
# Final protection
|
| 247 |
comb_3ch = np.stack([comb, comb, comb], axis=2)
|
| 248 |
final = final * comb_3ch + img * (1 - comb_3ch)
|
| 249 |
|
|
|
|
| 271 |
hair_mask, exclude_mask, mustache_mask, lip_mask = get_hair_and_exclude_masks(img_resized)
|
| 272 |
beard_mask = get_beard_mask_fast(img_resized, exclude_mask, lip_mask)
|
| 273 |
|
| 274 |
+
# Strong blending for beard + mustache
|
| 275 |
beard_mask = np.maximum(beard_mask, mustache_mask * 0.98)
|
| 276 |
|
| 277 |
+
# Extra boost for thin mustache
|
| 278 |
weak_mustache = (mustache_mask > 0.18) & (beard_mask < 0.48)
|
| 279 |
beard_mask[weak_mustache] = np.maximum(beard_mask[weak_mustache], 0.75)
|
| 280 |
|