|
|
|
|
|
import os |
|
|
from typing import Any, List |
|
|
|
|
|
|
|
|
import cv2 |
|
|
import numpy as np |
|
|
import gdown |
|
|
|
|
|
|
|
|
from deepface.commons import folder_utils |
|
|
from deepface.models.Detector import Detector, FacialAreaRegion |
|
|
from deepface.commons import logger as log |
|
|
|
|
|
logger = log.get_singletonish_logger() |
|
|
|
|
|
|
|
|
class YuNetClient(Detector): |
|
|
def __init__(self): |
|
|
self.model = self.build_model() |
|
|
|
|
|
def build_model(self) -> Any: |
|
|
""" |
|
|
Build a yunet detector model |
|
|
Returns: |
|
|
model (Any) |
|
|
""" |
|
|
|
|
|
opencv_version = cv2.__version__.split(".") |
|
|
if not len(opencv_version) >= 2: |
|
|
raise ValueError( |
|
|
f"OpenCv's version must have major and minor values but it is {opencv_version}" |
|
|
) |
|
|
|
|
|
opencv_version_major = int(opencv_version[0]) |
|
|
opencv_version_minor = int(opencv_version[1]) |
|
|
|
|
|
if opencv_version_major < 4 or (opencv_version_major == 4 and opencv_version_minor < 8): |
|
|
|
|
|
raise ValueError(f"YuNet requires opencv-python >= 4.8 but you have {cv2.__version__}") |
|
|
|
|
|
|
|
|
url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx" |
|
|
file_name = "face_detection_yunet_2023mar.onnx" |
|
|
home = folder_utils.get_deepface_home() |
|
|
if os.path.isfile(home + f"/.deepface/weights/{file_name}") is False: |
|
|
logger.info(f"{file_name} will be downloaded...") |
|
|
output = home + f"/.deepface/weights/{file_name}" |
|
|
gdown.download(url, output, quiet=False) |
|
|
|
|
|
try: |
|
|
face_detector = cv2.FaceDetectorYN_create( |
|
|
home + f"/.deepface/weights/{file_name}", "", (0, 0) |
|
|
) |
|
|
except Exception as err: |
|
|
raise ValueError( |
|
|
"Exception while calling opencv.FaceDetectorYN_create module." |
|
|
+ "This is an optional dependency." |
|
|
+ "You can install it as pip install opencv-contrib-python." |
|
|
) from err |
|
|
return face_detector |
|
|
|
|
|
def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]: |
|
|
""" |
|
|
Detect and align face with yunet |
|
|
|
|
|
Args: |
|
|
img (np.ndarray): pre-loaded image as numpy array |
|
|
|
|
|
Returns: |
|
|
results (List[FacialAreaRegion]): A list of FacialAreaRegion objects |
|
|
""" |
|
|
|
|
|
|
|
|
score_threshold = float(os.environ.get("yunet_score_threshold", "0.9")) |
|
|
resp = [] |
|
|
faces = [] |
|
|
height, width = img.shape[0], img.shape[1] |
|
|
|
|
|
|
|
|
resized = False |
|
|
r = 1 |
|
|
if height > 640 or width > 640: |
|
|
r = 640.0 / max(height, width) |
|
|
img = cv2.resize(img, (int(width * r), int(height * r))) |
|
|
height, width = img.shape[0], img.shape[1] |
|
|
resized = True |
|
|
self.model.setInputSize((width, height)) |
|
|
self.model.setScoreThreshold(score_threshold) |
|
|
_, faces = self.model.detect(img) |
|
|
if faces is None: |
|
|
return resp |
|
|
for face in faces: |
|
|
|
|
|
""" |
|
|
The detection output faces is a two-dimension array of type CV_32F, |
|
|
whose rows are the detected face instances, columns are the location |
|
|
of a face and 5 facial landmarks. |
|
|
The format of each row is as follows: |
|
|
x1, y1, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt, |
|
|
x_rcm, y_rcm, x_lcm, y_lcm, |
|
|
where x1, y1, w, h are the top-left coordinates, width and height of |
|
|
the face bounding box, |
|
|
{x, y}_{re, le, nt, rcm, lcm} stands for the coordinates of right eye, |
|
|
left eye, nose tip, the right corner and left corner of the mouth respectively. |
|
|
""" |
|
|
(x, y, w, h, x_le, y_le, x_re, y_re) = list(map(int, face[:8])) |
|
|
|
|
|
|
|
|
|
|
|
x = max(x, 0) |
|
|
y = max(y, 0) |
|
|
if resized: |
|
|
x, y, w, h = int(x / r), int(y / r), int(w / r), int(h / r) |
|
|
x_re, y_re, x_le, y_le = ( |
|
|
int(x_re / r), |
|
|
int(y_re / r), |
|
|
int(x_le / r), |
|
|
int(y_le / r), |
|
|
) |
|
|
confidence = float(face[-1]) |
|
|
|
|
|
facial_area = FacialAreaRegion( |
|
|
x=x, |
|
|
y=y, |
|
|
w=w, |
|
|
h=h, |
|
|
confidence=confidence, |
|
|
left_eye=(x_re, y_re), |
|
|
right_eye=(x_le, y_le), |
|
|
) |
|
|
resp.append(facial_area) |
|
|
return resp |
|
|
|