diff --git a/deepface/models/Detector.py b/deepface/models/Detector.py index 6db5e7b..e369c9d 100644 --- a/deepface/models/Detector.py +++ b/deepface/models/Detector.py @@ -1,37 +1,8 @@ from typing import List, Tuple, Optional, Union -from abc import ABC, abstractmethod +from abc import ABC from dataclasses import dataclass import numpy as np -# Notice that all facial detector models must be inherited from this class - - -# pylint: disable=unnecessary-pass, too-few-public-methods, too-many-instance-attributes -class Detector(ABC): - @abstractmethod - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]] - ) -> Union[List["FacialAreaRegion"], List[List["FacialAreaRegion"]]]: - """ - Interface for detect and align faces in a batch of images - - Args: - img (Union[np.ndarray, List[np.ndarray]]): - Pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]): - A list or a list of lists of FacialAreaRegion objects - where each object contains: - - - facial_area (FacialAreaRegion): The facial area region represented - as x, y, w, h, left_eye and right_eye. left eye and right eye are - eyes on the left and right respectively with respect to the person - instead of observer. - """ - pass - @dataclass class FacialAreaRegion: @@ -77,3 +48,57 @@ class DetectedFace: img: np.ndarray facial_area: FacialAreaRegion confidence: float + + +# Notice that all facial detector models must be inherited from this class + +# pylint: disable=unnecessary-pass, too-few-public-methods, too-many-instance-attributes +class Detector(ABC): + + def detect_faces( + self, + img: Union[np.ndarray, List[np.ndarray]], + ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: + """ + Detect and align faces in an image or a list of images + + Args: + img (Union[np.ndarray, List[np.ndarray]]): + pre-loaded image as numpy array or a list of those + + Returns: + results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): + A list or a list of lists of FacialAreaRegion objects + """ + is_batched_input = isinstance(img, list) + if not is_batched_input: + img = [img] + results = [self._process_single_image(single_img) for single_img in img] + if not is_batched_input: + return results[0] + return results + + def _process_single_image( + self, + img: np.ndarray + ) -> List[FacialAreaRegion]: + """ + Interface for detect and align faces in a single image + + Args: + img (Union[np.ndarray, List[np.ndarray]]): + Pre-loaded image as numpy array or a list of those + + Returns: + results (Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]): + A list or a list of lists of FacialAreaRegion objects + where each object contains: + + - facial_area (FacialAreaRegion): The facial area region represented + as x, y, w, h, left_eye and right_eye. left eye and right eye are + eyes on the left and right respectively with respect to the person + instead of observer. + """ + raise NotImplementedError( + "Subclasses that do not implement batch detection must implement this method" + ) diff --git a/deepface/models/face_detection/CenterFace.py b/deepface/models/face_detection/CenterFace.py index 523f2bf..e5ef2be 100644 --- a/deepface/models/face_detection/CenterFace.py +++ b/deepface/models/face_detection/CenterFace.py @@ -1,6 +1,6 @@ # built-in dependencies import os -from typing import List, Union +from typing import List # 3rd party dependencies import numpy as np @@ -34,35 +34,12 @@ class CenterFaceClient(Detector): return CenterFace(weight_path=weights_path) - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]], - ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: - """ - Detect and align face with CenterFace - - Args: - img (Union[np.ndarray, List[np.ndarray]]): - pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list or a list of lists of FacialAreaRegion objects - """ - is_batched_input = isinstance(img, list) - if not is_batched_input: - img = [img] - results = [self._process_single_image(single_img) for single_img in img] - if not is_batched_input: - return results[0] - return results - - def _process_single_image(self, single_img: np.ndarray) -> List[FacialAreaRegion]: + def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Helper function to detect faces in a single image. Args: - single_img (np.ndarray): pre-loaded image as numpy array + img (np.ndarray): pre-loaded image as numpy array Returns: results (List[FacialAreaRegion]): A list of FacialAreaRegion objects @@ -76,7 +53,7 @@ class CenterFaceClient(Detector): # img, img.shape[0], img.shape[1], threshold=threshold # ) detections, landmarks = self.build_model().forward( - single_img, single_img.shape[0], single_img.shape[1], threshold=threshold + img, img.shape[0], img.shape[1], threshold=threshold ) for i, detection in enumerate(detections): diff --git a/deepface/models/face_detection/Dlib.py b/deepface/models/face_detection/Dlib.py index 254a32b..e8afce2 100644 --- a/deepface/models/face_detection/Dlib.py +++ b/deepface/models/face_detection/Dlib.py @@ -1,5 +1,5 @@ # built-in dependencies -from typing import List, Union +from typing import List # 3rd party dependencies import numpy as np @@ -47,29 +47,6 @@ class DlibClient(Detector): detector["sp"] = sp return detector - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]], - ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: - """ - Detect and align face with dlib - - Args: - img (Union[np.ndarray, List[np.ndarray]]): - pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list or a list of lists of FacialAreaRegion objects - """ - is_batched_input = isinstance(img, list) - if not is_batched_input: - img = [img] - results = [self._process_single_image(single_img) for single_img in img] - if not is_batched_input: - return results[0] - return results - def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Helper function to detect faces in a single image. diff --git a/deepface/models/face_detection/FastMtCnn.py b/deepface/models/face_detection/FastMtCnn.py index 7d5ccf5..2499dc7 100644 --- a/deepface/models/face_detection/FastMtCnn.py +++ b/deepface/models/face_detection/FastMtCnn.py @@ -17,29 +17,6 @@ class FastMtCnnClient(Detector): def __init__(self): self.model = self.build_model() - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]], - ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: - """ - Detect and align face with mtcnn - - Args: - img (Union[np.ndarray, List[np.ndarray]]): - pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list or a list of lists of FacialAreaRegion objects - """ - is_batched_input = isinstance(img, list) - if not is_batched_input: - img = [img] - results = [self._process_single_image(single_img) for single_img in img] - if not is_batched_input: - return results[0] - return results - def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Helper function to detect faces in a single image. diff --git a/deepface/models/face_detection/MediaPipe.py b/deepface/models/face_detection/MediaPipe.py index 9fcdbbc..61bb0b5 100644 --- a/deepface/models/face_detection/MediaPipe.py +++ b/deepface/models/face_detection/MediaPipe.py @@ -1,6 +1,6 @@ # built-in dependencies import os -from typing import Any, List, Union +from typing import Any, List # 3rd party dependencies import numpy as np @@ -43,29 +43,6 @@ class MediaPipeClient(Detector): ) return face_detection - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]], - ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: - """ - Detect and align face with mediapipe - - Args: - img (Union[np.ndarray, List[np.ndarray]]): - pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list or a list of lists of FacialAreaRegion objects - """ - is_batched_input = isinstance(img, list) - if not is_batched_input: - img = [img] - results = [self._process_single_image(single_img) for single_img in img] - if not is_batched_input: - return results[0] - return results - def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Helper function to detect faces in a single image. diff --git a/deepface/models/face_detection/OpenCv.py b/deepface/models/face_detection/OpenCv.py index c86f2da..801ff11 100644 --- a/deepface/models/face_detection/OpenCv.py +++ b/deepface/models/face_detection/OpenCv.py @@ -1,6 +1,6 @@ # built-in dependencies import os -from typing import Any, List, Union +from typing import Any, List import logging # 3rd party dependencies @@ -45,65 +45,48 @@ class OpenCvClient(Detector): ) return supports_batch_detection - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]] - ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: + def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ - Detect and align face with opencv + Helper function to detect faces in a single image. Args: - img (Union[np.ndarray, List[np.ndarray]]): - Pre-loaded image as numpy array or a list of those + img (np.ndarray): pre-loaded image as numpy array Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list or a list of lists of FacialAreaRegion objects + results (List[FacialAreaRegion]): A list of FacialAreaRegion objects """ - if isinstance(img, np.ndarray): - imgs = [img] - elif self.supports_batch_detection: - imgs = img - else: - return [self.detect_faces(single_img) for single_img in img] + resp = [] + detected_face = None + faces = [] + try: + faces, _, scores = self.model["face_detector"].detectMultiScale3( + img, 1.1, 10, outputRejectLevels=True + ) + except: + pass - batch_results = [] + if len(faces) > 0: + for (x, y, w, h), confidence in zip(faces, scores): + detected_face = img[int(y):int(y + h), int(x):int(x + w)] + left_eye, right_eye = self.find_eyes(img=detected_face) - for single_img in imgs: - resp = [] - detected_face = None - faces = [] - try: - faces, _, scores = self.model["face_detector"].detectMultiScale3( - single_img, 1.1, 10, outputRejectLevels=True + if left_eye is not None: + left_eye = (int(x + left_eye[0]), int(y + left_eye[1])) + if right_eye is not None: + right_eye = (int(x + right_eye[0]), int(y + right_eye[1])) + + facial_area = FacialAreaRegion( + x=x, + y=y, + w=w, + h=h, + left_eye=left_eye, + right_eye=right_eye, + confidence=(100 - confidence) / 100, ) - except: - pass + resp.append(facial_area) - if len(faces) > 0: - for (x, y, w, h), confidence in zip(faces, scores): - detected_face = single_img[int(y):int(y + h), int(x):int(x + w)] - left_eye, right_eye = self.find_eyes(img=detected_face) - - if left_eye is not None: - left_eye = (int(x + left_eye[0]), int(y + left_eye[1])) - if right_eye is not None: - right_eye = (int(x + right_eye[0]), int(y + right_eye[1])) - - facial_area = FacialAreaRegion( - x=x, - y=y, - w=w, - h=h, - left_eye=left_eye, - right_eye=right_eye, - confidence=(100 - confidence) / 100, - ) - resp.append(facial_area) - - batch_results.append(resp) - - return batch_results if len(batch_results) > 1 else batch_results[0] + return resp def find_eyes(self, img: np.ndarray) -> tuple: """ diff --git a/deepface/models/face_detection/Ssd.py b/deepface/models/face_detection/Ssd.py index a620f96..381d664 100644 --- a/deepface/models/face_detection/Ssd.py +++ b/deepface/models/face_detection/Ssd.py @@ -1,5 +1,5 @@ # built-in dependencies -from typing import List, Union +from typing import List from enum import IntEnum # 3rd party dependencies @@ -54,48 +54,25 @@ class SsdClient(Detector): return {"face_detector": face_detector, "opencv_module": OpenCv.OpenCvClient()} - def detect_faces( - self, - img: Union[np.ndarray, List[np.ndarray]] - ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: - """ - Detect and align faces with ssd in a batch of images - - Args: - img (Union[np.ndarray, List[np.ndarray]]): - Pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list or a list of lists of FacialAreaRegion objects - """ - is_batched_input = isinstance(img, list) - if not is_batched_input: - img = [img] - results = [self._process_single_image(single_img) for single_img in img] - if not is_batched_input: - return results[0] - return results - - def _process_single_image(self, single_img: np.ndarray) -> List[FacialAreaRegion]: + def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Helper function to detect faces in a single image. Args: - single_img (np.ndarray): Pre-loaded image as numpy array + img (np.ndarray): Pre-loaded image as numpy array Returns: results (List[FacialAreaRegion]): A list of FacialAreaRegion objects """ # Because cv2.dnn.blobFromImage expects CV_8U (8-bit unsigned integer) values - if single_img.dtype != np.uint8: - single_img = single_img.astype(np.uint8) + if img.dtype != np.uint8: + img = img.astype(np.uint8) opencv_module: OpenCv.OpenCvClient = self.model["opencv_module"] target_size = (300, 300) - original_size = single_img.shape - current_img = cv2.resize(single_img, target_size) + original_size = img.shape + current_img = cv2.resize(img, target_size) aspect_ratio_x = original_size[1] / target_size[1] aspect_ratio_y = original_size[0] / target_size[0] @@ -132,7 +109,7 @@ class SsdClient(Detector): for face in faces: confidence = float(face[ssd_labels.confidence]) x, y, w, h = map(int, face[margins]) - detected_face = single_img[y : y + h, x : x + w] + detected_face = img[y : y + h, x : x + w] left_eye, right_eye = opencv_module.find_eyes(detected_face) diff --git a/deepface/models/face_detection/Yolo.py b/deepface/models/face_detection/Yolo.py index cbb8879..300bc45 100644 --- a/deepface/models/face_detection/Yolo.py +++ b/deepface/models/face_detection/Yolo.py @@ -78,7 +78,8 @@ class YoloDetectorClient(Detector): A list of lists of FacialAreaRegion objects for each image or a list of FacialAreaRegion objects """ - if not isinstance(img, list): + is_batched_input = isinstance(img, list) + if not is_batched_input: img = [img] all_results = [] @@ -142,7 +143,7 @@ class YoloDetectorClient(Detector): all_results.append(resp) - if len(all_results) == 1: + if not is_batched_input: return all_results[0] return all_results diff --git a/deepface/models/face_detection/YuNet.py b/deepface/models/face_detection/YuNet.py index 93e65a8..4b4b939 100644 --- a/deepface/models/face_detection/YuNet.py +++ b/deepface/models/face_detection/YuNet.py @@ -1,6 +1,6 @@ # built-in dependencies import os -from typing import Any, List, Union +from typing import Any, List # 3rd party dependencies import cv2 @@ -57,24 +57,6 @@ class YuNetClient(Detector): ) from err return face_detector - def detect_faces(self, img: Union[np.ndarray, List[np.ndarray]]) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: - """ - Detect and align face with yunet - - Args: - img (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those - - Returns: - results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): A list or a list of lists of FacialAreaRegion objects - """ - is_batched_input = isinstance(img, list) - if not is_batched_input: - img = [img] - results = [self._process_single_image(single_img) for single_img in img] - if not is_batched_input: - return results[0] - return results - def _process_single_image(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Helper function to detect faces in a single image.