Spaces:
Running
Running
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -4,7 +4,6 @@ FaceAge-DINOv3 — Gradio demo for HuggingFace Spaces.
|
|
| 4 |
Face detection : YuNet (OpenCV built-in, ~350 KB model, no extra deps)
|
| 5 |
Age/gender : FaceAge-DINOv3 ONNX (CPU, ~1.2 GB)
|
| 6 |
"""
|
| 7 |
-
import urllib.request
|
| 8 |
import os
|
| 9 |
import numpy as np
|
| 10 |
import gradio as gr
|
|
@@ -83,14 +82,11 @@ def _predict_crop(face_rgb: np.ndarray) -> dict:
|
|
| 83 |
|
| 84 |
|
| 85 |
# ---------------------------------------------------------------------------
|
| 86 |
-
# YuNet face detector (cv2.FaceDetectorYN,
|
| 87 |
# ---------------------------------------------------------------------------
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
"face_detection_yunet/face_detection_yunet_2023mar.onnx"
|
| 92 |
-
)
|
| 93 |
-
_YUNET_PATH = "/tmp/face_detection_yunet_2023mar.onnx"
|
| 94 |
_DETECTOR = None
|
| 95 |
|
| 96 |
|
|
@@ -99,21 +95,20 @@ def _load_detector():
|
|
| 99 |
if _DETECTOR is not None:
|
| 100 |
return
|
| 101 |
|
| 102 |
-
|
| 103 |
-
if not os.path.exists(_YUNET_PATH):
|
| 104 |
-
print(f"[YuNet] Downloading model …")
|
| 105 |
-
try:
|
| 106 |
-
urllib.request.urlretrieve(_YUNET_URL, _YUNET_PATH)
|
| 107 |
-
print(f"[YuNet] Saved to {_YUNET_PATH}")
|
| 108 |
-
except Exception as e:
|
| 109 |
-
print(f"[YuNet] Download failed: {e} — face detection disabled")
|
| 110 |
-
_DETECTOR = "unavailable"
|
| 111 |
-
return
|
| 112 |
-
|
| 113 |
import cv2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
try:
|
| 115 |
_DETECTOR = cv2.FaceDetectorYN.create(
|
| 116 |
-
model =
|
| 117 |
config = "",
|
| 118 |
input_size = (320, 320),
|
| 119 |
score_threshold = 0.6,
|
|
@@ -127,21 +122,21 @@ def _load_detector():
|
|
| 127 |
|
| 128 |
|
| 129 |
def _detect_faces(img_rgb: np.ndarray,
|
| 130 |
-
min_face_px: int = 20
|
|
|
|
| 131 |
"""
|
| 132 |
-
Returns list of (x0, y0, x1, y1) sorted by area
|
| 133 |
Falls back to empty list if YuNet is unavailable.
|
| 134 |
"""
|
| 135 |
if _DETECTOR == "unavailable" or _DETECTOR is None:
|
| 136 |
return []
|
| 137 |
|
| 138 |
import cv2
|
| 139 |
-
h, w
|
| 140 |
img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
|
| 141 |
|
| 142 |
-
# YuNet requires input_size to match the image
|
| 143 |
_DETECTOR.setInputSize((w, h))
|
| 144 |
-
_, faces = _DETECTOR.detect(img_bgr) #
|
| 145 |
|
| 146 |
if faces is None:
|
| 147 |
return []
|
|
@@ -149,8 +144,11 @@ def _detect_faces(img_rgb: np.ndarray,
|
|
| 149 |
bboxes = []
|
| 150 |
for face in faces:
|
| 151 |
x, y, fw, fh = face[:4].astype(int)
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
| 154 |
if (x1 - x0) >= min_face_px and (y1 - y0) >= min_face_px:
|
| 155 |
bboxes.append((x0, y0, x1, y1))
|
| 156 |
|
|
|
|
| 4 |
Face detection : YuNet (OpenCV built-in, ~350 KB model, no extra deps)
|
| 5 |
Age/gender : FaceAge-DINOv3 ONNX (CPU, ~1.2 GB)
|
| 6 |
"""
|
|
|
|
| 7 |
import os
|
| 8 |
import numpy as np
|
| 9 |
import gradio as gr
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
# ---------------------------------------------------------------------------
|
| 85 |
+
# YuNet face detector (cv2.FaceDetectorYN, loaded from HuggingFace Hub)
|
| 86 |
# ---------------------------------------------------------------------------
|
| 87 |
|
| 88 |
+
_YUNET_REPO = "opencv/face_detection_yunet"
|
| 89 |
+
_YUNET_FILE = "face_detection_yunet_2023mar.onnx"
|
|
|
|
|
|
|
|
|
|
| 90 |
_DETECTOR = None
|
| 91 |
|
| 92 |
|
|
|
|
| 95 |
if _DETECTOR is not None:
|
| 96 |
return
|
| 97 |
|
| 98 |
+
from huggingface_hub import hf_hub_download
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
import cv2
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
yunet_path = hf_hub_download(repo_id=_YUNET_REPO, filename=_YUNET_FILE)
|
| 103 |
+
print(f"[YuNet] Model: {yunet_path}")
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"[YuNet] Download failed: {e} — face detection disabled")
|
| 106 |
+
_DETECTOR = "unavailable"
|
| 107 |
+
return
|
| 108 |
+
|
| 109 |
try:
|
| 110 |
_DETECTOR = cv2.FaceDetectorYN.create(
|
| 111 |
+
model = yunet_path,
|
| 112 |
config = "",
|
| 113 |
input_size = (320, 320),
|
| 114 |
score_threshold = 0.6,
|
|
|
|
| 122 |
|
| 123 |
|
| 124 |
def _detect_faces(img_rgb: np.ndarray,
|
| 125 |
+
min_face_px: int = 20,
|
| 126 |
+
margin: int = 15) -> list[tuple[int, int, int, int]]:
|
| 127 |
"""
|
| 128 |
+
Returns list of (x0, y0, x1, y1) with margin padding, sorted by area desc.
|
| 129 |
Falls back to empty list if YuNet is unavailable.
|
| 130 |
"""
|
| 131 |
if _DETECTOR == "unavailable" or _DETECTOR is None:
|
| 132 |
return []
|
| 133 |
|
| 134 |
import cv2
|
| 135 |
+
h, w = img_rgb.shape[:2]
|
| 136 |
img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
|
| 137 |
|
|
|
|
| 138 |
_DETECTOR.setInputSize((w, h))
|
| 139 |
+
_, faces = _DETECTOR.detect(img_bgr) # None or Nx15: [x,y,w,h, ...]
|
| 140 |
|
| 141 |
if faces is None:
|
| 142 |
return []
|
|
|
|
| 144 |
bboxes = []
|
| 145 |
for face in faces:
|
| 146 |
x, y, fw, fh = face[:4].astype(int)
|
| 147 |
+
# Add margin, clamp to image bounds
|
| 148 |
+
x0 = max(0, x - margin)
|
| 149 |
+
y0 = max(0, y - margin)
|
| 150 |
+
x1 = min(w, x + fw + margin)
|
| 151 |
+
y1 = min(h, y + fh + margin)
|
| 152 |
if (x1 - x0) >= min_face_px and (y1 - y0) >= min_face_px:
|
| 153 |
bboxes.append((x0, y0, x1, y1))
|
| 154 |
|