Spaces:
Paused
Paused
Update app.py
Browse files
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 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 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 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
return False
|
| 231 |
|
|
|
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
if bgr is None:
|
| 239 |
-
return False
|
| 240 |
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 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 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 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) Если похоже
|
|
|
|
| 274 |
"""
|
| 275 |
if img is None:
|
| 276 |
return False, ""
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 514 |
-
# пока
|
| 515 |
-
# - если
|
| 516 |
-
# - если
|
| 517 |
-
|
| 518 |
-
return msg
|
| 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():
|