Seniordev22 commited on
Commit
e5bf4cd
·
verified ·
1 Parent(s): 86fdde4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -45
app.py CHANGED
@@ -73,13 +73,46 @@ def load_beard_model():
73
  beard_model = YOLO(BEARD_MODEL_PATH)
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
81
- img_small = pil_image.resize((128, 128), Image.BILINEAR)
82
 
 
83
  inputs = face_processor(images=img_small, return_tensors="pt").to(DEVICE)
84
 
85
  with torch.inference_mode():
@@ -92,11 +125,10 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
92
  hair = (probs[13].numpy() > 0.035).astype(np.float32)
93
  hair = cv2.GaussianBlur(hair, (3, 3), 1.0)
94
 
95
- # Face mask
96
  parsing = up.argmax(dim=1).squeeze(0).cpu().numpy()
97
  face_cls = list(range(1,6)) + list(range(8,13)) + [17,18]
98
  face_m = np.isin(parsing, face_cls).astype(np.float32)
99
-
100
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
101
  face_m = cv2.dilate(face_m, kernel, iterations=1)
102
 
@@ -105,6 +137,7 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
105
  forehead[:int(h * 0.32)] = 1.0
106
  face_m = face_m * (1 - forehead * 0.45)
107
  hair = hair * (1 - face_m)
 
108
  hair = cv2.resize(hair, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
109
 
110
  # ====================== EXCLUDE MASK ======================
@@ -114,56 +147,37 @@ def get_hair_and_exclude_masks(pil_image: Image.Image):
114
  exclude = np.maximum(exclude, (probs[12].numpy() > 0.35).astype(np.float32))
115
  exclude = np.maximum(exclude, (probs[4].numpy() > 0.35).astype(np.float32))
116
  exclude = np.maximum(exclude, (probs[5].numpy() > 0.35).astype(np.float32))
117
-
118
  exclude = cv2.dilate(exclude, kernel, iterations=2)
119
  exclude = cv2.GaussianBlur(exclude, (5, 5), 1.2)
120
  exclude = cv2.resize(exclude, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
121
 
122
- # ====================== MUSTACHE MASK (IMPROVED + SHIFTED DOWN) ======================
123
- mustache_upper = (probs[11].numpy() > 0.18).astype(np.float32)
124
- mustache_lower = (probs[12].numpy() > 0.18).astype(np.float32)
125
- mouth = (probs[10].numpy() > 0.18).astype(np.float32)
126
-
127
- mustache = np.maximum(mustache_upper, mustache_lower)
128
- mustache = np.maximum(mustache, mouth)
129
-
130
- kernel_must = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
131
- mustache = cv2.morphologyEx(mustache, cv2.MORPH_CLOSE, kernel_must, iterations=3)
132
- mustache = cv2.dilate(mustache, kernel_must, iterations=2)
133
- mustache = cv2.morphologyEx(mustache, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)), iterations=1)
134
- mustache = cv2.GaussianBlur(mustache, (5, 5), 1.2)
135
-
136
- # --- Shift mustache mask downward by 3 pixels (at 128x128 scale) ---
137
- shift_y = 3 # small downward shift
138
- M = np.float32([[1, 0, 0], [0, 1, shift_y]])
139
- mustache = cv2.warpAffine(mustache, M, (mustache.shape[1], mustache.shape[0]), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0)
140
-
141
- mustache = cv2.resize(mustache, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
142
- mustache = np.maximum(mustache - exclude, 0)
143
-
144
- # ====================== LIP MASK (HARD PROTECTION) ======================
145
  lip_mask = np.zeros((128, 128), dtype=np.float32)
146
- lip_mask = np.maximum(lip_mask, (probs[10].numpy() > 0.35).astype(np.float32))
147
- lip_mask = np.maximum(lip_mask, (probs[11].numpy() > 0.35).astype(np.float32))
148
- lip_mask = np.maximum(lip_mask, (probs[12].numpy() > 0.35).astype(np.float32))
149
- lip_mask = cv2.dilate(lip_mask, kernel, iterations=2)
150
  lip_mask = cv2.resize(lip_mask, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
151
  lip_mask = (lip_mask > 0.5).astype(np.float32)
152
 
153
- return hair, exclude, mustache, lip_mask
 
154
 
 
155
 
 
156
  @timed("Beard Mask")
157
  def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_mask: np.ndarray):
158
  model = load_beard_model()
159
  orig_w, orig_h = pil_image.size
 
160
  img_small = pil_image.resize((128, 128), Image.BILINEAR)
161
  img_array = np.array(img_small)
162
 
163
  results = model.predict(
164
  img_array,
165
  device=DEVICE.type,
166
- conf=0.20,
167
  iou=0.45,
168
  imgsz=128,
169
  half=False,
@@ -172,15 +186,14 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_ma
172
  )
173
 
174
  mask = np.zeros((orig_h, orig_w), dtype=np.float32)
175
-
176
  if results[0].masks is not None:
177
  for i, cls in enumerate(results[0].boxes.cls):
178
  if int(cls) == 0: # beard class
179
  m = results[0].masks.data[i].cpu().numpy()
180
  m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
181
- mask = np.maximum(mask, (m > 0.32).astype(np.float32))
182
 
183
- mask = np.maximum(mask - exclude_mask, 0)
184
 
185
  if mask.sum() > 25:
186
  kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
@@ -209,7 +222,6 @@ def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_ma
209
  mask[lip_mask > 0] = 0
210
  return mask
211
 
212
-
213
  # ====================== COLOR TRANSFER ======================
214
  @timed("Color Transfer")
215
  def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask: np.ndarray):
