diff --git a/deepface/models/Detector.py b/deepface/models/Detector.py index 70675cd..6db5e7b 100644 --- a/deepface/models/Detector.py +++ b/deepface/models/Detector.py @@ -10,14 +10,15 @@ import numpy as np class Detector(ABC): @abstractmethod def detect_faces( - self, - imgs: Union[np.ndarray, List[np.ndarray]] + 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: - imgs (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those + 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]]): diff --git a/deepface/models/face_detection/MtCnn.py b/deepface/models/face_detection/MtCnn.py index 8f4c624..de43b96 100644 --- a/deepface/models/face_detection/MtCnn.py +++ b/deepface/models/face_detection/MtCnn.py @@ -18,9 +18,8 @@ class MtCnnClient(Detector): self.model = MTCNN() def detect_faces( - self, - img: Union[np.ndarray, - List[np.ndarray]] + self, + img: Union[np.ndarray, List[np.ndarray]] ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: """ Detect and align faces with mtcnn for a list of images @@ -31,7 +30,8 @@ class MtCnnClient(Detector): Returns: results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): - A list of FacialAreaRegion objects for a single image or a list of lists of FacialAreaRegion objects for each image + A list of FacialAreaRegion objects for a single image + or a list of lists of FacialAreaRegion objects for each image """ if not isinstance(img, list): diff --git a/deepface/models/face_detection/OpenCv.py b/deepface/models/face_detection/OpenCv.py index ac376b8..f97f658 100644 --- a/deepface/models/face_detection/OpenCv.py +++ b/deepface/models/face_detection/OpenCv.py @@ -29,35 +29,42 @@ class OpenCvClient(Detector): detector["eye_detector"] = self.__build_cascade("haarcascade_eye") return detector - def detect_faces(self, imgs: Union[np.ndarray, List[np.ndarray]]) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: + def detect_faces( + self, + img: Union[np.ndarray, List[np.ndarray]] + ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: """ Detect and align face with opencv Args: - imgs (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those + 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 + results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): + A list or a list of lists of FacialAreaRegion objects """ - if isinstance(imgs, np.ndarray): - imgs = [imgs] + if isinstance(img, np.ndarray): + imgs = [img] + else: + imgs = img batch_results = [] - for img in imgs: + for single_img in imgs: resp = [] detected_face = None faces = [] try: faces, _, scores = self.model["face_detector"].detectMultiScale3( - img, 1.1, 10, outputRejectLevels=True + single_img, 1.1, 10, outputRejectLevels=True ) except: pass 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)] + 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: diff --git a/deepface/models/face_detection/RetinaFace.py b/deepface/models/face_detection/RetinaFace.py index ca98a54..b0d1c94 100644 --- a/deepface/models/face_detection/RetinaFace.py +++ b/deepface/models/face_detection/RetinaFace.py @@ -13,15 +13,20 @@ class RetinaFaceClient(Detector): def __init__(self): self.model = rf.build_model() - def detect_faces(self, img: Union[np.ndarray, List[np.ndarray]]) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: + def detect_faces( + self, + img: Union[np.ndarray, List[np.ndarray]] + ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: """ - Detect and align faces with retinaface in an image or a list of images + Detect and align faces with retinaface in a batch of images Args: - img (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those + 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 + results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): + A list or a list of lists of FacialAreaRegion objects """ if isinstance(img, np.ndarray): imgs = [img] @@ -30,9 +35,9 @@ class RetinaFaceClient(Detector): batch_results = [] - for img in imgs: + for single_img in imgs: resp = [] - obj = rf.detect_faces(img, model=self.model, threshold=0.9) + obj = rf.detect_faces(single_img, model=self.model, threshold=0.9) if isinstance(obj, dict): for face_idx in obj.keys(): diff --git a/deepface/models/face_detection/Ssd.py b/deepface/models/face_detection/Ssd.py index 449144f..c0ae2cb 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 +from typing import List, Union from enum import IntEnum # 3rd party dependencies @@ -54,83 +54,96 @@ class SsdClient(Detector): return {"face_detector": face_detector, "opencv_module": OpenCv.OpenCvClient()} - def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]: + def detect_faces( + self, + img: Union[np.ndarray, List[np.ndarray]] + ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: """ - Detect and align face with ssd + Detect and align faces with ssd in a batch of images Args: - img (np.ndarray): pre-loaded image as numpy array + img (Union[np.ndarray, List[np.ndarray]]): + Pre-loaded image as numpy array or a list of those Returns: - results (List[FacialAreaRegion]): A list of FacialAreaRegion objects + results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): + A list or a list of lists of FacialAreaRegion objects """ + if isinstance(img, np.ndarray): + imgs = [img] + else: + imgs = img - # Because cv2.dnn.blobFromImage expects CV_8U (8-bit unsigned integer) values - if img.dtype != np.uint8: - img = img.astype(np.uint8) + batch_results = [] - opencv_module: OpenCv.OpenCvClient = self.model["opencv_module"] + for single_img in imgs: + # 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) - target_size = (300, 300) + opencv_module: OpenCv.OpenCvClient = self.model["opencv_module"] - original_size = img.shape + target_size = (300, 300) + original_size = single_img.shape + current_img = cv2.resize(single_img, target_size) - 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] - aspect_ratio_x = original_size[1] / target_size[1] - aspect_ratio_y = original_size[0] / target_size[0] + imageBlob = cv2.dnn.blobFromImage(image=current_img) - imageBlob = cv2.dnn.blobFromImage(image=current_img) + face_detector = self.model["face_detector"] + face_detector.setInput(imageBlob) + detections = face_detector.forward() - face_detector = self.model["face_detector"] - face_detector.setInput(imageBlob) - detections = face_detector.forward() + class ssd_labels(IntEnum): + img_id = 0 + is_face = 1 + confidence = 2 + left = 3 + top = 4 + right = 5 + bottom = 6 - class ssd_labels(IntEnum): - img_id = 0 - is_face = 1 - confidence = 2 - left = 3 - top = 4 - right = 5 - bottom = 6 - - faces = detections[0][0] - faces = faces[ - (faces[:, ssd_labels.is_face] == 1) & (faces[:, ssd_labels.confidence] >= 0.90) - ] - margins = [ssd_labels.left, ssd_labels.top, ssd_labels.right, ssd_labels.bottom] - faces[:, margins] = np.int32(faces[:, margins] * 300) - faces[:, margins] = np.int32( - faces[:, margins] * [aspect_ratio_x, aspect_ratio_y, aspect_ratio_x, aspect_ratio_y] - ) - faces[:, [ssd_labels.right, ssd_labels.bottom]] -= faces[ - :, [ssd_labels.left, ssd_labels.top] - ] - - resp = [] - for face in faces: - confidence = float(face[ssd_labels.confidence]) - x, y, w, h = map(int, face[margins]) - detected_face = img[y : y + h, x : x + w] - - left_eye, right_eye = opencv_module.find_eyes(detected_face) - - # eyes found in the detected face instead image itself - # detected face's coordinates should be added - if left_eye is not None: - left_eye = x + int(left_eye[0]), y + int(left_eye[1]) - if right_eye is not None: - right_eye = x + int(right_eye[0]), y + int(right_eye[1]) - - facial_area = FacialAreaRegion( - x=x, - y=y, - w=w, - h=h, - left_eye=left_eye, - right_eye=right_eye, - confidence=confidence, + faces = detections[0][0] + faces = faces[ + (faces[:, ssd_labels.is_face] == 1) & (faces[:, ssd_labels.confidence] >= 0.90) + ] + margins = [ssd_labels.left, ssd_labels.top, ssd_labels.right, ssd_labels.bottom] + faces[:, margins] = np.int32(faces[:, margins] * 300) + faces[:, margins] = np.int32( + faces[:, margins] * [aspect_ratio_x, aspect_ratio_y, aspect_ratio_x, aspect_ratio_y] ) - resp.append(facial_area) - return resp + faces[:, [ssd_labels.right, ssd_labels.bottom]] -= faces[ + :, [ssd_labels.left, ssd_labels.top] + ] + + resp = [] + 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] + + left_eye, right_eye = opencv_module.find_eyes(detected_face) + + # eyes found in the detected face instead image itself + # detected face's coordinates should be added + if left_eye is not None: + left_eye = x + int(left_eye[0]), y + int(left_eye[1]) + if right_eye is not None: + right_eye = x + int(right_eye[0]), y + int(right_eye[1]) + + facial_area = FacialAreaRegion( + x=x, + y=y, + w=w, + h=h, + left_eye=left_eye, + right_eye=right_eye, + confidence=confidence, + ) + resp.append(facial_area) + + batch_results.append(resp) + + return batch_results if len(batch_results) > 1 else batch_results[0] diff --git a/deepface/models/face_detection/Yolo.py b/deepface/models/face_detection/Yolo.py index 34fdb01..cbb8879 100644 --- a/deepface/models/face_detection/Yolo.py +++ b/deepface/models/face_detection/Yolo.py @@ -1,6 +1,6 @@ # built-in dependencies import os -from typing import List, Any, Union, Tuple +from typing import List, Any, Union from enum import Enum # 3rd party dependencies @@ -62,25 +62,30 @@ class YoloDetectorClient(Detector): # Return face_detector return YOLO(weight_file) - def detect_faces(self, imgs: Union[np.ndarray, List[np.ndarray]]) -> Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]: + def detect_faces( + self, + img: Union[np.ndarray, List[np.ndarray]] + ) -> Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]: """ - Detect and align faces in an image or a list of images with yolo + Detect and align faces in a batch of images with yolo Args: - imgs (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those + 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 of lists of FacialAreaRegion objects for each image or a list of FacialAreaRegion objects + A list of lists of FacialAreaRegion objects + for each image or a list of FacialAreaRegion objects """ - if not isinstance(imgs, list): - imgs = [imgs] + if not isinstance(img, list): + img = [img] all_results = [] # Detect faces for all images results_list = self.model.predict( - imgs, + img, verbose=False, show=False, conf=float(os.getenv("YOLO_MIN_DETECTION_CONFIDENCE", "0.25")), @@ -113,9 +118,16 @@ class YoloDetectorClient(Detector): # eyes are list of float, need to cast them tuple of int # Ensure eyes are tuples of exactly two integers or None - left_eye = tuple(map(int, left_eye[:2])) if left_eye and len(left_eye) == 2 else None - right_eye = tuple(map(int, right_eye[:2])) if right_eye and len(right_eye) == 2 else None - + left_eye = ( + tuple(map(int, left_eye[:2])) + if left_eye and len(left_eye) == 2 + else None + ) + right_eye = ( + tuple(map(int, right_eye[:2])) + if right_eye and len(right_eye) == 2 + else None + ) x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h) facial_area = FacialAreaRegion( x=x, diff --git a/deepface/modules/detection.py b/deepface/modules/detection.py index 88cd63e..5d974df 100644 --- a/deepface/modules/detection.py +++ b/deepface/modules/detection.py @@ -34,8 +34,9 @@ def extract_faces( Extract faces from a given image or list of images Args: - img_paths (List[str or np.ndarray or IO[bytes]] or str or np.ndarray or IO[bytes]): Path(s) to the image(s). Accepts exact image path - as a string, numpy array (BGR), a file object that supports at least `.read` and is + img_paths (List[str or np.ndarray or IO[bytes]] or str or np.ndarray or IO[bytes]): + Path(s) to the image(s) as a string, + numpy array (BGR), a file object that supports at least `.read` and is opened in binary mode, or base64 encoded images. detector_backend (string): face detector backend. Options: 'opencv', 'retinaface', @@ -148,7 +149,10 @@ def extract_faces( elif color_face == "gray": current_img = cv2.cvtColor(current_img, cv2.COLOR_BGR2GRAY) else: - raise ValueError(f"The color_face can be rgb, bgr or gray, but it is {color_face}.") + raise ValueError( + f"The color_face can be rgb, bgr or gray, " + f"but it is {color_face}." + ) if normalize_face: current_img = current_img / 255 # normalize input in [0, 1] @@ -184,7 +188,10 @@ def extract_faces( if anti_spoofing is True: antispoof_model = modeling.build_model(task="spoofing", model_name="Fasnet") - is_real, antispoof_score = antispoof_model.analyze(img=img, facial_area=(x, y, w, h)) + is_real, antispoof_score = antispoof_model.analyze( + img=img, + facial_area=(x, y, w, h) + ) resp_obj["is_real"] = is_real resp_obj["antispoof_score"] = antispoof_score @@ -226,10 +233,18 @@ def detect_faces( """ if not isinstance(img, list): img = [img] - + if detector_backend == "skip": all_face_objs = [ - [DetectedFace(img=single_img, facial_area=FacialAreaRegion(x=0, y=0, w=single_img.shape[1], h=single_img.shape[0]), confidence=0)] + [ + DetectedFace( + img=single_img, + facial_area=FacialAreaRegion( + x=0, y=0, w=single_img.shape[1], h=single_img.shape[0] + ), + confidence=0, + ) + ] for single_img in img ] if len(img) == 1: @@ -280,7 +295,17 @@ def detect_faces( all_facial_areas = [all_facial_areas] all_detected_faces = [] - for single_img, facial_areas, width_border, height_border in zip(preprocessed_images, all_facial_areas, width_borders, height_borders): + for ( + single_img, + facial_areas, + width_border, + height_border + ) in zip( + preprocessed_images, + all_facial_areas, + width_borders, + height_borders + ): if not isinstance(facial_areas, list): facial_areas = [facial_areas]