Spaces:
Paused
Paused
Update app.py
Browse files
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
|
| 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) <
|
| 203 |
issues.append("низкое разрешение")
|
| 204 |
-
if brightness <
|
| 205 |
issues.append("слишком темно")
|
| 206 |
-
if sharpness <
|
| 207 |
-
issues.append("размыто")
|
| 208 |
|
| 209 |
-
if
|
| 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 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
| 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])
|