facial recognition, detector and demography models are now using interface

This commit is contained in:
Sefik Ilkin Serengil 2024-01-20 19:23:28 +00:00
parent 51bb1808d0
commit 0fd77e1c99
35 changed files with 1207 additions and 1049 deletions

View File

@ -8,7 +8,6 @@ from typing import Any, Dict, List, Tuple, Union
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import tensorflow as tf import tensorflow as tf
from deprecated import deprecated
# package dependencies # package dependencies
from deepface.commons import functions from deepface.commons import functions
@ -23,8 +22,6 @@ from deepface.modules import (
realtime, realtime,
) )
# pylint: disable=no-else-raise, simplifiable-if-expression
logger = Logger(module="DeepFace") logger = Logger(module="DeepFace")
# ----------------------------------- # -----------------------------------
@ -40,6 +37,8 @@ else:
from keras.models import Model from keras.models import Model
# ----------------------------------- # -----------------------------------
functions.initialize_folder()
def build_model(model_name: str) -> Union[Model, Any]: def build_model(model_name: str) -> Union[Model, Any]:
""" """
@ -413,69 +412,6 @@ def extract_faces(
) )
# ---------------------------
# deprecated functions
@deprecated(version="0.0.78", reason="Use DeepFace.extract_faces instead of DeepFace.detectFace")
def detectFace(
img_path: Union[str, np.ndarray],
target_size: tuple = (224, 224),
detector_backend: str = "opencv",
enforce_detection: bool = True,
align: bool = True,
) -> Union[np.ndarray, None]:
"""
Deprecated function. Use extract_faces for same functionality.
This function applies pre-processing stages of a face recognition pipeline
including detection and alignment
Parameters:
img_path: exact image path, numpy array (BGR) or base64 encoded image.
Source image can have many face. Then, result will be the size of number
of faces appearing in that source image.
target_size (tuple): final shape of facial image. black pixels will be
added to resize the image.
detector_backend (string): face detection backends are retinaface, mtcnn,
opencv, ssd or dlib
enforce_detection (boolean): function throws exception if face cannot be
detected in the fed image. Set this to False if you do not want to get
an exception and run the function anyway.
align (boolean): alignment according to the eye positions.
grayscale (boolean): extracting faces in rgb or gray scale
Returns:
detected and aligned face as numpy array
"""
logger.warn("Function detectFace is deprecated. Use extract_faces instead.")
face_objs = extract_faces(
img_path=img_path,
target_size=target_size,
detector_backend=detector_backend,
enforce_detection=enforce_detection,
align=align,
grayscale=False,
)
extracted_face = None
if len(face_objs) > 0:
extracted_face = face_objs[0]["face"]
return extracted_face
# ---------------------------
# main
functions.initialize_folder()
def cli() -> None: def cli() -> None:
""" """
command line interface function will be offered in this block command line interface function will be offered in this block

View File

@ -3,6 +3,7 @@ import gdown
import tensorflow as tf import tensorflow as tf
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.ArcFace") logger = Logger(module="basemodels.ArcFace")
@ -42,10 +43,25 @@ else:
Dense, Dense,
) )
# pylint: disable=too-few-public-methods
class ArcFace(FacialRecognition):
"""
ArcFace model class
"""
def loadModel( def __init__(self):
self.model = load_model()
self.model_name = "ArcFace"
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/arcface_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/arcface_weights.h5",
) -> Model: ) -> Model:
"""
Construct ArcFace model, download its weights and load
Returns:
model (Model)
"""
base_model = ResNet34() base_model = ResNet34()
inputs = base_model.inputs[0] inputs = base_model.inputs[0]
arcface_model = base_model.outputs[0] arcface_model = base_model.outputs[0]
@ -81,7 +97,11 @@ def loadModel(
def ResNet34() -> Model: def ResNet34() -> Model:
"""
ResNet34 model
Returns:
model (Model)
"""
img_input = Input(shape=(112, 112, 3)) img_input = Input(shape=(112, 112, 3))
x = ZeroPadding2D(padding=1, name="conv1_pad")(img_input) x = ZeroPadding2D(padding=1, name="conv1_pad")(img_input)

View File

@ -3,6 +3,7 @@ import gdown
import tensorflow as tf import tensorflow as tf
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.DeepID") logger = Logger(module="basemodels.DeepID")
@ -38,10 +39,23 @@ else:
# ------------------------------------- # -------------------------------------
# pylint: disable=too-few-public-methods
class DeepId(FacialRecognition):
"""
DeepId model class
"""
def loadModel( def __init__(self):
self.model = load_model()
self.model_name = "DeepId"
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/deepid_keras_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/deepid_keras_weights.h5",
) -> Model: ) -> Model:
"""
Construct DeepId model, download its weights and load
"""
myInput = Input(shape=(55, 47, 3)) myInput = Input(shape=(55, 47, 3))

View File

@ -4,12 +4,33 @@ import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.DlibResNet") logger = Logger(module="basemodels.DlibResNet")
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class Dlib(FacialRecognition):
"""
Dlib model class
"""
def __init__(self):
self.model = DlibResNet()
self.model_name = "Dlib"
def find_embeddings(self, img: np.ndarray) -> list:
"""
Custom find embeddings function of Dlib different than FacialRecognition's one
Args:
img (np.ndarray)
Retunrs:
embeddings (list)
"""
return self.model.predict(img)[0].tolist()
class DlibResNet: class DlibResNet:
def __init__(self): def __init__(self):

View File

@ -1,6 +0,0 @@
from typing import Any
from deepface.basemodels.DlibResNet import DlibResNet
def loadModel() -> Any:
return DlibResNet()

View File

@ -3,6 +3,7 @@ import gdown
import tensorflow as tf import tensorflow as tf
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.Facenet") logger = Logger(module="basemodels.Facenet")
@ -42,12 +43,39 @@ else:
# -------------------------------- # --------------------------------
# pylint: disable=too-few-public-methods
class FaceNet128d(FacialRecognition):
"""
FaceNet-128d model class
"""
def __init__(self):
self.model = load_facenet128d_model()
self.model_name = "FaceNet-128d"
class FaceNet512d(FacialRecognition):
"""
FaceNet-1512d model class
"""
def __init__(self):
self.model = load_facenet512d_model()
self.model_name = "FaceNet-512d"
def scaling(x, scale): def scaling(x, scale):
return x * scale return x * scale
def InceptionResNetV2(dimension=128) -> Model: def InceptionResNetV2(dimension: int = 128) -> Model:
"""
InceptionResNetV2 model
Args:
dimension (int): number of dimensions in the embedding layer
Returns:
model (Model)
"""
inputs = Input(shape=(160, 160, 3)) inputs = Input(shape=(160, 160, 3))
x = Conv2D(32, 3, strides=2, padding="valid", use_bias=False, name="Conv2d_1a_3x3")(inputs) x = Conv2D(32, 3, strides=2, padding="valid", use_bias=False, name="Conv2d_1a_3x3")(inputs)
@ -1618,9 +1646,16 @@ def InceptionResNetV2(dimension=128) -> Model:
return model return model
def loadModel( def load_facenet128d_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/facenet_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/facenet_weights.h5",
) -> Model: ) -> Model:
"""
Construct FaceNet-128d model, download weights and then load weights
Args:
dimension (int): construct FaceNet-128d or FaceNet-512d models
Returns:
model (Model)
"""
model = InceptionResNetV2() model = InceptionResNetV2()
# ----------------------------------- # -----------------------------------
@ -1640,3 +1675,33 @@ def loadModel(
# ----------------------------------- # -----------------------------------
return model return model
def load_facenet512d_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/facenet512_weights.h5",
) -> Model:
"""
Construct FaceNet-512d model, download its weights and load
Returns:
model (Model)
"""
model = InceptionResNetV2(dimension=512)
# -------------------------
home = functions.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/facenet512_weights.h5") != True:
logger.info("facenet512_weights.h5 will be downloaded...")
output = home + "/.deepface/weights/facenet512_weights.h5"
gdown.download(url, output, quiet=False)
# -------------------------
model.load_weights(home + "/.deepface/weights/facenet512_weights.h5")
# -------------------------
return model