@@ -259,27 +271,34 @@ def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask
259
  final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
260
  final = final * comb_3ch + img * (1 - comb_3ch)
261
  final = final + (np.array([9, 7, 5], dtype=np.float32)/255.0 * comb[..., None] * 0.18)
262
- final = np.clip(final * 255, 0, 255).astype(np.uint8)
263
 
 
264
  result = Image.fromarray(final)
265
  result = result.filter(ImageFilter.UnsharpMask(radius=0.7, percent=70, threshold=2))
266
  return result
267
 
268
-
269
  # ====================== MAIN PROCESSING ======================
270
  @timed("Total Processing")
271
  def process_face_whitening(input_image: Image.Image):
272
  orig = input_image.convert("RGB")
273
  ow, oh = orig.size
 
274
  target = min(SAFE_IMG_SIZE, max(ow, oh))
275
  if target % 2 != 0:
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
- beard_mask = np.maximum(beard_mask, mustache_mask * 0.88)
 
 
 
 
 
 
283
  beard_mask[lip_mask > 0] = 0
284
 
285
  final_resized = apply_strong_grey_hair(img_resized, hair_mask, beard_mask)
@@ -291,7 +310,6 @@ def process_face_whitening(input_image: Image.Image):
291
 
292
  return final_img
293
 
294
-
295
  # ====================== FASTAPI ======================
296
  app = FastAPI()
297
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
@@ -307,7 +325,6 @@ async def startup():
307
  _ = process_face_whitening(dummy)
308
  logger.info("✅ Server Ready!")
309
 
310
-
311
  @app.post("/age-face")
312
  async def age_face(file: UploadFile = File(...)):
313
  start_total = time.perf_counter()
@@ -326,7 +343,6 @@ async def age_face(file: UploadFile = File(...)):
326
 
327
  return StreamingResponse(buf, media_type="image/jpeg")
328
 
329
-
330
  if __name__ == "__main__":
331
  import uvicorn
332
  uvicorn.run(app, host="0.0.0.0", port=7860, workers=1)
 
73
  beard_model = YOLO(BEARD_MODEL_PATH)
74
  return beard_model
75
 
76
+ # ====================== IMPROVED MUSTACHE MASK ======================
77
+ @timed("Mustache Mask")
78
+ def get_mustache_mask(probs, orig_w, orig_h, exclude_mask):
79
+ # More sensitive for thin, light or sparse mustache
80
+ u_lip = (probs[11].numpy() > 0.16).astype(np.float32)
81
+ l_lip = (probs[12].numpy() > 0.16).astype(np.float32)
82
+ mouth = (probs[10].numpy() > 0.20).astype(np.float32)
83
+
84
+ mustache = np.maximum(u_lip, l_lip)
85
+ mustache = np.maximum(mustache, mouth * 0.55)
86
+
87
+ # Better morphology for mustache shape (horizontal bias)
88
+ kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
89
+ kernel_horizontal = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 3))
90
+
91
+ mustache = cv2.dilate(mustache, kernel_small, iterations=1)
92
+ mustache = cv2.morphologyEx(mustache, cv2.MORPH_CLOSE, kernel_horizontal, iterations=2)
93
+ mustache = cv2.GaussianBlur(mustache, (5, 5), 1.0)
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.65, 0)
105
+ mustache = (mustache > 0.20).astype(np.float32)
106
+
107
+ return mustache
108
+
109
+ # ====================== HAIR + EXCLUDE + LIP MASK ======================
110
  @timed("Hair + Exclude + Lip Mask")
111
  def get_hair_and_exclude_masks(pil_image: Image.Image):
112
  load_face_parser()
113
  orig_w, orig_h = pil_image.size
 
114
 
115
+ img_small = pil_image.resize((128, 128), Image.BILINEAR)
116
  inputs = face_processor(images=img_small, return_tensors="pt").to(DEVICE)
117
 
118
  with torch.inference_mode():
 
125
  hair = (probs[13].numpy() > 0.035).astype(np.float32)
126
  hair = cv2.GaussianBlur(hair, (3, 3), 1.0)
127
 
128
+ # Face mask (exclude forehead & other areas)
129
  parsing = up.argmax(dim=1).squeeze(0).cpu().numpy()
130
  face_cls = list(range(1,6)) + list(range(8,13)) + [17,18]
131
  face_m = np.isin(parsing, face_cls).astype(np.float32)
 
132
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
133
  face_m = cv2.dilate(face_m, kernel, iterations=1)
134
 
 
137
  forehead[:int(h * 0.32)] = 1.0
