Nekochu commited on
Commit
2a828f1
·
1 Parent(s): 1f4128d

Switch to OpenCV face detection

Browse files
Files changed (2) hide show
  1. app.py +62 -29
  2. requirements.txt +0 -1
app.py CHANGED
@@ -9,7 +9,6 @@ import time
9
  import cv2
10
  import numpy as np
11
  import onnxruntime as ort
12
- import mediapipe as mp
13
  import gradio as gr
14
  from PIL import Image
15
  from huggingface_hub import hf_hub_download
@@ -37,41 +36,75 @@ sess = ort.InferenceSession(
37
  print("Model loaded.")
38
 
39
  # ---------------------------------------------------------------------------
40
- # MediaPipe face detection
41
  # ---------------------------------------------------------------------------
42
- mp_face_detection = mp.solutions.face_detection
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  def detect_face_box(image_rgb: np.ndarray):
45
  """
46
- Detect the largest face bounding box using MediaPipe.
47
  Returns (x1, y1, x2, y2) in pixel coords or None.
48
  """
49
  h, w = image_rgb.shape[:2]
50
- with mp_face_detection.FaceDetection(
51
- model_selection=1, min_detection_confidence=0.5
52
- ) as face_det:
53
- results = face_det.process(image_rgb)
54
- if not results.detections:
55
- return None
56
-
57
- # Pick the largest detection by area
58
- best = None
59
- best_area = 0
60
- for det in results.detections:
61
- bb = det.location_data.relative_bounding_box
62
- area = bb.width * bb.height
63
- if area > best_area:
64
- best_area = area
65
- best = bb
66
-
67
- # Convert relative to absolute
68
- x1 = int(best.xmin * w)
69
- y1 = int(best.ymin * h)
70
- bw = int(best.width * w)
71
- bh = int(best.height * h)
72
- x2 = x1 + bw
73
- y2 = y1 + bh
74
- return (x1, y1, x2, y2)
 
 
 
 
 
 
 
 
75
 
76
  # ---------------------------------------------------------------------------
77
  # Face cropping with margin
 
9
  import cv2
10
  import numpy as np
11
  import onnxruntime as ort
 
12
  import gradio as gr
13
  from PIL import Image
14
  from huggingface_hub import hf_hub_download
 
36
  print("Model loaded.")
37
 
38
  # ---------------------------------------------------------------------------
39
+ # OpenCV DNN face detection (no extra dependencies)
40
  # ---------------------------------------------------------------------------
41
+ # Use OpenCV's built-in Haar cascade as primary, with DNN SSD as fallback
42
+ _face_cascade = cv2.CascadeClassifier(
43
+ cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
44
+ )
45
+
46
+ # Try to use the more accurate DNN face detector if available
47
+ _dnn_net = None
48
+ _dnn_model_path = os.path.join(os.path.dirname(__file__), "face_detection_yunet_2023mar.onnx")
49
+ YUNET_URL = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
50
+
51
+ def _ensure_yunet():
52
+ """Download YuNet face detector if not present."""
53
+ global _dnn_model_path
54
+ if not os.path.exists(_dnn_model_path):
55
+ print("Downloading YuNet face detector...")
56
+ try:
57
+ path = hf_hub_download(
58
+ repo_id="opencv/opencv_zoo",
59
+ filename="models/face_detection_yunet/face_detection_yunet_2023mar.onnx",
60
+ )
61
+ _dnn_model_path = path
62
+ except Exception:
63
+ import urllib.request
64
+ urllib.request.urlretrieve(YUNET_URL, _dnn_model_path)
65
+ print("YuNet downloaded.")
66
+ return _dnn_model_path
67
+
68
 
69
  def detect_face_box(image_rgb: np.ndarray):
70
  """
71
+ Detect the largest face bounding box.
72
  Returns (x1, y1, x2, y2) in pixel coords or None.
73
  """
74
  h, w = image_rgb.shape[:2]
75
+
76
+ # Try YuNet first (more accurate)
77
+ try:
78
+ yunet_path = _ensure_yunet()
79
+ detector = cv2.FaceDetectorYN.create(yunet_path, "", (w, h), 0.5, 0.3, 5000)
80
+ _, faces = detector.detect(image_rgb)
81
+ if faces is not None and len(faces) > 0:
82
+ # Pick largest face by area
83
+ best_idx = 0
84
+ best_area = 0
85
+ for i, face in enumerate(faces):
86
+ fw, fh = face[2], face[3]
87
+ area = fw * fh
88
+ if area > best_area:
89
+ best_area = area
90
+ best_idx = i
91
+ f = faces[best_idx]
92
+ x1, y1 = int(f[0]), int(f[1])
93
+ x2, y2 = int(f[0] + f[2]), int(f[1] + f[3])
94
+ return (max(x1, 0), max(y1, 0), min(x2, w), min(y2, h))
95
+ except Exception as e:
96
+ print(f"YuNet failed, falling back to Haar: {e}")
97
+
98
+ # Fallback: Haar cascade
99
+ gray = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
100
+ faces = _face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60))
101
+ if len(faces) == 0:
102
+ return None
103
+
104
+ # Pick largest
105
+ best_idx = np.argmax([fw * fh for (_, _, fw, fh) in faces])
106
+ x, y, fw, fh = faces[best_idx]
107
+ return (x, y, x + fw, y + fh)
108
 
109
  # ---------------------------------------------------------------------------
110
  # Face cropping with margin
requirements.txt CHANGED
@@ -3,4 +3,3 @@ onnxruntime
3
  opencv-python-headless
4
  numpy
5
  Pillow
6
- mediapipe==0.10.14
 
3
  opencv-python-headless
4
  numpy
5
  Pillow