|
19 | 19 |
|
20 | 20 | """Image utilities."""
|
21 | 21 |
|
| 22 | +from __future__ import annotations |
22 | 23 |
|
23 | 24 | import io
|
24 | 25 | import os
|
25 | 26 | import shutil
|
26 | 27 | import tempfile
|
27 | 28 | from pathlib import Path
|
28 |
| -from typing import BinaryIO, Callable, List, Tuple |
| 29 | +from typing import BinaryIO, Callable |
29 | 30 |
|
30 | 31 | import ffmpeg
|
31 | 32 | from pdf2image import convert_from_path
|
@@ -218,26 +219,49 @@ def __init__(self, path: FilenameOrPath, mime_type: str) -> None:
|
218 | 219 | super().__init__(stream=stream, mime_type=mime_type)
|
219 | 220 |
|
220 | 221 |
|
221 |
| -def detect_faces(stream: BinaryIO) -> List[Tuple[float, float, float, float]]: |
222 |
| - """Detect faces in an image (stream).""" |
| 222 | +def detect_faces(stream: BinaryIO) -> list[tuple[float, float, float, float]]: |
| 223 | + """Detect faces in an image (stream) using YuNet.""" |
| 224 | + # Read the image from the input stream |
223 | 225 | import cv2
|
224 | 226 | import numpy as np
|
225 | 227 |
|
226 | 228 | file_bytes = np.asarray(bytearray(stream.read()), dtype=np.uint8)
|
227 | 229 | cv_image = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
|
228 |
| - cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY) |
229 |
| - haarcascade_path = resource_filename( |
230 |
| - "gramps_webapi", "data/haarcascade_frontalface_default.xml" |
| 230 | + |
| 231 | + # Load the YuNet model |
| 232 | + model_path = resource_filename( |
| 233 | + "gramps_webapi", "data/face_detection_yunet_2023mar.onnx" |
| 234 | + ) |
| 235 | + face_detector = cv2.FaceDetectorYN.create( |
| 236 | + model_path, "", (320, 320), score_threshold=0.5 |
231 | 237 | )
|
232 |
| - cascade = cv2.CascadeClassifier(haarcascade_path) |
233 |
| - faces = cascade.detectMultiScale(cv_image, 1.1, 4) |
234 |
| - height, width = cv_image.shape |
235 |
| - return [ |
236 |
| - ( |
237 |
| - 100 * x / width, |
238 |
| - 100 * y / height, |
239 |
| - 100 * (x + w) / width, |
240 |
| - 100 * (y + h) / height, |
| 238 | + |
| 239 | + # Set input image size for YuNet |
| 240 | + height, width, _ = cv_image.shape |
| 241 | + face_detector.setInputSize((width, height)) |
| 242 | + |
| 243 | + # Detect faces |
| 244 | + faces = face_detector.detect(cv_image) |
| 245 | + |
| 246 | + # Check if any faces are detected |
| 247 | + if faces[1] is None: |
| 248 | + return [] |
| 249 | + |
| 250 | + # Extract and normalize face bounding boxes |
| 251 | + detected_faces = [] |
| 252 | + for face in faces[1]: |
| 253 | + x, y, w, h = face[:4] |
| 254 | + x = float(x) |
| 255 | + y = float(y) |
| 256 | + w = float(w) |
| 257 | + h = float(h) |
| 258 | + detected_faces.append( |
| 259 | + ( |
| 260 | + 100 * x / width, |
| 261 | + 100 * y / height, |
| 262 | + 100 * (x + w) / width, |
| 263 | + 100 * (y + h) / height, |
| 264 | + ) |
241 | 265 | )
|
242 |
| - for (x, y, w, h) in faces |
243 |
| - ] |
| 266 | + |
| 267 | + return detected_faces |
0 commit comments