ArmanRV commited on
Commit
ae35dbd
·
verified ·
1 Parent(s): 7d476e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -21
app.py CHANGED
@@ -84,6 +84,16 @@ 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
  # =========================
89
  # Auth (optional)
@@ -183,34 +193,109 @@ def allow_call(min_interval_sec: float = 2.5) -> Tuple[bool, str]:
183
  return True, ""
184
 
185
 
186
- def photo_quick_warning(img: Optional[Image.Image]) -> str:
187
- """
188
- Минималистичная проверка: показываем ТОЛЬКО если реально плохо.
189
- Никаких общих гайдов и диагностик.
190
- """
191
- if img is None:
192
- return ""
193
-
194
  img = img.convert("RGB")
195
  w, h = img.size
196
  gray = np.array(img.convert("L"))
197
  brightness = float(gray.mean())
198
  gy, gx = np.gradient(gray.astype(np.float32))
199
  sharpness = float((gx * gx + gy * gy).mean())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  issues = []
202
- if min(w, h) < 640:
203
  issues.append("низкое разрешение")
204
- if brightness < 60:
205
  issues.append("слишком темно")
206
- if sharpness < 12:
207
- issues.append("размыто")
208
 
209
- if not issues:
210
- return ""
211
 
212
- # коротко и по делу
213
- return "⚠️ Фото может дать худший результат (" + ", ".join(issues) + "). Попробуйте другое фото."
214
 
215
 
216
  # =========================
@@ -425,9 +510,12 @@ def on_gallery_select(files_list: List[str], evt: gr.SelectData):
425
  return files_list[idx], f"👕 Выбрано: {files_list[idx]}"
426
 
427
  def on_person_change(person_pil):
428
- # Показываем только короткий warning, либо ничего
429
- msg = photo_quick_warning(person_pil)
430
- return msg
 
 
 
431
 
432
  def tryon_ui_imageslider(person_pil, selected_filename):
433
  yield (None, None), "⏳ Проверяем ввод..."
@@ -440,6 +528,12 @@ def tryon_ui_imageslider(person_pil, selected_filename):
440
  if person_pil is None:
441
  yield (None, None), "❌ Загрузите фото человека"
442
  return
 
 
 
 
 
 
443
  if not selected_filename:
444
  yield (None, None), "❌ Выберите одежду (клик по превью)"
445
  return
@@ -449,7 +543,6 @@ def tryon_ui_imageslider(person_pil, selected_filename):
449
  yield (None, None), "❌ Не удалось загрузить выбранную одежду"
450
  return
451
 
452
- # 3) мягкий прогресс
453
  yield (None, None), "🧠 Анализируем силуэт..."
454
  time.sleep(0.05)
455
  yield (None, None), "✨ Примеряем..."
@@ -478,6 +571,12 @@ def tryon_ui_pair(person_pil, selected_filename):
478
  if person_pil is None:
479
  yield None, None, "❌ Загрузите фото человека"
480
  return
 
 
 
 
 
 
481
  if not selected_filename:
482
  yield None, None, "❌ Выберите одежду (клик по превью)"
483
  return
@@ -520,7 +619,7 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
520
  with gr.Column():
521
  person = gr.Image(label="Фото человека", type="pil", height=420)
522
 
523
- # Минималистичное предупреждение (пусто если всё ок)
524
  warning = gr.Markdown("")
525
 
526
  person.change(fn=on_person_change, inputs=[person], outputs=[warning])
 
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)
 
193
  return True, ""
194
 
195
 
196
+ def _quality_metrics(img: Image.Image) -> Tuple[int, int, float, float]:
197
+ """(w, h, brightness, sharpness)"""
 
 
 
 
 
 
198
  img = img.convert("RGB")
199
  w, h = img.size
200
  gray = np.array(img.convert("L"))
201
  brightness = float(gray.mean())
202
  gy, gx = np.gradient(gray.astype(np.float32))
203
  sharpness = float((gx * gx + gy * gy).mean())
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
+
268
+
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("слишком темно")
292
+ if sharpness < 8:
293
+ issues.append("сильно размыто")
294
 
295
+ if issues:
296
+ return True, "⚠️ Фото может плохо подойти для примерки (" + ", ".join(issues) + "). Лучше загрузить другое."
297
 
298
+ return True, "✅ Фото подходит для примерки."
 
299
 
300
 
301
  # =========================
 
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), "⏳ Проверяем ввод..."
 
528
  if person_pil is None:
529
  yield (None, None), "❌ Загрузите фото человека"
530
  return
531
+
532
+ is_person, verdict = evaluate_person_photo(person_pil)
533
+ if not is_person:
534
+ yield (None, None), verdict
535
+ return
536
+
537
  if not selected_filename:
538
  yield (None, None), "❌ Выберите одежду (клик по превью)"
539
  return
 
543
  yield (None, None), "❌ Не удалось загрузить выбранную одежду"
544
  return
545
 
 
546
  yield (None, None), "🧠 Анализируем силуэт..."
547
  time.sleep(0.05)
548
  yield (None, None), "✨ Примеряем..."
 
571
  if person_pil is None:
572
  yield None, None, "❌ Загрузите фото человека"
573
  return
574
+
575
+ is_person, verdict = evaluate_person_photo(person_pil)
576
+ if not is_person:
577
+ yield None, None, verdict
578
+ return
579
+
580
  if not selected_filename:
581
  yield None, None, "❌ Выберите одежду (клик по превью)"
582
  return
 
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])