mirror of
https://github.com/serengil/deepface.git
synced 2025-06-07 12:05:22 +00:00
facial recognition, detector and demography models are now using interface
This commit is contained in:
parent
51bb1808d0
commit
0fd77e1c99
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from deepface.basemodels.DlibResNet import DlibResNet
|
|
||||||
|
|
||||||
|
|
||||||
def loadModel() -> Any:
|
|
||||||
return DlibResNet()
|
|
@ -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
|
||||||
|
@ -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
|
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
|
@ -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"
|
||||||
|
@ -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:
|
||||||
|
67
deepface/detectors/DetectorWrapper.py
Normal file
67
deepface/detectors/DetectorWrapper.py
Normal 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)
|
@ -3,106 +3,110 @@ 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):
|
||||||
Build a dlib hog face detector model
|
self.model = self.build_model()
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
|
||||||
home = functions.get_deepface_home()
|
|
||||||
|
|
||||||
# this is not a must dependency. do not import it in the global level.
|
def build_model(self) -> dict:
|
||||||
try:
|
"""
|
||||||
import dlib
|
Build a dlib hog face detector model
|
||||||
except ModuleNotFoundError as e:
|
Returns:
|
||||||
raise ImportError(
|
model (Any)
|
||||||
"Dlib is an optional detector, ensure the library is installed."
|
"""
|
||||||
"Please install using 'pip install dlib' "
|
home = functions.get_deepface_home()
|
||||||
) from e
|
|
||||||
|
|
||||||
# check required file exists in the home/.deepface/weights folder
|
# this is not a must dependency. do not import it in the global level.
|
||||||
if os.path.isfile(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat") != True:
|
try:
|
||||||
|
import dlib
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"Dlib is an optional detector, ensure the library is installed."
|
||||||
|
"Please install using 'pip install dlib' "
|
||||||
|
) from e
|
||||||
|
|
||||||
file_name = "shape_predictor_5_face_landmarks.dat.bz2"
|
# check required file exists in the home/.deepface/weights folder
|
||||||
logger.info(f"{file_name} is going to be downloaded")
|
if os.path.isfile(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat") != True:
|
||||||
|
|
||||||
url = f"http://dlib.net/files/{file_name}"
|
file_name = "shape_predictor_5_face_landmarks.dat.bz2"
|
||||||
output = f"{home}/.deepface/weights/{file_name}"
|
logger.info(f"{file_name} is going to be downloaded")
|
||||||
|
|
||||||
gdown.download(url, output, quiet=False)
|
url = f"http://dlib.net/files/{file_name}"
|
||||||
|
output = f"{home}/.deepface/weights/{file_name}"
|
||||||
|
|
||||||
zipfile = bz2.BZ2File(output)
|
gdown.download(url, output, quiet=False)
|
||||||
data = zipfile.read()
|
|
||||||
newfilepath = output[:-4] # discard .bz2 extension
|
|
||||||
with open(newfilepath, "wb") as f:
|
|
||||||
f.write(data)
|
|
||||||
|
|
||||||
face_detector = dlib.get_frontal_face_detector()
|
zipfile = bz2.BZ2File(output)
|
||||||
sp = dlib.shape_predictor(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat")
|
data = zipfile.read()
|
||||||
|
newfilepath = output[:-4] # discard .bz2 extension
|
||||||
|
with open(newfilepath, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
detector = {}
|
face_detector = dlib.get_frontal_face_detector()
|
||||||
detector["face_detector"] = face_detector
|
sp = dlib.shape_predictor(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat")
|
||||||
detector["sp"] = sp
|
|
||||||
return detector
|
|
||||||
|
|
||||||
|
detector = {}
|
||||||
|
detector["face_detector"] = face_detector
|
||||||
|
detector["sp"] = sp
|
||||||
|
return detector
|
||||||
|
|
||||||
def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
|
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||||
"""
|
"""
|
||||||
Detect and align face with dlib
|
Detect and align face with dlib
|
||||||
Args:
|
Args:
|
||||||
face_detector (Any): dlib face detector object
|
face_detector (Any): dlib 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:
|
||||||
list of detected and aligned faces
|
list of detected and aligned faces
|
||||||
"""
|
"""
|
||||||
# this is not a must dependency. do not import it in the global level.
|
# this is not a must dependency. do not import it in the global level.
|
||||||
try:
|
try:
|
||||||
import dlib
|
import dlib
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Dlib is an optional detector, ensure the library is installed."
|
"Dlib is an optional detector, ensure the library is installed."
|
||||||
"Please install using 'pip install dlib' "
|
"Please install using 'pip install dlib' "
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
if len(detections) > 0:
|
if len(detections) > 0:
|
||||||
|
|
||||||
for idx, d in enumerate(detections):
|
for idx, d in enumerate(detections):
|
||||||
left = d.left()
|
left = d.left()
|
||||||
right = d.right()
|
right = d.right()
|
||||||
top = d.top()
|
top = d.top()
|
||||||
bottom = d.bottom()
|
bottom = d.bottom()
|
||||||
|
|
||||||
# detected_face = img[top:bottom, left:right]
|
# detected_face = img[top:bottom, left:right]
|
||||||
detected_face = img[
|
detected_face = img[
|
||||||
max(0, top) : min(bottom, img.shape[0]), max(0, left) : min(right, img.shape[1])
|
max(0, top) : min(bottom, img.shape[0]), max(0, left) : min(right, img.shape[1])
|
||||||
]
|
]
|
||||||
|
|
||||||
img_region = [left, top, right - left, bottom - top]
|
img_region = [left, top, right - left, bottom - top]
|
||||||
confidence = scores[idx]
|
confidence = scores[idx]
|
||||||
|
|
||||||
if align:
|
if align:
|
||||||
img_shape = sp(img, detections[idx])
|
img_shape = sp(img, detections[idx])
|
||||||
detected_face = dlib.get_face_chip(img, img_shape, size=detected_face.shape[0])
|
detected_face = dlib.get_face_chip(img, img_shape, size=detected_face.shape[0])
|
||||||
|
|
||||||
resp.append((detected_face, img_region, confidence))
|
resp.append((detected_face, img_region, confidence))
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
@ -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
|
|
@ -1,35 +1,76 @@
|
|||||||
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):
|
||||||
Build a fast mtcnn face detector model
|
self.model = self.build_model()
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
|
||||||
# this is not a must dependency. do not import it in the global level.
|
|
||||||
try:
|
|
||||||
from facenet_pytorch import MTCNN as fast_mtcnn
|
|
||||||
except ModuleNotFoundError as e:
|
|
||||||
raise ImportError(
|
|
||||||
"FastMtcnn is an optional detector, ensure the library is installed."
|
|
||||||
"Please install using 'pip install facenet-pytorch' "
|
|
||||||
) from e
|
|
||||||
|
|
||||||
face_detector = fast_mtcnn(
|
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||||
image_size=160,
|
"""
|
||||||
thresholds=[0.6, 0.7, 0.7], # MTCNN thresholds
|
Detect and align face with mtcnn
|
||||||
post_process=True,
|
Args:
|
||||||
device="cpu",
|
img (np.ndarray): pre-loaded image
|
||||||
select_largest=False, # return result in descending order
|
align (bool): default is true
|
||||||
)
|
Returns:
|
||||||
return face_detector
|
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
|
||||||
|
Returns:
|
||||||
|
model (Any)
|
||||||
|
"""
|
||||||
|
# this is not a must dependency. do not import it in the global level.
|
||||||
|
try:
|
||||||
|
from facenet_pytorch import MTCNN as fast_mtcnn
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"FastMtcnn is an optional detector, ensure the library is installed."
|
||||||
|
"Please install using 'pip install facenet-pytorch' "
|
||||||
|
) from e
|
||||||
|
|
||||||
|
face_detector = fast_mtcnn(
|
||||||
|
image_size=160,
|
||||||
|
thresholds=[0.6, 0.7, 0.7], # MTCNN thresholds
|
||||||
|
post_process=True,
|
||||||
|
device="cpu",
|
||||||
|
select_largest=False, # return result in descending order
|
||||||
|
)
|
||||||
|
return face_detector
|
||||||
|
|
||||||
|
|
||||||
def xyxy_to_xywh(xyxy: Union[list, tuple]) -> list:
|
def xyxy_to_xywh(xyxy: Union[list, tuple]) -> list:
|
||||||
@ -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
|
|
||||||
|
@ -1,78 +1,82 @@
|
|||||||
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):
|
||||||
Build a mediapipe face detector model
|
self.model = self.build_model()
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
|
||||||
# this is not a must dependency. do not import it in the global level.
|
|
||||||
try:
|
|
||||||
import mediapipe as mp
|
|
||||||
except ModuleNotFoundError as e:
|
|
||||||
raise ImportError(
|
|
||||||
"MediaPipe is an optional detector, ensure the library is installed."
|
|
||||||
"Please install using 'pip install mediapipe' "
|
|
||||||
) from e
|
|
||||||
|
|
||||||
mp_face_detection = mp.solutions.face_detection
|
def build_model(self) -> Any:
|
||||||
face_detection = mp_face_detection.FaceDetection(min_detection_confidence=0.7)
|
"""
|
||||||
return face_detection
|
Build a mediapipe face detector model
|
||||||
|
Returns:
|
||||||
|
model (Any)
|
||||||
|
"""
|
||||||
|
# this is not a must dependency. do not import it in the global level.
|
||||||
|
try:
|
||||||
|
import mediapipe as mp
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"MediaPipe is an optional detector, ensure the library is installed."
|
||||||
|
"Please install using 'pip install mediapipe' "
|
||||||
|
) from e
|
||||||
|
|
||||||
|
mp_face_detection = mp.solutions.face_detection
|
||||||
|
face_detection = mp_face_detection.FaceDetection(min_detection_confidence=0.7)
|
||||||
|
return face_detection
|
||||||
|
|
||||||
def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list:
|
def detect_faces(self, 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:
|
list of detected and aligned faces
|
||||||
list of detected and aligned faces
|
"""
|
||||||
"""
|
resp = []
|
||||||
resp = []
|
|
||||||
|
|
||||||
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 results.detections is None:
|
||||||
|
return resp
|
||||||
|
|
||||||
|
# Extract the bounding box, the landmarks and the confidence score
|
||||||
|
for detection in results.detections:
|
||||||
|
(confidence,) = detection.score
|
||||||
|
|
||||||
|
bounding_box = detection.location_data.relative_bounding_box
|
||||||
|
landmarks = detection.location_data.relative_keypoints
|
||||||
|
|
||||||
|
x = int(bounding_box.xmin * img_width)
|
||||||
|
w = int(bounding_box.width * img_width)
|
||||||
|
y = int(bounding_box.ymin * img_height)
|
||||||
|
h = int(bounding_box.height * img_height)
|
||||||
|
|
||||||
|
# Extract landmarks
|
||||||
|
left_eye = (int(landmarks[0].x * img_width), int(landmarks[0].y * img_height))
|
||||||
|
right_eye = (int(landmarks[1].x * img_width), int(landmarks[1].y * img_height))
|
||||||
|
# nose = (int(landmarks[2].x * img_width), int(landmarks[2].y * img_height))
|
||||||
|
# mouth = (int(landmarks[3].x * img_width), int(landmarks[3].y * img_height))
|
||||||
|
# right_ear = (int(landmarks[4].x * img_width), int(landmarks[4].y * img_height))
|
||||||
|
# left_ear = (int(landmarks[5].x * img_width), int(landmarks[5].y * img_height))
|
||||||
|
|
||||||
|
if x > 0 and y > 0:
|
||||||
|
detected_face = img[y : y + h, x : x + w]
|
||||||
|
img_region = [x, y, w, h]
|
||||||
|
|
||||||
|
if align:
|
||||||
|
detected_face = self.align_face(
|
||||||
|
img=detected_face, left_eye=left_eye, right_eye=right_eye
|
||||||
|
)
|
||||||
|
|
||||||
|
resp.append((detected_face, img_region, confidence))
|
||||||
|
|
||||||
# If no face has been detected, return an empty list
|
|
||||||
if results.detections is None:
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
# Extract the bounding box, the landmarks and the confidence score
|
|
||||||
for detection in results.detections:
|
|
||||||
(confidence,) = detection.score
|
|
||||||
|
|
||||||
bounding_box = detection.location_data.relative_bounding_box
|
|
||||||
landmarks = detection.location_data.relative_keypoints
|
|
||||||
|
|
||||||
x = int(bounding_box.xmin * img_width)
|
|
||||||
w = int(bounding_box.width * img_width)
|
|
||||||
y = int(bounding_box.ymin * img_height)
|
|
||||||
h = int(bounding_box.height * img_height)
|
|
||||||
|
|
||||||
# Extract landmarks
|
|
||||||
left_eye = (int(landmarks[0].x * img_width), int(landmarks[0].y * img_height))
|
|
||||||
right_eye = (int(landmarks[1].x * img_width), int(landmarks[1].y * img_height))
|
|
||||||
# nose = (int(landmarks[2].x * img_width), int(landmarks[2].y * img_height))
|
|
||||||
# mouth = (int(landmarks[3].x * img_width), int(landmarks[3].y * img_height))
|
|
||||||
# right_ear = (int(landmarks[4].x * img_width), int(landmarks[4].y * img_height))
|
|
||||||
# left_ear = (int(landmarks[5].x * img_width), int(landmarks[5].y * img_height))
|
|
||||||
|
|
||||||
if x > 0 and y > 0:
|
|
||||||
detected_face = img[y : y + h, x : x + w]
|
|
||||||
img_region = [x, y, w, h]
|
|
||||||
|
|
||||||
if align:
|
|
||||||
detected_face = FaceDetector.alignment_procedure(detected_face, left_eye, right_eye)
|
|
||||||
|
|
||||||
resp.append((detected_face, img_region, confidence))
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
@ -1,54 +1,51 @@
|
|||||||
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()
|
|
||||||
return face_detector
|
|
||||||
|
|
||||||
|
|
||||||
def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list:
|
|
||||||
"""
|
|
||||||
Detect and align face with mtcnn
|
|
||||||
Args:
|
|
||||||
face_detector (mtcnn.MTCNN): mtcnn face detector object
|
|
||||||
img (np.ndarray): pre-loaded image
|
|
||||||
align (bool): default is true
|
|
||||||
Returns:
|
|
||||||
list of detected and aligned faces
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
resp = []
|
def __init__(self):
|
||||||
|
self.model = MTCNN()
|
||||||
|
|
||||||
detected_face = None
|
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
|
resp = []
|
||||||
detections = face_detector.detect_faces(img_rgb)
|
|
||||||
|
|
||||||
if len(detections) > 0:
|
detected_face = None
|
||||||
|
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||||
|
|
||||||
for detection in detections:
|
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
|
||||||
x, y, w, h = detection["box"]
|
detections = self.model.detect_faces(img_rgb)
|
||||||
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
|
|
||||||
img_region = [x, y, w, h]
|
|
||||||
confidence = detection["confidence"]
|
|
||||||
|
|
||||||
if align:
|
if len(detections) > 0:
|
||||||
keypoints = detection["keypoints"]
|
|
||||||
left_eye = keypoints["left_eye"]
|
|
||||||
right_eye = keypoints["right_eye"]
|
|
||||||
detected_face = FaceDetector.alignment_procedure(detected_face, left_eye, right_eye)
|
|
||||||
|
|
||||||
resp.append((detected_face, img_region, confidence))
|
for detection in detections:
|
||||||
|
x, y, w, h = detection["box"]
|
||||||
|
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
|
||||||
|
img_region = [x, y, w, h]
|
||||||
|
confidence = detection["confidence"]
|
||||||
|
|
||||||
return resp
|
if align:
|
||||||
|
keypoints = detection["keypoints"]
|
||||||
|
left_eye = keypoints["left_eye"]
|
||||||
|
right_eye = keypoints["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))
|
||||||
|
|
||||||
|
return resp
|
||||||
|
@ -2,157 +2,164 @@ 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
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
"""
|
||||||
detector = {}
|
|
||||||
detector["face_detector"] = build_cascade("haarcascade")
|
|
||||||
detector["eye_detector"] = build_cascade("haarcascade_eye")
|
|
||||||
return detector
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.model = self.build_model()
|
||||||
|
|
||||||
def build_cascade(model_name="haarcascade") -> Any:
|
def build_model(self):
|
||||||
"""
|
"""
|
||||||
Build a opencv face&eye detector models
|
Build opencv's face and eye detector models
|
||||||
Returns:
|
Returns:
|
||||||
model (Any)
|
model (dict): including face_detector and eye_detector keys
|
||||||
"""
|
"""
|
||||||
opencv_path = get_opencv_path()
|
detector = {}
|
||||||
if model_name == "haarcascade":
|
detector["face_detector"] = self.__build_cascade("haarcascade")
|
||||||
face_detector_path = opencv_path + "haarcascade_frontalface_default.xml"
|
detector["eye_detector"] = self.__build_cascade("haarcascade_eye")
|
||||||
if os.path.isfile(face_detector_path) != True:
|
return detector
|
||||||
raise ValueError(
|
|
||||||
"Confirm that opencv is installed on your environment! Expected path ",
|
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||||
face_detector_path,
|
"""
|
||||||
" violated.",
|
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 = self.model["face_detector"].detectMultiScale3(
|
||||||
|
img, 1.1, 10, outputRejectLevels=True
|
||||||
)
|
)
|
||||||
detector = cv2.CascadeClassifier(face_detector_path)
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
elif model_name == "haarcascade_eye":
|
if len(faces) > 0:
|
||||||
eye_detector_path = opencv_path + "haarcascade_eye.xml"
|
for (x, y, w, h), confidence in zip(faces, scores):
|
||||||
if os.path.isfile(eye_detector_path) != True:
|
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
|
||||||
raise ValueError(
|
|
||||||
"Confirm that opencv is installed on your environment! Expected path ",
|
if align:
|
||||||
eye_detector_path,
|
left_eye, right_eye = self.find_eyes(img=detected_face)
|
||||||
" violated.",
|
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)),
|
||||||
)
|
)
|
||||||
detector = cv2.CascadeClassifier(eye_detector_path)
|
return left_eye, right_eye
|
||||||
|
|
||||||
else:
|
def __build_cascade(self, model_name="haarcascade") -> Any:
|
||||||
raise ValueError(f"unimplemented model_name for build_cascade - {model_name}")
|
"""
|
||||||
|
Build a opencv face&eye detector models
|
||||||
|
Returns:
|
||||||
|
model (Any)
|
||||||
|
"""
|
||||||
|
opencv_path = self.__get_opencv_path()
|
||||||
|
if model_name == "haarcascade":
|
||||||
|
face_detector_path = opencv_path + "haarcascade_frontalface_default.xml"
|
||||||
|
if os.path.isfile(face_detector_path) != True:
|
||||||
|
raise ValueError(
|
||||||
|
"Confirm that opencv is installed on your environment! Expected path ",
|
||||||
|
face_detector_path,
|
||||||
|
" violated.",
|
||||||
|
)
|
||||||
|
detector = cv2.CascadeClassifier(face_detector_path)
|
||||||
|
|
||||||
return detector
|
elif model_name == "haarcascade_eye":
|
||||||
|
eye_detector_path = opencv_path + "haarcascade_eye.xml"
|
||||||
|
if os.path.isfile(eye_detector_path) != True:
|
||||||
|
raise ValueError(
|
||||||
|
"Confirm that opencv is installed on your environment! Expected path ",
|
||||||
|
eye_detector_path,
|
||||||
|
" violated.",
|
||||||
|
)
|
||||||
|
detector = cv2.CascadeClassifier(eye_detector_path)
|
||||||
|
|
||||||
|
|
||||||
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:
|
else:
|
||||||
left_eye = eye_2
|
raise ValueError(f"unimplemented model_name for build_cascade - {model_name}")
|
||||||
right_eye = eye_1
|
|
||||||
|
|
||||||
# -----------------------
|
return detector
|
||||||
# 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(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns where opencv installed
|
||||||
|
Returns:
|
||||||
|
installation_path (str)
|
||||||
|
"""
|
||||||
|
opencv_home = cv2.__file__
|
||||||
|
folders = opencv_home.split(os.path.sep)[0:-1]
|
||||||
|
|
||||||
def get_opencv_path() -> str:
|
path = folders[0]
|
||||||
"""
|
for folder in folders[1:]:
|
||||||
Returns where opencv installed
|
path = path + "/" + folder
|
||||||
Returns:
|
|
||||||
installation_path (str)
|
|
||||||
"""
|
|
||||||
opencv_home = cv2.__file__
|
|
||||||
folders = opencv_home.split(os.path.sep)[0:-1]
|
|
||||||
|
|
||||||
path = folders[0]
|
return path + "/data/"
|
||||||
for folder in folders[1:]:
|
|
||||||
path = path + "/" + folder
|
|
||||||
|
|
||||||
return path + "/data/"
|
|
||||||
|
@ -1,60 +1,55 @@
|
|||||||
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:
|
||||||
|
"""
|
||||||
|
Detect and align face with retinaface
|
||||||
|
Args:
|
||||||
|
img (np.ndarray): pre-loaded image
|
||||||
|
align (bool): default is true
|
||||||
|
Returns:
|
||||||
|
list of detected and aligned faces
|
||||||
|
"""
|
||||||
|
resp = []
|
||||||
|
|
||||||
def detect_face(face_detector: Any, img: np.ndarray, align: bool = True) -> list:
|
obj = rf.detect_faces(img, model=self.model, threshold=0.9)
|
||||||
"""
|
|
||||||
Detect and align face with retinaface
|
|
||||||
Args:
|
|
||||||
face_detector (Any): retinaface face detector object
|
|
||||||
img (np.ndarray): pre-loaded image
|
|
||||||
align (bool): default is true
|
|
||||||
Returns:
|
|
||||||
list of detected and aligned faces
|
|
||||||
"""
|
|
||||||
resp = []
|
|
||||||
|
|
||||||
obj = RetinaFace.detect_faces(img, model=face_detector, threshold=0.9)
|
if isinstance(obj, dict):
|
||||||
|
for face_idx in obj.keys():
|
||||||
|
identity = obj[face_idx]
|
||||||
|
facial_area = identity["facial_area"]
|
||||||
|
|
||||||
if isinstance(obj, dict):
|
y = facial_area[1]
|
||||||
for face_idx in obj.keys():
|
h = facial_area[3] - y
|
||||||
identity = obj[face_idx]
|
x = facial_area[0]
|
||||||
facial_area = identity["facial_area"]
|
w = facial_area[2] - x
|
||||||
|
img_region = [x, y, w, h]
|
||||||
|
confidence = identity["score"]
|
||||||
|
|
||||||
y = facial_area[1]
|
# detected_face = img[int(y):int(y+h), int(x):int(x+w)] #opencv
|
||||||
h = facial_area[3] - y
|
detected_face = img[
|
||||||
x = facial_area[0]
|
facial_area[1] : facial_area[3], facial_area[0] : facial_area[2]
|
||||||
w = facial_area[2] - x
|
]
|
||||||
img_region = [x, y, w, h]
|
|
||||||
confidence = identity["score"]
|
|
||||||
|
|
||||||
# detected_face = img[int(y):int(y+h), int(x):int(x+w)] #opencv
|
if align:
|
||||||
detected_face = img[facial_area[1] : facial_area[3], facial_area[0] : facial_area[2]]
|
landmarks = identity["landmarks"]
|
||||||
|
left_eye = landmarks["left_eye"]
|
||||||
|
right_eye = landmarks["right_eye"]
|
||||||
|
nose = landmarks["nose"]
|
||||||
|
# mouth_right = landmarks["mouth_right"]
|
||||||
|
# mouth_left = landmarks["mouth_left"]
|
||||||
|
|
||||||
if align:
|
detected_face = postprocess.alignment_procedure(
|
||||||
landmarks = identity["landmarks"]
|
detected_face, right_eye, left_eye, nose
|
||||||
left_eye = landmarks["left_eye"]
|
)
|
||||||
right_eye = landmarks["right_eye"]
|
|
||||||
nose = landmarks["nose"]
|
|
||||||
# mouth_right = landmarks["mouth_right"]
|
|
||||||
# mouth_left = landmarks["mouth_left"]
|
|
||||||
|
|
||||||
detected_face = postprocess.alignment_procedure(
|
resp.append((detected_face, img_region, confidence))
|
||||||
detected_face, right_eye, left_eye, nose
|
|
||||||
)
|
|
||||||
|
|
||||||
resp.append((detected_face, img_region, confidence))
|
return resp
|
||||||
|
|
||||||
return resp
|
|
||||||
|
@ -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,126 +13,132 @@ 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):
|
||||||
Build a ssd detector model
|
self.model = self.build_model()
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
|
||||||
|
|
||||||
home = functions.get_deepface_home()
|
def build_model(self) -> dict:
|
||||||
|
"""
|
||||||
|
Build a ssd detector model
|
||||||
|
Returns:
|
||||||
|
model (dict)
|
||||||
|
"""
|
||||||
|
|
||||||
# model structure
|
home = functions.get_deepface_home()
|
||||||
if os.path.isfile(home + "/.deepface/weights/deploy.prototxt") != True:
|
|
||||||
|
|
||||||
logger.info("deploy.prototxt will be downloaded...")
|
# model structure
|
||||||
|
if os.path.isfile(home + "/.deepface/weights/deploy.prototxt") != True:
|
||||||
|
|
||||||
url = "https://github.com/opencv/opencv/raw/3.4.0/samples/dnn/face_detector/deploy.prototxt"
|
logger.info("deploy.prototxt will be downloaded...")
|
||||||
|
|
||||||
output = home + "/.deepface/weights/deploy.prototxt"
|
url = "https://github.com/opencv/opencv/raw/3.4.0/samples/dnn/face_detector/deploy.prototxt"
|
||||||
|
|
||||||
gdown.download(url, output, quiet=False)
|
output = home + "/.deepface/weights/deploy.prototxt"
|
||||||
|
|
||||||
# pre-trained weights
|
gdown.download(url, output, quiet=False)
|
||||||
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...")
|
# pre-trained weights
|
||||||
|
if (
|
||||||
|
os.path.isfile(home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel")
|
||||||
|
!= True
|
||||||
|
):
|
||||||
|
|
||||||
url = "https://github.com/opencv/opencv_3rdparty/raw/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel"
|
logger.info("res10_300x300_ssd_iter_140000.caffemodel will be downloaded...")
|
||||||
|
|
||||||
output = home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel"
|
url = "https://github.com/opencv/opencv_3rdparty/raw/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel"
|
||||||
|
|
||||||
gdown.download(url, output, quiet=False)
|
output = home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel"
|
||||||
|
|
||||||
try:
|
gdown.download(url, output, quiet=False)
|
||||||
face_detector = cv2.dnn.readNetFromCaffe(
|
|
||||||
home + "/.deepface/weights/deploy.prototxt",
|
|
||||||
home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel",
|
|
||||||
)
|
|
||||||
except Exception as err:
|
|
||||||
raise ValueError(
|
|
||||||
"Exception while calling opencv.dnn module."
|
|
||||||
+ "This is an optional dependency."
|
|
||||||
+ "You can install it as pip install opencv-contrib-python."
|
|
||||||
) from err
|
|
||||||
|
|
||||||
eye_detector = OpenCvWrapper.build_cascade("haarcascade_eye")
|
try:
|
||||||
|
face_detector = cv2.dnn.readNetFromCaffe(
|
||||||
|
home + "/.deepface/weights/deploy.prototxt",
|
||||||
|
home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel",
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise ValueError(
|
||||||
|
"Exception while calling opencv.dnn module."
|
||||||
|
+ "This is an optional dependency."
|
||||||
|
+ "You can install it as pip install opencv-contrib-python."
|
||||||
|
) from err
|
||||||
|
|
||||||
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:
|
||||||
|
"""
|
||||||
|
Detect and align face with ssd
|
||||||
|
Args:
|
||||||
|
img (np.ndarray): pre-loaded image
|
||||||
|
align (bool): default is true
|
||||||
|
Returns:
|
||||||
|
list of detected and aligned faces
|
||||||
|
"""
|
||||||
|
resp = []
|
||||||
|
|
||||||
def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
|
detected_face = None
|
||||||
"""
|
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||||
Detect and align face with ssd
|
|
||||||
Args:
|
|
||||||
face_detector (Any): ssd face detector object
|
|
||||||
img (np.ndarray): pre-loaded image
|
|
||||||
align (bool): default is true
|
|
||||||
Returns:
|
|
||||||
list of detected and aligned faces
|
|
||||||
"""
|
|
||||||
resp = []
|
|
||||||
|
|
||||||
detected_face = None
|
ssd_labels = ["img_id", "is_face", "confidence", "left", "top", "right", "bottom"]
|
||||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
|
||||||
|
|
||||||
ssd_labels = ["img_id", "is_face", "confidence", "left", "top", "right", "bottom"]
|
target_size = (300, 300)
|
||||||
|
|
||||||
target_size = (300, 300)
|
base_img = img.copy() # we will restore base_img to img later
|
||||||
|
|
||||||
base_img = img.copy() # we will restore base_img to img later
|
original_size = img.shape
|
||||||
|
|
||||||
original_size = img.shape
|
img = cv2.resize(img, target_size)
|
||||||
|
|
||||||
img = cv2.resize(img, target_size)
|
aspect_ratio_x = original_size[1] / target_size[1]
|
||||||
|
aspect_ratio_y = original_size[0] / target_size[0]
|
||||||
|
|
||||||
aspect_ratio_x = original_size[1] / target_size[1]
|
imageBlob = cv2.dnn.blobFromImage(image=img)
|
||||||
aspect_ratio_y = original_size[0] / target_size[0]
|
|
||||||
|
|
||||||
imageBlob = cv2.dnn.blobFromImage(image=img)
|
face_detector = self.model["face_detector"]
|
||||||
|
face_detector.setInput(imageBlob)
|
||||||
|
detections = face_detector.forward()
|
||||||
|
|
||||||
face_detector = detector["face_detector"]
|
detections_df = pd.DataFrame(detections[0][0], columns=ssd_labels)
|
||||||
face_detector.setInput(imageBlob)
|
|
||||||
detections = face_detector.forward()
|
|
||||||
|
|
||||||
detections_df = pd.DataFrame(detections[0][0], columns=ssd_labels)
|
detections_df = detections_df[detections_df["is_face"] == 1] # 0: background, 1: face
|
||||||
|
detections_df = detections_df[detections_df["confidence"] >= 0.90]
|
||||||
|
|
||||||
detections_df = detections_df[detections_df["is_face"] == 1] # 0: background, 1: face
|
detections_df["left"] = (detections_df["left"] * 300).astype(int)
|
||||||
detections_df = detections_df[detections_df["confidence"] >= 0.90]
|
detections_df["bottom"] = (detections_df["bottom"] * 300).astype(int)
|
||||||
|
detections_df["right"] = (detections_df["right"] * 300).astype(int)
|
||||||
|
detections_df["top"] = (detections_df["top"] * 300).astype(int)
|
||||||
|
|
||||||
detections_df["left"] = (detections_df["left"] * 300).astype(int)
|
if detections_df.shape[0] > 0:
|
||||||
detections_df["bottom"] = (detections_df["bottom"] * 300).astype(int)
|
|
||||||
detections_df["right"] = (detections_df["right"] * 300).astype(int)
|
|
||||||
detections_df["top"] = (detections_df["top"] * 300).astype(int)
|
|
||||||
|
|
||||||
if detections_df.shape[0] > 0:
|
for _, instance in detections_df.iterrows():
|
||||||
|
|
||||||
for _, instance in detections_df.iterrows():
|
left = instance["left"]
|
||||||
|
right = instance["right"]
|
||||||
|
bottom = instance["bottom"]
|
||||||
|
top = instance["top"]
|
||||||
|
|
||||||
left = instance["left"]
|
detected_face = base_img[
|
||||||
right = instance["right"]
|
int(top * aspect_ratio_y) : int(bottom * aspect_ratio_y),
|
||||||
bottom = instance["bottom"]
|
int(left * aspect_ratio_x) : int(right * aspect_ratio_x),
|
||||||
top = instance["top"]
|
]
|
||||||
|
img_region = [
|
||||||
|
int(left * aspect_ratio_x),
|
||||||
|
int(top * aspect_ratio_y),
|
||||||
|
int(right * aspect_ratio_x) - int(left * aspect_ratio_x),
|
||||||
|
int(bottom * aspect_ratio_y) - int(top * aspect_ratio_y),
|
||||||
|
]
|
||||||
|
confidence = instance["confidence"]
|
||||||
|
|
||||||
detected_face = base_img[
|
if align:
|
||||||
int(top * aspect_ratio_y) : int(bottom * aspect_ratio_y),
|
opencv_module: OpenCvWrapper.OpenCv = self.model["opencv_module"]
|
||||||
int(left * aspect_ratio_x) : int(right * aspect_ratio_x),
|
left_eye, right_eye = opencv_module.find_eyes(detected_face)
|
||||||
]
|
detected_face = self.align_face(
|
||||||
img_region = [
|
img=detected_face, left_eye=left_eye, right_eye=right_eye
|
||||||
int(left * aspect_ratio_x),
|
)
|
||||||
int(top * aspect_ratio_y),
|
|
||||||
int(right * aspect_ratio_x) - int(left * aspect_ratio_x),
|
|
||||||
int(bottom * aspect_ratio_y) - int(top * aspect_ratio_y),
|
|
||||||
]
|
|
||||||
confidence = instance["confidence"]
|
|
||||||
|
|
||||||
if align:
|
resp.append((detected_face, img_region, confidence))
|
||||||
detected_face = OpenCvWrapper.align_face(detector["eye_detector"], detected_face)
|
return resp
|
||||||
|
|
||||||
resp.append((detected_face, img_region, confidence))
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
@ -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,75 +16,78 @@ 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):
|
||||||
Build a yolo detector model
|
self.model = self.build_model()
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
|
||||||
import gdown
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Import the Ultralytics YOLO model
|
def build_model(self) -> Any:
|
||||||
try:
|
"""
|
||||||
from ultralytics import YOLO
|
Build a yolo detector model
|
||||||
except ModuleNotFoundError as e:
|
Returns:
|
||||||
raise ImportError(
|
model (Any)
|
||||||
"Yolo is an optional detector, ensure the library is installed. \
|
"""
|
||||||
Please install using 'pip install ultralytics' "
|
import gdown
|
||||||
) from e
|
import os
|
||||||
|
|
||||||
from deepface.commons.functions import get_deepface_home
|
# Import the Ultralytics YOLO model
|
||||||
|
try:
|
||||||
|
from ultralytics import YOLO
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"Yolo is an optional detector, ensure the library is installed. \
|
||||||
|
Please install using 'pip install ultralytics' "
|
||||||
|
) from e
|
||||||
|
|
||||||
weight_path = f"{get_deepface_home()}{PATH}"
|
from deepface.commons.functions import get_deepface_home
|
||||||
|
|
||||||
# Download the model's weights if they don't exist
|
weight_path = f"{get_deepface_home()}{PATH}"
|
||||||
if not os.path.isfile(weight_path):
|
|
||||||
gdown.download(WEIGHT_URL, weight_path, quiet=False)
|
|
||||||
logger.info(f"Downloaded YOLO model {os.path.basename(weight_path)}")
|
|
||||||
|
|
||||||
# Return face_detector
|
# Download the model's weights if they don't exist
|
||||||
return YOLO(weight_path)
|
if not os.path.isfile(weight_path):
|
||||||
|
gdown.download(WEIGHT_URL, weight_path, quiet=False)
|
||||||
|
logger.info(f"Downloaded YOLO model {os.path.basename(weight_path)}")
|
||||||
|
|
||||||
|
# Return face_detector
|
||||||
|
return YOLO(weight_path)
|
||||||
|
|
||||||
def detect_face(face_detector: Any, img: np.ndarray, align: bool = False) -> list:
|
def detect_faces(self, img: np.ndarray, align: bool = False) -> list:
|
||||||
"""
|
"""
|
||||||
Detect and align face with yolo
|
Detect and align face with yolo
|
||||||
Args:
|
Args:
|
||||||
face_detector (Any): yolo face detector object
|
face_detector (Any): yolo 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:
|
||||||
list of detected and aligned faces
|
list of detected and aligned faces
|
||||||
"""
|
"""
|
||||||
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:
|
||||||
# Extract the bounding box and the confidence
|
# Extract the bounding box and the confidence
|
||||||
x, y, w, h = result.boxes.xywh.tolist()[0]
|
x, y, w, h = result.boxes.xywh.tolist()[0]
|
||||||
confidence = result.boxes.conf.tolist()[0]
|
confidence = result.boxes.conf.tolist()[0]
|
||||||
|
|
||||||
x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h)
|
x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h)
|
||||||
detected_face = img[y : y + h, x : x + w].copy()
|
detected_face = img[y : y + h, x : x + w].copy()
|
||||||
|
|
||||||
if align:
|
if align:
|
||||||
# Tuple of x,y and confidence for left eye
|
# Tuple of x,y and confidence for left eye
|
||||||
left_eye = result.keypoints.xy[0][0], result.keypoints.conf[0][0]
|
left_eye = result.keypoints.xy[0][0], result.keypoints.conf[0][0]
|
||||||
# Tuple of x,y and confidence for right eye
|
# Tuple of x,y and confidence for right eye
|
||||||
right_eye = result.keypoints.xy[0][1], result.keypoints.conf[0][1]
|
right_eye = result.keypoints.xy[0][1], result.keypoints.conf[0][1]
|
||||||
|
|
||||||
# Check the landmarks confidence before alignment
|
# Check the landmarks confidence before alignment
|
||||||
if (
|
if (
|
||||||
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))
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
@ -3,112 +3,110 @@ 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):
|
||||||
Build a yunet detector model
|
self.model = self.build_model()
|
||||||
Returns:
|
|
||||||
model (Any)
|
|
||||||
"""
|
|
||||||
# pylint: disable=C0301
|
|
||||||
url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
|
|
||||||
file_name = "face_detection_yunet_2023mar.onnx"
|
|
||||||
home = functions.get_deepface_home()
|
|
||||||
if os.path.isfile(home + f"/.deepface/weights/{file_name}") is False:
|
|
||||||
logger.info(f"{file_name} will be downloaded...")
|
|
||||||
output = home + f"/.deepface/weights/{file_name}"
|
|
||||||
gdown.download(url, output, quiet=False)
|
|
||||||
|
|
||||||
try:
|
def build_model(self) -> Any:
|
||||||
face_detector = cv2.FaceDetectorYN_create(
|
"""
|
||||||
home + f"/.deepface/weights/{file_name}", "", (0, 0)
|
Build a yunet detector model
|
||||||
)
|
Returns:
|
||||||
except Exception as err:
|
model (Any)
|
||||||
raise ValueError(
|
"""
|
||||||
"Exception while calling opencv.FaceDetectorYN_create module."
|
# pylint: disable=C0301
|
||||||
+ "This is an optional dependency."
|
url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
|
||||||
+ "You can install it as pip install opencv-contrib-python."
|
file_name = "face_detection_yunet_2023mar.onnx"
|
||||||
) from err
|
home = functions.get_deepface_home()
|
||||||
return face_detector
|
if os.path.isfile(home + f"/.deepface/weights/{file_name}") is False:
|
||||||
|
logger.info(f"{file_name} will be downloaded...")
|
||||||
|
output = home + f"/.deepface/weights/{file_name}"
|
||||||
|
gdown.download(url, output, quiet=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
face_detector = cv2.FaceDetectorYN_create(
|
||||||
|
home + f"/.deepface/weights/{file_name}", "", (0, 0)
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise ValueError(
|
||||||
|
"Exception while calling opencv.FaceDetectorYN_create module."
|
||||||
|
+ "This is an optional dependency."
|
||||||
|
+ "You can install it as pip install opencv-contrib-python."
|
||||||
|
) from err
|
||||||
|
return face_detector
|
||||||
|
|
||||||
def detect_face(
|
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||||
detector: Any, image: np.ndarray, align: bool = True, score_threshold: float = 0.9
|
"""
|
||||||
) -> list:
|
Detect and align face with yunet
|
||||||
"""
|
Args:
|
||||||
Detect and align face with yunet
|
img (np.ndarray): pre-loaded image
|
||||||
Args:
|
align (bool): default is true
|
||||||
face_detector (Any): yunet face detector object
|
Returns:
|
||||||
img (np.ndarray): pre-loaded image
|
list of detected and aligned faces
|
||||||
align (bool): default is true
|
"""
|
||||||
Returns:
|
# FaceDetector.detect_faces does not support score_threshold parameter.
|
||||||
list of detected and aligned faces
|
# We can set it via environment variable.
|
||||||
"""
|
score_threshold = os.environ.get("yunet_score_threshold", "0.9")
|
||||||
# FaceDetector.detect_faces does not support score_threshold parameter.
|
resp = []
|
||||||
# We can set it via environment variable.
|
detected_face = None
|
||||||
score_threshold = os.environ.get("yunet_score_threshold", score_threshold)
|
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||||
resp = []
|
faces = []
|
||||||
detected_face = None
|
height, width = img.shape[0], img.shape[1]
|
||||||
img_region = [0, 0, image.shape[1], image.shape[0]]
|
# resize image if it is too large (Yunet fails to detect faces on large input sometimes)
|
||||||
faces = []
|
# I picked 640 as a threshold because it is the default value of max_size in Yunet.
|
||||||
height, width = image.shape[0], image.shape[1]
|
resized = False
|
||||||
# resize image if it is too large (Yunet fails to detect faces on large input sometimes)
|
if height > 640 or width > 640:
|
||||||
# I picked 640 as a threshold because it is the default value of max_size in Yunet.
|
r = 640.0 / max(height, width)
|
||||||
resized = False
|
original_image = img.copy()
|
||||||
if height > 640 or width > 640:
|
img = cv2.resize(img, (int(width * r), int(height * r)))
|
||||||
r = 640.0 / max(height, width)
|
height, width = img.shape[0], img.shape[1]
|
||||||
original_image = image.copy()
|
resized = True
|
||||||
image = cv2.resize(image, (int(width * r), int(height * r)))
|
self.model.setInputSize((width, height))
|
||||||
height, width = image.shape[0], image.shape[1]
|
self.model.setScoreThreshold(score_threshold)
|
||||||
resized = True
|
_, faces = self.model.detect(img)
|
||||||
detector.setInputSize((width, height))
|
if faces is None:
|
||||||
detector.setScoreThreshold(score_threshold)
|
return resp
|
||||||
_, faces = detector.detect(image)
|
for face in faces:
|
||||||
if faces is None:
|
# pylint: disable=W0105
|
||||||
|
"""
|
||||||
|
The detection output faces is a two-dimension array of type CV_32F,
|
||||||
|
whose rows are the detected face instances, columns are the location
|
||||||
|
of a face and 5 facial landmarks.
|
||||||
|
The format of each row is as follows:
|
||||||
|
x1, y1, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt,
|
||||||
|
x_rcm, y_rcm, x_lcm, y_lcm,
|
||||||
|
where x1, y1, w, h are the top-left coordinates, width and height of
|
||||||
|
the face bounding box,
|
||||||
|
{x, y}_{re, le, nt, rcm, lcm} stands for the coordinates of right eye,
|
||||||
|
left eye, nose tip, the right corner and left corner of the mouth respectively.
|
||||||
|
"""
|
||||||
|
(x, y, w, h, x_re, y_re, x_le, y_le) = list(map(int, face[:8]))
|
||||||
|
|
||||||
|
# Yunet returns negative coordinates if it thinks part of
|
||||||
|
# the detected face is outside the frame.
|
||||||
|
# We set the coordinate to 0 if they are negative.
|
||||||
|
x = max(x, 0)
|
||||||
|
y = max(y, 0)
|
||||||
|
if resized:
|
||||||
|
img = original_image
|
||||||
|
x, y, w, h = int(x / r), int(y / r), int(w / r), int(h / r)
|
||||||
|
x_re, y_re, x_le, y_le = (
|
||||||
|
int(x_re / r),
|
||||||
|
int(y_re / r),
|
||||||
|
int(x_le / r),
|
||||||
|
int(y_le / r),
|
||||||
|
)
|
||||||
|
confidence = face[-1]
|
||||||
|
confidence = f"{confidence:.2f}"
|
||||||
|
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
|
||||||
|
img_region = [x, y, w, h]
|
||||||
|
if align:
|
||||||
|
detected_face = self.align_face(detected_face, (x_re, y_re), (x_le, y_le))
|
||||||
|
resp.append((detected_face, img_region, confidence))
|
||||||
return resp
|
return resp
|
||||||
for face in faces:
|
|
||||||
# pylint: disable=W0105
|
|
||||||
"""
|
|
||||||
The detection output faces is a two-dimension array of type CV_32F,
|
|
||||||
whose rows are the detected face instances, columns are the location
|
|
||||||
of a face and 5 facial landmarks.
|
|
||||||
The format of each row is as follows:
|
|
||||||
x1, y1, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt,
|
|
||||||
x_rcm, y_rcm, x_lcm, y_lcm,
|
|
||||||
where x1, y1, w, h are the top-left coordinates, width and height of
|
|
||||||
the face bounding box,
|
|
||||||
{x, y}_{re, le, nt, rcm, lcm} stands for the coordinates of right eye,
|
|
||||||
left eye, nose tip, the right corner and left corner of the mouth respectively.
|
|
||||||
"""
|
|
||||||
(x, y, w, h, x_re, y_re, x_le, y_le) = list(map(int, face[:8]))
|
|
||||||
|
|
||||||
# Yunet returns negative coordinates if it thinks part of
|
|
||||||
# the detected face is outside the frame.
|
|
||||||
# We set the coordinate to 0 if they are negative.
|
|
||||||
x = max(x, 0)
|
|
||||||
y = max(y, 0)
|
|
||||||
if resized:
|
|
||||||
image = original_image
|
|
||||||
x, y, w, h = int(x / r), int(y / r), int(w / r), int(h / r)
|
|
||||||
x_re, y_re, x_le, y_le = (
|
|
||||||
int(x_re / r),
|
|
||||||
int(y_re / r),
|
|
||||||
int(x_le / r),
|
|
||||||
int(y_le / r),
|
|
||||||
)
|
|
||||||
confidence = face[-1]
|
|
||||||
confidence = f"{confidence:.2f}"
|
|
||||||
detected_face = image[int(y) : int(y + h), int(x) : int(x + w)]
|
|
||||||
img_region = [x, y, w, h]
|
|
||||||
if align:
|
|
||||||
detected_face = FaceDetector.alignment_procedure(
|
|
||||||
detected_face, (x_re, y_re), (x_le, y_le)
|
|
||||||
)
|
|
||||||
resp.append((detected_face, img_region, confidence))
|
|
||||||
return resp
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|
||||||
|
23
deepface/models/Demography.py
Normal file
23
deepface/models/Demography.py
Normal 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
|
39
deepface/models/Detector.py
Normal file
39
deepface/models/Detector.py
Normal 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
|
28
deepface/models/FacialRecognition.py
Normal file
28
deepface/models/FacialRecognition.py
Normal 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()
|
@ -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"] = {}
|
||||||
|
@ -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():
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user