Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -106,10 +106,19 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
|
|
| 106 |
|
| 107 |
hair = cv2.resize(hair, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 108 |
|
|
|
|
| 109 |
exclude = np.zeros((128, 128), dtype=np.float32)
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
exclude = np.maximum(exclude, (probs[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
|
| 114 |
exclude = cv2.dilate(exclude, kernel, iterations=1)
|
| 115 |
|
|
@@ -122,11 +131,12 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray):
|
|
| 122 |
img_small = pil_image.resize((128, 128), Image.BILINEAR)
|
| 123 |
img_array = np.array(img_small)
|
| 124 |
|
|
|
|
| 125 |
results = model.predict(
|
| 126 |
img_array,
|
| 127 |
device=DEVICE.type,
|
| 128 |
-
conf=0.
|
| 129 |
-
iou=0.
|
| 130 |
imgsz=128,
|
| 131 |
half=(DEVICE.type == "cuda"),
|
| 132 |
verbose=False,
|
|
@@ -136,21 +146,25 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray):
|
|
| 136 |
mask = np.zeros((orig_h, orig_w), dtype=np.float32)
|
| 137 |
if results[0].masks is not None:
|
| 138 |
for i, cls in enumerate(results[0].boxes.cls):
|
| 139 |
-
if int(cls) == 0:
|
| 140 |
m = results[0].masks.data[i].cpu().numpy()
|
| 141 |
m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 142 |
-
|
|
|
|
| 143 |
|
|
|
|
| 144 |
mask = np.maximum(mask - exclude_mask, 0)
|
| 145 |
|
| 146 |
if mask.sum() > 30:
|
| 147 |
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
|
|
|
|
|
|
|
| 148 |
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
|
| 149 |
mask = cv2.GaussianBlur(mask, (3,3), 0.8)
|
| 150 |
|
| 151 |
return mask
|
| 152 |
|
| 153 |
-
# ======================
|
| 154 |
@timed("Color Transfer")
|
| 155 |
def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask: np.ndarray):
|
| 156 |
comb = np.maximum(hair_mask, beard_mask)
|
|
@@ -167,12 +181,12 @@ def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask
|
|
| 167 |
# Desaturation (grey effect)
|
| 168 |
hsv_hair[..., 1] = hsv_hair[..., 1] * (1 - 0.78 * hair_mask)
|
| 169 |
|
| 170 |
-
#
|
| 171 |
original_v = hsv[..., 2]
|
| 172 |
-
boost_amount = 89 * hair_mask
|
| 173 |
hsv_hair[..., 2] = np.clip(
|
| 174 |
original_v + boost_amount - (original_v * 0.35 * hair_mask),
|
| 175 |
-
110, 210
|
| 176 |
)
|
| 177 |
|
| 178 |
hair_grey = cv2.cvtColor(hsv_hair.astype(np.uint8), cv2.COLOR_HSV2RGB).astype(np.float32) / 255.0
|
|
@@ -205,7 +219,7 @@ def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask
|
|
| 205 |
final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
|
| 206 |
final = final * comb_3ch + img * (1 - comb_3ch)
|
| 207 |
|
| 208 |
-
# Soft cool tint
|
| 209 |
final = final + (np.array([9, 7, 5], dtype=np.float32)/255.0 * comb[..., None] * 0.18)
|
| 210 |
|
| 211 |
final = np.clip(final * 255, 0, 255).astype(np.uint8)
|
|
|
|
| 106 |
|
| 107 |
hair = cv2.resize(hair, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 108 |
|
| 109 |
+
# ====================== IMPROVED EXCLUDE MASK (LIPS + EYES) ======================
|
| 110 |
exclude = np.zeros((128, 128), dtype=np.float32)
|
| 111 |
+
# Mouth / lips classes (common indices from CelebAMask-HQ via Segformer)
|
| 112 |
+
# 10 = mouth, 11 = upper lip, 12 = lower lip
|
| 113 |
+
exclude = np.maximum(exclude, (probs[10] > 0.4).cpu().numpy().astype(np.float32))
|
| 114 |
+
exclude = np.maximum(exclude, (probs[11] > 0.4).cpu().numpy().astype(np.float32))
|
| 115 |
+
exclude = np.maximum(exclude, (probs[12] > 0.4).cpu().numpy().astype(np.float32))
|
| 116 |
+
# Eyes protection (optional but safe)
|
| 117 |
+
exclude = np.maximum(exclude, (probs[4] > 0.4).cpu().numpy().astype(np.float32)) # left eye
|
| 118 |
+
exclude = np.maximum(exclude, (probs[5] > 0.4).cpu().numpy().astype(np.float32)) # right eye
|
| 119 |
+
# Original nose (class 2) – keep as is (beard can overlap)
|
| 120 |
+
# But we already have exclude from lips/eyes only.
|
| 121 |
+
|
| 122 |
exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
|
| 123 |
exclude = cv2.dilate(exclude, kernel, iterations=1)
|
| 124 |
|
|
|
|
| 131 |
img_small = pil_image.resize((128, 128), Image.BILINEAR)
|
| 132 |
img_array = np.array(img_small)
|
| 133 |
|
| 134 |
+
# ====================== LOWER CONFIDENCE FOR MORE DETECTION ======================
|
| 135 |
results = model.predict(
|
| 136 |
img_array,
|
| 137 |
device=DEVICE.type,
|
| 138 |
+
conf=0.20, # was 0.28 – catch more beard
|
| 139 |
+
iou=0.45, # was 0.50
|
| 140 |
imgsz=128,
|
| 141 |
half=(DEVICE.type == "cuda"),
|
| 142 |
verbose=False,
|
|
|
|
| 146 |
mask = np.zeros((orig_h, orig_w), dtype=np.float32)
|
| 147 |
if results[0].masks is not None:
|
| 148 |
for i, cls in enumerate(results[0].boxes.cls):
|
| 149 |
+
if int(cls) == 0: # beard class
|
| 150 |
m = results[0].masks.data[i].cpu().numpy()
|
| 151 |
m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 152 |
+
# ====================== LOWER MASK THRESHOLD ======================
|
| 153 |
+
mask = np.maximum(mask, (m > 0.35).astype(np.float32)) # was 0.42
|
| 154 |
|
| 155 |
+
# Remove areas that should not change (lips, eyes)
|
| 156 |
mask = np.maximum(mask - exclude_mask, 0)
|
| 157 |
|
| 158 |
if mask.sum() > 30:
|
| 159 |
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
|
| 160 |
+
# ====================== ADD DILATION TO COVER HAIRS ======================
|
| 161 |
+
mask = cv2.dilate(mask, kernel, iterations=1) # NEW: expand beard coverage
|
| 162 |
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
|
| 163 |
mask = cv2.GaussianBlur(mask, (3,3), 0.8)
|
| 164 |
|
| 165 |
return mask
|
| 166 |
|
| 167 |
+
# ====================== COLOR TRANSFER (Unchanged, Balanced) ======================
|
| 168 |
@timed("Color Transfer")
|
| 169 |
def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask: np.ndarray):
|
| 170 |
comb = np.maximum(hair_mask, beard_mask)
|
|
|
|
| 181 |
# Desaturation (grey effect)
|
| 182 |
hsv_hair[..., 1] = hsv_hair[..., 1] * (1 - 0.78 * hair_mask)
|
| 183 |
|
| 184 |
+
# Balanced Brightness
|
| 185 |
original_v = hsv[..., 2]
|
| 186 |
+
boost_amount = 89 * hair_mask
|
| 187 |
hsv_hair[..., 2] = np.clip(
|
| 188 |
original_v + boost_amount - (original_v * 0.35 * hair_mask),
|
| 189 |
+
110, 210
|
| 190 |
)
|
| 191 |
|
| 192 |
hair_grey = cv2.cvtColor(hsv_hair.astype(np.uint8), cv2.COLOR_HSV2RGB).astype(np.float32) / 255.0
|
|
|
|
| 219 |
final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
|
| 220 |
final = final * comb_3ch + img * (1 - comb_3ch)
|
| 221 |
|
| 222 |
+
# Soft cool tint
|
| 223 |
final = final + (np.array([9, 7, 5], dtype=np.float32)/255.0 * comb[..., None] * 0.18)
|
| 224 |
|
| 225 |
final = np.clip(final * 255, 0, 255).astype(np.uint8)
|