Skip to content

Commit

Permalink
Merge branch 'serengil:master' into distances
Browse files Browse the repository at this point in the history
  • Loading branch information
kremnik authored Aug 4, 2024
2 parents ef228d9 + a718285 commit 1d3de83
Show file tree
Hide file tree
Showing 16 changed files with 130 additions and 117 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand All @@ -35,15 +35,15 @@ 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
$ 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
Expand Down
18 changes: 12 additions & 6 deletions deepface/DeepFace.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,23 @@
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(
Expand Down
2 changes: 1 addition & 1 deletion deepface/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.92"
__version__ = "0.0.93"
59 changes: 5 additions & 54 deletions deepface/detectors/DetectorWrapper.py
Original file line number Diff line number Diff line change
@@ -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]:
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion deepface/extendedmodels/Age.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ def __init__(self):
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)


Expand Down
5 changes: 4 additions & 1 deletion deepface/extendedmodels/Emotion.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ def predict(self, img: np.ndarray) -> np.ndarray:
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


Expand Down
4 changes: 3 additions & 1 deletion deepface/extendedmodels/Gender.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def __init__(self):
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(
Expand Down
4 changes: 3 additions & 1 deletion deepface/extendedmodels/Race.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ def __init__(self):
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(
Expand Down
16 changes: 12 additions & 4 deletions deepface/modules/demography.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"] = {}
Expand All @@ -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]
Expand All @@ -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"] = {}
Expand Down
14 changes: 6 additions & 8 deletions deepface/modules/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]:
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down
95 changes: 66 additions & 29 deletions deepface/modules/modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,88 @@
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 "cached_models" in globals():
cached_models = {current_task: {} for current_task in models.keys()}

if not model_name in model_obj.keys():
model = models.get(model_name)
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]
4 changes: 3 additions & 1 deletion deepface/modules/representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 1d3de83

Please sign in to comment.