mirror of
https://github.com/serengil/deepface.git
synced 2025-06-06 19:45:21 +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 pandas as pd
|
||||
import tensorflow as tf
|
||||
from deprecated import deprecated
|
||||
|
||||
# package dependencies
|
||||
from deepface.commons import functions
|
||||
@ -23,8 +22,6 @@ from deepface.modules import (
|
||||
realtime,
|
||||
)
|
||||
|
||||
# pylint: disable=no-else-raise, simplifiable-if-expression
|
||||
|
||||
logger = Logger(module="DeepFace")
|
||||
|
||||
# -----------------------------------
|
||||
@ -40,6 +37,8 @@ else:
|
||||
from keras.models import Model
|
||||
# -----------------------------------
|
||||
|
||||
functions.initialize_folder()
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
command line interface function will be offered in this block
|
||||
|
@ -3,6 +3,7 @@ import gdown
|
||||
import tensorflow as tf
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
logger = Logger(module="basemodels.ArcFace")
|
||||
|
||||
@ -42,10 +43,25 @@ else:
|
||||
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",
|
||||
) -> Model:
|
||||
"""
|
||||
Construct ArcFace model, download its weights and load
|
||||
Returns:
|
||||
model (Model)
|
||||
"""
|
||||
base_model = ResNet34()
|
||||
inputs = base_model.inputs[0]
|
||||
arcface_model = base_model.outputs[0]
|
||||
@ -81,7 +97,11 @@ def loadModel(
|
||||
|
||||
|
||||
def ResNet34() -> Model:
|
||||
|
||||
"""
|
||||
ResNet34 model
|
||||
Returns:
|
||||
model (Model)
|
||||
"""
|
||||
img_input = Input(shape=(112, 112, 3))
|
||||
|
||||
x = ZeroPadding2D(padding=1, name="conv1_pad")(img_input)
|
||||
|
@ -3,6 +3,7 @@ import gdown
|
||||
import tensorflow as tf
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
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",
|
||||
) -> Model:
|
||||
"""
|
||||
Construct DeepId model, download its weights and load
|
||||
"""
|
||||
|
||||
myInput = Input(shape=(55, 47, 3))
|
||||
|
||||
|
@ -4,12 +4,33 @@ import gdown
|
||||
import numpy as np
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
logger = Logger(module="basemodels.DlibResNet")
|
||||
|
||||
# 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:
|
||||
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
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
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):
|
||||
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))
|
||||
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
|
||||
|
||||
|
||||
def loadModel(
|
||||
def load_facenet128d_model(
|
||||
url="https://github.com/serengil/deepface_models/releases/download/v1.0/facenet_weights.h5",
|
||||
) -> 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()
|
||||
|
||||
# -----------------------------------
|
||||
@ -1640,3 +1675,33 @@ def loadModel(
|
||||
# -----------------------------------
|
||||
|
||||
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
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
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",
|
||||
) -> Model:
|
||||
"""
|
||||
Construct DeepFace model, download its weights and load
|
||||
"""
|
||||
base_model = Sequential()
|
||||
base_model.add(
|
||||
Convolution2D(32, (11, 11), activation="relu", name="C1", input_shape=(152, 152, 3))
|
||||
|
@ -3,6 +3,7 @@ import gdown
|
||||
import tensorflow as tf
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
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",
|
||||
) -> Model:
|
||||
"""
|
||||
Consturct OpenFace model, download its weights and load
|
||||
Returns:
|
||||
model (Model)
|
||||
"""
|
||||
myInput = Input(shape=(96, 96, 3))
|
||||
|
||||
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.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
logger = Logger(module="basemodels.SFace")
|
||||
|
||||
# pylint: disable=line-too-long, too-few-public-methods
|
||||
|
||||
|
||||
class _Layer:
|
||||
input_shape = (None, 112, 112, 3)
|
||||
output_shape = (None, 1, 128)
|
||||
class SFace(FacialRecognition):
|
||||
"""
|
||||
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):
|
||||
|
||||
"""
|
||||
SFace wrapper covering model construction, layer infos and predict
|
||||
"""
|
||||
try:
|
||||
self.model = cv.FaceRecognizerSF.create(
|
||||
model=model_path, config="", backend_id=0, target_id=0
|
||||
@ -46,20 +86,6 @@ class SFaceModel:
|
||||
return embeddings
|
||||
|
||||
|
||||
def load_model(
|
||||
url="https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx",
|
||||
) -> Any:
|
||||
|
||||
home = functions.get_deepface_home()
|
||||
|
||||
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
|
||||
class _Layer:
|
||||
input_shape = (None, 112, 112, 3)
|
||||
output_shape = (None, 1, 128)
|
||||
|
@ -3,6 +3,7 @@ import gdown
|
||||
import tensorflow as tf
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
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.add(ZeroPadding2D((1, 1), input_shape=(224, 224, 3)))
|
||||
model.add(Convolution2D(64, (3, 3), activation="relu"))
|
||||
@ -87,11 +103,16 @@ def baseModel() -> Sequential:
|
||||
return model
|
||||
|
||||
|
||||
def loadModel(
|
||||
def load_model(
|
||||
url="https://github.com/serengil/deepface_models/releases/download/v1.0/vgg_face_weights.h5",
|
||||
) -> 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()
|
||||
output = home + "/.deepface/weights/vgg_face_weights.h5"
|
||||
|
@ -12,7 +12,7 @@ import tensorflow as tf
|
||||
from deprecated import deprecated
|
||||
|
||||
# package dependencies
|
||||
from deepface.detectors import FaceDetector
|
||||
from deepface.detectors import DetectorWrapper
|
||||
from deepface.commons.logger import Logger
|
||||
|
||||
logger = Logger(module="commons.functions")
|
||||
@ -168,8 +168,7 @@ def extract_faces(
|
||||
if detector_backend == "skip":
|
||||
face_objs = [(img, img_region, 0)]
|
||||
else:
|
||||
face_detector = FaceDetector.build_model(detector_backend)
|
||||
face_objs = FaceDetector.detect_faces(face_detector, detector_backend, img, align)
|
||||
face_objs = DetectorWrapper.detect_faces(detector_backend, img, align)
|
||||
|
||||
# in case of no face found
|
||||
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 numpy as np
|
||||
from deepface.commons import functions
|
||||
from deepface.models.Detector import Detector
|
||||
from deepface.commons.logger import Logger
|
||||
|
||||
logger = Logger(module="detectors.DlibWrapper")
|
||||
|
||||
|
||||
def build_model() -> dict:
|
||||
"""
|
||||
Build a dlib hog face detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
home = functions.get_deepface_home()
|
||||
class Dlib(Detector):
|
||||
def __init__(self):
|
||||
self.model = self.build_model()
|
||||
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
import dlib
|
||||
except ModuleNotFoundError as e:
|
||||
raise ImportError(
|
||||
"Dlib is an optional detector, ensure the library is installed."
|
||||
"Please install using 'pip install dlib' "
|
||||
) from e
|
||||
def build_model(self) -> dict:
|
||||
"""
|
||||
Build a dlib hog face detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
home = functions.get_deepface_home()
|
||||
|
||||
# check required file exists in the home/.deepface/weights folder
|
||||
if os.path.isfile(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat") != True:
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
import dlib
|
||||
except ModuleNotFoundError as e:
|
||||
raise ImportError(
|
||||
"Dlib is an optional detector, ensure the library is installed."
|
||||
"Please install using 'pip install dlib' "
|
||||
) from e
|
||||
|
||||
file_name = "shape_predictor_5_face_landmarks.dat.bz2"
|
||||
logger.info(f"{file_name} is going to be downloaded")
|
||||
# check required file exists in the home/.deepface/weights folder
|
||||
if os.path.isfile(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat") != True:
|
||||
|
||||
url = f"http://dlib.net/files/{file_name}"
|
||||
output = f"{home}/.deepface/weights/{file_name}"
|
||||
file_name = "shape_predictor_5_face_landmarks.dat.bz2"
|
||||
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)
|
||||
data = zipfile.read()
|
||||
newfilepath = output[:-4] # discard .bz2 extension
|
||||
with open(newfilepath, "wb") as f:
|
||||
f.write(data)
|
||||
gdown.download(url, output, quiet=False)
|
||||
|
||||
face_detector = dlib.get_frontal_face_detector()
|
||||
sp = dlib.shape_predictor(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat")
|
||||
zipfile = bz2.BZ2File(output)
|
||||
data = zipfile.read()
|
||||
newfilepath = output[:-4] # discard .bz2 extension
|
||||
with open(newfilepath, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
detector = {}
|
||||
detector["face_detector"] = face_detector
|
||||
detector["sp"] = sp
|
||||
return detector
|
||||
face_detector = dlib.get_frontal_face_detector()
|
||||
sp = dlib.shape_predictor(home + "/.deepface/weights/shape_predictor_5_face_landmarks.dat")
|
||||
|
||||
detector = {}
|
||||
detector["face_detector"] = face_detector
|
||||
detector["sp"] = sp
|
||||
return detector
|
||||
|
||||
def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with dlib
|
||||
Args:
|
||||
face_detector (Any): dlib face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
import dlib
|
||||
except ModuleNotFoundError as e:
|
||||
raise ImportError(
|
||||
"Dlib is an optional detector, ensure the library is installed."
|
||||
"Please install using 'pip install dlib' "
|
||||
) from e
|
||||
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with dlib
|
||||
Args:
|
||||
face_detector (Any): dlib face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
import dlib
|
||||
except ModuleNotFoundError as e:
|
||||
raise ImportError(
|
||||
"Dlib is an optional detector, ensure the library is installed."
|
||||
"Please install using 'pip install dlib' "
|
||||
) 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
|
||||
detections, scores, _ = face_detector.run(img, 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)
|
||||
|
||||
if len(detections) > 0:
|
||||
if len(detections) > 0:
|
||||
|
||||
for idx, d in enumerate(detections):
|
||||
left = d.left()
|
||||
right = d.right()
|
||||
top = d.top()
|
||||
bottom = d.bottom()
|
||||
for idx, d in enumerate(detections):
|
||||
left = d.left()
|
||||
right = d.right()
|
||||
top = d.top()
|
||||
bottom = d.bottom()
|
||||
|
||||
# detected_face = img[top:bottom, left:right]
|
||||
detected_face = img[
|
||||
max(0, top) : min(bottom, img.shape[0]), max(0, left) : min(right, img.shape[1])
|
||||
]
|
||||
# detected_face = img[top:bottom, left:right]
|
||||
detected_face = img[
|
||||
max(0, top) : min(bottom, img.shape[0]), max(0, left) : min(right, img.shape[1])
|
||||
]
|
||||
|
||||
img_region = [left, top, right - left, bottom - top]
|
||||
confidence = scores[idx]
|
||||
img_region = [left, top, right - left, bottom - top]
|
||||
confidence = scores[idx]
|
||||
|
||||
if align:
|
||||
img_shape = sp(img, detections[idx])
|
||||
detected_face = dlib.get_face_chip(img, img_shape, size=detected_face.shape[0])
|
||||
if align:
|
||||
img_shape = sp(img, detections[idx])
|
||||
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
|
||||
import cv2
|
||||
import numpy as np
|
||||
from deepface.detectors import FaceDetector
|
||||
from deepface.models.Detector import Detector
|
||||
|
||||
# Link -> https://github.com/timesler/facenet-pytorch
|
||||
# Examples https://www.kaggle.com/timesler/guide-to-mtcnn-in-facenet-pytorch
|
||||
|
||||
|
||||
def build_model() -> Any:
|
||||
"""
|
||||
Build a fast mtcnn face detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
from facenet_pytorch import MTCNN as fast_mtcnn
|
||||
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
|
||||
class FastMtCnn(Detector):
|
||||
def __init__(self):
|
||||
self.model = self.build_model()
|
||||
|
||||
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 detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with mtcnn
|
||||
Args:
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
resp = []
|
||||
|
||||
detected_face = None
|
||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||
|
||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
|
||||
detections = self.model.detect(
|
||||
img_rgb, landmarks=True
|
||||
) # returns boundingbox, prob, landmark
|
||||
if len(detections[0]) > 0:
|
||||
|
||||
for detection in zip(*detections):
|
||||
x, y, w, h = xyxy_to_xywh(detection[0])
|
||||
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
|
||||
img_region = [x, y, w, h]
|
||||
confidence = detection[1]
|
||||
|
||||
if align:
|
||||
left_eye = detection[2][0]
|
||||
right_eye = detection[2][1]
|
||||
detected_face = self.align_face(
|
||||
img=detected_face, left_eye=left_eye, right_eye=right_eye
|
||||
)
|
||||
|
||||
resp.append((detected_face, img_region, confidence))
|
||||
|
||||
return resp
|
||||
|
||||
def build_model(self) -> Any:
|
||||
"""
|
||||
Build a fast mtcnn face detector model
|
||||
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:
|
||||
@ -40,40 +81,3 @@ def xyxy_to_xywh(xyxy: Union[list, tuple]) -> list:
|
||||
w = xyxy[2] - x + 1
|
||||
h = xyxy[3] - y + 1
|
||||
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
|
||||
import numpy as np
|
||||
from deepface.detectors import FaceDetector
|
||||
from deepface.models.Detector import Detector
|
||||
|
||||
# Link - https://google.github.io/mediapipe/solutions/face_detection
|
||||
|
||||
|
||||
def build_model() -> Any:
|
||||
"""
|
||||
Build a mediapipe face detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
import mediapipe as mp
|
||||
except ModuleNotFoundError as e:
|
||||
raise ImportError(
|
||||
"MediaPipe is an optional detector, ensure the library is installed."
|
||||
"Please install using 'pip install mediapipe' "
|
||||
) from e
|
||||
class MediaPipe(Detector):
|
||||
def __init__(self):
|
||||
self.model = self.build_model()
|
||||
|
||||
mp_face_detection = mp.solutions.face_detection
|
||||
face_detection = mp_face_detection.FaceDetection(min_detection_confidence=0.7)
|
||||
return face_detection
|
||||
def build_model(self) -> Any:
|
||||
"""
|
||||
Build a mediapipe face detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
# this is not a must dependency. do not import it in the global level.
|
||||
try:
|
||||
import mediapipe as mp
|
||||
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:
|
||||
"""
|
||||
Detect and align face with mediapipe
|
||||
Args:
|
||||
face_detector (Any): mediapipe face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
resp = []
|
||||
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with mediapipe
|
||||
Args:
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
resp = []
|
||||
|
||||
img_width = img.shape[1]
|
||||
img_height = img.shape[0]
|
||||
img_width = img.shape[1]
|
||||
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
|
||||
|
||||
# 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 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
|
||||
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
|
||||
Class to cover common face detection functionalitiy for MtCnn backend
|
||||
"""
|
||||
|
||||
resp = []
|
||||
def __init__(self):
|
||||
self.model = MTCNN()
|
||||
|
||||
detected_face = None
|
||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with mtcnn
|
||||
Args:
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
|
||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
|
||||
detections = face_detector.detect_faces(img_rgb)
|
||||
resp = []
|
||||
|
||||
if len(detections) > 0:
|
||||
detected_face = None
|
||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||
|
||||
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"]
|
||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mtcnn expects RGB but OpenCV read BGR
|
||||
detections = self.model.detect_faces(img_rgb)
|
||||
|
||||
if align:
|
||||
keypoints = detection["keypoints"]
|
||||
left_eye = keypoints["left_eye"]
|
||||
right_eye = keypoints["right_eye"]
|
||||
detected_face = FaceDetector.alignment_procedure(detected_face, left_eye, right_eye)
|
||||
if len(detections) > 0:
|
||||
|
||||
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
|
||||
import cv2
|
||||
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
|
||||
Returns:
|
||||
model (Any)
|
||||
Class to cover common face detection functionalitiy for OpenCv backend
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Build a opencv face&eye detector models
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
opencv_path = get_opencv_path()
|
||||
if model_name == "haarcascade":
|
||||
face_detector_path = opencv_path + "haarcascade_frontalface_default.xml"
|
||||
if os.path.isfile(face_detector_path) != True:
|
||||
raise ValueError(
|
||||
"Confirm that opencv is installed on your environment! Expected path ",
|
||||
face_detector_path,
|
||||
" violated.",
|
||||
def build_model(self):
|
||||
"""
|
||||
Build opencv's face and eye detector models
|
||||
Returns:
|
||||
model (dict): including face_detector and eye_detector keys
|
||||
"""
|
||||
detector = {}
|
||||
detector["face_detector"] = self.__build_cascade("haarcascade")
|
||||
detector["eye_detector"] = self.__build_cascade("haarcascade_eye")
|
||||
return detector
|
||||
|
||||
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with opencv
|
||||
Args:
|
||||
face_detector (Any): opencv face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
resp = []
|
||||
|
||||
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":
|
||||
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.",
|
||||
if len(faces) > 0:
|
||||
for (x, y, w, h), confidence in zip(faces, scores):
|
||||
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
|
||||
|
||||
if align:
|
||||
left_eye, right_eye = self.find_eyes(img=detected_face)
|
||||
detected_face = self.align_face(detected_face, left_eye, right_eye)
|
||||
|
||||
img_region = [x, y, w, h]
|
||||
|
||||
resp.append((detected_face, img_region, confidence))
|
||||
|
||||
return resp
|
||||
|
||||
def find_eyes(self, img: np.ndarray) -> tuple:
|
||||
"""
|
||||
Find the left and right eye coordinates of given image
|
||||
Args:
|
||||
img (np.ndarray): given image
|
||||
Returns:
|
||||
left and right eye (tuple)
|
||||
"""
|
||||
left_eye = None
|
||||
right_eye = None
|
||||
|
||||
# if image has unexpectedly 0 dimension then skip alignment
|
||||
if img.shape[0] == 0 or img.shape[1] == 0:
|
||||
return left_eye, right_eye
|
||||
|
||||
detected_face_gray = cv2.cvtColor(
|
||||
img, cv2.COLOR_BGR2GRAY
|
||||
) # eye detector expects gray scale image
|
||||
|
||||
eyes = self.model["eye_detector"].detectMultiScale(detected_face_gray, 1.1, 10)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# opencv eye detection module is not strong. it might find more than 2 eyes!
|
||||
# besides, it returns eyes with different order in each call (issue 435)
|
||||
# this is an important issue because opencv is the default detector and ssd also uses this
|
||||
# find the largest 2 eye. Thanks to @thelostpeace
|
||||
|
||||
eyes = sorted(eyes, key=lambda v: abs(v[2] * v[3]), reverse=True)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
if len(eyes) >= 2:
|
||||
# decide left and right eye
|
||||
|
||||
eye_1 = eyes[0]
|
||||
eye_2 = eyes[1]
|
||||
|
||||
if eye_1[0] < eye_2[0]:
|
||||
left_eye = eye_1
|
||||
right_eye = eye_2
|
||||
else:
|
||||
left_eye = eye_2
|
||||
right_eye = eye_1
|
||||
|
||||
# -----------------------
|
||||
# find center of eyes
|
||||
left_eye = (int(left_eye[0] + (left_eye[2] / 2)), int(left_eye[1] + (left_eye[3] / 2)))
|
||||
right_eye = (
|
||||
int(right_eye[0] + (right_eye[2] / 2)),
|
||||
int(right_eye[1] + (right_eye[3] / 2)),
|
||||
)
|
||||
detector = cv2.CascadeClassifier(eye_detector_path)
|
||||
return left_eye, right_eye
|
||||
|
||||
else:
|
||||
raise ValueError(f"unimplemented model_name for build_cascade - {model_name}")
|
||||
def __build_cascade(self, model_name="haarcascade") -> Any:
|
||||
"""
|
||||
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:
|
||||
left_eye = eye_2
|
||||
right_eye = eye_1
|
||||
raise ValueError(f"unimplemented model_name for build_cascade - {model_name}")
|
||||
|
||||
# -----------------------
|
||||
# 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
|
||||
return detector
|
||||
|
||||
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:
|
||||
"""
|
||||
Returns where opencv installed
|
||||
Returns:
|
||||
installation_path (str)
|
||||
"""
|
||||
opencv_home = cv2.__file__
|
||||
folders = opencv_home.split(os.path.sep)[0:-1]
|
||||
path = folders[0]
|
||||
for folder in folders[1:]:
|
||||
path = path + "/" + folder
|
||||
|
||||
path = folders[0]
|
||||
for folder in folders[1:]:
|
||||
path = path + "/" + folder
|
||||
|
||||
return path + "/data/"
|
||||
return path + "/data/"
|
||||
|
@ -1,60 +1,55 @@
|
||||
from typing import Any
|
||||
import numpy as np
|
||||
from retinaface import RetinaFace
|
||||
from retinaface import RetinaFace as rf
|
||||
from retinaface.commons import postprocess
|
||||
from deepface.models.Detector import Detector
|
||||
|
||||
|
||||
def build_model() -> Any:
|
||||
"""
|
||||
Build a retinaface detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
face_detector = RetinaFace.build_model()
|
||||
return face_detector
|
||||
class RetinaFace(Detector):
|
||||
def __init__(self):
|
||||
self.model = rf.build_model()
|
||||
|
||||
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:
|
||||
"""
|
||||
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 = rf.detect_faces(img, model=self.model, threshold=0.9)
|
||||
|
||||
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):
|
||||
for face_idx in obj.keys():
|
||||
identity = obj[face_idx]
|
||||
facial_area = identity["facial_area"]
|
||||
y = facial_area[1]
|
||||
h = facial_area[3] - y
|
||||
x = facial_area[0]
|
||||
w = facial_area[2] - x
|
||||
img_region = [x, y, w, h]
|
||||
confidence = identity["score"]
|
||||
|
||||
y = facial_area[1]
|
||||
h = facial_area[3] - y
|
||||
x = facial_area[0]
|
||||
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
|
||||
detected_face = img[
|
||||
facial_area[1] : facial_area[3], facial_area[0] : facial_area[2]
|
||||
]
|
||||
|
||||
# detected_face = img[int(y):int(y+h), int(x):int(x+w)] #opencv
|
||||
detected_face = img[facial_area[1] : facial_area[3], facial_area[0] : facial_area[2]]
|
||||
if align:
|
||||
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:
|
||||
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"]
|
||||
detected_face = postprocess.alignment_procedure(
|
||||
detected_face, right_eye, left_eye, nose
|
||||
)
|
||||
|
||||
detected_face = postprocess.alignment_procedure(
|
||||
detected_face, right_eye, left_eye, nose
|
||||
)
|
||||
resp.append((detected_face, img_region, confidence))
|
||||
|
||||
resp.append((detected_face, img_region, confidence))
|
||||
|
||||
return resp
|
||||
return resp
|
||||
|
@ -5,6 +5,7 @@ import pandas as pd
|
||||
import numpy as np
|
||||
from deepface.detectors import OpenCvWrapper
|
||||
from deepface.commons import functions
|
||||
from deepface.models.Detector import Detector
|
||||
from deepface.commons.logger import Logger
|
||||
|
||||
logger = Logger(module="detectors.SsdWrapper")
|
||||
@ -12,126 +13,132 @@ logger = Logger(module="detectors.SsdWrapper")
|
||||
# pylint: disable=line-too-long
|
||||
|
||||
|
||||
def build_model() -> dict:
|
||||
"""
|
||||
Build a ssd detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
class Ssd(Detector):
|
||||
def __init__(self):
|
||||
self.model = self.build_model()
|
||||
|
||||
home = functions.get_deepface_home()
|
||||
def build_model(self) -> dict:
|
||||
"""
|
||||
Build a ssd detector model
|
||||
Returns:
|
||||
model (dict)
|
||||
"""
|
||||
|
||||
# model structure
|
||||
if os.path.isfile(home + "/.deepface/weights/deploy.prototxt") != True:
|
||||
home = functions.get_deepface_home()
|
||||
|
||||
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
|
||||
if os.path.isfile(home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel") != True:
|
||||
gdown.download(url, output, quiet=False)
|
||||
|
||||
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:
|
||||
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
|
||||
gdown.download(url, output, quiet=False)
|
||||
|
||||
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["face_detector"] = face_detector
|
||||
detector["eye_detector"] = eye_detector
|
||||
detector = {}
|
||||
detector["face_detector"] = face_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:
|
||||
"""
|
||||
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
|
||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||
|
||||
detected_face = None
|
||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||
ssd_labels = ["img_id", "is_face", "confidence", "left", "top", "right", "bottom"]
|
||||
|
||||
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]
|
||||
aspect_ratio_y = original_size[0] / target_size[0]
|
||||
imageBlob = cv2.dnn.blobFromImage(image=img)
|
||||
|
||||
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"]
|
||||
face_detector.setInput(imageBlob)
|
||||
detections = face_detector.forward()
|
||||
detections_df = pd.DataFrame(detections[0][0], columns=ssd_labels)
|
||||
|
||||
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 = detections_df[detections_df["confidence"] >= 0.90]
|
||||
detections_df["left"] = (detections_df["left"] * 300).astype(int)
|
||||
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)
|
||||
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:
|
||||
|
||||
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"]
|
||||
right = instance["right"]
|
||||
bottom = instance["bottom"]
|
||||
top = instance["top"]
|
||||
detected_face = base_img[
|
||||
int(top * aspect_ratio_y) : int(bottom * aspect_ratio_y),
|
||||
int(left * aspect_ratio_x) : int(right * aspect_ratio_x),
|
||||
]
|
||||
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[
|
||||
int(top * aspect_ratio_y) : int(bottom * aspect_ratio_y),
|
||||
int(left * aspect_ratio_x) : int(right * aspect_ratio_x),
|
||||
]
|
||||
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"]
|
||||
if align:
|
||||
opencv_module: OpenCvWrapper.OpenCv = self.model["opencv_module"]
|
||||
left_eye, right_eye = opencv_module.find_eyes(detected_face)
|
||||
detected_face = self.align_face(
|
||||
img=detected_face, left_eye=left_eye, right_eye=right_eye
|
||||
)
|
||||
|
||||
if align:
|
||||
detected_face = OpenCvWrapper.align_face(detector["eye_detector"], detected_face)
|
||||
|
||||
resp.append((detected_face, img_region, confidence))
|
||||
|
||||
return resp
|
||||
resp.append((detected_face, img_region, confidence))
|
||||
return resp
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import Any
|
||||
import numpy as np
|
||||
from deepface.detectors import FaceDetector
|
||||
from deepface.models.Detector import Detector
|
||||
from deepface.commons.logger import Logger
|
||||
|
||||
logger = Logger()
|
||||
@ -16,75 +16,78 @@ WEIGHT_URL = "https://drive.google.com/uc?id=1qcr9DbgsX3ryrz2uU8w4Xm3cOrRywXqb"
|
||||
LANDMARKS_CONFIDENCE_THRESHOLD = 0.5
|
||||
|
||||
|
||||
def build_model() -> Any:
|
||||
"""
|
||||
Build a yolo detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
import gdown
|
||||
import os
|
||||
class Yolo(Detector):
|
||||
def __init__(self):
|
||||
self.model = self.build_model()
|
||||
|
||||
# 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
|
||||
def build_model(self) -> Any:
|
||||
"""
|
||||
Build a yolo detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
import gdown
|
||||
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
|
||||
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)}")
|
||||
weight_path = f"{get_deepface_home()}{PATH}"
|
||||
|
||||
# Return face_detector
|
||||
return YOLO(weight_path)
|
||||
# Download the model's weights if they don't exist
|
||||
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:
|
||||
"""
|
||||
Detect and align face with yolo
|
||||
Args:
|
||||
face_detector (Any): yolo face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
resp = []
|
||||
def detect_faces(self, img: np.ndarray, align: bool = False) -> list:
|
||||
"""
|
||||
Detect and align face with yolo
|
||||
Args:
|
||||
face_detector (Any): yolo face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
resp = []
|
||||
|
||||
# Detect faces
|
||||
results = face_detector.predict(img, verbose=False, show=False, conf=0.25)[0]
|
||||
# Detect faces
|
||||
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 result in results:
|
||||
# Extract the bounding box and the confidence
|
||||
x, y, w, h = result.boxes.xywh.tolist()[0]
|
||||
confidence = result.boxes.conf.tolist()[0]
|
||||
# For each face, extract the bounding box, the landmarks and confidence
|
||||
for result in results:
|
||||
# Extract the bounding box and the confidence
|
||||
x, y, w, h = result.boxes.xywh.tolist()[0]
|
||||
confidence = result.boxes.conf.tolist()[0]
|
||||
|
||||
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()
|
||||
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()
|
||||
|
||||
if align:
|
||||
# Tuple of x,y and confidence for left eye
|
||||
left_eye = result.keypoints.xy[0][0], result.keypoints.conf[0][0]
|
||||
# Tuple of x,y and confidence for right eye
|
||||
right_eye = result.keypoints.xy[0][1], result.keypoints.conf[0][1]
|
||||
if align:
|
||||
# Tuple of x,y and confidence for left eye
|
||||
left_eye = result.keypoints.xy[0][0], result.keypoints.conf[0][0]
|
||||
# Tuple of x,y and confidence for right eye
|
||||
right_eye = result.keypoints.xy[0][1], result.keypoints.conf[0][1]
|
||||
|
||||
# Check the landmarks confidence before alignment
|
||||
if (
|
||||
left_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD
|
||||
and right_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD
|
||||
):
|
||||
detected_face = FaceDetector.alignment_procedure(
|
||||
detected_face, left_eye[0].cpu(), right_eye[0].cpu()
|
||||
)
|
||||
resp.append((detected_face, [x, y, w, h], confidence))
|
||||
# Check the landmarks confidence before alignment
|
||||
if (
|
||||
left_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD
|
||||
and right_eye[1] > LANDMARKS_CONFIDENCE_THRESHOLD
|
||||
):
|
||||
detected_face = self.align_face(
|
||||
img=detected_face, left_eye=left_eye[0].cpu(), right_eye=right_eye[0].cpu()
|
||||
)
|
||||
resp.append((detected_face, [x, y, w, h], confidence))
|
||||
|
||||
return resp
|
||||
return resp
|
||||
|
@ -3,112 +3,110 @@ from typing import Any
|
||||
import cv2
|
||||
import numpy as np
|
||||
import gdown
|
||||
from deepface.detectors import FaceDetector
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.Detector import Detector
|
||||
|
||||
logger = Logger(module="detectors.YunetWrapper")
|
||||
|
||||
|
||||
def build_model() -> Any:
|
||||
"""
|
||||
Build a yunet detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
# pylint: disable=C0301
|
||||
url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
|
||||
file_name = "face_detection_yunet_2023mar.onnx"
|
||||
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)
|
||||
class YuNet(Detector):
|
||||
def __init__(self):
|
||||
self.model = self.build_model()
|
||||
|
||||
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 build_model(self) -> Any:
|
||||
"""
|
||||
Build a yunet detector model
|
||||
Returns:
|
||||
model (Any)
|
||||
"""
|
||||
# pylint: disable=C0301
|
||||
url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
|
||||
file_name = "face_detection_yunet_2023mar.onnx"
|
||||
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:
|
||||
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(
|
||||
detector: Any, image: np.ndarray, align: bool = True, score_threshold: float = 0.9
|
||||
) -> list:
|
||||
"""
|
||||
Detect and align face with yunet
|
||||
Args:
|
||||
face_detector (Any): yunet face detector object
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
# FaceDetector.detect_faces does not support score_threshold parameter.
|
||||
# We can set it via environment variable.
|
||||
score_threshold = os.environ.get("yunet_score_threshold", score_threshold)
|
||||
resp = []
|
||||
detected_face = None
|
||||
img_region = [0, 0, image.shape[1], image.shape[0]]
|
||||
faces = []
|
||||
height, width = image.shape[0], image.shape[1]
|
||||
# resize image if it is too large (Yunet fails to detect faces on large input sometimes)
|
||||
# I picked 640 as a threshold because it is the default value of max_size in Yunet.
|
||||
resized = False
|
||||
if height > 640 or width > 640:
|
||||
r = 640.0 / max(height, width)
|
||||
original_image = image.copy()
|
||||
image = cv2.resize(image, (int(width * r), int(height * r)))
|
||||
height, width = image.shape[0], image.shape[1]
|
||||
resized = True
|
||||
detector.setInputSize((width, height))
|
||||
detector.setScoreThreshold(score_threshold)
|
||||
_, faces = detector.detect(image)
|
||||
if faces is None:
|
||||
def detect_faces(self, img: np.ndarray, align: bool = True) -> list:
|
||||
"""
|
||||
Detect and align face with yunet
|
||||
Args:
|
||||
img (np.ndarray): pre-loaded image
|
||||
align (bool): default is true
|
||||
Returns:
|
||||
list of detected and aligned faces
|
||||
"""
|
||||
# FaceDetector.detect_faces does not support score_threshold parameter.
|
||||
# We can set it via environment variable.
|
||||
score_threshold = os.environ.get("yunet_score_threshold", "0.9")
|
||||
resp = []
|
||||
detected_face = None
|
||||
img_region = [0, 0, img.shape[1], img.shape[0]]
|
||||
faces = []
|
||||
height, width = img.shape[0], img.shape[1]
|
||||
# resize image if it is too large (Yunet fails to detect faces on large input sometimes)
|
||||
# I picked 640 as a threshold because it is the default value of max_size in Yunet.
|
||||
resized = False
|
||||
if height > 640 or width > 640:
|
||||
r = 640.0 / max(height, width)
|
||||
original_image = img.copy()
|
||||
img = cv2.resize(img, (int(width * r), int(height * r)))
|
||||
height, width = img.shape[0], img.shape[1]
|
||||
resized = True
|
||||
self.model.setInputSize((width, height))
|
||||
self.model.setScoreThreshold(score_threshold)
|
||||
_, faces = self.model.detect(img)
|
||||
if faces is None:
|
||||
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:
|
||||
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
|
||||
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.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.Demography import Demography
|
||||
|
||||
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",
|
||||
) -> 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)))
|
||||
apparent_age = np.sum(age_predictions * output_indexes)
|
||||
return apparent_age
|
||||
|
@ -1,8 +1,11 @@
|
||||
import os
|
||||
import gdown
|
||||
import tensorflow as tf
|
||||
import numpy as np
|
||||
import cv2
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.Demography import Demography
|
||||
|
||||
logger = Logger(module="extendedmodels.Emotion")
|
||||
|
||||
@ -30,10 +33,31 @@ else:
|
||||
# Labels for the emotions that can be detected by the model.
|
||||
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",
|
||||
) -> Sequential:
|
||||
"""
|
||||
Consruct emotion model, download and load weights
|
||||
"""
|
||||
|
||||
num_classes = 7
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import os
|
||||
import gdown
|
||||
import tensorflow as tf
|
||||
import numpy as np
|
||||
from deepface.basemodels import VGGFace
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.Demography import Demography
|
||||
|
||||
logger = Logger(module="extendedmodels.Gender")
|
||||
|
||||
@ -25,12 +27,30 @@ else:
|
||||
# Labels for the genders that can be detected by the model.
|
||||
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",
|
||||
) -> 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 gdown
|
||||
import tensorflow as tf
|
||||
import numpy as np
|
||||
from deepface.basemodels import VGGFace
|
||||
from deepface.commons import functions
|
||||
from deepface.commons.logger import Logger
|
||||
from deepface.models.Demography import Demography
|
||||
|
||||
logger = Logger(module="extendedmodels.Race")
|
||||
|
||||
@ -23,12 +25,28 @@ else:
|
||||
# Labels for the ethnic phenotypes that can be detected by the model.
|
||||
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",
|
||||
) -> 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
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
import cv2
|
||||
|
||||
# project dependencies
|
||||
from deepface.modules import modeling
|
||||
from deepface.commons import functions
|
||||
from deepface.extendedmodels import Age, Gender, Race, Emotion
|
||||
from deepface.extendedmodels import Gender, Race, Emotion
|
||||
|
||||
|
||||
def analyze(
|
||||
@ -123,18 +122,10 @@ def analyze(
|
||||
pbar.set_description(f"Action: {action}")
|
||||
|
||||
if action == "emotion":
|
||||
img_gray = cv2.cvtColor(img_content[0], cv2.COLOR_BGR2GRAY)
|
||||
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, :]
|
||||
|
||||
emotion_predictions = modeling.build_model("Emotion").predict(img_content)
|
||||
sum_of_predictions = emotion_predictions.sum()
|
||||
|
||||
obj["emotion"] = {}
|
||||
|
||||
for i, emotion_label in enumerate(Emotion.labels):
|
||||
emotion_prediction = 100 * emotion_predictions[i] / sum_of_predictions
|
||||
obj["emotion"][emotion_label] = emotion_prediction
|
||||
@ -142,17 +133,12 @@ def analyze(
|
||||
obj["dominant_emotion"] = Emotion.labels[np.argmax(emotion_predictions)]
|
||||
|
||||
elif action == "age":
|
||||
age_predictions = modeling.build_model("Age").predict(img_content, verbose=0)[
|
||||
0, :
|
||||
]
|
||||
apparent_age = Age.findApparentAge(age_predictions)
|
||||
apparent_age = modeling.build_model("Age").predict(img_content)
|
||||
# int cast is for exception - object of type 'float32' is not JSON serializable
|
||||
obj["age"] = int(apparent_age)
|
||||
|
||||
elif action == "gender":
|
||||
gender_predictions = modeling.build_model("Gender").predict(
|
||||
img_content, verbose=0
|
||||
)[0, :]
|
||||
gender_predictions = modeling.build_model("Gender").predict(img_content)
|
||||
obj["gender"] = {}
|
||||
for i, gender_label in enumerate(Gender.labels):
|
||||
gender_prediction = 100 * gender_predictions[i]
|
||||
@ -161,9 +147,7 @@ def analyze(
|
||||
obj["dominant_gender"] = Gender.labels[np.argmax(gender_predictions)]
|
||||
|
||||
elif action == "race":
|
||||
race_predictions = modeling.build_model("Race").predict(img_content, verbose=0)[
|
||||
0, :
|
||||
]
|
||||
race_predictions = modeling.build_model("Race").predict(img_content)
|
||||
sum_of_predictions = race_predictions.sum()
|
||||
|
||||
obj["race"] = {}
|
||||
|
@ -1,32 +1,21 @@
|
||||
# built-in dependencies
|
||||
from typing import Any, Union
|
||||
|
||||
# 3rd party dependencies
|
||||
import tensorflow as tf
|
||||
from typing import Any
|
||||
|
||||
# project dependencies
|
||||
from deepface.basemodels import (
|
||||
VGGFace,
|
||||
OpenFace,
|
||||
Facenet,
|
||||
Facenet512,
|
||||
FbDeepFace,
|
||||
DeepID,
|
||||
DlibWrapper,
|
||||
DlibResNet,
|
||||
ArcFace,
|
||||
SFace,
|
||||
)
|
||||
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) -> Union[Model, Any]:
|
||||
def build_model(model_name: str) -> Any:
|
||||
"""
|
||||
This function builds a deepface model
|
||||
Parameters:
|
||||
@ -35,26 +24,26 @@ def build_model(model_name: str) -> Union[Model, Any]:
|
||||
Age, Gender, Emotion, Race for facial attributes
|
||||
|
||||
Returns:
|
||||
built deepface model ( (tf.)keras.models.Model )
|
||||
built model class
|
||||
"""
|
||||
|
||||
# singleton design pattern
|
||||
global model_obj
|
||||
|
||||
models = {
|
||||
"VGG-Face": VGGFace.loadModel,
|
||||
"OpenFace": OpenFace.loadModel,
|
||||
"Facenet": Facenet.loadModel,
|
||||
"Facenet512": Facenet512.loadModel,
|
||||
"DeepFace": FbDeepFace.loadModel,
|
||||
"DeepID": DeepID.loadModel,
|
||||
"Dlib": DlibWrapper.loadModel,
|
||||
"ArcFace": ArcFace.loadModel,
|
||||
"SFace": SFace.load_model,
|
||||
"Emotion": Emotion.loadModel,
|
||||
"Age": Age.loadModel,
|
||||
"Gender": Gender.loadModel,
|
||||
"Race": Race.loadModel,
|
||||
"VGG-Face": VGGFace.VggFace,
|
||||
"OpenFace": OpenFace.OpenFace,
|
||||
"Facenet": Facenet.FaceNet128d,
|
||||
"Facenet512": Facenet.FaceNet512d,
|
||||
"DeepFace": FbDeepFace.DeepFace,
|
||||
"DeepID": DeepID.DeepId,
|
||||
"Dlib": DlibResNet.Dlib,
|
||||
"ArcFace": ArcFace.ArcFace,
|
||||
"SFace": SFace.SFace,
|
||||
"Emotion": Emotion.FacialExpression,
|
||||
"Age": Age.ApparentAge,
|
||||
"Gender": Gender.Gender,
|
||||
"Race": Race.Race,
|
||||
}
|
||||
|
||||
if not "model_obj" in globals():
|
||||
|
@ -4,18 +4,11 @@ from typing import Any, Dict, List, Union
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
import cv2
|
||||
import tensorflow as tf
|
||||
|
||||
# project dependencies
|
||||
from deepface.modules import modeling
|
||||
from deepface.commons import functions
|
||||
|
||||
# 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
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
|
||||
|
||||
def represent(
|
||||
@ -71,7 +64,7 @@ def represent(
|
||||
"""
|
||||
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.
|
||||
@ -107,18 +100,7 @@ def represent(
|
||||
# custom normalization
|
||||
img = functions.normalize_input(img=img, normalization=normalization)
|
||||
|
||||
# represent
|
||||
# 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()
|
||||
embedding = model.find_embeddings(img)
|
||||
|
||||
resp_obj = {}
|
||||
resp_obj["embedding"] = embedding
|
||||
|
@ -11,4 +11,3 @@ mtcnn>=0.1.0
|
||||
retina-face>=0.0.1
|
||||
fire>=0.4.0
|
||||
gunicorn>=20.1.0
|
||||
Deprecated>=1.2.13
|
@ -4,6 +4,10 @@ from deepface.commons.logger import 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 = [
|
||||
"VGG-Face",
|
||||
"Facenet",
|
||||
@ -17,6 +21,7 @@ model_names = [
|
||||
]
|
||||
detector_backends = ["opencv", "ssd", "dlib", "mtcnn", "retinaface"]
|
||||
|
||||
|
||||
# verification
|
||||
for model_name in model_names:
|
||||
obj = DeepFace.verify(
|
||||
|
Loading…
x
Reference in New Issue
Block a user