ArmanRV commited on
Commit
0d734ed
·
verified ·
1 Parent(s): ae35dbd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -78
app.py CHANGED
@@ -84,16 +84,6 @@ from preprocess.humanparsing.run_parsing import Parsing
84
  from preprocess.openpose.run_openpose import OpenPose
85
  from detectron2.data.detection_utils import convert_PIL_to_numpy, _apply_exif_orientation
86
 
87
- # =========================
88
- # Optional: OpenCV for face + HOG people detector
89
- # =========================
90
- try:
91
- import cv2 # type: ignore
92
- _HAS_CV2 = True
93
- except Exception:
94
- cv2 = None # type: ignore
95
- _HAS_CV2 = False
96
-
97
 
98
  # =========================
99
  # Auth (optional)
@@ -204,64 +194,68 @@ def _quality_metrics(img: Image.Image) -> Tuple[int, int, float, float]:
204
  return w, h, brightness, sharpness
205
 
206
 
207
- def _cv2_bgr(img: Image.Image) -> Optional[np.ndarray]:
208
- if not _HAS_CV2:
209
- return None
210
- rgb = np.array(img.convert("RGB"))
211
- return cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
212
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
- def _detect_face_haar(img: Image.Image) -> bool:
215
- """True если нашли лицо (явно)."""
216
- if not _HAS_CV2:
217
- return False
218
- bgr = _cv2_bgr(img)
219
- if bgr is None:
220
- return False
221
- gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
222
- gray = cv2.equalizeHist(gray)
223
 
 
 
 
 
 
 
224
  try:
225
- cascade_path = os.path.join(getattr(cv2, "data").haarcascades, "haarcascade_frontalface_default.xml")
226
- face_cascade = cv2.CascadeClassifier(cascade_path)
227
- faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60))
228
- return len(faces) > 0
229
- except Exception:
230
- return False
231
 
 
232
 
233
- def _detect_person_hog(img: Image.Image) -> bool:
234
- """True если нашли силуэт человека (HOG people detector)."""
235
- if not _HAS_CV2:
236
- return False
237
- bgr = _cv2_bgr(img)
238
- if bgr is None:
239
- return False
240
 
241
- # ускорение: уменьшим до адекватного размера
242
- h, w = bgr.shape[:2]
243
- max_w = 900
244
- if w > max_w:
245
- scale = max_w / float(w)
246
- bgr = cv2.resize(bgr, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_AREA)
247
 
248
- try:
249
- hog = cv2.HOGDescriptor()
250
- hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
251
- rects, weights = hog.detectMultiScale(
252
- bgr,
253
- winStride=(8, 8),
254
- padding=(8, 8),
255
- scale=1.05,
256
- )
257
- if rects is None or len(rects) == 0:
258
- return False
259
- # считаем "явной" детекцию если есть хотя бы один бокс нормального размера
260
- hh, ww = bgr.shape[:2]
261
- for (x, y, rw, rh) in rects:
262
- if rh >= 0.35 * hh and rw >= 0.12 * ww:
263
- return True
264
- return True
265
  except Exception:
266
  return False
267
 
@@ -269,23 +263,22 @@ def _detect_person_hog(img: Image.Image) -> bool:
269
  def evaluate_person_photo(img: Optional[Image.Image]) -> Tuple[bool, str]:
270
  """
271
  UX-логика:
272
- 1) Если НЕ похоже на фото человека (нет лица И нет силуэта) => предупреждение + просим другое фото.
273
- 2) Если похоже => "✅ Фото подходит..." ИЛИ (только при явной проблеме качества) короткий warning.
 
274
  """
275
  if img is None:
276
  return False, ""
277
 
278
- # 1) человекоподобность: лицо ИЛИ силуэт
279
- has_face = _detect_face_haar(img)
280
- has_person = _detect_person_hog(img)
281
-
282
- if not (has_face or has_person):
283
  return False, "⚠️ Не похоже на фото человека. Загрузите фото человека (по пояс или в полный рост)."
284
 
285
- # 2) качество — предупреждаем только если прям плохо
286
  w, h, brightness, sharpness = _quality_metrics(img)
 
287
  issues = []
288
- if min(w, h) < 520: # чуть строже, но только явные случаи
 
289
  issues.append("низкое разрешение")
290
  if brightness < 50:
291
  issues.append("слишком темно")
@@ -510,12 +503,12 @@ def on_gallery_select(files_list: List[str], evt: gr.SelectData):
510
  return files_list[idx], f"👕 Выбрано: {files_list[idx]}"
511
 
512
  def on_person_change(person_pil):
513
- ok, msg = evaluate_person_photo(person_pil)
514
- # показываем:
515
- # - если нет человека: ⚠️...
516
- # - если всё ок: ✅...
517
- # - если есть явные проблемы качества: ⚠️...
518
- return msg if msg else ""
519
 
520
  def tryon_ui_imageslider(person_pil, selected_filename):
521
  yield (None, None), "⏳ Проверяем ввод..."
@@ -619,9 +612,8 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
619
  with gr.Column():
620
  person = gr.Image(label="Фото человека", type="pil", height=420)
621
 
