Seniordev22 commited on
Commit
8aae84a
·
verified ·
1 Parent(s): 12a8a6d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -21
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
- return hair, exclude, mustache
 
 
 
 
 
 
 
 
 
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)) # 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
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)