138
  face_m = face_m * (1 - forehead * 0.45)
139
  hair = hair * (1 - face_m)
140
+
141
  hair = cv2.resize(hair, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
142
 
143
  # ====================== EXCLUDE MASK ======================
 
147
  exclude = np.maximum(exclude, (probs[12].numpy() > 0.35).astype(np.float32))
148
  exclude = np.maximum(exclude, (probs[4].numpy() > 0.35).astype(np.float32))
149
  exclude = np.maximum(exclude, (probs[5].numpy() > 0.35).astype(np.float32))
 
150
  exclude = cv2.dilate(exclude, kernel, iterations=2)
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 (Hard Protection) ======================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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))
158
+ lip_mask = np.maximum(lip_mask, (probs[12].numpy() > 0.42).astype(np.float32))
159
+ lip_mask = cv2.dilate(lip_mask, kernel, iterations=1)
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
+ # Get improved mustache
164
+ mustache = get_mustache_mask(probs, orig_w, orig_h, exclude)
165
 
166
+ return hair, exclude, mustache, lip_mask
167
 
168
+ # ====================== BEARD MASK ======================
169
  @timed("Beard Mask")
170
  def get_beard_mask_fast(pil_image: Image.Image, exclude_mask: np.ndarray, lip_mask: np.ndarray):
171
  model = load_beard_model()
172
  orig_w, orig_h = pil_image.size
173
+
174
  img_small = pil_image.resize((128, 128), Image.BILINEAR)
175
  img_array = np.array(img_small)
176
 
177
  results = model.predict(
178
  img_array,
179
  device=DEVICE.type,
180
+ conf=0.18, # thoda lower for better mustache detection
181
  iou=0.45,
182
  imgsz=128,
183
  half=False,
 
186
  )
187
 
188
  mask = np.zeros((orig_h, orig_w), dtype=np.float32)
 
189
  if results[0].masks is not None:
190
  for i, cls in enumerate(results[0].boxes.cls):
191
  if int(cls) == 0: # beard class
192
  m = results[0].masks.data[i].cpu().numpy()
193
  m = cv2.resize(m, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
194
+ mask = np.maximum(mask, (m > 0.26).astype(np.float32)) # lower threshold
195
 
196
+ mask = np.maximum(mask - exclude_mask * 0.6, 0)
197
 
198
  if mask.sum() > 25:
199
  kernel_erode = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
 
222
  mask[lip_mask > 0] = 0
223
  return mask
224
 
 
225
  # ====================== COLOR TRANSFER ======================
226
  @timed("Color Transfer")
227
  def apply_strong_grey_hair(image: Image.Image, hair_mask: np.ndarray, beard_mask: np.ndarray):
 
271
  final = hair_grey * hair_mask_3ch + final * (1 - hair_mask_3ch)
272
  final = final * comb_3ch + img * (1 - comb_3ch)
273
  final = final + (np.array([9, 7, 5], dtype=np.float32)/255.0 * comb[..., None] * 0.18)
 
274
 
275
+ final = np.clip(final * 255, 0, 255).astype(np.uint8)
276
  result = Image.fromarray(final)
277
  result = result.filter(ImageFilter.UnsharpMask(radius=0.7, percent=70, threshold=2))
278
  return result
279
 
 
280
  # ====================== MAIN PROCESSING ======================
281
  @timed("Total Processing")
282
  def process_face_whitening(input_image: Image.Image):
283
  orig = input_image.convert("RGB")
284
  ow, oh = orig.size
285
+
286
  target = min(SAFE_IMG_SIZE, max(ow, oh))
287
  if target % 2 != 0:
288
  target -= 1
289
+
290
  img_resized = orig.resize((target, target), Image.BILINEAR)
291
 
292
  hair_mask, exclude_mask, mustache_mask, lip_mask = get_hair_and_exclude_masks(img_resized)
293
  beard_mask = get_beard_mask_fast(img_resized, exclude_mask, lip_mask)
294
+
295
+ # Improved mustache blending
296
+ beard_mask = np.maximum(beard_mask, mustache_mask * 0.96)
297
+
298
+ # Extra boost for thin mustache areas
299
+ thin_mustache = (mustache_mask > 0.25) & (beard_mask < 0.45)
300
+ beard_mask[thin_mustache] = np.maximum(beard_mask[thin_mustache], 0.68)
301
+
302
  beard_mask[lip_mask > 0] = 0
303
 
304
  final_resized = apply_strong_grey_hair(img_resized, hair_mask, beard_mask)
 
310
 
311
  return final_img
312
 
 
313
  # ====================== FASTAPI ======================
314
  app = FastAPI()
315
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
 
325
  _ = process_face_whitening(dummy)
326
  logger.info("✅ Server Ready!")
327
 
 
328
  @app.post("/age-face")
329
  async def age_face(file: UploadFile = File(...)):
330
  start_total = time.perf_counter()
 
343
 
344
  return StreamingResponse(buf, media_type="image/jpeg")
345
 
 
346
  if __name__ == "__main__":
347
  import uvicorn
348
  uvicorn.run(app, host="0.0.0.0", port=7860, workers=1)