From 96d29ab069346ba8ea0ab6b053b2033c8d899395 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Wed, 31 Jan 2024 23:43:30 +0000 Subject: [PATCH] adding expand percentage argument for detection --- deepface/DeepFace.py | 20 +++++++++++ deepface/detectors/DetectorWrapper.py | 31 +++++++++++++---- deepface/detectors/Dlib.py | 25 +++++++++++--- deepface/detectors/FastMtCnn.py | 29 +++++++++++++--- deepface/detectors/MediaPipe.py | 29 +++++++++++++--- deepface/detectors/MtCnn.py | 29 +++++++++++++--- deepface/detectors/OpenCv.py | 27 ++++++++++++--- deepface/detectors/RetinaFace.py | 30 ++++++++++++----- deepface/detectors/Ssd.py | 48 +++++++++++++++++---------- deepface/detectors/Yolo.py | 27 ++++++++++++--- deepface/detectors/YuNet.py | 29 +++++++++++++--- deepface/models/Detector.py | 27 ++++++++++----- deepface/modules/demography.py | 4 +++ deepface/modules/detection.py | 10 +++++- deepface/modules/recognition.py | 19 +++++++++-- deepface/modules/representation.py | 4 +++ deepface/modules/verification.py | 5 +++ 17 files changed, 314 insertions(+), 79 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 4b395724..f3d4f693 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -58,6 +58,7 @@ def verify( distance_metric: str = "cosine", enforce_detection: bool = True, align: bool = True, + expand_percentage: int = 0, normalization: str = "base", ) -> Dict[str, Any]: """ @@ -83,6 +84,8 @@ def verify( align (bool): Flag to enable face alignment (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + normalization (string): Normalize the input image before feeding it to the model. Options: base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace (default is base) @@ -119,6 +122,7 @@ def verify( distance_metric=distance_metric, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, normalization=normalization, ) @@ -129,6 +133,7 @@ def analyze( enforce_detection: bool = True, detector_backend: str = "opencv", align: bool = True, + expand_percentage: int = 0, silent: bool = False, ) -> List[Dict[str, Any]]: """ @@ -152,6 +157,8 @@ def analyze( align (boolean): Perform alignment based on the eye positions (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + silent (boolean): Suppress or allow some log messages for a quieter analysis process (default is False). @@ -209,6 +216,7 @@ def analyze( enforce_detection=enforce_detection, detector_backend=detector_backend, align=align, + expand_percentage=expand_percentage, silent=silent, ) @@ -221,6 +229,7 @@ def find( enforce_detection: bool = True, detector_backend: str = "opencv", align: bool = True, + expand_percentage: int = 0, threshold: Optional[float] = None, normalization: str = "base", silent: bool = False, @@ -249,6 +258,8 @@ def find( align (boolean): Perform alignment based on the eye positions (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + threshold (float): Specify a threshold to determine whether a pair represents the same person or different individuals. This threshold is used for comparing distances. If left unset, default pre-tuned threshold values will be applied based on the specified @@ -286,6 +297,7 @@ def find( enforce_detection=enforce_detection, detector_backend=detector_backend, align=align, + expand_percentage=expand_percentage, threshold=threshold, normalization=normalization, silent=silent, @@ -298,6 +310,7 @@ def represent( enforce_detection: bool = True, detector_backend: str = "opencv", align: bool = True, + expand_percentage: int = 0, normalization: str = "base", ) -> List[Dict[str, Any]]: """ @@ -320,6 +333,8 @@ def represent( align (boolean): Perform alignment based on the eye positions (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + normalization (string): Normalize the input image before feeding it to the model. Default is base. Options: base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace (default is base). @@ -346,6 +361,7 @@ def represent( enforce_detection=enforce_detection, detector_backend=detector_backend, align=align, + expand_percentage=expand_percentage, normalization=normalization, ) @@ -409,6 +425,7 @@ def extract_faces( detector_backend: str = "opencv", enforce_detection: bool = True, align: bool = True, + expand_percentage: int = 0, grayscale: bool = False, ) -> List[Dict[str, Any]]: """ @@ -429,6 +446,8 @@ def extract_faces( align (bool): Flag to enable face alignment (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + grayscale (boolean): Flag to convert the image to grayscale before processing (default is False). @@ -448,6 +467,7 @@ def extract_faces( detector_backend=detector_backend, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, grayscale=grayscale, human_readable=True, ) diff --git a/deepface/detectors/DetectorWrapper.py b/deepface/detectors/DetectorWrapper.py index 54bc7a50..196fac3c 100644 --- a/deepface/detectors/DetectorWrapper.py +++ b/deepface/detectors/DetectorWrapper.py @@ -12,6 +12,9 @@ Yolo, YuNet, ) +from deepface.commons.logger import Logger + +logger = Logger(module="deepface/detectors/DetectorWrapper.py") def build_model(detector_backend: str) -> Any: @@ -52,19 +55,35 @@ def build_model(detector_backend: str) -> Any: return face_detector_obj[detector_backend] -def detect_faces(detector_backend: str, img: np.ndarray, align: bool = True) -> List[DetectedFace]: +def detect_faces( + detector_backend: str, img: np.ndarray, align: bool = True, expand_percentage: int = 0 +) -> List[DetectedFace]: """ Detect face(s) from a given image Args: detector_backend (str): detector name + img (np.ndarray): pre-loaded image - alig (bool): enable or disable alignment after detection + + align (bool): enable or disable alignment after detection + + expand_percentage (int): expand detected facial area with a percentage (default is 0). + Returns: results (List[DetectedFace]): A list of DetectedFace objects where each object contains: - - img (np.ndarray): The detected face as a NumPy array. - - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h - - confidence (float): The confidence score associated with the detected face. + + - img (np.ndarray): The detected face as a NumPy array. + + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + + - confidence (float): The confidence score associated with the detected face. """ face_detector: Detector = build_model(detector_backend) - return face_detector.detect_faces(img=img, align=align) + if expand_percentage < 0: + logger.warn( + f"Expand percentage cannot be negative but you set it to {expand_percentage}." + "Overwritten it to 0." + ) + expand_percentage = 0 + return face_detector.detect_faces(img=img, align=align, expand_percentage=expand_percentage) diff --git a/deepface/detectors/Dlib.py b/deepface/detectors/Dlib.py index a64068ac..9a66b06c 100644 --- a/deepface/detectors/Dlib.py +++ b/deepface/detectors/Dlib.py @@ -56,18 +56,27 @@ def build_model(self) -> dict: detector["sp"] = sp return detector - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with dlib + Args: - face_detector (Any): dlib face detector object - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace objects + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ # this is not a must dependency. do not import it in the global level. @@ -79,6 +88,12 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace "Please install using 'pip install dlib' " ) from e + if expand_percentage != 0: + logger.warn( + f"You set expand_percentage argument to {expand_percentage}," + "but dlib hog handles detection by itself" + ) + resp = [] sp = self.model["sp"] diff --git a/deepface/detectors/FastMtCnn.py b/deepface/detectors/FastMtCnn.py index a4942ee0..c6fd5229 100644 --- a/deepface/detectors/FastMtCnn.py +++ b/deepface/detectors/FastMtCnn.py @@ -12,17 +12,27 @@ class FastMtCnnClient(Detector): def __init__(self): self.model = self.build_model() - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with mtcnn + Args: - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace objects + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ resp = [] @@ -37,7 +47,16 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace for current_detection in zip(*detections): x, y, w, h = xyxy_to_xywh(current_detection[0]) - detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] + img_region = FacialAreaRegion(x=x, y=y, w=w, h=h) confidence = current_detection[1] diff --git a/deepface/detectors/MediaPipe.py b/deepface/detectors/MediaPipe.py index e9f112e3..fe6b7de0 100644 --- a/deepface/detectors/MediaPipe.py +++ b/deepface/detectors/MediaPipe.py @@ -29,17 +29,27 @@ def build_model(self) -> Any: face_detection = mp_face_detection.FaceDetection(min_detection_confidence=0.7) return face_detection - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with mediapipe + Args: - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace): A list of DetectedFace objects + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ resp = [] @@ -74,7 +84,16 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace # left_ear = (int(landmarks[5].x * img_width), int(landmarks[5].y * img_height)) if x > 0 and y > 0: - detected_face = img[y : y + h, x : x + w] + + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] + img_region = FacialAreaRegion(x=x, y=y, w=w, h=h) if align: diff --git a/deepface/detectors/MtCnn.py b/deepface/detectors/MtCnn.py index e1608d1a..308574f1 100644 --- a/deepface/detectors/MtCnn.py +++ b/deepface/detectors/MtCnn.py @@ -13,17 +13,27 @@ class MtCnnClient(Detector): def __init__(self): self.model = MTCNN() - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with mtcnn + Args: - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace objects + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ @@ -40,7 +50,16 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace for current_detection in detections: x, y, w, h = current_detection["box"] - detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] + img_region = FacialAreaRegion(x=x, y=y, w=w, h=h) confidence = current_detection["confidence"] diff --git a/deepface/detectors/OpenCv.py b/deepface/detectors/OpenCv.py index 353892c0..eafdb071 100644 --- a/deepface/detectors/OpenCv.py +++ b/deepface/detectors/OpenCv.py @@ -25,18 +25,27 @@ def build_model(self): detector["eye_detector"] = self.__build_cascade("haarcascade_eye") return detector - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with opencv + Args: - face_detector (Any): opencv face detector object - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ resp = [] @@ -56,7 +65,15 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace 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)] + + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] if align: left_eye, right_eye = self.find_eyes(img=detected_face) diff --git a/deepface/detectors/RetinaFace.py b/deepface/detectors/RetinaFace.py index 16b30c07..632d5e3f 100644 --- a/deepface/detectors/RetinaFace.py +++ b/deepface/detectors/RetinaFace.py @@ -9,17 +9,27 @@ class RetinaFaceClient(Detector): def __init__(self): self.model = rf.build_model() - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with retinaface + Args: - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace object + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ resp = [] @@ -38,10 +48,14 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace img_region = FacialAreaRegion(x=x, y=y, w=w, h=h) confidence = identity["score"] - # detected_face = img[int(y):int(y+h), int(x):int(x+w)] #opencv - detected_face = img[ - facial_area[1] : facial_area[3], facial_area[0] : facial_area[2] - ] + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] if align: landmarks = identity["landmarks"] diff --git a/deepface/detectors/Ssd.py b/deepface/detectors/Ssd.py index 8775b517..0c6ee9c1 100644 --- a/deepface/detectors/Ssd.py +++ b/deepface/detectors/Ssd.py @@ -71,17 +71,27 @@ def build_model(self) -> dict: return detector - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with ssd + Args: - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace object + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ resp = [] @@ -92,16 +102,14 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace target_size = (300, 300) - base_img = img.copy() # we will restore base_img to img later - original_size = img.shape - img = cv2.resize(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] - imageBlob = cv2.dnn.blobFromImage(image=img) + imageBlob = cv2.dnn.blobFromImage(image=current_img) face_detector = self.model["face_detector"] face_detector.setInput(imageBlob) @@ -126,17 +134,21 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace bottom = instance["bottom"] top = instance["top"] - detected_face = base_img[ - int(top * aspect_ratio_y) : int(bottom * aspect_ratio_y), - int(left * aspect_ratio_x) : int(right * aspect_ratio_x), - ] + x = int(left * aspect_ratio_x) + y = int(top * aspect_ratio_y) + w = int(right * aspect_ratio_x) - int(left * aspect_ratio_x) + h = int(bottom * aspect_ratio_y) - int(top * aspect_ratio_y) - face_region = FacialAreaRegion( - x=int(left * aspect_ratio_x), - y=int(top * aspect_ratio_y), - w=int(right * aspect_ratio_x) - int(left * aspect_ratio_x), - h=int(bottom * aspect_ratio_y) - int(top * aspect_ratio_y), - ) + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] + + face_region = FacialAreaRegion(x=x, y=y, w=w, h=h) confidence = instance["confidence"] diff --git a/deepface/detectors/Yolo.py b/deepface/detectors/Yolo.py index 79e1eaa7..e23d3c17 100644 --- a/deepface/detectors/Yolo.py +++ b/deepface/detectors/Yolo.py @@ -51,18 +51,27 @@ def build_model(self) -> Any: # Return face_detector return YOLO(weight_path) - def detect_faces(self, img: np.ndarray, align: bool = False) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = False, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with yolo + Args: - face_detector (Any): yolo face detector object - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ resp = [] @@ -78,7 +87,15 @@ def detect_faces(self, img: np.ndarray, align: bool = False) -> List[DetectedFac x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h) region = FacialAreaRegion(x=x, y=y, w=w, h=h) - detected_face = img[y : y + h, x : x + w].copy() + + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] if align: # Tuple of x,y and confidence for left eye diff --git a/deepface/detectors/YuNet.py b/deepface/detectors/YuNet.py index d079428f..f7728b6d 100644 --- a/deepface/detectors/YuNet.py +++ b/deepface/detectors/YuNet.py @@ -49,17 +49,27 @@ def build_model(self) -> Any: ) from err return face_detector - def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List[DetectedFace]: """ Detect and align face with yunet + Args: - img (np.ndarray): pre-loaded image - align (bool): default is true + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace objects + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: + - img (np.ndarray): The detected face as a NumPy array. + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + - confidence (float): The confidence score associated with the detected face. """ # FaceDetector.detect_faces does not support score_threshold parameter. @@ -115,7 +125,16 @@ def detect_faces(self, img: np.ndarray, align: bool = True) -> List[DetectedFace ) confidence = face[-1] confidence = f"{confidence:.2f}" - detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + + # expand the facial area to be extracted and stay within img.shape limits + x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left + y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top + w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right + h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] + img_region = FacialAreaRegion(x=x, y=y, w=w, h=h) if align: detected_face = detection.align_face(detected_face, (x_re, y_re), (x_le, y_le)) diff --git a/deepface/models/Detector.py b/deepface/models/Detector.py index 52d562a0..bd369d83 100644 --- a/deepface/models/Detector.py +++ b/deepface/models/Detector.py @@ -8,19 +8,28 @@ # pylint: disable=unnecessary-pass, too-few-public-methods class Detector(ABC): @abstractmethod - def detect_faces(self, img: np.ndarray, align: bool = True) -> List["DetectedFace"]: + def detect_faces( + self, img: np.ndarray, align: bool = True, expand_percentage: int = 0 + ) -> List["DetectedFace"]: """ - Detect faces from a given image + Interface for detect and align face + Args: - img (np.ndarray): pre-loaded image as a NumPy array - align (bool): enable or disable alignment after face detection + img (np.ndarray): pre-loaded image as numpy array + + align (bool): flag to enable or disable alignment after detection (default is True) + + expand_percentage (int): expand detected facial area with a percentage + Returns: - results (List[DetectedFace]): A list of DetectedFace object + results (List[Tuple[DetectedFace]): A list of DetectedFace objects where each object contains: - - face (np.ndarray): The detected face as a NumPy array. - - face_region (List[float]): The image region represented as - a list of floats e.g. [x, y, w, h] - - confidence (float): The confidence score associated with the detected face. + + - img (np.ndarray): The detected face as a NumPy array. + + - facial_area (FacialAreaRegion): The facial area region represented as x, y, w, h + + - confidence (float): The confidence score associated with the detected face. """ pass diff --git a/deepface/modules/demography.py b/deepface/modules/demography.py index 3372b69f..9f4516a2 100644 --- a/deepface/modules/demography.py +++ b/deepface/modules/demography.py @@ -16,6 +16,7 @@ def analyze( enforce_detection: bool = True, detector_backend: str = "opencv", align: bool = True, + expand_percentage: int = 0, silent: bool = False, ) -> List[Dict[str, Any]]: """ @@ -40,6 +41,8 @@ def analyze( align (boolean): Perform alignment based on the eye positions (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + silent (boolean): Suppress or allow some log messages for a quieter analysis process (default is False). @@ -120,6 +123,7 @@ def analyze( grayscale=False, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, ) for img_obj in img_objs: diff --git a/deepface/modules/detection.py b/deepface/modules/detection.py index 2085d764..97763b34 100644 --- a/deepface/modules/detection.py +++ b/deepface/modules/detection.py @@ -31,6 +31,7 @@ def extract_faces( detector_backend: str = "opencv", enforce_detection: bool = True, align: bool = True, + expand_percentage: int = 0, grayscale: bool = False, human_readable=False, ) -> List[Dict[str, Any]]: @@ -52,6 +53,8 @@ def extract_faces( align (bool): Flag to enable face alignment (default is True). + expand_percentage (int): expand detected facial area with a percentage + grayscale (boolean): Flag to convert the image to grayscale before processing (default is False). @@ -75,7 +78,12 @@ def extract_faces( if detector_backend == "skip": face_objs = [DetectedFace(img=img, facial_area=base_region, confidence=0)] else: - face_objs = DetectorWrapper.detect_faces(detector_backend, img, align) + face_objs = DetectorWrapper.detect_faces( + detector_backend=detector_backend, + img=img, + align=align, + expand_percentage=expand_percentage, + ) # in case of no face found if len(face_objs) == 0 and enforce_detection is True: diff --git a/deepface/modules/recognition.py b/deepface/modules/recognition.py index 6c21e350..04fbb673 100644 --- a/deepface/modules/recognition.py +++ b/deepface/modules/recognition.py @@ -26,6 +26,7 @@ def find( enforce_detection: bool = True, detector_backend: str = "opencv", align: bool = True, + expand_percentage: int = 0, threshold: Optional[float] = None, normalization: str = "base", silent: bool = False, @@ -55,6 +56,8 @@ def find( align (boolean): Perform alignment based on the eye positions. + expand_percentage (int): expand detected facial area with a percentage (default is 0). + threshold (float): Specify a threshold to determine whether a pair represents the same person or different individuals. This threshold is used for comparing distances. If left unset, default pre-tuned threshold values will be applied based on the specified @@ -211,6 +214,7 @@ def find( grayscale=False, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, ) resp_obj = [] @@ -309,6 +313,7 @@ def __find_bulk_embeddings( detector_backend: str = "opencv", enforce_detection: bool = True, align: bool = True, + expand_percentage: int = 0, normalization: str = "base", silent: bool = False, ): @@ -317,15 +322,24 @@ def __find_bulk_embeddings( Args: employees (list): list of exact image paths + model_name (str): facial recognition model name - target_size (tuple): expected input shape of facial - recognition model + + target_size (tuple): expected input shape of facial recognition model + detector_backend (str): face detector model name + enforce_detection (bool): set this to False if you want to proceed when you cannot detect any face + align (bool): enable or disable alignment of image before feeding to facial recognition model + + expand_percentage (int): expand detected facial area with a + percentage (default is 0). + normalization (bool): normalization technique + silent (bool): enable or disable informative logging Returns: representations (list): pivot list of embeddings with @@ -344,6 +358,7 @@ def __find_bulk_embeddings( grayscale=False, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, ) for img_obj in img_objs: diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index 2b76835a..00e65a1e 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -16,6 +16,7 @@ def represent( enforce_detection: bool = True, detector_backend: str = "opencv", align: bool = True, + expand_percentage: int = 0, normalization: str = "base", ) -> List[Dict[str, Any]]: """ @@ -37,6 +38,8 @@ def represent( align (boolean): Perform alignment based on the eye positions. + expand_percentage (int): expand detected facial area with a percentage (default is 0). + normalization (string): Normalize the input image before feeding it to the model. Default is base. Options: base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace @@ -69,6 +72,7 @@ def represent( grayscale=False, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, ) else: # skip # Try load. If load error, will raise exception internal diff --git a/deepface/modules/verification.py b/deepface/modules/verification.py index ab6c9dd5..e014ed39 100644 --- a/deepface/modules/verification.py +++ b/deepface/modules/verification.py @@ -19,6 +19,7 @@ def verify( distance_metric: str = "cosine", enforce_detection: bool = True, align: bool = True, + expand_percentage: int = 0, normalization: str = "base", ) -> Dict[str, Any]: """ @@ -49,6 +50,8 @@ def verify( align (bool): Flag to enable face alignment (default is True). + expand_percentage (int): expand detected facial area with a percentage (default is 0). + normalization (string): Normalize the input image before feeding it to the model. Options: base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace (default is base) @@ -91,6 +94,7 @@ def verify( grayscale=False, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, ) img2_objs = detection.extract_faces( @@ -100,6 +104,7 @@ def verify( grayscale=False, enforce_detection=enforce_detection, align=align, + expand_percentage=expand_percentage, ) # -------------------------------- distances = []