From f23ab85fd193cdcd60e24c0881b2799ce2bd733a Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 24 Dec 2023 14:38:14 +0000 Subject: [PATCH] using type hinting for backend functions --- deepface/DeepFace.py | 10 ++-- deepface/basemodels/ArcFace.py | 14 ++--- deepface/basemodels/DeepID.py | 2 +- deepface/basemodels/DlibResNet.py | 12 +++-- deepface/basemodels/DlibWrapper.py | 3 +- deepface/basemodels/Facenet.py | 7 +-- deepface/basemodels/Facenet512.py | 11 +++- deepface/basemodels/FbDeepFace.py | 2 +- deepface/basemodels/OpenFace.py | 2 +- deepface/basemodels/SFace.py | 6 ++- deepface/basemodels/VGGFace.py | 14 +---- deepface/commons/distance.py | 19 +++++-- deepface/commons/functions.py | 51 ++++++++++-------- deepface/commons/logger.py | 3 +- deepface/detectors/DlibWrapper.py | 21 ++++++-- deepface/detectors/FaceDetector.py | 70 +++++++++++++++++++++---- deepface/detectors/FastMtcnnWrapper.py | 23 ++++++-- deepface/detectors/MediapipeWrapper.py | 20 ++++++- deepface/detectors/MtcnnWrapper.py | 20 ++++++- deepface/detectors/OpenCvWrapper.py | 27 ++++++++-- deepface/detectors/RetinaFaceWrapper.py | 34 +++++++----- deepface/detectors/SsdWrapper.py | 20 +++++-- deepface/detectors/YoloWrapper.py | 21 ++++++-- deepface/detectors/YunetWrapper.py | 33 ++++++++---- deepface/extendedmodels/Age.py | 6 +-- deepface/extendedmodels/Emotion.py | 4 +- deepface/extendedmodels/Gender.py | 4 +- deepface/extendedmodels/Race.py | 4 +- tests/test_represent.py | 2 +- 29 files changed, 335 insertions(+), 130 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 69a783d0..7a83587c 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -889,8 +889,12 @@ def extract_faces( @deprecated(version="0.0.78", reason="Use DeepFace.extract_faces instead of DeepFace.detectFace") def detectFace( - img_path, target_size=(224, 224), detector_backend="opencv", enforce_detection=True, align=True -): + img_path: Union[str, np.ndarray], + target_size: tuple = (224, 224), + detector_backend: str = "opencv", + enforce_detection: bool = True, + align: bool = True, +) -> np.ndarray: """ Deprecated function. Use extract_faces for same functionality. @@ -942,7 +946,7 @@ def detectFace( functions.initialize_folder() -def cli(): +def cli() -> None: """ command line interface function will be offered in this block """ diff --git a/deepface/basemodels/ArcFace.py b/deepface/basemodels/ArcFace.py index 07044eef..b3059bda 100644 --- a/deepface/basemodels/ArcFace.py +++ b/deepface/basemodels/ArcFace.py @@ -14,8 +14,8 @@ tf_version = int(tf.__version__.split(".", maxsplit=1)[0]) if tf_version == 1: + from keras.models import Model from keras.engine import training - import keras from keras.layers import ( ZeroPadding2D, Input, @@ -28,8 +28,8 @@ Dense, ) else: + from tensorflow.keras.models import Model from tensorflow.python.keras.engine import training - from tensorflow import keras from tensorflow.keras.layers import ( ZeroPadding2D, Input, @@ -41,15 +41,11 @@ Flatten, Dense, ) -# -------------------------------- - - -# url = "https://drive.google.com/uc?id=1LVB3CdVejpmGHM28BpqqkbZP5hDEcdZY" def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/arcface_weights.h5", -): +) -> Model: base_model = ResNet34() inputs = base_model.inputs[0] arcface_model = base_model.outputs[0] @@ -62,7 +58,7 @@ def loadModel( embedding = BatchNormalization(momentum=0.9, epsilon=2e-5, name="embedding", scale=True)( arcface_model ) - model = keras.models.Model(inputs, embedding, name=base_model.name) + model = Model(inputs, embedding, name=base_model.name) # --------------------------------------- # check the availability of pre-trained weights @@ -84,7 +80,7 @@ def loadModel( return model -def ResNet34(): +def ResNet34() -> Model: img_input = Input(shape=(112, 112, 3)) diff --git a/deepface/basemodels/DeepID.py b/deepface/basemodels/DeepID.py index 870cce72..fa128b08 100644 --- a/deepface/basemodels/DeepID.py +++ b/deepface/basemodels/DeepID.py @@ -41,7 +41,7 @@ def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/deepid_keras_weights.h5", -): +) -> Model: myInput = Input(shape=(55, 47, 3)) diff --git a/deepface/basemodels/DlibResNet.py b/deepface/basemodels/DlibResNet.py index 643b3939..3cc57f10 100644 --- a/deepface/basemodels/DlibResNet.py +++ b/deepface/basemodels/DlibResNet.py @@ -13,8 +13,14 @@ class DlibResNet: def __init__(self): - # this is not a must dependency - import dlib # 19.20.0 + ## this is not a must dependency. do not import it in the global level. + try: + import dlib + except ModuleNotFoundError as e: + raise ImportError( + "Dlib is an optional dependency, ensure the library is installed." + "Please install using 'pip install dlib' " + ) from e self.layers = [DlibMetaData()] @@ -49,7 +55,7 @@ def __init__(self): # return None # classes must return None - def predict(self, img_aligned): + def predict(self, img_aligned: np.ndarray) -> np.ndarray: # functions.detectFace returns 4 dimensional images if len(img_aligned.shape) == 4: diff --git a/deepface/basemodels/DlibWrapper.py b/deepface/basemodels/DlibWrapper.py index 648ddcf4..51f0a0b9 100644 --- a/deepface/basemodels/DlibWrapper.py +++ b/deepface/basemodels/DlibWrapper.py @@ -1,5 +1,6 @@ +from typing import Any from deepface.basemodels.DlibResNet import DlibResNet -def loadModel(): +def loadModel() -> Any: return DlibResNet() diff --git a/deepface/basemodels/Facenet.py b/deepface/basemodels/Facenet.py index bdf92a71..1ad4f879 100644 --- a/deepface/basemodels/Facenet.py +++ b/deepface/basemodels/Facenet.py @@ -47,7 +47,7 @@ def scaling(x, scale): return x * scale -def InceptionResNetV2(dimension=128): +def InceptionResNetV2(dimension=128) -> Model: inputs = Input(shape=(160, 160, 3)) x = Conv2D(32, 3, strides=2, padding="valid", use_bias=False, name="Conv2d_1a_3x3")(inputs) @@ -1618,12 +1618,9 @@ def InceptionResNetV2(dimension=128): return model -# url = 'https://drive.google.com/uc?id=1971Xk5RwedbudGgTIrGAL4F7Aifu7id1' - - def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/facenet_weights.h5", -): +) -> Model: model = InceptionResNetV2() # ----------------------------------- diff --git a/deepface/basemodels/Facenet512.py b/deepface/basemodels/Facenet512.py index 7e2b27c3..95aca65e 100644 --- a/deepface/basemodels/Facenet512.py +++ b/deepface/basemodels/Facenet512.py @@ -1,14 +1,23 @@ import os import gdown +import tensorflow as tf from deepface.basemodels import Facenet from deepface.commons import functions from deepface.commons.logger import Logger logger = Logger(module="basemodels.Facenet512") +tf_version = int(tf.__version__.split(".", maxsplit=1)[0]) + +if tf_version == 1: + from keras.models import Model +else: + from tensorflow.keras.models import Model + + def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/facenet512_weights.h5", -): +) -> Model: model = Facenet.InceptionResNetV2(dimension=512) diff --git a/deepface/basemodels/FbDeepFace.py b/deepface/basemodels/FbDeepFace.py index 4d215aaf..9f66a80e 100644 --- a/deepface/basemodels/FbDeepFace.py +++ b/deepface/basemodels/FbDeepFace.py @@ -40,7 +40,7 @@ def loadModel( url="https://github.com/swghosh/DeepFace/releases/download/weights-vggface2-2d-aligned/VGGFace2_DeepFace_weights_val-0.9034.h5.zip", -): +) -> Model: base_model = Sequential() base_model.add( Convolution2D(32, (11, 11), activation="relu", name="C1", input_shape=(152, 152, 3)) diff --git a/deepface/basemodels/OpenFace.py b/deepface/basemodels/OpenFace.py index 6555ee9a..9ba161e7 100644 --- a/deepface/basemodels/OpenFace.py +++ b/deepface/basemodels/OpenFace.py @@ -27,7 +27,7 @@ def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/openface_weights.h5", -): +) -> Model: myInput = Input(shape=(96, 96, 3)) x = ZeroPadding2D(padding=(3, 3), input_shape=(96, 96, 3))(myInput) diff --git a/deepface/basemodels/SFace.py b/deepface/basemodels/SFace.py index 52ce05f8..882fa887 100644 --- a/deepface/basemodels/SFace.py +++ b/deepface/basemodels/SFace.py @@ -1,4 +1,6 @@ import os +from typing import Any + import numpy as np import cv2 as cv import gdown @@ -25,7 +27,7 @@ def __init__(self, model_path): self.layers = [_Layer()] - def predict(self, image): + def predict(self, image: np.ndarray) -> np.ndarray: # Preprocess input_blob = (image[0] * 255).astype( np.uint8 @@ -39,7 +41,7 @@ def predict(self, image): def load_model( url="https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx", -): +) -> Any: home = functions.get_deepface_home() diff --git a/deepface/basemodels/VGGFace.py b/deepface/basemodels/VGGFace.py index a58b1f69..d02909d6 100644 --- a/deepface/basemodels/VGGFace.py +++ b/deepface/basemodels/VGGFace.py @@ -34,7 +34,7 @@ # --------------------------------------- -def baseModel(): +def baseModel() -> Sequential: model = Sequential() model.add(ZeroPadding2D((1, 1), input_shape=(224, 224, 3))) model.add(Convolution2D(64, (3, 3), activation="relu")) @@ -83,17 +83,12 @@ def baseModel(): return model -# url = 'https://drive.google.com/uc?id=1CPSeum3HpopfomUEK1gybeuIVoeJT_Eo' - - def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/vgg_face_weights.h5", -): +) -> Model: model = baseModel() - # ----------------------------------- - home = functions.get_deepface_home() output = home + "/.deepface/weights/vgg_face_weights.h5" @@ -101,13 +96,8 @@ def loadModel( logger.info("vgg_face_weights.h5 will be downloaded...") gdown.download(url, output, quiet=False) - # ----------------------------------- - model.load_weights(output) - # ----------------------------------- - - # TO-DO: why? vgg_face_descriptor = Model(inputs=model.layers[0].input, outputs=model.layers[-2].output) return vgg_face_descriptor diff --git a/deepface/commons/distance.py b/deepface/commons/distance.py index be95263b..4ed22472 100644 --- a/deepface/commons/distance.py +++ b/deepface/commons/distance.py @@ -1,14 +1,25 @@ +from typing import Union import numpy as np -def findCosineDistance(source_representation, test_representation): +def findCosineDistance( + source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list] +) -> np.float64: + if isinstance(source_representation, list): + source_representation = np.array(source_representation) + + if isinstance(test_representation, list): + test_representation = np.array(test_representation) + a = np.matmul(np.transpose(source_representation), test_representation) b = np.sum(np.multiply(source_representation, source_representation)) c = np.sum(np.multiply(test_representation, test_representation)) return 1 - (a / (np.sqrt(b) * np.sqrt(c))) -def findEuclideanDistance(source_representation, test_representation): +def findEuclideanDistance( + source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list] +) -> np.float64: if isinstance(source_representation, list): source_representation = np.array(source_representation) @@ -21,11 +32,11 @@ def findEuclideanDistance(source_representation, test_representation): return euclidean_distance -def l2_normalize(x): +def l2_normalize(x: np.ndarray) -> np.ndarray: return x / np.sqrt(np.sum(np.multiply(x, x))) -def findThreshold(model_name, distance_metric): +def findThreshold(model_name: str, distance_metric: str) -> float: base_threshold = {"cosine": 0.40, "euclidean": 0.55, "euclidean_l2": 0.75} diff --git a/deepface/commons/functions.py b/deepface/commons/functions.py index a9aa4693..46fb6d27 100644 --- a/deepface/commons/functions.py +++ b/deepface/commons/functions.py @@ -1,10 +1,11 @@ import os +from typing import Union, Tuple import base64 from pathlib import Path -from PIL import Image -import requests # 3rd party dependencies +from PIL import Image +import requests import numpy as np import cv2 import tensorflow as tf @@ -33,7 +34,7 @@ # -------------------------------------------------- -def initialize_folder(): +def initialize_folder() -> None: """Initialize the folder for storing weights and models. Raises: @@ -52,7 +53,7 @@ def initialize_folder(): logger.info(f"Directory {home}/.deepface/weights created") -def get_deepface_home(): +def get_deepface_home() -> str: """Get the home directory for storing weights and models. Returns: @@ -64,7 +65,7 @@ def get_deepface_home(): # -------------------------------------------------- -def loadBase64Img(uri): +def loadBase64Img(uri: str) -> np.ndarray: """Load image from base64 string. Args: @@ -80,7 +81,7 @@ def loadBase64Img(uri): return img_bgr -def load_image(img): +def load_image(img: Union[str, np.ndarray]) -> Tuple[np.ndarray, str]: """ Load image from path, url, base64 or numpy array. Args: @@ -91,15 +92,18 @@ def load_image(img): """ # The image is already a numpy array - if type(img).__module__ == np.__name__: - return img, None + if isinstance(img, np.ndarray): + return img, "numpy array" if isinstance(img, Path): img = str(img) + if not isinstance(img, str): + raise ValueError(f"img must be numpy array or str but it is {type(img)}") + # The image is a base64 string if img.startswith("data:image/"): - return loadBase64Img(img), None + return loadBase64Img(img), "base64 encoded string" # The image is a url if img.startswith("http"): @@ -128,13 +132,13 @@ def load_image(img): def extract_faces( - img, - target_size=(224, 224), - detector_backend="opencv", - grayscale=False, - enforce_detection=True, - align=True, -): + img: Union[str, np.ndarray], + target_size: tuple = (224, 224), + detector_backend: str = "opencv", + grayscale: bool = False, + enforce_detection: bool = True, + align: bool = True, +) -> list: """Extract faces from an image. Args: @@ -252,7 +256,7 @@ def extract_faces( return extracted_faces -def normalize_input(img, normalization="base"): +def normalize_input(img: np.ndarray, normalization: str = "base") -> np.ndarray: """Normalize input image. Args: @@ -310,7 +314,7 @@ def normalize_input(img, normalization="base"): return img -def find_target_size(model_name): +def find_target_size(model_name: str) -> tuple: """Find the target size of the model. Args: @@ -346,17 +350,18 @@ def find_target_size(model_name): @deprecated(version="0.0.78", reason="Use extract_faces instead of preprocess_face") def preprocess_face( - img, + img: Union[str, np.ndarray], target_size=(224, 224), detector_backend="opencv", grayscale=False, enforce_detection=True, align=True, -): - """Preprocess face. +) -> Union[np.ndarray, None]: + """ + Preprocess only one face Args: - img (numpy array): the input image. + img (str or numpy): the input image. target_size (tuple, optional): the target size. Defaults to (224, 224). detector_backend (str, optional): the detector backend. Defaults to "opencv". grayscale (bool, optional): whether to convert to grayscale. Defaults to False. @@ -364,7 +369,7 @@ def preprocess_face( align (bool, optional): whether to align the face. Defaults to True. Returns: - numpy array: the preprocessed face. + loaded image (numpt): the preprocessed face. Raises: ValueError: if face is not detected and enforce_detection is True. diff --git a/deepface/commons/logger.py b/deepface/commons/logger.py index 5d9b7594..80a8a34c 100644 --- a/deepface/commons/logger.py +++ b/deepface/commons/logger.py @@ -12,7 +12,8 @@ def __init__(self, module=None): except Exception as err: self.dump_log( f"Exception while parsing $DEEPFACE_LOG_LEVEL." - f"Expected int but it is {log_level} ({str(err)})" + f"Expected int but it is {log_level} ({str(err)})." + "Setting app log level to info." ) self.log_level = logging.INFO diff --git a/deepface/detectors/DlibWrapper.py b/deepface/detectors/DlibWrapper.py index 6d98b102..86e8f881 100644 --- a/deepface/detectors/DlibWrapper.py +++ b/deepface/detectors/DlibWrapper.py @@ -1,14 +1,19 @@ import os import bz2 import gdown +import numpy as np from deepface.commons import functions from deepface.commons.logger import Logger logger = Logger(module="detectors.DlibWrapper") -def build_model(): - +def build_model() -> dict: + """ + Build a dlib hog face detector model + Returns: + model (Any) + """ home = functions.get_deepface_home() # this is not a must dependency. do not import it in the global level. @@ -46,8 +51,16 @@ def build_model(): return detector -def detect_face(detector, img, align=True): - +def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list: + """ + 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 + Returns: + list of detected and aligned faces + """ # this is not a must dependency. do not import it in the global level. try: import dlib diff --git a/deepface/detectors/FaceDetector.py b/deepface/detectors/FaceDetector.py index 71a4d742..7dea56e3 100644 --- a/deepface/detectors/FaceDetector.py +++ b/deepface/detectors/FaceDetector.py @@ -1,3 +1,4 @@ +from typing import Any, Union from PIL import Image import numpy as np from deepface.detectors import ( @@ -13,7 +14,14 @@ ) -def build_model(detector_backend): +def build_model(detector_backend: str) -> Any: + """ + Build a face detector model + Args: + detector_backend (str): backend detector name + Returns: + built detector (Any) + """ global face_detector_obj # singleton design pattern backends = { @@ -44,7 +52,20 @@ def build_model(detector_backend): return face_detector_obj[detector_backend] -def detect_face(face_detector, detector_backend, img, align=True): +def detect_face( + face_detector: Any, detector_backend: str, img: np.ndarray, align: bool = True +) -> tuple: + """ + Detect a single face from a given image + Args: + face_detector (Any): pre-built face detector object + detector_backend (str): detector name + img (np.ndarray): pre-loaded image + alig (bool): enable or disable alignment after detection + Returns + result (tuple): tuple of face (np.ndarray), face region (list) + , confidence score (float) + """ obj = detect_faces(face_detector, detector_backend, img, align) if len(obj) > 0: @@ -60,7 +81,20 @@ def detect_face(face_detector, detector_backend, img, align=True): return face, region, confidence -def detect_faces(face_detector, detector_backend, img, align=True): +def detect_faces( + face_detector: Any, detector_backend: str, img: np.ndarray, align: bool = True +) -> list: + """ + Detect face(s) from a given image + Args: + face_detector (Any): pre-built face detector object + detector_backend (str): detector name + img (np.ndarray): pre-loaded image + alig (bool): enable or disable alignment after detection + Returns + result (list): tuple of face (np.ndarray), face region (list) + , confidence score (float) + """ backends = { "opencv": OpenCvWrapper.detect_face, "ssd": SsdWrapper.detect_face, @@ -83,18 +117,32 @@ def detect_faces(face_detector, detector_backend, img, align=True): raise ValueError("invalid detector_backend passed - " + detector_backend) -def get_alignment_angle_arctan2(left_eye, right_eye): +def get_alignment_angle_arctan2( + left_eye: Union[list, tuple], right_eye: Union[list, tuple] +) -> float: """ - The left_eye is the eye to the left of the viewer, - i.e., right eye of the person in the image. - The top-left point of the frame is (0, 0). + Find the angle between eyes + Args: + left_eye: coordinates of left eye with respect to the you + right_eye: coordinates of right eye with respect to the you + Returns: + angle (float) """ - return float(np.degrees( - np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0]) - )) + return float(np.degrees(np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0]))) -def alignment_procedure(img, left_eye, right_eye): +def alignment_procedure( + img: np.ndarray, left_eye: Union[list, tuple], right_eye: Union[list, tuple] +) -> np.ndarray: + """ + Rotate given image until eyes are on a horizontal line + Args: + img (np.ndarray): pre-loaded image + left_eye: coordinates of left eye with respect to the you + right_eye: coordinates of right eye with respect to the you + Returns: + result (np.ndarray): aligned face + """ angle = get_alignment_angle_arctan2(left_eye, right_eye) img = Image.fromarray(img) img = np.array(img.rotate(angle)) diff --git a/deepface/detectors/FastMtcnnWrapper.py b/deepface/detectors/FastMtcnnWrapper.py index 5e259415..be50db9f 100644 --- a/deepface/detectors/FastMtcnnWrapper.py +++ b/deepface/detectors/FastMtcnnWrapper.py @@ -1,11 +1,18 @@ +from typing import Any, Union import cv2 +import numpy as np from deepface.detectors import FaceDetector # Link -> https://github.com/timesler/facenet-pytorch # Examples https://www.kaggle.com/timesler/guide-to-mtcnn-in-facenet-pytorch -def build_model(): +def build_model() -> Any: + """ + Build a fast mtcnn face detector model + Returns: + model (Any) + """ # this is not a must dependency. do not import it in the global level. try: from facenet_pytorch import MTCNN as fast_mtcnn @@ -25,7 +32,7 @@ def build_model(): return face_detector -def xyxy_to_xywh(xyxy): +def xyxy_to_xywh(xyxy: Union[list, tuple]) -> list: """ Convert xyxy format to xywh format. """ @@ -35,8 +42,16 @@ def xyxy_to_xywh(xyxy): return [x, y, w, h] -def detect_face(face_detector, img, align=True): - +def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list: + """ + Detect and align face with mtcnn + Args: + face_detector (Any): mtcnn face detector object + img (np.ndarray): pre-loaded image + align (bool): default is true + Returns: + list of detected and aligned faces + """ resp = [] detected_face = None diff --git a/deepface/detectors/MediapipeWrapper.py b/deepface/detectors/MediapipeWrapper.py index 332be4fe..bf9e0d0a 100644 --- a/deepface/detectors/MediapipeWrapper.py +++ b/deepface/detectors/MediapipeWrapper.py @@ -1,9 +1,16 @@ +from typing import Any +import numpy as np from deepface.detectors import FaceDetector # Link - https://google.github.io/mediapipe/solutions/face_detection -def build_model(): +def build_model() -> Any: + """ + Build a mediapipe face detector model + Returns: + model (Any) + """ # this is not a must dependency. do not import it in the global level. try: import mediapipe as mp @@ -18,7 +25,16 @@ def build_model(): return face_detection -def detect_face(face_detector, img, align=True): +def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list: + """ + Detect and align face with mediapipe + Args: + face_detector (Any): mediapipe face detector object + img (np.ndarray): pre-loaded image + align (bool): default is true + Returns: + list of detected and aligned faces + """ resp = [] img_width = img.shape[1] diff --git a/deepface/detectors/MtcnnWrapper.py b/deepface/detectors/MtcnnWrapper.py index 8d1e018c..f7465415 100644 --- a/deepface/detectors/MtcnnWrapper.py +++ b/deepface/detectors/MtcnnWrapper.py @@ -1,15 +1,31 @@ +from typing import Any import cv2 +import numpy as np from deepface.detectors import FaceDetector -def build_model(): +def build_model() -> Any: + """ + Build a mtcnn face detector model + Returns: + model (Any) + """ from mtcnn import MTCNN face_detector = MTCNN() return face_detector -def detect_face(face_detector, img, align=True): +def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list: + """ + Detect and align face with mtcnn + Args: + face_detector (mtcnn.MTCNN): mtcnn face detector object + img (np.ndarray): pre-loaded image + align (bool): default is true + Returns: + list of detected and aligned faces + """ resp = [] diff --git a/deepface/detectors/OpenCvWrapper.py b/deepface/detectors/OpenCvWrapper.py index cd0fc958..099e3743 100644 --- a/deepface/detectors/OpenCvWrapper.py +++ b/deepface/detectors/OpenCvWrapper.py @@ -1,16 +1,28 @@ import os +from typing import Any import cv2 +import numpy as np from deepface.detectors import FaceDetector -def build_model(): +def build_model() -> dict: + """ + Build a opencv face&eye detector models + Returns: + model (Any) + """ detector = {} detector["face_detector"] = build_cascade("haarcascade") detector["eye_detector"] = build_cascade("haarcascade_eye") return detector -def build_cascade(model_name="haarcascade"): +def build_cascade(model_name="haarcascade") -> Any: + """ + Build a opencv face&eye detector models + Returns: + model (Any) + """ opencv_path = get_opencv_path() if model_name == "haarcascade": face_detector_path = opencv_path + "haarcascade_frontalface_default.xml" @@ -38,7 +50,16 @@ def build_cascade(model_name="haarcascade"): return detector -def detect_face(detector, img, align=True): +def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list: + """ + 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 + Returns: + list of detected and aligned faces + """ resp = [] detected_face = None diff --git a/deepface/detectors/RetinaFaceWrapper.py b/deepface/detectors/RetinaFaceWrapper.py index 514d9627..dc470264 100644 --- a/deepface/detectors/RetinaFaceWrapper.py +++ b/deepface/detectors/RetinaFaceWrapper.py @@ -1,21 +1,31 @@ -def build_model(): - from retinaface import RetinaFace # this is not a must dependency - +from typing import Any +import numpy as np +from retinaface import RetinaFace +from retinaface.commons import postprocess + + +def build_model() -> Any: + """ + Build a retinaface detector model + Returns: + model (Any) + """ face_detector = RetinaFace.build_model() return face_detector -def detect_face(face_detector, img, align=True): - - from retinaface import RetinaFace # this is not a must dependency - from retinaface.commons import postprocess - - # --------------------------------- - +def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list: + """ + Detect and align face with retinaface + Args: + face_detector (Any): retinaface face detector object + img (np.ndarray): pre-loaded image + align (bool): default is true + Returns: + list of detected and aligned faces + """ resp = [] - # -------------------------- - obj = RetinaFace.detect_faces(img, model=face_detector, threshold=0.9) if isinstance(obj, dict): diff --git a/deepface/detectors/SsdWrapper.py b/deepface/detectors/SsdWrapper.py index 43a01bee..a445d6eb 100644 --- a/deepface/detectors/SsdWrapper.py +++ b/deepface/detectors/SsdWrapper.py @@ -2,6 +2,7 @@ import gdown import cv2 import pandas as pd +import numpy as np from deepface.detectors import OpenCvWrapper from deepface.commons import functions from deepface.commons.logger import Logger @@ -11,7 +12,12 @@ # pylint: disable=line-too-long -def build_model(): +def build_model() -> dict: + """ + Build a ssd detector model + Returns: + model (Any) + """ home = functions.get_deepface_home() @@ -51,8 +57,16 @@ def build_model(): return detector -def detect_face(detector, img, align=True): - +def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list: + """ + Detect and align face with ssd + Args: + face_detector (Any): ssd face detector object + img (np.ndarray): pre-loaded image + align (bool): default is true + Returns: + list of detected and aligned faces + """ resp = [] detected_face = None diff --git a/deepface/detectors/YoloWrapper.py b/deepface/detectors/YoloWrapper.py index e6e67de8..0786cc15 100644 --- a/deepface/detectors/YoloWrapper.py +++ b/deepface/detectors/YoloWrapper.py @@ -1,3 +1,5 @@ +from typing import Any +import numpy as np from deepface.detectors import FaceDetector from deepface.commons.logger import Logger @@ -14,8 +16,12 @@ LANDMARKS_CONFIDENCE_THRESHOLD = 0.5 -def build_model(): - """Build YOLO (yolov8n-face) model""" +def build_model() -> Any: + """ + Build a yolo detector model + Returns: + model (Any) + """ import gdown import os @@ -41,7 +47,16 @@ def build_model(): return YOLO(weight_path) -def detect_face(face_detector, img, align=False): +def detect_face(face_detector: Any, img: np.ndarray, align: bool = False) -> list: + """ + 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 + Returns: + list of detected and aligned faces + """ resp = [] # Detect faces diff --git a/deepface/detectors/YunetWrapper.py b/deepface/detectors/YunetWrapper.py index da34c0b8..58d4aa70 100644 --- a/deepface/detectors/YunetWrapper.py +++ b/deepface/detectors/YunetWrapper.py @@ -1,5 +1,7 @@ import os +from typing import Any import cv2 +import numpy as np import gdown from deepface.detectors import FaceDetector from deepface.commons import functions @@ -7,7 +9,13 @@ logger = Logger(module="detectors.YunetWrapper") -def build_model(): + +def build_model() -> Any: + """ + Build a yunet detector model + Returns: + model (Any) + """ # pylint: disable=C0301 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" @@ -20,7 +28,18 @@ def build_model(): return face_detector -def detect_face(detector, image, align=True, score_threshold=0.9): +def detect_face( + detector: Any, image: np.ndarray, align: bool = True, score_threshold: float = 0.9 +) -> list: + """ + Detect and align face with yunet + Args: + face_detector (Any): yunet face detector object + img (np.ndarray): pre-loaded image + align (bool): default is true + Returns: + list of detected and aligned faces + """ # FaceDetector.detect_faces does not support score_threshold parameter. # We can set it via environment variable. score_threshold = os.environ.get("yunet_score_threshold", score_threshold) @@ -78,12 +97,8 @@ def detect_face(detector, image, align=True, score_threshold=0.9): detected_face = image[int(y) : int(y + h), int(x) : int(x + w)] img_region = [x, y, w, h] if align: - detected_face = yunet_align_face(detected_face, x_re, y_re, x_le, y_le) + detected_face = FaceDetector.alignment_procedure( + detected_face, (x_re, y_re), (x_le, y_le) + ) resp.append((detected_face, img_region, confidence)) return resp - - -# x_re, y_re, x_le, y_le stands for the coordinates of right eye, left eye -def yunet_align_face(img, x_re, y_re, x_le, y_le): - img = FaceDetector.alignment_procedure(img, (x_re, y_re), (x_le, y_le)) - return img diff --git a/deepface/extendedmodels/Age.py b/deepface/extendedmodels/Age.py index 133e0668..73ca6cc2 100644 --- a/deepface/extendedmodels/Age.py +++ b/deepface/extendedmodels/Age.py @@ -16,7 +16,7 @@ if tf_version == 1: from keras.models import Model, Sequential from keras.layers import Convolution2D, Flatten, Activation -elif tf_version == 2: +else: from tensorflow.keras.models import Model, Sequential from tensorflow.keras.layers import Convolution2D, Flatten, Activation @@ -25,7 +25,7 @@ def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/age_model_weights.h5", -): +) -> Model: model = VGGFace.baseModel() @@ -60,7 +60,7 @@ def loadModel( # -------------------------- -def findApparentAge(age_predictions): +def findApparentAge(age_predictions) -> np.float64: output_indexes = np.array(list(range(0, 101))) apparent_age = np.sum(age_predictions * output_indexes) return apparent_age diff --git a/deepface/extendedmodels/Emotion.py b/deepface/extendedmodels/Emotion.py index 6cb7e87b..41214a18 100644 --- a/deepface/extendedmodels/Emotion.py +++ b/deepface/extendedmodels/Emotion.py @@ -15,7 +15,7 @@ if tf_version == 1: from keras.models import Sequential from keras.layers import Conv2D, MaxPooling2D, AveragePooling2D, Flatten, Dense, Dropout -elif tf_version == 2: +else: from tensorflow.keras.models import Sequential from tensorflow.keras.layers import ( Conv2D, @@ -33,7 +33,7 @@ def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/facial_expression_model_weights.h5", -): +) -> Sequential: num_classes = 7 diff --git a/deepface/extendedmodels/Gender.py b/deepface/extendedmodels/Gender.py index ab2d728e..d53f6f25 100644 --- a/deepface/extendedmodels/Gender.py +++ b/deepface/extendedmodels/Gender.py @@ -17,7 +17,7 @@ if tf_version == 1: from keras.models import Model, Sequential from keras.layers import Convolution2D, Flatten, Activation -elif tf_version == 2: +else: from tensorflow.keras.models import Model, Sequential from tensorflow.keras.layers import Convolution2D, Flatten, Activation # ------------------------------------- @@ -28,7 +28,7 @@ def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/gender_model_weights.h5", -): +) -> Model: model = VGGFace.baseModel() diff --git a/deepface/extendedmodels/Race.py b/deepface/extendedmodels/Race.py index f0a0a84e..50087c10 100644 --- a/deepface/extendedmodels/Race.py +++ b/deepface/extendedmodels/Race.py @@ -16,7 +16,7 @@ if tf_version == 1: from keras.models import Model, Sequential from keras.layers import Convolution2D, Flatten, Activation -elif tf_version == 2: +else: from tensorflow.keras.models import Model, Sequential from tensorflow.keras.layers import Convolution2D, Flatten, Activation # -------------------------- @@ -26,7 +26,7 @@ def loadModel( url="https://github.com/serengil/deepface_models/releases/download/v1.0/race_model_single_batch.h5", -): +) -> Model: model = VGGFace.baseModel() diff --git a/tests/test_represent.py b/tests/test_represent.py index c66f8960..2dd68eae 100644 --- a/tests/test_represent.py +++ b/tests/test_represent.py @@ -9,7 +9,7 @@ def test_standard_represent(): embedding_objs = DeepFace.represent(img_path) for embedding_obj in embedding_objs: embedding = embedding_obj["embedding"] - logger.info(f"Function returned {len(embedding)} dimensional vector") + logger.debug(f"Function returned {len(embedding)} dimensional vector") assert len(embedding) == 2622 logger.info("✅ test standard represent function done")