View File

@ -1,40 +0,0 @@
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)
# -------------------------
home = functions.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/facenet512_weights.h5") != True:
logger.info("facenet512_weights.h5 will be downloaded...")
output = home + "/.deepface/weights/facenet512_weights.h5"
gdown.download(url, output, quiet=False)
# -------------------------
model.load_weights(home + "/.deepface/weights/facenet512_weights.h5")
# -------------------------
return model

View File

@ -4,6 +4,7 @@ import gdown
import tensorflow as tf import tensorflow as tf
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.FbDeepFace") logger = Logger(module="basemodels.FbDeepFace")
@ -35,12 +36,23 @@ else:
# ------------------------------------- # -------------------------------------
# pylint: disable=line-too-long # pylint: disable=line-too-long, too-few-public-methods
class DeepFace(FacialRecognition):
"""
Fb's DeepFace model class
"""
def __init__(self):
self.model = load_model()
self.model_name = "DeepFace"
def loadModel( def load_model(
url="https://github.com/swghosh/DeepFace/releases/download/weights-vggface2-2d-aligned/VGGFace2_DeepFace_weights_val-0.9034.h5.zip", url="https://github.com/swghosh/DeepFace/releases/download/weights-vggface2-2d-aligned/VGGFace2_DeepFace_weights_val-0.9034.h5.zip",
) -> Model: ) -> Model:
"""
Construct DeepFace model, download its weights and load
"""
base_model = Sequential() base_model = Sequential()
base_model.add( base_model.add(
Convolution2D(32, (11, 11), activation="relu", name="C1", input_shape=(152, 152, 3)) Convolution2D(32, (11, 11), activation="relu", name="C1", input_shape=(152, 152, 3))

View File

@ -3,6 +3,7 @@ import gdown
import tensorflow as tf import tensorflow as tf
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.OpenFace") logger = Logger(module="basemodels.OpenFace")
@ -24,10 +25,24 @@ else:
# --------------------------------------- # ---------------------------------------
# pylint: disable=too-few-public-methods
class OpenFace(FacialRecognition):
"""
OpenFace model class
"""
def __init__(self):
self.model = load_model()
self.model_name = "OpenFace"
def loadModel(
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/openface_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/openface_weights.h5",
) -> Model: ) -> Model:
"""
Consturct OpenFace model, download its weights and load
Returns:
model (Model)
"""
myInput = Input(shape=(96, 96, 3)) myInput = Input(shape=(96, 96, 3))
x = ZeroPadding2D(padding=(3, 3), input_shape=(96, 96, 3))(myInput) x = ZeroPadding2D(padding=(3, 3), input_shape=(96, 96, 3))(myInput)

View File

@ -7,20 +7,60 @@ import gdown
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.SFace") logger = Logger(module="basemodels.SFace")
# pylint: disable=line-too-long, too-few-public-methods # pylint: disable=line-too-long, too-few-public-methods
class _Layer: class SFace(FacialRecognition):
input_shape = (None, 112, 112, 3) """
output_shape = (None, 1, 128) SFace model class
"""
def __init__(self):
self.model = load_model()
self.model_name = "SFace"
def find_embeddings(self, img: np.ndarray) -> list:
"""
Custom find embeddings function of SFace different than FacialRecognition's one
Args:
img (np.ndarray)
Retunrs:
embeddings (list)
"""
return self.model.predict(img)[0].tolist()
class SFaceModel: def load_model(
url="https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx",
) -> Any:
"""
Construct SFace model, download its weights and load
"""
home = functions.get_deepface_home()
file_name = home + "/.deepface/weights/face_recognition_sface_2021dec.onnx"
if not os.path.isfile(file_name):
logger.info("sface weights will be downloaded...")
gdown.download(url, file_name, quiet=False)
model = SFaceWrapper(model_path=file_name)
return model
class SFaceWrapper:
def __init__(self, model_path): def __init__(self, model_path):
"""
SFace wrapper covering model construction, layer infos and predict
"""
try: try:
self.model = cv.FaceRecognizerSF.create( self.model = cv.FaceRecognizerSF.create(
model=model_path, config="", backend_id=0, target_id=0 model=model_path, config="", backend_id=0, target_id=0
@ -46,20 +86,6 @@ class SFaceModel:
return embeddings return embeddings
def load_model( class _Layer:
url="https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx", input_shape = (None, 112, 112, 3)
) -> Any: output_shape = (None, 1, 128)
home = functions.get_deepface_home()
file_name = home + "/.deepface/weights/face_recognition_sface_2021dec.onnx"
if not os.path.isfile(file_name):
logger.info("sface weights will be downloaded...")
gdown.download(url, file_name, quiet=False)
model = SFaceModel(model_path=file_name)
return model

View File

@ -3,6 +3,7 @@ import gdown
import tensorflow as tf import tensorflow as tf
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.VGGFace") logger = Logger(module="basemodels.VGGFace")
@ -37,8 +38,23 @@ else:
# --------------------------------------- # ---------------------------------------
# pylint: disable=too-few-public-methods
class VggFace(FacialRecognition):
"""
VGG-Face model class
"""
def baseModel() -> Sequential: def __init__(self):
self.model = load_model()
self.model_name = "VGG-Face"
def base_model() -> Sequential:
"""
Base model of VGG-Face being used for classification - not to find embeddings
Returns:
model (Sequential): model was trained to classify 2622 identities
"""
model = Sequential() model = Sequential()
model.add(ZeroPadding2D((1, 1), input_shape=(224, 224, 3))) model.add(ZeroPadding2D((1, 1), input_shape=(224, 224, 3)))
model.add(Convolution2D(64, (3, 3), activation="relu")) model.add(Convolution2D(64, (3, 3), activation="relu"))
@ -87,11 +103,16 @@ def baseModel() -> Sequential:
return model return model
def loadModel( def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/vgg_face_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/vgg_face_weights.h5",
) -> Model: ) -> Model:
"""
Final VGG-Face model being used for finding embeddings
Returns:
model (Model): returning 4096 dimensional vectors
"""
model = baseModel() model = base_model()
home = functions.get_deepface_home() home = functions.get_deepface_home()
output = home + "/.deepface/weights/vgg_face_weights.h5" output = home + "/.deepface/weights/vgg_face_weights.h5"

View File

@ -12,7 +12,7 @@ import tensorflow as tf
from deprecated import deprecated from deprecated import deprecated
# package dependencies # package dependencies
from deepface.detectors import FaceDetector from deepface.detectors import DetectorWrapper
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger(module="commons.functions") logger = Logger(module="commons.functions")
@ -168,8 +168,7 @@ def extract_faces(
if detector_backend == "skip": if detector_backend == "skip":
face_objs = [(img, img_region, 0)] face_objs = [(img, img_region, 0)]
else: else:
face_detector = FaceDetector.build_model(detector_backend) face_objs = DetectorWrapper.detect_faces(detector_backend, img, align)
face_objs = FaceDetector.detect_faces(face_detector, detector_backend, img, align)
# in case of no face found # in case of no face found
if len(face_objs) == 0 and enforce_detection is True: if len(face_objs) == 0 and enforce_detection is True:

View File

@ -0,0 +1,67 @@
from typing import Any
import numpy as np
from deepface.models.Detector import Detector
from deepface.detectors import (
OpenCvWrapper,
SsdWrapper,
DlibWrapper,
MtcnnWrapper,
RetinaFaceWrapper,
MediapipeWrapper,
YoloWrapper,
YunetWrapper,
FastMtcnnWrapper,
)
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": OpenCvWrapper.OpenCv,
"mtcnn": MtcnnWrapper.MtCnn,
"ssd": SsdWrapper.Ssd,
"dlib": DlibWrapper.Dlib,
"retinaface": RetinaFaceWrapper.RetinaFace,
"mediapipe": MediapipeWrapper.MediaPipe,
"yolov8": YoloWrapper.Yolo,
"yunet": YunetWrapper.YuNet,
"fastmtcnn": FastMtcnnWrapper.FastMtCnn,
}
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) -> list:
"""
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
Returns
result (list): tuple of face (np.ndarray), face region (list)
, confidence score (float)
"""
face_detector: Detector = build_model(detector_backend)
return face_detector.detect_faces(img=img, align=align)

View File

@ -3,12 +3,17 @@ import bz2
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import functions
from deepface.models.Detector import Detector
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger(module="detectors.DlibWrapper") logger = Logger(module="detectors.DlibWrapper")
def build_model() -> dict: class Dlib(Detector):
def __init__(self):
self.model = self.build_model()
def build_model(self) -> dict:
""" """
Build a dlib hog face detector model Build a dlib hog face detector model
Returns: Returns:
@ -50,8 +55,7 @@ def build_model() -> dict:
detector["sp"] = sp detector["sp"] = sp
return detector return detector
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
""" """
Detect and align face with dlib Detect and align face with dlib
Args: Args:
@ -72,13 +76,13 @@ def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
resp = [] resp = []
sp = detector["sp"] sp = self.model["sp"]
detected_face = None detected_face = None
img_region = [0, 0, img.shape[1], img.shape[0]] img_region = [0, 0, img.shape[1], img.shape[0]]
face_detector = detector["face_detector"] face_detector = self.model["face_detector"]
# note that, by design, dlib's fhog face detector scores are >0 but not capped at 1 # note that, by design, dlib's fhog face detector scores are >0 but not capped at 1
detections, scores, _ = face_detector.run(img, 1) detections, scores, _ = face_detector.run(img, 1)

View File

@ -1,149 +0,0 @@
from typing import Any, Union
from PIL import Image
import numpy as np
from deepface.detectors import (
OpenCvWrapper,
SsdWrapper,
DlibWrapper,
MtcnnWrapper,
RetinaFaceWrapper,
MediapipeWrapper,
YoloWrapper,
YunetWrapper,
FastMtcnnWrapper,
)
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": OpenCvWrapper.build_model,
"ssd": SsdWrapper.build_model,
"dlib": DlibWrapper.build_model,
"mtcnn": MtcnnWrapper.build_model,
"retinaface": RetinaFaceWrapper.build_model,
"mediapipe": MediapipeWrapper.build_model,
"yolov8": YoloWrapper.build_model,
"yunet": YunetWrapper.build_model,
"fastmtcnn": FastMtcnnWrapper.build_model,
}
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_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:
face, region, confidence = obj[0] # discard multiple faces
# If no face is detected, set face to None,
# image region to full image, and confidence to 0.
else: # len(obj) == 0
face = None
region = [0, 0, img.shape[1], img.shape[0]]
confidence = 0
return face, region, confidence
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,
"dlib": DlibWrapper.detect_face,
"mtcnn": MtcnnWrapper.detect_face,
"retinaface": RetinaFaceWrapper.detect_face,
"mediapipe": MediapipeWrapper.detect_face,
"yolov8": YoloWrapper.detect_face,
"yunet": YunetWrapper.detect_face,
"fastmtcnn": FastMtcnnWrapper.detect_face,
}
detect_face_fn = backends.get(detector_backend)
if detect_face_fn: # pylint: disable=no-else-return
obj = detect_face_fn(face_detector, img, align)
# obj stores list of (detected_face, region, confidence)
return obj
else:
raise ValueError("invalid detector_backend passed - " + detector_backend)
def get_alignment_angle_arctan2(
left_eye: Union[list, tuple], right_eye: Union[list, tuple]
) -> float:
"""
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])))
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))
return img

View File

@ -1,13 +1,54 @@
from typing import Any, Union from typing import Any, Union
import cv2 import cv2
import numpy as np import numpy as np
from deepface.detectors import FaceDetector from deepface.models.Detector import Detector
# Link -> https://github.com/timesler/facenet-pytorch # Link -> https://github.com/timesler/facenet-pytorch
# Examples https://www.kaggle.com/timesler/guide-to-mtcnn-in-facenet-pytorch # Examples https://www.kaggle.com/timesler/guide-to-mtcnn-in-facenet-pytorch
def build_model() -> Any: class FastMtCnn(Detector):
def __init__(self):
self.model = self.build_model()
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
"""
Detect and align face with mtcnn
Args:
img (np.ndarray): pre-loaded image
align (bool): default is true
Returns:
list of detected and aligned faces
"""
resp = []
detected_face = None
img_region = [0, 0, img.shape[1], img.shape[0]]
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
detections = self.model.detect(
img_rgb, landmarks=True
) # returns boundingbox, prob, landmark
if len(detections[0]) > 0:
for detection in zip(*detections):
x, y, w, h = xyxy_to_xywh(detection[0])
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
img_region = [x, y, w, h]
confidence = detection[1]
if align:
left_eye = detection[2][0]
right_eye = detection[2][1]
detected_face = self.align_face(
img=detected_face, left_eye=left_eye, right_eye=right_eye
)
resp.append((detected_face, img_region, confidence))
return resp
def build_model(self) -> Any:
""" """
Build a fast mtcnn face detector model Build a fast mtcnn face detector model
Returns: Returns:
@ -40,40 +81,3 @@ def xyxy_to_xywh(xyxy: Union[list, tuple]) -> list:
w = xyxy[2] - x + 1 w = xyxy[2] - x + 1
h = xyxy[3] - y + 1 h = xyxy[3] - y + 1
return [x, y, w, h] return [x, y, w, h]
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
img_region = [0, 0, img.shape[1], img.shape[0]]
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
detections = face_detector.detect(
img_rgb, landmarks=True
) # returns boundingbox, prob, landmark
if len(detections[0]) > 0:
for detection in zip(*detections):
x, y, w, h = xyxy_to_xywh(detection[0])
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
img_region = [x, y, w, h]
confidence = detection[1]
if align:
left_eye = detection[2][0]
right_eye = detection[2][1]
detected_face = FaceDetector.alignment_procedure(detected_face, left_eye, right_eye)
resp.append((detected_face, img_region, confidence))
return resp

View File

@ -1,11 +1,15 @@
from typing import Any from typing import Any
import numpy as np import numpy as np
from deepface.detectors import FaceDetector from deepface.models.Detector import Detector
# Link - https://google.github.io/mediapipe/solutions/face_detection # Link - https://google.github.io/mediapipe/solutions/face_detection
def build_model() -> Any: class MediaPipe(Detector):
def __init__(self):
self.model = self.build_model()
def build_model(self) -> Any:
""" """
Build a mediapipe face detector model Build a mediapipe face detector model
Returns: Returns:
@ -24,12 +28,10 @@ def build_model() -> Any:
face_detection = mp_face_detection.FaceDetection(min_detection_confidence=0.7) face_detection = mp_face_detection.FaceDetection(min_detection_confidence=0.7)
return face_detection return face_detection
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list:
""" """
Detect and align face with mediapipe Detect and align face with mediapipe
Args: Args:
face_detector (Any): mediapipe face detector object
img (np.ndarray): pre-loaded image img (np.ndarray): pre-loaded image
align (bool): default is true align (bool): default is true
Returns: Returns:
@ -40,7 +42,7 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list
img_width = img.shape[1] img_width = img.shape[1]
img_height = img.shape[0] img_height = img.shape[0]
results = face_detector.process(img) results = self.model.process(img)
# If no face has been detected, return an empty list # If no face has been detected, return an empty list
if results.detections is None: if results.detections is None:
@ -71,7 +73,9 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list
img_region = [x, y, w, h] img_region = [x, y, w, h]
if align: if align:
detected_face = FaceDetector.alignment_procedure(detected_face, left_eye, right_eye) detected_face = self.align_face(
img=detected_face, left_eye=left_eye, right_eye=right_eye
)
resp.append((detected_face, img_region, confidence)) resp.append((detected_face, img_region, confidence))

View File

@ -1,26 +1,21 @@
from typing import Any
import cv2 import cv2
import numpy as np import numpy as np
from deepface.detectors import FaceDetector from mtcnn import MTCNN
from deepface.models.Detector import Detector
def build_model() -> Any: class MtCnn(Detector):
""" """
Build a mtcnn face detector model Class to cover common face detection functionalitiy for MtCnn backend
Returns:
model (Any)
""" """
from mtcnn import MTCNN
face_detector = MTCNN() def __init__(self):
return face_detector self.model = MTCNN()
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list:
""" """
Detect and align face with mtcnn Detect and align face with mtcnn
Args: Args:
face_detector (mtcnn.MTCNN): mtcnn face detector object
img (np.ndarray): pre-loaded image img (np.ndarray): pre-loaded image
align (bool): default is true align (bool): default is true
Returns: Returns:
@ -33,7 +28,7 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list
img_region = [0, 0, img.shape[1], img.shape[0]] img_region = [0, 0, img.shape[1], img.shape[0]]
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
detections = face_detector.detect_faces(img_rgb) detections = self.model.detect_faces(img_rgb)
if len(detections) > 0: if len(detections) > 0:
@ -47,7 +42,9 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list
keypoints = detection["keypoints"] keypoints = detection["keypoints"]
left_eye = keypoints["left_eye"] left_eye = keypoints["left_eye"]
right_eye = keypoints["right_eye"] right_eye = keypoints["right_eye"]
detected_face = FaceDetector.alignment_procedure(detected_face, left_eye, right_eye) detected_face = self.align_face(
img=detected_face, left_eye=left_eye, right_eye=right_eye
)
resp.append((detected_face, img_region, confidence)) resp.append((detected_face, img_region, confidence))

View File

@ -2,28 +2,128 @@ import os
from typing import Any from typing import Any
import cv2 import cv2
import numpy as np import numpy as np
from deepface.detectors import FaceDetector from deepface.models.Detector import Detector
def build_model() -> dict: class OpenCv(Detector):
""" """
Build a opencv face&eye detector models Class to cover common face detection functionalitiy for OpenCv backend
"""
def __init__(self):
self.model = self.build_model()
def build_model(self):
"""
Build opencv's face and eye detector models
Returns: Returns:
model (Any) model (dict): including face_detector and eye_detector keys
""" """
detector = {} detector = {}
detector["face_detector"] = build_cascade("haarcascade") detector["face_detector"] = self.__build_cascade("haarcascade")
detector["eye_detector"] = build_cascade("haarcascade_eye") detector["eye_detector"] = self.__build_cascade("haarcascade_eye")
return detector return detector
def detect_faces(self, 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 = []
def build_cascade(model_name="haarcascade") -> Any: detected_face = None
img_region = [0, 0, img.shape[1], img.shape[0]]
faces = []
try:
# faces = detector["face_detector"].detectMultiScale(img, 1.3, 5)
# note that, by design, opencv's haarcascade scores are >0 but not capped at 1
faces, _, scores = self.model["face_detector"].detectMultiScale3(
img, 1.1, 10, outputRejectLevels=True
)
except:
pass
if len(faces) > 0:
for (x, y, w, h), confidence in zip(faces, scores):
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
if align:
left_eye, right_eye = self.find_eyes(img=detected_face)
detected_face = self.align_face(detected_face, left_eye, right_eye)
img_region = [x, y, w, h]
resp.append((detected_face, img_region, confidence))
return resp
def find_eyes(self, img: np.ndarray) -> tuple:
"""
Find the left and right eye coordinates of given image
Args:
img (np.ndarray): given image
Returns:
left and right eye (tuple)
"""
left_eye = None
right_eye = None
# if image has unexpectedly 0 dimension then skip alignment
if img.shape[0] == 0 or img.shape[1] == 0:
return left_eye, right_eye
detected_face_gray = cv2.cvtColor(
img, cv2.COLOR_BGR2GRAY
) # eye detector expects gray scale image
eyes = self.model["eye_detector"].detectMultiScale(detected_face_gray, 1.1, 10)
# ----------------------------------------------------------------
# opencv eye detection module is not strong. it might find more than 2 eyes!
# besides, it returns eyes with different order in each call (issue 435)
# this is an important issue because opencv is the default detector and ssd also uses this
# find the largest 2 eye. Thanks to @thelostpeace
eyes = sorted(eyes, key=lambda v: abs(v[2] * v[3]), reverse=True)
# ----------------------------------------------------------------
if len(eyes) >= 2:
# decide left and right eye
eye_1 = eyes[0]
eye_2 = eyes[1]
if eye_1[0] < eye_2[0]:
left_eye = eye_1
right_eye = eye_2
else:
left_eye = eye_2
right_eye = eye_1
# -----------------------
# find center of eyes
left_eye = (int(left_eye[0] + (left_eye[2] / 2)), int(left_eye[1] + (left_eye[3] / 2)))
right_eye = (
int(right_eye[0] + (right_eye[2] / 2)),
int(right_eye[1] + (right_eye[3] / 2)),
)
return left_eye, right_eye
def __build_cascade(self, model_name="haarcascade") -> Any:
""" """
Build a opencv face&eye detector models Build a opencv face&eye detector models
Returns: Returns:
model (Any) model (Any)
""" """
opencv_path = get_opencv_path() opencv_path = self.__get_opencv_path()
if model_name == "haarcascade": if model_name == "haarcascade":
face_detector_path = opencv_path + "haarcascade_frontalface_default.xml" face_detector_path = opencv_path + "haarcascade_frontalface_default.xml"
if os.path.isfile(face_detector_path) != True: if os.path.isfile(face_detector_path) != True:
@ -49,100 +149,7 @@ def build_cascade(model_name="haarcascade") -> Any:
return detector return detector
def __get_opencv_path(self) -> str:
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
img_region = [0, 0, img.shape[1], img.shape[0]]
faces = []
try:
# faces = detector["face_detector"].detectMultiScale(img, 1.3, 5)
# note that, by design, opencv's haarcascade scores are >0 but not capped at 1
faces, _, scores = detector["face_detector"].detectMultiScale3(
img, 1.1, 10, outputRejectLevels=True
)
except:
pass
if len(faces) > 0:
for (x, y, w, h), confidence in zip(faces, scores):
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
if align:
detected_face = align_face(detector["eye_detector"], detected_face)
img_region = [x, y, w, h]
resp.append((detected_face, img_region, confidence))
return resp
def align_face(eye_detector: Any, img: np.ndarray) -> np.ndarray:
"""
Align a given image with the pre-built eye_detector
Args:
eye_detector (Any): cascade classifier object
img (np.ndarray): given image
Returns:
aligned_img (np.ndarray)
"""
# if image has unexpectedly 0 dimension then skip alignment
if img.shape[0] == 0 or img.shape[1] == 0:
return img
detected_face_gray = cv2.cvtColor(
img, cv2.COLOR_BGR2GRAY
) # eye detector expects gray scale image
# eyes = eye_detector.detectMultiScale(detected_face_gray, 1.3, 5)
eyes = eye_detector.detectMultiScale(detected_face_gray, 1.1, 10)
# ----------------------------------------------------------------
# opencv eye detectin module is not strong. it might find more than 2 eyes!
# besides, it returns eyes with different order in each call (issue 435)
# this is an important issue because opencv is the default detector and ssd also uses this
# find the largest 2 eye. Thanks to @thelostpeace
eyes = sorted(eyes, key=lambda v: abs(v[2] * v[3]), reverse=True)
# ----------------------------------------------------------------
if len(eyes) >= 2:
# decide left and right eye
eye_1 = eyes[0]
eye_2 = eyes[1]
if eye_1[0] < eye_2[0]:
left_eye = eye_1
right_eye = eye_2
else:
left_eye = eye_2
right_eye = eye_1
# -----------------------
# find center of eyes
left_eye = (int(left_eye[0] + (left_eye[2] / 2)), int(left_eye[1] + (left_eye[3] / 2)))
right_eye = (int(right_eye[0] + (right_eye[2] / 2)), int(right_eye[1] + (right_eye[3] / 2)))
img = FaceDetector.alignment_procedure(img, left_eye, right_eye)
return img # return img anyway
def get_opencv_path() -> str:
""" """
Returns where opencv installed Returns where opencv installed
Returns: Returns:

View File

@ -1,24 +1,17 @@
from typing import Any
import numpy as np import numpy as np
from retinaface import RetinaFace from retinaface import RetinaFace as rf
from retinaface.commons import postprocess from retinaface.commons import postprocess
from deepface.models.Detector import Detector
def build_model() -> Any: class RetinaFace(Detector):
""" def __init__(self):
Build a retinaface detector model self.model = rf.build_model()
Returns:
model (Any)
"""
face_detector = RetinaFace.build_model()
return face_detector
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list:
""" """
Detect and align face with retinaface Detect and align face with retinaface
Args: Args:
face_detector (Any): retinaface face detector object
img (np.ndarray): pre-loaded image img (np.ndarray): pre-loaded image
align (bool): default is true align (bool): default is true
Returns: Returns:
@ -26,7 +19,7 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list
""" """
resp = [] resp = []
obj = RetinaFace.detect_faces(img, model=face_detector, threshold=0.9) obj = rf.detect_faces(img, model=self.model, threshold=0.9)
if isinstance(obj, dict): if isinstance(obj, dict):
for face_idx in obj.keys(): for face_idx in obj.keys():
@ -41,7 +34,9 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list
confidence = identity["score"] confidence = identity["score"]
# detected_face = img[int(y):int(y+h), int(x):int(x+w)] #opencv # 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]] detected_face = img[
facial_area[1] : facial_area[3], facial_area[0] : facial_area[2]
]
if align: if align:
landmarks = identity["landmarks"] landmarks = identity["landmarks"]

View File

@ -5,6 +5,7 @@ import pandas as pd
import numpy as np import numpy as np
from deepface.detectors import OpenCvWrapper from deepface.detectors import OpenCvWrapper
from deepface.commons import functions from deepface.commons import functions
from deepface.models.Detector import Detector
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger(module="detectors.SsdWrapper") logger = Logger(module="detectors.SsdWrapper")
@ -12,11 +13,15 @@ logger = Logger(module="detectors.SsdWrapper")
# pylint: disable=line-too-long # pylint: disable=line-too-long
def build_model() -> dict: class Ssd(Detector):
def __init__(self):
self.model = self.build_model()
def build_model(self) -> dict:
""" """
Build a ssd detector model Build a ssd detector model
Returns: Returns:
model (Any) model (dict)
""" """
home = functions.get_deepface_home() home = functions.get_deepface_home()
@ -33,7 +38,10 @@ def build_model() -> dict:
gdown.download(url, output, quiet=False) gdown.download(url, output, quiet=False)
# pre-trained weights # pre-trained weights
if os.path.isfile(home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel") != True: if (
os.path.isfile(home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel")
!= True
):
logger.info("res10_300x300_ssd_iter_140000.caffemodel will be downloaded...") logger.info("res10_300x300_ssd_iter_140000.caffemodel will be downloaded...")
@ -55,20 +63,16 @@ def build_model() -> dict:
+ "You can install it as pip install opencv-contrib-python." + "You can install it as pip install opencv-contrib-python."
) from err ) from err
eye_detector = OpenCvWrapper.build_cascade("haarcascade_eye")
detector = {} detector = {}
detector["face_detector"] = face_detector detector["face_detector"] = face_detector
detector["eye_detector"] = eye_detector detector["opencv_module"] = OpenCvWrapper.OpenCv()
return detector return detector
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
""" """
Detect and align face with ssd Detect and align face with ssd
Args: Args:
face_detector (Any): ssd face detector object
img (np.ndarray): pre-loaded image img (np.ndarray): pre-loaded image
align (bool): default is true align (bool): default is true
Returns: Returns:
@ -94,7 +98,7 @@ def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
imageBlob = cv2.dnn.blobFromImage(image=img) imageBlob = cv2.dnn.blobFromImage(image=img)
face_detector = detector["face_detector"] face_detector = self.model["face_detector"]
face_detector.setInput(imageBlob) face_detector.setInput(imageBlob)
detections = face_detector.forward() detections = face_detector.forward()
@ -130,8 +134,11 @@ def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
confidence = instance["confidence"] confidence = instance["confidence"]
if align: if align:
detected_face = OpenCvWrapper.align_face(detector["eye_detector"], detected_face) opencv_module: OpenCvWrapper.OpenCv = self.model["opencv_module"]
left_eye, right_eye = opencv_module.find_eyes(detected_face)
detected_face = self.align_face(
img=detected_face, left_eye=left_eye, right_eye=right_eye
)
resp.append((detected_face, img_region, confidence)) resp.append((detected_face, img_region, confidence))
return resp return resp

View File

@ -1,6 +1,6 @@
from typing import Any from typing import Any
import numpy as np import numpy as np
from deepface.detectors import FaceDetector from deepface.models.Detector import Detector
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger() logger = Logger()
@ -16,7 +16,11 @@ WEIGHT_URL = "https://drive.google.com/uc?id=1qcr9DbgsX3ryrz2uU8w4Xm3cOrRywXqb"
LANDMARKS_CONFIDENCE_THRESHOLD = 0.5 LANDMARKS_CONFIDENCE_THRESHOLD = 0.5
def build_model() -> Any: class Yolo(Detector):
def __init__(self):
self.model = self.build_model()
def build_model(self) -> Any:
""" """
Build a yolo detector model Build a yolo detector model
Returns: Returns:
@ -46,8 +50,7 @@ def build_model() -> Any:
# Return face_detector # Return face_detector
return YOLO(weight_path) return YOLO(weight_path)
def detect_faces(self, img: np.ndarray, align: bool = False) -> list:
def detect_face(face_detector: Any, img: np.ndarray, align: bool = False) -> list:
""" """
Detect and align face with yolo Detect and align face with yolo
Args: Args:
@ -60,7 +63,7 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = False) -> lis
resp = [] resp = []
# Detect faces # Detect faces
results = face_detector.predict(img, verbose=False, show=False, conf=0.25)[0] results = self.model.predict(img, verbose=False, show=False, conf=0.25)[0]
# For each face, extract the bounding box, the landmarks and confidence # For each face, extract the bounding box, the landmarks and confidence
for result in results: for result in results:
@ -82,8 +85,8 @@ def detect_face(face_detector: Any, img: np.ndarray, align: bool = False) -> lis
left_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD left_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD
and right_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD and right_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD
): ):
detected_face = FaceDetector.alignment_procedure( detected_face = self.align_face(
detected_face, left_eye[0].cpu(), right_eye[0].cpu() img=detected_face, left_eye=left_eye[0].cpu(), right_eye=right_eye[0].cpu()
) )
resp.append((detected_face, [x, y, w, h], confidence)) resp.append((detected_face, [x, y, w, h], confidence))

View File

@ -3,14 +3,18 @@ from typing import Any
import cv2 import cv2
import numpy as np import numpy as np
import gdown import gdown
from deepface.detectors import FaceDetector
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Detector import Detector
logger = Logger(module="detectors.YunetWrapper") logger = Logger(module="detectors.YunetWrapper")
def build_model() -> Any: class YuNet(Detector):
def __init__(self):
self.model = self.build_model()
def build_model(self) -> Any:
""" """
Build a yunet detector model Build a yunet detector model
Returns: Returns:
@ -37,14 +41,10 @@ def build_model() -> Any:
) from err ) from err
return face_detector return face_detector
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
def detect_face(
detector: Any, image: np.ndarray, align: bool = True, score_threshold: float = 0.9
) -> list:
""" """
Detect and align face with yunet Detect and align face with yunet
Args: Args:
face_detector (Any): yunet face detector object
img (np.ndarray): pre-loaded image img (np.ndarray): pre-loaded image
align (bool): default is true align (bool): default is true
Returns: Returns:
@ -52,24 +52,24 @@ def detect_face(
""" """
# FaceDetector.detect_faces does not support score_threshold parameter. # FaceDetector.detect_faces does not support score_threshold parameter.
# We can set it via environment variable. # We can set it via environment variable.
score_threshold = os.environ.get("yunet_score_threshold", score_threshold) score_threshold = os.environ.get("yunet_score_threshold", "0.9")
resp = [] resp = []
detected_face = None detected_face = None
img_region = [0, 0, image.shape[1], image.shape[0]] img_region = [0, 0, img.shape[1], img.shape[0]]
faces = [] faces = []
height, width = image.shape[0], image.shape[1] height, width = img.shape[0], img.shape[1]
# resize image if it is too large (Yunet fails to detect faces on large input sometimes) # resize image if it is too large (Yunet fails to detect faces on large input sometimes)
# I picked 640 as a threshold because it is the default value of max_size in Yunet. # I picked 640 as a threshold because it is the default value of max_size in Yunet.
resized = False resized = False
if height > 640 or width > 640: if height > 640 or width > 640:
r = 640.0 / max(height, width) r = 640.0 / max(height, width)
original_image = image.copy() original_image = img.copy()
image = cv2.resize(image, (int(width * r), int(height * r))) img = cv2.resize(img, (int(width * r), int(height * r)))
height, width = image.shape[0], image.shape[1] height, width = img.shape[0], img.shape[1]
resized = True resized = True
detector.setInputSize((width, height)) self.model.setInputSize((width, height))
detector.setScoreThreshold(score_threshold) self.model.setScoreThreshold(score_threshold)
_, faces = detector.detect(image) _, faces = self.model.detect(img)
if faces is None: if faces is None:
return resp return resp
for face in faces: for face in faces:
@ -94,7 +94,7 @@ def detect_face(
x = max(x, 0) x = max(x, 0)
y = max(y, 0) y = max(y, 0)
if resized: if resized:
image = original_image img = original_image
x, y, w, h = int(x / r), int(y / r), int(w / r), int(h / r) x, y, w, h = int(x / r), int(y / r), int(w / r), int(h / r)
x_re, y_re, x_le, y_le = ( x_re, y_re, x_le, y_le = (
int(x_re / r), int(x_re / r),
@ -104,11 +104,9 @@ def detect_face(
) )
confidence = face[-1] confidence = face[-1]
confidence = f"{confidence:.2f}" confidence = f"{confidence:.2f}"
detected_face = image[int(y) : int(y + h), int(x) : int(x + w)] detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
img_region = [x, y, w, h] img_region = [x, y, w, h]
if align: if align:
detected_face = FaceDetector.alignment_procedure( detected_face = self.align_face(detected_face, (x_re, y_re), (x_le, y_le))
detected_face, (x_re, y_re), (x_le, y_le)
)
resp.append((detected_face, img_region, confidence)) resp.append((detected_face, img_region, confidence))
return resp return resp

View File

@ -5,6 +5,7 @@ import tensorflow as tf
from deepface.basemodels import VGGFace from deepface.basemodels import VGGFace
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography
logger = Logger(module="extendedmodels.Age") logger = Logger(module="extendedmodels.Age")
@ -22,12 +23,31 @@ else:
# ---------------------------------------- # ----------------------------------------
# pylint: disable=too-few-public-methods
class ApparentAge(Demography):
"""
Age model class
"""
def loadModel( def __init__(self):
self.model = load_model()
self.model_name = "Age"
def predict(self, img: np.ndarray) -> np.float64:
age_predictions = self.model.predict(img, verbose=0)[0, :]
return find_apparent_age(age_predictions)
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/age_model_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/age_model_weights.h5",
) -> Model: ) -> Model:
"""
Construct age model, download its weights and load
Returns:
model (Model)
"""
model = VGGFace.baseModel() model = VGGFace.base_model()
# -------------------------- # --------------------------
@ -60,7 +80,14 @@ def loadModel(
# -------------------------- # --------------------------
def findApparentAge(age_predictions) -> np.float64: def find_apparent_age(age_predictions: np.ndarray) -> np.float64:
"""
Find apparent age prediction from a given probas of ages
Args:
age_predictions (?)
Returns:
apparent_age (float)
"""
output_indexes = np.array(list(range(0, 101))) output_indexes = np.array(list(range(0, 101)))
apparent_age = np.sum(age_predictions * output_indexes) apparent_age = np.sum(age_predictions * output_indexes)
return apparent_age return apparent_age

View File

@ -1,8 +1,11 @@
import os import os
import gdown import gdown
import tensorflow as tf import tensorflow as tf
import numpy as np
import cv2
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography
logger = Logger(module="extendedmodels.Emotion") logger = Logger(module="extendedmodels.Emotion")
@ -30,10 +33,31 @@ else:
# Labels for the emotions that can be detected by the model. # Labels for the emotions that can be detected by the model.
labels = ["angry", "disgust", "fear", "happy", "sad", "surprise", "neutral"] labels = ["angry", "disgust", "fear", "happy", "sad", "surprise", "neutral"]
# pylint: disable=too-few-public-methods
class FacialExpression(Demography):
"""
Emotion model class
"""
def loadModel( def __init__(self):
self.model = load_model()
self.model_name = "Emotion"
def predict(self, img: np.ndarray) -> np.ndarray:
img_gray = cv2.cvtColor(img[0], cv2.COLOR_BGR2GRAY)
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, :]
return emotion_predictions
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/facial_expression_model_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/facial_expression_model_weights.h5",
) -> Sequential: ) -> Sequential:
"""
Consruct emotion model, download and load weights
"""
num_classes = 7 num_classes = 7

View File

@ -1,9 +1,11 @@
import os import os
import gdown import gdown
import tensorflow as tf import tensorflow as tf
import numpy as np
from deepface.basemodels import VGGFace from deepface.basemodels import VGGFace
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography
logger = Logger(module="extendedmodels.Gender") logger = Logger(module="extendedmodels.Gender")
@ -25,12 +27,30 @@ else:
# Labels for the genders that can be detected by the model. # Labels for the genders that can be detected by the model.
labels = ["Woman", "Man"] labels = ["Woman", "Man"]
# pylint: disable=too-few-public-methods
class Gender(Demography):
"""
Gender model class
"""
def loadModel( def __init__(self):
self.model = load_model()
self.model_name = "Gender"
def predict(self, img: np.ndarray) -> np.ndarray:
return self.model.predict(img, verbose=0)[0, :]
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/gender_model_weights.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/gender_model_weights.h5",
) -> Model: ) -> Model:
"""
Construct gender model, download its weights and load
Returns:
model (Model)
"""
model = VGGFace.baseModel() model = VGGFace.base_model()
# -------------------------- # --------------------------

View File

@ -1,9 +1,11 @@
import os import os
import gdown import gdown
import tensorflow as tf import tensorflow as tf
import numpy as np
from deepface.basemodels import VGGFace from deepface.basemodels import VGGFace
from deepface.commons import functions from deepface.commons import functions
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography
logger = Logger(module="extendedmodels.Race") logger = Logger(module="extendedmodels.Race")
@ -23,12 +25,28 @@ else:
# Labels for the ethnic phenotypes that can be detected by the model. # Labels for the ethnic phenotypes that can be detected by the model.
labels = ["asian", "indian", "black", "white", "middle eastern", "latino hispanic"] labels = ["asian", "indian", "black", "white", "middle eastern", "latino hispanic"]
# pylint: disable=too-few-public-methods
class Race(Demography):
"""
Race model class
"""
def loadModel( def __init__(self):
self.model = load_model()
self.model_name = "Race"
def predict(self, img: np.ndarray) -> np.ndarray:
return self.model.predict(img, verbose=0)[0, :]
def load_model(
url="https://github.com/serengil/deepface_models/releases/download/v1.0/race_model_single_batch.h5", url="https://github.com/serengil/deepface_models/releases/download/v1.0/race_model_single_batch.h5",
) -> Model: ) -> Model:
"""
Construct race model, download its weights and load
"""
model = VGGFace.baseModel() model = VGGFace.base_model()
# -------------------------- # --------------------------

View File

@ -0,0 +1,23 @@
from typing import Union
from abc import ABC, abstractmethod
import numpy as np
import tensorflow as tf
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
# Notice that all facial attribute analysis models must be inherited from this class
# pylint: disable=too-few-public-methods
class Demography(ABC):
model: Model
model_name: str
@abstractmethod
def predict(self, img: np.ndarray) -> Union[np.ndarray, np.float64]:
pass

View File

@ -0,0 +1,39 @@
from abc import ABC, abstractmethod
from typing import Union
import numpy as np
from PIL import Image
# Notice that all facial detector models must be inherited from this class
class Detector(ABC):
@abstractmethod
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
pass
def align_face(
self, img: np.ndarray, left_eye: Union[list, tuple], right_eye: Union[list, tuple]
) -> np.ndarray:
"""
Align a given image horizantally with respect to their left and right eye locations
Args:
img (np.ndarray): pre-loaded image with detected face
left_eye (list or tuple): coordinates of left eye with respect to the you
right_eye(list or tuple): coordinates of right eye with respect to the you
Returns:
img (np.ndarray): aligned facial image
"""
# if eye could not be detected for the given image, return image itself
if left_eye is None or right_eye is None:
return img
# sometimes unexpectedly detected images come with nil dimensions
if img.shape[0] == 0 or img.shape[1] == 0:
return img
angle = float(
np.degrees(np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0]))
)
img = Image.fromarray(img)
img = np.array(img.rotate(angle))
return img

View File

@ -0,0 +1,28 @@
from abc import ABC
from typing import Any, Union
import numpy as np
import tensorflow as tf
tf_version = int(tf.__version__.split(".", maxsplit=1)[0])
if tf_version == 2:
from tensorflow.keras.models import Model
else:
from keras.models import Model
# Notice that all facial recognition models must be inherited from this class
# pylint: disable=too-few-public-methods
class FacialRecognition(ABC):
model: Union[Model, Any]
model_name: str
def find_embeddings(self, img: np.ndarray) -> list:
if not isinstance(self.model, Model):
raise ValueError(
"If a facial recognition model is not type of (tf.)keras.models.Model,"
"Then its find_embeddings method must be implemented its own module."
f"However {self.model_name}'s model type is {type(self.model)}"
)
# model.predict causes memory issue when it is called in a for loop
# embedding = model.predict(img, verbose=0)[0].tolist()
return self.model(img, training=False).numpy()[0].tolist()

View File

@ -4,12 +4,11 @@ from typing import Any, Dict, List, Union
# 3rd party dependencies # 3rd party dependencies
import numpy as np import numpy as np
from tqdm import tqdm from tqdm import tqdm
import cv2
# project dependencies # project dependencies
from deepface.modules import modeling from deepface.modules import modeling
from deepface.commons import functions from deepface.commons import functions
from deepface.extendedmodels import Age, Gender, Race, Emotion from deepface.extendedmodels import Gender, Race, Emotion
def analyze( def analyze(
@ -123,18 +122,10 @@ def analyze(
pbar.set_description(f"Action: {action}") pbar.set_description(f"Action: {action}")
if action == "emotion": if action == "emotion":
img_gray = cv2.cvtColor(img_content[0], cv2.COLOR_BGR2GRAY) emotion_predictions = modeling.build_model("Emotion").predict(img_content)
img_gray = cv2.resize(img_gray, (48, 48))
img_gray = np.expand_dims(img_gray, axis=0)
emotion_predictions = modeling.build_model("Emotion").predict(
img_gray, verbose=0
)[0, :]
sum_of_predictions = emotion_predictions.sum() sum_of_predictions = emotion_predictions.sum()
obj["emotion"] = {} obj["emotion"] = {}
for i, emotion_label in enumerate(Emotion.labels): for i, emotion_label in enumerate(Emotion.labels):
emotion_prediction = 100 * emotion_predictions[i] / sum_of_predictions emotion_prediction = 100 * emotion_predictions[i] / sum_of_predictions
obj["emotion"][emotion_label] = emotion_prediction obj["emotion"][emotion_label] = emotion_prediction
@ -142,17 +133,12 @@ def analyze(
obj["dominant_emotion"] = Emotion.labels[np.argmax(emotion_predictions)] obj["dominant_emotion"] = Emotion.labels[np.argmax(emotion_predictions)]
elif action == "age": elif action == "age":
age_predictions = modeling.build_model("Age").predict(img_content, verbose=0)[ apparent_age = modeling.build_model("Age").predict(img_content)
0, :
]
apparent_age = Age.findApparentAge(age_predictions)
# int cast is for exception - object of type 'float32' is not JSON serializable # int cast is for exception - object of type 'float32' is not JSON serializable
obj["age"] = int(apparent_age) obj["age"] = int(apparent_age)
elif action == "gender": elif action == "gender":
gender_predictions = modeling.build_model("Gender").predict( gender_predictions = modeling.build_model("Gender").predict(img_content)
img_content, verbose=0
)[0, :]
obj["gender"] = {} obj["gender"] = {}
for i, gender_label in enumerate(Gender.labels): for i, gender_label in enumerate(Gender.labels):
gender_prediction = 100 * gender_predictions[i] gender_prediction = 100 * gender_predictions[i]
@ -161,9 +147,7 @@ def analyze(
obj["dominant_gender"] = Gender.labels[np.argmax(gender_predictions)] obj["dominant_gender"] = Gender.labels[np.argmax(gender_predictions)]
elif action == "race": elif action == "race":
race_predictions = modeling.build_model("Race").predict(img_content, verbose=0)[ race_predictions = modeling.build_model("Race").predict(img_content)
0, :
]
sum_of_predictions = race_predictions.sum() sum_of_predictions = race_predictions.sum()
obj["race"] = {} obj["race"] = {}

View File

@ -1,32 +1,21 @@
# built-in dependencies # built-in dependencies
from typing import Any, Union from typing import Any
# 3rd party dependencies
import tensorflow as tf
# project dependencies # project dependencies
from deepface.basemodels import ( from deepface.basemodels import (
VGGFace, VGGFace,
OpenFace, OpenFace,
Facenet, Facenet,
Facenet512,
FbDeepFace, FbDeepFace,
DeepID, DeepID,
DlibWrapper, DlibResNet,
ArcFace, ArcFace,
SFace, SFace,
) )
from deepface.extendedmodels import Age, Gender, Race, Emotion from deepface.extendedmodels import Age, Gender, Race, Emotion
# conditional dependencies
tf_version = int(tf.__version__.split(".", maxsplit=1)[0])
if tf_version == 2:
from tensorflow.keras.models import Model
else:
from keras.models import Model
def build_model(model_name: str) -> Any:
def build_model(model_name: str) -> Union[Model, Any]:
""" """
This function builds a deepface model This function builds a deepface model
Parameters: Parameters:
@ -35,26 +24,26 @@ def build_model(model_name: str) -> Union[Model, Any]:
Age, Gender, Emotion, Race for facial attributes Age, Gender, Emotion, Race for facial attributes
Returns: Returns:
built deepface model ( (tf.)keras.models.Model ) built model class
""" """
# singleton design pattern # singleton design pattern
global model_obj global model_obj
models = { models = {
"VGG-Face": VGGFace.loadModel, "VGG-Face": VGGFace.VggFace,
"OpenFace": OpenFace.loadModel, "OpenFace": OpenFace.OpenFace,
"Facenet": Facenet.loadModel, "Facenet": Facenet.FaceNet128d,
"Facenet512": Facenet512.loadModel, "Facenet512": Facenet.FaceNet512d,
"DeepFace": FbDeepFace.loadModel, "DeepFace": FbDeepFace.DeepFace,
"DeepID": DeepID.loadModel, "DeepID": DeepID.DeepId,
"Dlib": DlibWrapper.loadModel, "Dlib": DlibResNet.Dlib,
"ArcFace": ArcFace.loadModel, "ArcFace": ArcFace.ArcFace,
"SFace": SFace.load_model, "SFace": SFace.SFace,
"Emotion": Emotion.loadModel, "Emotion": Emotion.FacialExpression,
"Age": Age.loadModel, "Age": Age.ApparentAge,
"Gender": Gender.loadModel, "Gender": Gender.Gender,
"Race": Race.loadModel, "Race": Race.Race,
} }
if not "model_obj" in globals(): if not "model_obj" in globals():

View File

@ -4,18 +4,11 @@ from typing import Any, Dict, List, Union
# 3rd party dependencies # 3rd party dependencies
import numpy as np import numpy as np
import cv2 import cv2
import tensorflow as tf
# project dependencies # project dependencies
from deepface.modules import modeling from deepface.modules import modeling
from deepface.commons import functions from deepface.commons import functions
from deepface.models.FacialRecognition import FacialRecognition
# conditional dependencies
tf_version = int(tf.__version__.split(".", maxsplit=1)[0])
if tf_version == 2:
from tensorflow.keras.models import Model
else:
from keras.models import Model
def represent( def represent(
@ -71,7 +64,7 @@ def represent(
""" """
resp_objs = [] resp_objs = []
model = modeling.build_model(model_name) model: FacialRecognition = modeling.build_model(model_name)
# --------------------------------- # ---------------------------------
# we have run pre-process in verification. so, this can be skipped if it is coming from verify. # we have run pre-process in verification. so, this can be skipped if it is coming from verify.
@ -107,18 +100,7 @@ def represent(
# custom normalization # custom normalization
img = functions.normalize_input(img=img, normalization=normalization) img = functions.normalize_input(img=img, normalization=normalization)
# represent embedding = model.find_embeddings(img)
# if "keras" in str(type(model)):
if isinstance(model, Model):
# model.predict causes memory issue when it is called in a for loop
# embedding = model.predict(img, verbose=0)[0].tolist()
embedding = model(img, training=False).numpy()[0].tolist()
# if you still get verbose logging. try call
# - `tf.keras.utils.disable_interactive_logging()`
# in your main program
else:
# SFace and Dlib are not keras models and no verbose arguments
embedding = model.predict(img)[0].tolist()
resp_obj = {} resp_obj = {}
resp_obj["embedding"] = embedding resp_obj["embedding"] = embedding

View File

@ -11,4 +11,3 @@ mtcnn>=0.1.0
retina-face>=0.0.1 retina-face>=0.0.1
fire>=0.4.0 fire>=0.4.0
gunicorn>=20.1.0 gunicorn>=20.1.0
Deprecated>=1.2.13

View File

@ -4,6 +4,10 @@ from deepface.commons.logger import Logger
logger = Logger() logger = Logger()
# some models (e.g. Dlib) and detectors (e.g. retinaface) do not have test cases
# because they require to install huge packages
# this module is for local runs
model_names = [ model_names = [
"VGG-Face", "VGG-Face",
"Facenet", "Facenet",
@ -17,6 +21,7 @@ model_names = [
] ]
detector_backends = ["opencv", "ssd", "dlib", "mtcnn", "retinaface"] detector_backends = ["opencv", "ssd", "dlib", "mtcnn", "retinaface"]
# verification # verification
for model_name in model_names: for model_name in model_names:
obj = DeepFace.verify( obj = DeepFace.verify(