Kanae-K commited on
Commit
75bfdf3
·
verified ·
1 Parent(s): 0220815

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -58
app.py CHANGED
@@ -50,7 +50,7 @@ PART_GROUPS = {
50
  "mouth": {"indices": [24, 25, 26, 27], "use_box": True},
51
  "browL": {"indices": [8, 9, 10], "use_box": True},
52
  "browR": {"indices": [5, 6, 7], "use_box": True},
53
- "face": {"indices": [0, 1, 2, 3, 4], "use_box": False}, # 顎だけなのボックス不向き
54
  }
55
 
56
 
@@ -144,6 +144,53 @@ def segment_part(image: Image.Image, image_embeddings, part_points: List[List[fl
144
  return (best_mask > 0).astype(np.uint8) * 255
145
 
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def estimate_hair_points(points: List[dict], image_size: Tuple[int, int]) -> List[List[float]]:
148
  """ランドマークから髪の推定ポイントを生成(頭上 + 左右 + サイド)"""
149
  w, h = image_size
@@ -211,19 +258,10 @@ def run_with_landmarks(image: Image.Image, landmarks_json: str) -> Tuple[List[st
211
  status_parts = []
212
  all_masks = [] # 全パーツマスクを蓄積(残り領域計算用)
213
 
214
- # 髪のポイントを先に計算face ネガティブプロンプトに使う
215
- hair_points = estimate_hair_points(points, image.size)
216
-
217
- # 各パーツをセグメント
218
  for part_name, config in PART_GROUPS.items():
219
  part_points = get_all_points(points, config["indices"])
220
-
221
- # face: 髪の推定ポイントをネガティブに渡して髪領域を除外
222
- neg = None
223
- if part_name == "face" and hair_points:
224
- neg = hair_points[:3] # 頭頂3点を背景ポイントに
225
-
226
- mask_arr = segment_part(image, image_embeddings, part_points, original_size, reshaped_size, use_box=config.get("use_box", True), negative_points=neg)
227
 
228
  if mask_arr is not None:
229
  mask_img = Image.fromarray(mask_arr, "L")
@@ -236,54 +274,33 @@ def run_with_landmarks(image: Image.Image, landmarks_json: str) -> Tuple[List[st
236
  status_parts.append(part_name)
237
  all_masks.append(mask_arr)
238
 
239
- # 髪のセグメント の中心ネガティブポイントして顔領域を
240
- if hair_points:
241
- jaw_points = get_all_points(points, [0, 1, 2, 3, 4])
242
- face_center = None
243
- if jaw_points:
244
- face_center = [[
245
- sum(p[0] for p in jaw_points) / len(jaw_points),
246
- sum(p[1] for p in jaw_points) / len(jaw_points),
247
- ]]
248
- hair_mask = segment_part(image, image_embeddings, hair_points, original_size, reshaped_size, use_box=False, negative_points=face_center)
249
- if hair_mask is not None:
250
- hair_img = Image.fromarray(hair_mask, "L")
251
- if hair_img.size != image.size:
252
- hair_img = hair_img.resize(image.size)
253
- hair_mask = np.array(hair_img)
254
- path = os.path.join(out_dir, "hair.png")
255
- hair_img.save(path)
256
- mask_files.append(path)
257
- status_parts.append("hair")
258
- all_masks.append(hair_mask)
259
-
260
- # ===== 後処理: マスク同士の重なりを除去 =====
261
- # 優先順位: 小パーツ(目・眉・口) > 顔 > 髪
262
- # 小パー��領域を顔・髪から引き算し、顔領域を髪から引き算
263
  part_masks = dict(zip(status_parts, all_masks))
264
  small_parts = ["eyeL", "eyeR", "mouth", "browL", "browR"]
 
 
 
 
 
 
265
 
266
  # 小パーツの和集合
267
- small_union = np.zeros_like(all_masks[0]) if all_masks else None
268
  for sp in small_parts:
269
  if sp in part_masks:
270
  small_union = np.maximum(small_union, part_masks[sp])
271
 
272
- # 顔から小パーツを引く
273
- if "face" in part_masks and small_union is not None:
274
- part_masks["face"] = np.where(small_union > 0, 0, part_masks["face"]).astype(np.uint8)
275
-
276
- # 髪から顔+小パーツを引く
277
- if "hair" in part_masks:
278
- exclude = small_union.copy() if small_union is not None else np.zeros_like(all_masks[0])
279
- if "face" in part_masks:
280
- exclude = np.maximum(exclude, part_masks["face"])
281
- part_masks["hair"] = np.where(exclude > 0, 0, part_masks["hair"]).astype(np.uint8)
282
 
283
  # 修正後のマスクを再保存
284
  mask_files = []
285
  all_masks = []
286
- for part_name in status_parts:
 
 
 
287
  arr = part_masks[part_name]
288
  mask_img = Image.fromarray(arr, "L")
289
  path = os.path.join(out_dir, f"{part_name}.png")
@@ -291,20 +308,65 @@ def run_with_landmarks(image: Image.Image, landmarks_json: str) -> Tuple[List[st
291
  mask_files.append(path)
292
  all_masks.append(arr)
293
 
294
- # 残り領域マスク(body): どのパーツも属さない部
295
- if all_masks:
296
- union = np.zeros(all_masks[0].shape, dtype=np.uint8)
297
- for m in all_masks:
298
- union = np.maximum(union, m)
299
- remainder = np.where(union > 0, 0, 255).astype(np.uint8)
300
- # 残りが意味のあるサイズ(全体の1%以上)なら保存
301
- if np.sum(remainder > 0) > remainder.size * 0.01:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  remainder_img = Image.fromarray(remainder, "L")
303
  path = os.path.join(out_dir, "body.png")
304
  remainder_img.save(path)
305
  mask_files.append(path)
306
- status_parts.append("body")
307
- all_masks.append(np.array(remainder_img))
 
308
 
309
  # ZIP
310
  if mask_files:
 
50
  "mouth": {"indices": [24, 25, 26, 27], "use_box": True},
51
  "browL": {"indices": [8, 9, 10], "use_box": True},
52
  "browR": {"indices": [5, 6, 7], "use_box": True},
53
+ # face はSAMを使わず幾何学的に生成(後処理追加)
54
  }
55
 
56
 
 
144
  return (best_mask > 0).astype(np.uint8) * 255
145
 
146
 
147
+ def compute_face_mask(points: List[dict], image_size: Tuple[int, int]) -> np.ndarray:
148
+ """顎(0-4)の輪郭を上側で円弧で閉じた顔マスクを生成"""
149
+ from PIL import ImageDraw
150
+ import math
151
+
152
+ w, h = image_size
153
+
154
+ # 顎の5点
155
+ jaw = [(points[i]["x"], points[i]["y"]) for i in range(5) if i < len(points)]
156
+ if len(jaw) < 3:
157
+ return np.zeros((h, w), dtype=np.uint8)
158
+
159
+ # 眉の上端 → 額の位置
160
+ brow_ys = [points[i]["y"] for i in range(5, 11) if i < len(points)]
161
+ forehead_y = min(brow_ys) if brow_ys else jaw[0][1]
162
+ # 額にマージンを追加
163
+ face_width = abs(jaw[0][0] - jaw[-1][0])
164
+ top_y = forehead_y - face_width * 0.2
165
+
166
+ # 左端(jaw[0])と右端(jaw[-1])を円弧で繋ぐ
167
+ # 上部の制御点を生成(半円弧を点列で近似)
168
+ left = jaw[0] # 左の顎端
169
+ right = jaw[-1] # 右の顎端
170
+ cx = (left[0] + right[0]) / 2
171
+ rx = abs(right[0] - left[0]) / 2 * 1.1 # 少し広め
172
+ ry = max(left[1], right[1]) - top_y
173
+
174
+ # 右端→上→左端の円弧(反時計回り)
175
+ arc_points = []
176
+ n_arc = 16
177
+ for i in range(n_arc + 1):
178
+ angle = math.pi * i / n_arc # 0 → π(右→上→左)
179
+ ax = cx + rx * math.cos(angle)
180
+ ay = min(left[1], right[1]) - ry * math.sin(angle)
181
+ arc_points.append((ax, ay))
182
+
183
+ # 完全なポリゴン: 顎(0→4) + 円弧(右端→上→左端)
184
+ polygon = list(jaw) + arc_points
185
+
186
+ # ポリゴンを塗りつぶしてマスク生成
187
+ mask_img = Image.new("L", (w, h), 0)
188
+ draw = ImageDraw.Draw(mask_img)
189
+ draw.polygon(polygon, fill=255)
190
+
191
+ return np.array(mask_img)
192
+
193
+
194
  def estimate_hair_points(points: List[dict], image_size: Tuple[int, int]) -> List[List[float]]:
195
  """ランドマークから髪の推定ポイントを生成(頭上 + 左右 + サイド)"""
196
  w, h = image_size
 
258
  status_parts = []
259
  all_masks = [] # 全パーツマスクを蓄積(残り領域計算用)
260
 
261
+ # 各パーツをセグメント(目・口・眉み — SAMが得意な小パーツ
 
 
 
262
  for part_name, config in PART_GROUPS.items():
263
  part_points = get_all_points(points, config["indices"])
264
+ mask_arr = segment_part(image, image_embeddings, part_points, original_size, reshaped_size, use_box=config.get("use_box", True))
 
 
 
 
 
 
265
 
266
  if mask_arr is not None:
267
  mask_img = Image.fromarray(mask_arr, "L")
 
274
  status_parts.append(part_name)
275
  all_masks.append(mask_arr)
276
 
277
+ # ===== 後処理: 顔を幾何学的追加 + 重なり去 =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  part_masks = dict(zip(status_parts, all_masks))
279
  small_parts = ["eyeL", "eyeR", "mouth", "browL", "browR"]
280
+ h_img, w_img = all_masks[0].shape if all_masks else (image.size[1], image.size[0])
281
+
282
+ # 幾何学的な顔マスク(楕円)を生成、小パーツを除外
283
+ face_ellipse = compute_face_mask(points, image.size)
284
+ if face_ellipse.shape != (h_img, w_img):
285
+ face_ellipse = np.array(Image.fromarray(face_ellipse, "L").resize((w_img, h_img)))
286
 
287
  # 小パーツの和集合
288
+ small_union = np.zeros((h_img, w_img), dtype=np.uint8)
289
  for sp in small_parts:
290
  if sp in part_masks:
291
  small_union = np.maximum(small_union, part_masks[sp])
292
 
293
+ # 顔 = 楕円マスク − 小パーツ
294
+ face_mask = np.where(small_union > 0, 0, face_ellipse).astype(np.uint8)
295
+ part_masks["face"] = face_mask
 
 
 
 
 
 
 
296
 
297
  # 修正後のマスクを再保存
298
  mask_files = []
299
  all_masks = []
300
+ status_parts_clean = [p for p in status_parts]
301
+ if "face" not in status_parts_clean:
302
+ status_parts_clean.append("face")
303
+ for part_name in status_parts_clean:
304
  arr = part_masks[part_name]
305
  mask_img = Image.fromarray(arr, "L")
306
  path = os.path.join(out_dir, f"{part_name}.png")
 
308
  mask_files.append(path)
309
  all_masks.append(arr)
310
 
311
+ # ===== 残り領域を「髪」と「体」に分割 =====
312
+ union = np.zeros((h_img, w_img), dtype=np.uint8)
313
+ for m in all_masks:
314
+ union = np.maximum(union, m)
315
+ remainder = np.where(union > 0, 0, 255).astype(np.uint8)
316
+
317
+ if np.sum(remainder > 0) > remainder.size * 0.005:
318
+ # 顔の中心Y座標を基準に上下分割
319
+ jaw_ys = [points[i]["y"] for i in range(5) if i < len(points)]
320
+ jaw_xs = [points[i]["x"] for i in range(5) if i < len(points)]
321
+ if jaw_ys:
322
+ face_center_y = sum(jaw_ys) / len(jaw_ys)
323
+ face_center_x = sum(jaw_xs) / len(jaw_xs)
324
+ face_w = (max(jaw_xs) - min(jaw_xs)) if len(jaw_xs) > 1 else w_img * 0.4
325
+
326
+ # 髪 = 残りのうち、顔の上部 or 顔の横(顔中心Yより上 + 顔幅の外側で上半分)
327
+ hair_mask = np.zeros_like(remainder)
328
+ for y in range(h_img):
329
+ for x in range(w_img):
330
+ if remainder[y, x] == 0:
331
+ continue
332
+ # 顔中心より上 → 髪
333
+ if y < face_center_y:
334
+ hair_mask[y, x] = 255
335
+ # 顔の横で上半分 → サイドの髪
336
+ elif y < face_center_y + face_w * 0.5:
337
+ dist_from_center = abs(x - face_center_x)
338
+ if dist_from_center > face_w * 0.35:
339
+ hair_mask[y, x] = 255
340
+
341
+ # 体 = 残り − 髪
342
+ body_mask = np.where(hair_mask > 0, 0, remainder).astype(np.uint8)
343
+
344
+ # 髪マスクを保存(意味のあるサイズなら)
345
+ if np.sum(hair_mask > 0) > remainder.size * 0.005:
346
+ hair_img = Image.fromarray(hair_mask, "L")
347
+ path = os.path.join(out_dir, "hair.png")
348
+ hair_img.save(path)
349
+ mask_files.append(path)
350
+ status_parts_clean.append("hair")
351
+ all_masks.append(hair_mask)
352
+
353
+ # 体マスクを保存
354
+ if np.sum(body_mask > 0) > remainder.size * 0.005:
355
+ body_img = Image.fromarray(body_mask, "L")
356
+ path = os.path.join(out_dir, "body.png")
357
+ body_img.save(path)
358
+ mask_files.append(path)
359
+ status_parts_clean.append("body")
360
+ all_masks.append(body_mask)
361
+ else:
362
+ # ランドマークなしの場合は全部bodyに
363
  remainder_img = Image.fromarray(remainder, "L")
364
  path = os.path.join(out_dir, "body.png")
365
  remainder_img.save(path)
366
  mask_files.append(path)
367
+ status_parts_clean.append("body")
368
+
369
+ status_parts = status_parts_clean
370
 
371
  # ZIP
372
  if mask_files: