diff --git a/README.md b/README.md index f6b1a6d..206e7fc 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ DeepFace is a lightweight [face recognition](https://sefiks.com/2018/08/06/deep-face-recognition-with-keras/) and facial attribute analysis ([age](https://sefiks.com/2019/02/13/apparent-age-and-gender-prediction-in-keras/), [gender](https://sefiks.com/2019/02/13/apparent-age-and-gender-prediction-in-keras/), [emotion](https://sefiks.com/2018/01/01/facial-expression-recognition-with-keras/) and [race](https://sefiks.com/2019/11/11/race-and-ethnicity-prediction-in-keras/)) framework for python. It is a hybrid face recognition framework wrapping **state-of-the-art** models: [`VGG-Face`](https://sefiks.com/2018/08/06/deep-face-recognition-with-keras/), [`FaceNet`](https://sefiks.com/2018/09/03/face-recognition-with-facenet-in-keras/), [`OpenFace`](https://sefiks.com/2019/07/21/face-recognition-with-openface-in-keras/), [`DeepFace`](https://sefiks.com/2020/02/17/face-recognition-with-facebook-deepface-in-keras/), [`DeepID`](https://sefiks.com/2020/06/16/face-recognition-with-deepid-in-keras/), [`ArcFace`](https://sefiks.com/2020/12/14/deep-face-recognition-with-arcface-in-keras-and-python/), [`Dlib`](https://sefiks.com/2020/07/11/face-recognition-with-dlib-in-python/), `SFace` and `GhostFaceNet`. -[`Experiments`](https://github.com/serengil/deepface/tree/master/benchmarks) show that human beings have 97.53% accuracy on facial recognition tasks whereas those models already reached and passed that accuracy level. +[`Experiments`](https://github.com/serengil/deepface/tree/master/benchmarks) show that **human beings have 97.53% accuracy** on facial recognition tasks whereas those models already reached and passed that accuracy level. ## Installation [![PyPI](https://img.shields.io/pypi/v/deepface.svg)](https://pypi.org/project/deepface/) @@ -35,7 +35,7 @@ The easiest way to install deepface is to download it from [`PyPI`](https://pypi $ pip install deepface ``` -You can also install deepface from its source code. +Alternatively, you can also install deepface from its source code. Source code may have new features not published in pip release yet. ```shell $ git clone https://github.com/serengil/deepface.git @@ -43,7 +43,7 @@ $ cd deepface $ pip install -e . ``` -Then you will be able to import the library and use its functionalities. +Once you installed the library, then you will be able to import it and use its functionalities. ```python from deepface import DeepFace diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 6813691..5848d7b 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -48,17 +48,23 @@ if tf_version == 2: folder_utils.initialize_folder() -def build_model(model_name: str) -> Any: +def build_model(model_name: str, task: str = "facial_recognition") -> Any: """ - This function builds a deepface model + This function builds a pre-trained model Args: - model_name (string): face recognition or facial attribute model - VGG-Face, Facenet, OpenFace, DeepFace, DeepID for face recognition - Age, Gender, Emotion, Race for facial attributes + model_name (str): model identifier + - VGG-Face, Facenet, Facenet512, OpenFace, DeepFace, DeepID, Dlib, + ArcFace, SFace, GhostFaceNet for face recognition + - Age, Gender, Emotion, Race for facial attributes + - opencv, mtcnn, ssd, dlib, retinaface, mediapipe, yolov8, yunet, + fastmtcnn or centerface for face detectors + - Fasnet for spoofing + task (str): facial_recognition, facial_attribute, face_detector, spoofing + default is facial_recognition Returns: built_model """ - return modeling.build_model(model_name=model_name) + return modeling.build_model(task=task, model_name=model_name) def verify( diff --git a/deepface/__init__.py b/deepface/__init__.py index 418ccd1..a31af38 100644 --- a/deepface/__init__.py +++ b/deepface/__init__.py @@ -1 +1 @@ -__version__ = "0.0.92" +__version__ = "0.0.93" diff --git a/deepface/detectors/DetectorWrapper.py b/deepface/detectors/DetectorWrapper.py index 75f82a6..79ab72a 100644 --- a/deepface/detectors/DetectorWrapper.py +++ b/deepface/detectors/DetectorWrapper.py @@ -1,64 +1,13 @@ -from typing import Any, List, Tuple +from typing import List, Tuple import numpy as np import cv2 -from deepface.modules import detection +from deepface.modules import detection, modeling from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion -from deepface.detectors import ( - FastMtCnn, - MediaPipe, - MtCnn, - OpenCv, - Dlib, - RetinaFace, - Ssd, - Yolo, - YuNet, - CenterFace, -) from deepface.commons.logger import Logger logger = Logger() -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 = { - "opencv": OpenCv.OpenCvClient, - "mtcnn": MtCnn.MtCnnClient, - "ssd": Ssd.SsdClient, - "dlib": Dlib.DlibClient, - "retinaface": RetinaFace.RetinaFaceClient, - "mediapipe": MediaPipe.MediaPipeClient, - "yolov8": Yolo.YoloClient, - "yunet": YuNet.YuNetClient, - "fastmtcnn": FastMtCnn.FastMtCnnClient, - "centerface": CenterFace.CenterFaceClient, - } - - if not "face_detector_obj" in globals(): - face_detector_obj = {} - - built_models = list(face_detector_obj.keys()) - if detector_backend not in built_models: - face_detector = backends.get(detector_backend) - - if face_detector: - face_detector = face_detector() - face_detector_obj[detector_backend] = face_detector - else: - raise ValueError("invalid detector_backend passed - " + detector_backend) - - return face_detector_obj[detector_backend] - - def detect_faces( detector_backend: str, img: np.ndarray, align: bool = True, expand_percentage: int = 0 ) -> List[DetectedFace]: @@ -87,7 +36,9 @@ def detect_faces( """ height, width, _ = img.shape - face_detector: Detector = build_model(detector_backend) + face_detector: Detector = modeling.build_model( + task="face_detector", model_name=detector_backend + ) # validate expand percentage score if expand_percentage < 0: diff --git a/deepface/extendedmodels/Age.py b/deepface/extendedmodels/Age.py index 8d0f0cf..e9363a1 100644 --- a/deepface/extendedmodels/Age.py +++ b/deepface/extendedmodels/Age.py @@ -33,7 +33,9 @@ class ApparentAgeClient(Demography): self.model_name = "Age" def predict(self, img: np.ndarray) -> np.float64: - age_predictions = self.model.predict(img, verbose=0)[0, :] + # model.predict causes memory issue when it is called in a for loop + # age_predictions = self.model.predict(img, verbose=0)[0, :] + age_predictions = self.model(img, training=False).numpy()[0, :] return find_apparent_age(age_predictions) diff --git a/deepface/extendedmodels/Emotion.py b/deepface/extendedmodels/Emotion.py index 7338131..a6cf288 100644 --- a/deepface/extendedmodels/Emotion.py +++ b/deepface/extendedmodels/Emotion.py @@ -52,7 +52,10 @@ class EmotionClient(Demography): img_gray = cv2.resize(img_gray, (48, 48)) img_gray = np.expand_dims(img_gray, axis=0) - emotion_predictions = self.model.predict(img_gray, verbose=0)[0, :] + # model.predict causes memory issue when it is called in a for loop + # emotion_predictions = self.model.predict(img_gray, verbose=0)[0, :] + emotion_predictions = self.model(img_gray, training=False).numpy()[0, :] + return emotion_predictions diff --git a/deepface/extendedmodels/Gender.py b/deepface/extendedmodels/Gender.py index cb766fe..fff8e2d 100644 --- a/deepface/extendedmodels/Gender.py +++ b/deepface/extendedmodels/Gender.py @@ -41,7 +41,9 @@ class GenderClient(Demography): self.model_name = "Gender" def predict(self, img: np.ndarray) -> np.ndarray: - return self.model.predict(img, verbose=0)[0, :] + # model.predict causes memory issue when it is called in a for loop + # return self.model.predict(img, verbose=0)[0, :] + return self.model(img, training=False).numpy()[0, :] def load_model( diff --git a/deepface/extendedmodels/Race.py b/deepface/extendedmodels/Race.py index 6f9e643..5e3b3e2 100644 --- a/deepface/extendedmodels/Race.py +++ b/deepface/extendedmodels/Race.py @@ -40,7 +40,9 @@ class RaceClient(Demography): self.model_name = "Race" def predict(self, img: np.ndarray) -> np.ndarray: - return self.model.predict(img, verbose=0)[0, :] + # model.predict causes memory issue when it is called in a for loop + # return self.model.predict(img, verbose=0)[0, :] + return self.model(img, training=False).numpy()[0, :] def load_model( diff --git a/deepface/modules/demography.py b/deepface/modules/demography.py index 1634571..e33cbce 100644 --- a/deepface/modules/demography.py +++ b/deepface/modules/demography.py @@ -158,7 +158,9 @@ def analyze( pbar.set_description(f"Action: {action}") if action == "emotion": - emotion_predictions = modeling.build_model("Emotion").predict(img_content) + emotion_predictions = modeling.build_model( + task="facial_attribute", model_name="Emotion" + ).predict(img_content) sum_of_predictions = emotion_predictions.sum() obj["emotion"] = {} @@ -169,12 +171,16 @@ def analyze( obj["dominant_emotion"] = Emotion.labels[np.argmax(emotion_predictions)] elif action == "age": - apparent_age = modeling.build_model("Age").predict(img_content) + apparent_age = modeling.build_model( + task="facial_attribute", model_name="Age" + ).predict(img_content) # int cast is for exception - object of type 'float32' is not JSON serializable obj["age"] = int(apparent_age) elif action == "gender": - gender_predictions = modeling.build_model("Gender").predict(img_content) + gender_predictions = modeling.build_model( + task="facial_attribute", model_name="Gender" + ).predict(img_content) obj["gender"] = {} for i, gender_label in enumerate(Gender.labels): gender_prediction = 100 * gender_predictions[i] @@ -183,7 +189,9 @@ def analyze( obj["dominant_gender"] = Gender.labels[np.argmax(gender_predictions)] elif action == "race": - race_predictions = modeling.build_model("Race").predict(img_content) + race_predictions = modeling.build_model( + task="facial_attribute", model_name="Race" + ).predict(img_content) sum_of_predictions = race_predictions.sum() obj["race"] = {} diff --git a/deepface/modules/detection.py b/deepface/modules/detection.py index 9edb0f2..f0a3874 100644 --- a/deepface/modules/detection.py +++ b/deepface/modules/detection.py @@ -25,7 +25,7 @@ def extract_faces( align: bool = True, expand_percentage: int = 0, grayscale: bool = False, - color_face: str = 'rgb', + color_face: str = "rgb", normalize_face: bool = True, anti_spoofing: bool = False, ) -> List[Dict[str, Any]]: @@ -126,16 +126,14 @@ def extract_faces( logger.warn("Parameter grayscale is deprecated. Use color_face instead.") current_img = cv2.cvtColor(current_img, cv2.COLOR_BGR2GRAY) else: - if color_face == 'rgb': + if color_face == "rgb": current_img = current_img[:, :, ::-1] - elif color_face == 'bgr': + elif color_face == "bgr": pass # image is in BGR - elif color_face == 'gray': + 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, but it is {color_face}.") if normalize_face: current_img = current_img / 255 # normalize input in [0, 1] @@ -159,7 +157,7 @@ def extract_faces( } if anti_spoofing is True: - antispoof_model = modeling.build_model(model_name="Fasnet") + 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)) resp_obj["is_real"] = is_real resp_obj["antispoof_score"] = antispoof_score diff --git a/deepface/modules/modeling.py b/deepface/modules/modeling.py index 60b6a71..efbdfe5 100644 --- a/deepface/modules/modeling.py +++ b/deepface/modules/modeling.py @@ -13,51 +13,88 @@ from deepface.basemodels import ( Facenet, GhostFaceNet, ) +from deepface.detectors import ( + FastMtCnn, + MediaPipe, + MtCnn, + OpenCv, + Dlib as DlibDetector, + RetinaFace, + Ssd, + Yolo, + YuNet, + CenterFace, +) from deepface.extendedmodels import Age, Gender, Race, Emotion from deepface.spoofmodels import FasNet -def build_model(model_name: str) -> Any: +def build_model(task: str, model_name: str) -> Any: """ - This function builds a deepface model + This function loads a pre-trained models as singletonish way Parameters: - model_name (string): face recognition or facial attribute model - VGG-Face, Facenet, OpenFace, DeepFace, DeepID for face recognition - Age, Gender, Emotion, Race for facial attributes - + task (str): facial_recognition, facial_attribute, face_detector, spoofing + model_name (str): model identifier + - VGG-Face, Facenet, Facenet512, OpenFace, DeepFace, DeepID, Dlib, + ArcFace, SFace, GhostFaceNet for face recognition + - Age, Gender, Emotion, Race for facial attributes + - opencv, mtcnn, ssd, dlib, retinaface, mediapipe, yolov8, yunet, + fastmtcnn or centerface for face detectors + - Fasnet for spoofing Returns: built model class """ # singleton design pattern - global model_obj + global cached_models models = { - "VGG-Face": VGGFace.VggFaceClient, - "OpenFace": OpenFace.OpenFaceClient, - "Facenet": Facenet.FaceNet128dClient, - "Facenet512": Facenet.FaceNet512dClient, - "DeepFace": FbDeepFace.DeepFaceClient, - "DeepID": DeepID.DeepIdClient, - "Dlib": Dlib.DlibClient, - "ArcFace": ArcFace.ArcFaceClient, - "SFace": SFace.SFaceClient, - "GhostFaceNet": GhostFaceNet.GhostFaceNetClient, - "Emotion": Emotion.EmotionClient, - "Age": Age.ApparentAgeClient, - "Gender": Gender.GenderClient, - "Race": Race.RaceClient, - "Fasnet": FasNet.Fasnet, + "facial_recognition": { + "VGG-Face": VGGFace.VggFaceClient, + "OpenFace": OpenFace.OpenFaceClient, + "Facenet": Facenet.FaceNet128dClient, + "Facenet512": Facenet.FaceNet512dClient, + "DeepFace": FbDeepFace.DeepFaceClient, + "DeepID": DeepID.DeepIdClient, + "Dlib": Dlib.DlibClient, + "ArcFace": ArcFace.ArcFaceClient, + "SFace": SFace.SFaceClient, + "GhostFaceNet": GhostFaceNet.GhostFaceNetClient, + }, + "spoofing": { + "Fasnet": FasNet.Fasnet, + }, + "facial_attribute": { + "Emotion": Emotion.EmotionClient, + "Age": Age.ApparentAgeClient, + "Gender": Gender.GenderClient, + "Race": Race.RaceClient, + }, + "face_detector": { + "opencv": OpenCv.OpenCvClient, + "mtcnn": MtCnn.MtCnnClient, + "ssd": Ssd.SsdClient, + "dlib": DlibDetector.DlibClient, + "retinaface": RetinaFace.RetinaFaceClient, + "mediapipe": MediaPipe.MediaPipeClient, + "yolov8": Yolo.YoloClient, + "yunet": YuNet.YuNetClient, + "fastmtcnn": FastMtCnn.FastMtCnnClient, + "centerface": CenterFace.CenterFaceClient, + }, } - if not "model_obj" in globals(): - model_obj = {} + if models.get(task) is None: + raise ValueError(f"unimplemented task - {task}") - if not model_name in model_obj.keys(): - model = models.get(model_name) + if not "cached_models" in globals(): + cached_models = {current_task: {} for current_task in models.keys()} + + if cached_models[task].get(model_name) is None: + model = models[task].get(model_name) if model: - model_obj[model_name] = model() + cached_models[task][model_name] = model() else: - raise ValueError(f"Invalid model_name passed - {model_name}") + raise ValueError(f"Invalid model_name passed - {task}/{model_name}") - return model_obj[model_name] + return cached_models[task][model_name] diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index 5a1036b..b187ce4 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -65,7 +65,9 @@ def represent( """ resp_objs = [] - model: FacialRecognition = modeling.build_model(model_name) + model: FacialRecognition = modeling.build_model( + task="facial_recognition", model_name=model_name + ) # --------------------------------- # we have run pre-process in verification. so, this can be skipped if it is coming from verify. diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index fe27464..bd77ce1 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -171,7 +171,7 @@ def build_facial_recognition_model(model_name: str) -> None: Returns input_shape (tuple): input shape of given facial recognitio n model. """ - _ = DeepFace.build_model(model_name=model_name) + _ = DeepFace.build_model(task="facial_recognition", model_name=model_name) logger.info(f"{model_name} is built") @@ -267,11 +267,11 @@ def build_demography_models(enable_face_analysis: bool) -> None: """ if enable_face_analysis is False: return - DeepFace.build_model(model_name="Age") + DeepFace.build_model(task="facial_attribute", model_name="Age") logger.info("Age model is just built") - DeepFace.build_model(model_name="Gender") + DeepFace.build_model(task="facial_attribute", model_name="Gender") logger.info("Gender model is just built") - DeepFace.build_model(model_name="Emotion") + DeepFace.build_model(task="facial_attribute", model_name="Emotion") logger.info("Emotion model is just built") diff --git a/deepface/modules/verification.py b/deepface/modules/verification.py index 0ceb65c..a18ad05 100644 --- a/deepface/modules/verification.py +++ b/deepface/modules/verification.py @@ -100,7 +100,9 @@ def verify( tic = time.time() - model: FacialRecognition = modeling.build_model(model_name) + model: FacialRecognition = modeling.build_model( + task="facial_recognition", model_name=model_name + ) dims = model.output_shape # extract faces from img1 diff --git a/package_info.json b/package_info.json index 87e13d0..bcaf8e9 100644 --- a/package_info.json +++ b/package_info.json @@ -1,3 +1,3 @@ { - "version": "0.0.92" + "version": "0.0.93" } \ No newline at end of file diff --git a/tests/face-recognition-how.py b/tests/face-recognition-how.py index f33e1af..cb26f2f 100644 --- a/tests/face-recognition-how.py +++ b/tests/face-recognition-how.py @@ -16,7 +16,7 @@ logger = Logger() model_name = "VGG-Face" -model: FacialRecognition = DeepFace.build_model(model_name=model_name) +model: FacialRecognition = DeepFace.build_model(task="facial_recognition", model_name=model_name) target_size = model.input_shape