622
- # Оценка/предупреждение по фото (пишем если ок, ⚠️ если явная проблема)
623
  warning = gr.Markdown("")
624
-
625
  person.change(fn=on_person_change, inputs=[person], outputs=[warning])
626
 
627
  with gr.Row():
 
84
  from preprocess.openpose.run_openpose import OpenPose
85
  from detectron2.data.detection_utils import convert_PIL_to_numpy, _apply_exif_orientation
86
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  # =========================
89
  # Auth (optional)
 
194
  return w, h, brightness, sharpness
195
 
196
 
197
+ # =========================
198
+ # Person photo evaluation (UX gate)
199
+ # - главное: если НЕ похоже на фото человека -> предупреждение и блокируем try-on
200
+ # - предупреждения по качеству показываем только при явной проблеме
201
+ # =========================
202
+ def _count_openpose_keypoints(keypoints) -> int:
203
+ """
204
+ Пытаемся универсально посчитать найденные ключевые точки (score > 0.2)
205
+ под разные форматы, которые могут возвращать разные реализации OpenPose.
206
+ """
207
+ try:
208
+ if isinstance(keypoints, dict):
209
+ cand = keypoints.get("candidate", None)
210
+ if cand is None:
211
+ # иногда внутри другой ключ
212
+ cand = keypoints.get("candidates", None)
213
+ if cand is not None:
214
+ cand = np.array(cand)
215
+ if cand.ndim >= 2 and cand.shape[-1] >= 3:
216
+ return int((cand[:, 2] > 0.2).sum())
217
+ # иногда subset/candidate в другом виде — если не распознали, возвращаем 0
218
+ return 0
219
+
220
+ arr = np.array(keypoints)
221
+ if arr.ndim >= 2 and arr.shape[-1] >= 3:
222
+ return int((arr[..., 2] > 0.2).sum())
223
+ except Exception:
224
+ return 0
225
+ return 0
226
 
 
 
 
 
 
 
 
 
 
227
 
228
+ def _detect_person_openpose_or_parsing(img: Image.Image) -> bool:
229
+ """
230
+ True если похоже на человека:
231
+ - OpenPose нашёл достаточно keypoints, ИЛИ
232
+ - Human Parsing дал заметную область "не фон"
233
+ """
234
  try:
235
+ # EXIF-поворот (часто ломает детект на телефонных фотках)
236
+ try:
237
+ img = _apply_exif_orientation(img)
238
+ except Exception:
239
+ pass
 
240
 
241
+ small = img.convert("RGB").resize((384, 512))
242
 
243
+ # 1) OpenPose
244
+ keypoints = openpose_model(small)
245
+ kpt_count = _count_openpose_keypoints(keypoints)
246
+ if kpt_count >= 6:
247
+ return True
 
 
248
 
249
+ # 2) Parsing
250
+ model_parse, _ = parsing_model(small)
251
+ mp = np.array(model_parse) if not isinstance(model_parse, np.ndarray) else model_parse
 
 
 
252
 
253
+ # доля пикселей не фона
254
+ non_bg = float((mp > 0).mean())
255
+ if non_bg >= 0.03:
256
+ return True
257
+
258
+ return False
 
 
 
 
 
 
 
 
 
 
 
259
  except Exception:
260
  return False
261
 
 
263
  def evaluate_person_photo(img: Optional[Image.Image]) -> Tuple[bool, str]:
264
  """
265
  UX-логика:
266
+ 1) Если НЕ похоже на фото человека (нет keypoints и нет parsing-области) -> ⚠️ и просим другое фото
267
+ 2) Если похоже -> ✅ Фото подходит
268
+ ЛИБО ⚠️ (только при явной проблеме качества)
269
  """
270
  if img is None:
271
  return False, ""
272
 
273
+ is_person = _detect_person_openpose_or_parsing(img)
274
+ if not is_person:
 
 
 
275
  return False, "⚠️ Не похоже на фото человека. Загрузите фото человека (по пояс или в полный рост)."
276
 
 
277
  w, h, brightness, sharpness = _quality_metrics(img)
278
+
279
  issues = []
280
+ # только явные проблемы
281
+ if min(w, h) < 520:
282
  issues.append("низкое разрешение")
283
  if brightness < 50:
284
  issues.append("слишком темно")
 
503
  return files_list[idx], f"👕 Выбрано: {files_list[idx]}"
504
 
505
  def on_person_change(person_pil):
506
+ # Показываем либо:
507
+ # - ⚠️ (если не похоже на человека или явное плохое качество)
508
+ # - ✅ (если подходит)
509
+ # - "" (если нет фото)
510
+ _, msg = evaluate_person_photo(person_pil)
511
+ return msg or ""
512
 
513
  def tryon_ui_imageslider(person_pil, selected_filename):
514
  yield (None, None), "⏳ Проверяем ввод..."
 
612
  with gr.Column():
613
  person = gr.Image(label="Фото человека", type="pil", height=420)
614
 
615
+ # Оценка/предупреждение по фото (✅/⚠️/пусто)
616
  warning = gr.Markdown("")
 
617
  person.change(fn=on_person_change, inputs=[person], outputs=[warning])
618
 
619
  with gr.Row():