Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -146,7 +146,7 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
|
|
| 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))
|
|
@@ -214,7 +214,7 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_ma
|
|
| 214 |
mask[lip_mask > 0] = 0
|
| 215 |
return mask
|
| 216 |
|
| 217 |
-
# ======================
|
| 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)
|
|
@@ -222,47 +222,41 @@ def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask
|
|
| 222 |
comb = cv2.GaussianBlur(comb, (5,5), 1.5)
|
| 223 |
|
| 224 |
img = np.array(image).astype(np.float32) / 255.0
|
| 225 |
-
|
| 226 |
-
# LAB for better color control
|
| 227 |
-
lab = cv2.cvtColor((img * 255).astype(np.uint8), cv2.COLOR_RGB2LAB).astype(np.float32)
|
| 228 |
hsv = cv2.cvtColor((img * 255).astype(np.uint8), cv2.COLOR_RGB2HSV).astype(np.float32)
|
| 229 |
|
| 230 |
-
# Hair Grey Base
|
| 231 |
hsv_hair = hsv.copy()
|
| 232 |
-
hsv_hair[..., 1] = hsv_hair[..., 1] * (1 - 0.
|
| 233 |
original_v = hsv[..., 2]
|
|
|
|
| 234 |
hsv_hair[..., 2] = np.clip(
|
| 235 |
-
original_v +
|
| 236 |
-
|
| 237 |
)
|
| 238 |
-
|
| 239 |
-
hair_grey_lab = cv2.cvtColor((hair_grey_rgb * 255).astype(np.uint8), cv2.COLOR_RGB2LAB).astype(np.float32)
|
| 240 |
|
| 241 |
-
#
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
final_lab = final_lab * (1 - beard_mask_3ch) + hair_grey_lab * beard_mask_3ch
|
| 245 |
|
| 246 |
-
#
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
final_lab[beard_area, 1] = final_lab[beard_area, 1] * 0.20 # strong red neutralization
|
| 250 |
|
| 251 |
-
#
|
| 252 |
hair_mask_3ch = np.stack([hair_mask, hair_mask, hair_mask], axis=2)
|
| 253 |
-
|
| 254 |
|
| 255 |
-
#
|
| 256 |
-
final = cv2.cvtColor(final_lab.astype(np.uint8), cv2.COLOR_LAB2RGB).astype(np.float32) / 255.0
|
| 257 |
-
|
| 258 |
-
# Final protection + cool tint
|
| 259 |
comb_3ch = np.stack([comb, comb, comb], axis=2)
|
| 260 |
final = final * comb_3ch + img * (1 - comb_3ch)
|
| 261 |
-
|
|
|
|
|
|
|
| 262 |
|
| 263 |
final = np.clip(final * 255, 0, 255).astype(np.uint8)
|
| 264 |
result = Image.fromarray(final)
|
| 265 |
-
result = result.filter(ImageFilter.UnsharpMask(radius=0.8, percent=
|
| 266 |
|
| 267 |
return result
|
| 268 |
|
|
@@ -281,7 +275,7 @@ def process_face_whitening(input_image: Image.Image):
|
|
| 281 |
hair_mask, exclude_mask, mustache_mask, lip_mask = get_hair_and_exclude_masks(img_resized)
|
| 282 |
beard_mask = get_beard_mask_fast(img_resized, exclude_mask, lip_mask)
|
| 283 |
|
| 284 |
-
# Strong blending
|
| 285 |
beard_mask = np.maximum(beard_mask, mustache_mask * 0.98)
|
| 286 |
|
| 287 |
# Extra boost for thin mustache
|
|
|
|
| 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))
|
|
|
|
| 214 |
mask[lip_mask > 0] = 0
|
| 215 |
return mask
|
| 216 |
|
| 217 |
+
# ====================== COLOR TRANSFER - BEARD SAME AS HAIR (FIXED) ======================
|
| 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)
|
|
|
|
| 222 |
comb = cv2.GaussianBlur(comb, (5,5), 1.5)
|
| 223 |
|
| 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]
|
| 231 |
+
boost_amount = 89 * hair_mask
|
| 232 |
hsv_hair[..., 2] = np.clip(
|
| 233 |
+
original_v + boost_amount - (original_v * 0.35 * hair_mask),
|
| 234 |
+
110, 210
|
| 235 |
)
|
| 236 |
+
hair_grey = cv2.cvtColor(hsv_hair.astype(np.uint8), cv2.COLOR_HSV2RGB).astype(np.float32) / 255.0
|
|
|
|
| 237 |
|
| 238 |
+
# === Create a fully grey image for beard (FIX) ===
|
| 239 |
+
gray = cv2.cvtColor((img * 255).astype(np.uint8), cv2.COLOR_RGB2GRAY)
|
| 240 |
+
gray = np.stack([gray, gray, gray], axis=2).astype(np.float32) / 255.0
|
|
|
|
| 241 |
|
| 242 |
+
# === Beard + Mustache get the grey image (no more red!) ===
|
| 243 |
+
beard_mask_3ch = np.stack([beard_mask, beard_mask, beard_mask], axis=2)
|
| 244 |
+
final = gray * beard_mask_3ch + img * (1 - beard_mask_3ch)
|
|
|
|
| 245 |
|
| 246 |
+
# === Hair area gets the hair_grey (preserves texture) ===
|
| 247 |
hair_mask_3ch = np.stack([hair_mask, hair_mask, hair_mask], axis=2)
|
| 248 |
+
final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
|
| 249 |
|
| 250 |
+
# === Final protection (avoid artifacts outside hair+beard) ===
|
|
|
|
|
|
|
|
|
|
| 251 |
comb_3ch = np.stack([comb, comb, comb], axis=2)
|
| 252 |
final = final * comb_3ch + img * (1 - comb_3ch)
|
| 253 |
+
|
| 254 |
+
# === Light cool tint for natural look ===
|
| 255 |
+
final = final + (np.array([9, 7, 5], dtype=np.float32) / 255.0 * comb[..., None] * 0.18)
|
| 256 |
|
| 257 |
final = np.clip(final * 255, 0, 255).astype(np.uint8)
|
| 258 |
result = Image.fromarray(final)
|
| 259 |
+
result = result.filter(ImageFilter.UnsharpMask(radius=0.8, percent=75, threshold=1))
|
| 260 |
|
| 261 |
return result
|
| 262 |
|
|
|
|
| 275 |
hair_mask, exclude_mask, mustache_mask, lip_mask = get_hair_and_exclude_masks(img_resized)
|
| 276 |
beard_mask = get_beard_mask_fast(img_resized, exclude_mask, lip_mask)
|
| 277 |
|
| 278 |
+
# Strong blending for beard + mustache
|
| 279 |
beard_mask = np.maximum(beard_mask, mustache_mask * 0.98)
|
| 280 |
|
| 281 |
# Extra boost for thin mustache
|