open issues

This commit is contained in:
Sefik Ilkin Serengil 2024-02-02 17:33:31 +00:00
parent 64d33717a9
commit 8497682171
42 changed files with 326 additions and 224 deletions

41
CITATION.md Normal file
View File

@ -0,0 +1,41 @@
## Cite DeepFace Papers
Please cite deepface in your publications if it helps your research. Here are its BibTex entries:
### Facial Recognition
If you use deepface in your research for facial recogntion purposes, please cite the this publication.
```BibTeX
@inproceedings{serengil2020lightface,
title = {LightFace: A Hybrid Deep Face Recognition Framework},
author = {Serengil, Sefik Ilkin and Ozpinar, Alper},
booktitle = {2020 Innovations in Intelligent Systems and Applications Conference (ASYU)},
pages = {23-27},
year = {2020},
doi = {10.1109/ASYU50717.2020.9259802},
url = {https://doi.org/10.1109/ASYU50717.2020.9259802},
organization = {IEEE}
}
```
### Facial Attribute Analysis
If you use deepface in your research for facial attribute analysis purposes such as age, gender, emotion or ethnicity prediction or face detection purposes, please cite the this publication.
```BibTeX
@inproceedings{serengil2021lightface,
title = {HyperExtended LightFace: A Facial Attribute Analysis Framework},
author = {Serengil, Sefik Ilkin and Ozpinar, Alper},
booktitle = {2021 International Conference on Engineering and Emerging Technologies (ICEET)},
pages = {1-4},
year = {2021},
doi = {10.1109/ICEET53442.2021.9659697},
url = {https://doi.org/10.1109/ICEET53442.2021.9659697},
organization = {IEEE}
}
```
### Repositories
Also, if you use deepface in your GitHub projects, please add `deepface` in the `requirements.txt`. Thereafter, your project will be listed in its [dependency graph](https://github.com/serengil/deepface/network/dependents).

View File

@ -19,10 +19,10 @@ RUN apt-get install ffmpeg libsm6 libxext6 -y
# ----------------------------------- # -----------------------------------
# Copy required files from repo into image # Copy required files from repo into image
COPY ./deepface /app/deepface COPY ./deepface /app/deepface
COPY ./api/app.py /app/ COPY ./api/src/app.py /app/
COPY ./api/api.py /app/ COPY ./api/src/api.py /app/
COPY ./api/routes.py /app/ COPY ./api/src/routes.py /app/
COPY ./api/service.py /app/ COPY ./api/src/service.py /app/
COPY ./requirements.txt /app/ COPY ./requirements.txt /app/
COPY ./setup.py /app/ COPY ./setup.py /app/
COPY ./README.md /app/ COPY ./README.md /app/

View File

@ -289,7 +289,7 @@ cd scripts
<p align="center"><img src="https://raw.githubusercontent.com/serengil/deepface/master/icon/deepface-api.jpg" width="90%" height="90%"></p> <p align="center"><img src="https://raw.githubusercontent.com/serengil/deepface/master/icon/deepface-api.jpg" width="90%" height="90%"></p>
Face recognition, facial attribute analysis and vector representation functions are covered in the API. You are expected to call these functions as http post methods. Default service endpoints will be `http://localhost:5000/verify` for face recognition, `http://localhost:5000/analyze` for facial attribute analysis, and `http://localhost:5000/represent` for vector representation. You can pass input images as exact image paths on your environment, base64 encoded strings or images on web. [Here](https://github.com/serengil/deepface/tree/master/api), you can find a postman project to find out how these methods should be called. Face recognition, facial attribute analysis and vector representation functions are covered in the API. You are expected to call these functions as http post methods. Default service endpoints will be `http://localhost:5000/verify` for face recognition, `http://localhost:5000/analyze` for facial attribute analysis, and `http://localhost:5000/represent` for vector representation. You can pass input images as exact image paths on your environment, base64 encoded strings or images on web. [Here](https://github.com/serengil/deepface/tree/master/api/postman), you can find a postman project to find out how these methods should be called.
**Dockerized Service** **Dockerized Service**
@ -332,9 +332,9 @@ You can also support this work on [Patreon](https://www.patreon.com/serengil?rep
## Citation ## Citation
Please cite deepface in your publications if it helps your research. Here are its BibTex entries: Please cite deepface in your publications if it helps your research - see [`CITATIONS`](https://github.com/serengil/deepface/blob/master/CITATIONS.md) for more details. Here are its BibTex entries:
If you use deepface for facial recogntion purposes, please cite the this publication. If you use deepface in your research for facial recogntion purposes, please cite this publication.
```BibTeX ```BibTeX
@inproceedings{serengil2020lightface, @inproceedings{serengil2020lightface,
@ -349,7 +349,7 @@ If you use deepface for facial recogntion purposes, please cite the this publica
} }
``` ```
If you use deepface for facial attribute analysis purposes such as age, gender, emotion or ethnicity prediction or face detection purposes, please cite the this publication. If you use deepface in your research for facial attribute analysis purposes such as age, gender, emotion or ethnicity prediction or face detection purposes, please cite this publication.
```BibTeX ```BibTeX
@inproceedings{serengil2021lightface, @inproceedings{serengil2021lightface,

View File

@ -10,7 +10,7 @@ import pandas as pd
import tensorflow as tf import tensorflow as tf
# package dependencies # package dependencies
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.modules import ( from deepface.modules import (
modeling, modeling,
@ -24,17 +24,21 @@ from deepface.modules import (
logger = Logger(module="DeepFace") logger = Logger(module="DeepFace")
# current package version of deepface
__version__ = package_utils.find_package_version()
# ----------------------------------- # -----------------------------------
# configurations for dependencies # configurations for dependencies
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 2: if tf_version == 2:
tf.get_logger().setLevel(logging.ERROR) tf.get_logger().setLevel(logging.ERROR)
# ----------------------------------- # -----------------------------------
functions.initialize_folder() # create required folders if necessary to store model weights
folder_utils.initialize_folder()
def build_model(model_name: str) -> Any: def build_model(model_name: str) -> Any:
@ -511,7 +515,7 @@ def detectFace(
align (bool): Flag to enable face alignment (default is True). align (bool): Flag to enable face alignment (default is True).
Returns: Returns:
img (np.ndarray): detected (and aligned) facial area image as numpy array img (np.ndarray): detected (and aligned) facial area image as numpy array
""" """
logger.warn("Function detectFace is deprecated. Use extract_faces instead.") logger.warn("Function detectFace is deprecated. Use extract_faces instead.")
face_objs = extract_faces( face_objs = extract_faces(

View File

@ -2,7 +2,7 @@ from typing import List
import os import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
@ -13,7 +13,7 @@ logger = Logger(module="basemodels.ArcFace")
# -------------------------------- # --------------------------------
# dependency configuration # dependency configuration
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model from keras.models import Model
@ -94,7 +94,7 @@ def load_model(
# --------------------------------------- # ---------------------------------------
# check the availability of pre-trained weights # check the availability of pre-trained weights
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
file_name = "arcface_weights.h5" file_name = "arcface_weights.h5"
output = home + "/.deepface/weights/" + file_name output = home + "/.deepface/weights/" + file_name

View File

@ -2,13 +2,13 @@ from typing import List
import os import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.DeepID") logger = Logger(module="basemodels.DeepID")
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model from keras.models import Model
@ -100,7 +100,7 @@ def load_model(
# --------------------------------- # ---------------------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/deepid_keras_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/deepid_keras_weights.h5") != True:
logger.info("deepid_keras_weights.h5 will be downloaded...") logger.info("deepid_keras_weights.h5 will be downloaded...")

View File

@ -3,7 +3,7 @@ import os
import bz2 import bz2
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
@ -68,7 +68,7 @@ class DlibResNet:
# --------------------- # ---------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
weight_file = home + "/.deepface/weights/dlib_face_recognition_resnet_model_v1.dat" weight_file = home + "/.deepface/weights/dlib_face_recognition_resnet_model_v1.dat"
# --------------------- # ---------------------

View File

@ -2,7 +2,7 @@ from typing import List
import os import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
@ -11,7 +11,7 @@ logger = Logger(module="basemodels.Facenet")
# -------------------------------- # --------------------------------
# dependency configuration # dependency configuration
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model from keras.models import Model
@ -1689,7 +1689,7 @@ def load_facenet128d_model(
# ----------------------------------- # -----------------------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/facenet_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/facenet_weights.h5") != True:
logger.info("facenet_weights.h5 will be downloaded...") logger.info("facenet_weights.h5 will be downloaded...")
@ -1719,7 +1719,7 @@ def load_facenet512d_model(
# ------------------------- # -------------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/facenet512_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/facenet512_weights.h5") != True:
logger.info("facenet512_weights.h5 will be downloaded...") logger.info("facenet512_weights.h5 will be downloaded...")

View File

@ -3,7 +3,7 @@ import os
import zipfile import zipfile
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
@ -12,7 +12,7 @@ logger = Logger(module="basemodels.FbDeepFace")
# -------------------------------- # --------------------------------
# dependency configuration # dependency configuration
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model, Sequential from keras.models import Model, Sequential
@ -84,7 +84,7 @@ def load_model(
# --------------------------------- # ---------------------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/VGGFace2_DeepFace_weights_val-0.9034.h5") != True: if os.path.isfile(home + "/.deepface/weights/VGGFace2_DeepFace_weights_val-0.9034.h5") != True:
logger.info("VGGFace2_DeepFace_weights_val-0.9034.h5 will be downloaded...") logger.info("VGGFace2_DeepFace_weights_val-0.9034.h5 will be downloaded...")

View File

@ -3,13 +3,13 @@ import os
import gdown import gdown
import tensorflow as tf import tensorflow as tf
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="basemodels.OpenFace") logger = Logger(module="basemodels.OpenFace")
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model from keras.models import Model
from keras.layers import Conv2D, ZeroPadding2D, Input, concatenate from keras.layers import Conv2D, ZeroPadding2D, Input, concatenate
@ -394,7 +394,7 @@ def load_model(
# ----------------------------------- # -----------------------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/openface_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/openface_weights.h5") != True:
logger.info("openface_weights.h5 will be downloaded...") logger.info("openface_weights.h5 will be downloaded...")

View File

@ -5,7 +5,7 @@ import numpy as np
import cv2 as cv import cv2 as cv
import gdown import gdown
from deepface.commons import functions from deepface.commons import folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
@ -50,7 +50,7 @@ def load_model(
Construct SFace model, download its weights and load Construct SFace model, download its weights and load
""" """
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
file_name = home + "/.deepface/weights/face_recognition_sface_2021dec.onnx" file_name = home + "/.deepface/weights/face_recognition_sface_2021dec.onnx"

View File

@ -2,7 +2,8 @@ from typing import List
import os import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions, distance from deepface.commons import package_utils, folder_utils
from deepface.modules import verification
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
@ -10,7 +11,7 @@ logger = Logger(module="basemodels.VGGFace")
# --------------------------------------- # ---------------------------------------
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model, Sequential from keras.models import Model, Sequential
from keras.layers import ( from keras.layers import (
@ -59,7 +60,7 @@ class VggFaceClient(FacialRecognition):
# having normalization layer in descriptor troubles for some gpu users (e.g. issue 957, 966) # having normalization layer in descriptor troubles for some gpu users (e.g. issue 957, 966)
# instead we are now calculating it with traditional way not with keras backend # instead we are now calculating it with traditional way not with keras backend
embedding = self.model(img, training=False).numpy()[0].tolist() embedding = self.model(img, training=False).numpy()[0].tolist()
embedding = distance.l2_normalize(embedding) embedding = verification.l2_normalize(embedding)
return embedding.tolist() return embedding.tolist()
@ -128,7 +129,7 @@ def load_model(
model = base_model() model = base_model()
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
output = home + "/.deepface/weights/vgg_face_weights.h5" output = home + "/.deepface/weights/vgg_face_weights.h5"
if os.path.isfile(output) != True: if os.path.isfile(output) != True:

View File

@ -0,0 +1,4 @@
import os
SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ROOT_DIR = os.path.dirname(SRC_DIR)

View File

@ -1,64 +0,0 @@
from typing import Union
import numpy as np
def find_cosine_distance(
source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list]
) -> np.float64:
if isinstance(source_representation, list):
source_representation = np.array(source_representation)
if isinstance(test_representation, list):
test_representation = np.array(test_representation)
a = np.matmul(np.transpose(source_representation), test_representation)
b = np.sum(np.multiply(source_representation, source_representation))
c = np.sum(np.multiply(test_representation, test_representation))
return 1 - (a / (np.sqrt(b) * np.sqrt(c)))
def find_euclidean_distance(
source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list]
) -> np.float64:
if isinstance(source_representation, list):
source_representation = np.array(source_representation)
if isinstance(test_representation, list):
test_representation = np.array(test_representation)
euclidean_distance = source_representation - test_representation
euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
euclidean_distance = np.sqrt(euclidean_distance)
return euclidean_distance
def l2_normalize(x: Union[np.ndarray, list]) -> np.ndarray:
if isinstance(x, list):
x = np.array(x)
return x / np.sqrt(np.sum(np.multiply(x, x)))
def find_threshold(model_name: str, distance_metric: str) -> float:
base_threshold = {"cosine": 0.40, "euclidean": 0.55, "euclidean_l2": 0.75}
thresholds = {
# "VGG-Face": {"cosine": 0.40, "euclidean": 0.60, "euclidean_l2": 0.86}, # 2622d
"VGG-Face": {
"cosine": 0.68,
"euclidean": 1.17,
"euclidean_l2": 1.17,
}, # 4096d - tuned with LFW
"Facenet": {"cosine": 0.40, "euclidean": 10, "euclidean_l2": 0.80},
"Facenet512": {"cosine": 0.30, "euclidean": 23.56, "euclidean_l2": 1.04},
"ArcFace": {"cosine": 0.68, "euclidean": 4.15, "euclidean_l2": 1.13},
"Dlib": {"cosine": 0.07, "euclidean": 0.6, "euclidean_l2": 0.4},
"SFace": {"cosine": 0.593, "euclidean": 10.734, "euclidean_l2": 1.055},
"OpenFace": {"cosine": 0.10, "euclidean": 0.55, "euclidean_l2": 0.55},
"DeepFace": {"cosine": 0.23, "euclidean": 64, "euclidean_l2": 0.64},
"DeepID": {"cosine": 0.015, "euclidean": 45, "euclidean_l2": 0.17},
}
threshold = thresholds.get(model_name, base_threshold).get(distance_metric, 0.4)
return threshold

View File

@ -0,0 +1,35 @@
import os
from pathlib import Path
from deepface.commons.logger import Logger
logger = Logger(module="deepface/commons/folder_utils.py")
def initialize_folder() -> None:
"""
Initialize the folder for storing model weights.
Raises:
OSError: if the folder cannot be created.
"""
home = get_deepface_home()
deepface_home_path = home + "/.deepface"
weights_path = deepface_home_path + "/weights"
if not os.path.exists(deepface_home_path):
os.makedirs(deepface_home_path, exist_ok=True)
logger.info(f"Directory {home}/.deepface created")
if not os.path.exists(weights_path):
os.makedirs(weights_path, exist_ok=True)
logger.info(f"Directory {home}/.deepface/weights created")
def get_deepface_home() -> str:
"""
Get the home directory for storing model weights
Returns:
str: the home directory.
"""
return str(os.getenv("DEEPFACE_HOME", default=str(Path.home())))

View File

@ -1,42 +0,0 @@
import os
from pathlib import Path
# 3rd party dependencies
import tensorflow as tf
# package dependencies
from deepface.commons.logger import Logger
logger = Logger(module="commons.functions")
def get_tf_major_version() -> int:
return int(tf.__version__.split(".", maxsplit=1)[0])
def initialize_folder() -> None:
"""Initialize the folder for storing weights and models.
Raises:
OSError: if the folder cannot be created.
"""
home = get_deepface_home()
deepFaceHomePath = home + "/.deepface"
weightsPath = deepFaceHomePath + "/weights"
if not os.path.exists(deepFaceHomePath):
os.makedirs(deepFaceHomePath, exist_ok=True)
logger.info(f"Directory {home}/.deepface created")
if not os.path.exists(weightsPath):
os.makedirs(weightsPath, exist_ok=True)
logger.info(f"Directory {home}/.deepface/weights created")
def get_deepface_home() -> str:
"""Get the home directory for storing weights and models.
Returns:
str: the home directory.
"""
return str(os.getenv("DEEPFACE_HOME", default=str(Path.home())))

View File

@ -0,0 +1,36 @@
import json
# 3rd party dependencies
import tensorflow as tf
# package dependencies
from deepface.commons.logger import Logger
from deepface.commons import constant
logger = Logger(module="commons.package_utils")
def get_tf_major_version() -> int:
"""
Find tensorflow's major version
Returns
major_version (int)
"""
return int(tf.__version__.split(".", maxsplit=1)[0])
def find_package_version() -> str:
"""
Find the currenct package version
Returns:
version (str)
"""
version_info = "N/A"
try:
with open(f"{constant.ROOT_DIR}/package_info.json", "r", encoding="utf-8") as f:
package_info = json.load(f)
version_info = package_info["version"]
except Exception as err: # pylint: disable=broad-except
logger.error(f"Exception while getting version info: {str(err)}")
return version_info

View File

@ -3,7 +3,7 @@ import os
import bz2 import bz2
import gdown import gdown
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import folder_utils
from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
@ -20,7 +20,7 @@ class DlibClient(Detector):
Returns: Returns:
model (Any) model (Any)
""" """
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
# this is not a must dependency. do not import it in the global level. # this is not a must dependency. do not import it in the global level.
try: try:

View File

@ -36,45 +36,47 @@ class RetinaFaceClient(Detector):
obj = rf.detect_faces(img, model=self.model, threshold=0.9) obj = rf.detect_faces(img, model=self.model, threshold=0.9)
if isinstance(obj, dict): if not isinstance(obj, dict):
for face_idx in obj.keys(): return resp
identity = obj[face_idx]
facial_area = identity["facial_area"]
y = facial_area[1] for face_idx in obj.keys():
h = facial_area[3] - y identity = obj[face_idx]
x = facial_area[0] facial_area = identity["facial_area"]
w = facial_area[2] - x
img_region = FacialAreaRegion(x=x, y=y, w=w, h=h)
confidence = identity["score"]
# expand the facial area to be extracted and stay within img.shape limits y = facial_area[1]
x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left h = facial_area[3] - y
y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top x = facial_area[0]
w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right w = facial_area[2] - x
h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom img_region = FacialAreaRegion(x=x, y=y, w=w, h=h)
confidence = identity["score"]
# detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] # expand the facial area to be extracted and stay within img.shape limits
detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] x2 = max(0, x - int((w * expand_percentage) / 100)) # expand left
y2 = max(0, y - int((h * expand_percentage) / 100)) # expand top
w2 = min(img.shape[1], w + int((w * expand_percentage) / 100)) # expand right
h2 = min(img.shape[0], h + int((h * expand_percentage) / 100)) # expand bottom
if align: # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)]
landmarks = identity["landmarks"] detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)]
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( if align:
detected_face, right_eye, left_eye, nose 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_obj = DetectedFace( detected_face = postprocess.alignment_procedure(
img=detected_face, detected_face, right_eye, left_eye, nose
facial_area=img_region,
confidence=confidence,
) )
resp.append(detected_face_obj) detected_face_obj = DetectedFace(
img=detected_face,
facial_area=img_region,
confidence=confidence,
)
resp.append(detected_face_obj)
return resp return resp

View File

@ -5,7 +5,7 @@ import cv2
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from deepface.detectors import OpenCv from deepface.detectors import OpenCv
from deepface.commons import functions from deepface.commons import folder_utils
from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion
from deepface.modules import detection from deepface.modules import detection
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
@ -26,7 +26,7 @@ class SsdClient(Detector):
model (dict) model (dict)
""" """
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
# model structure # model structure
if os.path.isfile(home + "/.deepface/weights/deploy.prototxt") != True: if os.path.isfile(home + "/.deepface/weights/deploy.prototxt") != True:

View File

@ -4,6 +4,7 @@ import numpy as np
import gdown import gdown
from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion
from deepface.modules import detection from deepface.modules import detection
from deepface.commons import folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger() logger = Logger()
@ -39,9 +40,7 @@ class YoloClient(Detector):
Please install using 'pip install ultralytics' " Please install using 'pip install ultralytics' "
) from e ) from e
from deepface.commons.functions import get_deepface_home weight_path = f"{folder_utils.get_deepface_home()}{PATH}"
weight_path = f"{get_deepface_home()}{PATH}"
# Download the model's weights if they don't exist # Download the model's weights if they don't exist
if not os.path.isfile(weight_path): if not os.path.isfile(weight_path):

View File

@ -3,7 +3,7 @@ from typing import Any, List
import cv2 import cv2
import numpy as np import numpy as np
import gdown import gdown
from deepface.commons import functions from deepface.commons import folder_utils
from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion
from deepface.modules import detection from deepface.modules import detection
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
@ -31,7 +31,7 @@ class YuNetClient(Detector):
# pylint: disable=C0301 # pylint: disable=C0301
url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx" 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" file_name = "face_detection_yunet_2023mar.onnx"
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + f"/.deepface/weights/{file_name}") is False: if os.path.isfile(home + f"/.deepface/weights/{file_name}") is False:
logger.info(f"{file_name} will be downloaded...") logger.info(f"{file_name} will be downloaded...")
output = home + f"/.deepface/weights/{file_name}" output = home + f"/.deepface/weights/{file_name}"

View File

@ -2,7 +2,7 @@ import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.basemodels import VGGFace from deepface.basemodels import VGGFace
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography from deepface.models.Demography import Demography
@ -11,7 +11,7 @@ logger = Logger(module="extendedmodels.Age")
# ---------------------------------------- # ----------------------------------------
# dependency configurations # dependency configurations
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model, Sequential from keras.models import Model, Sequential
@ -64,7 +64,7 @@ def load_model(
# load weights # load weights
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/age_model_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/age_model_weights.h5") != True:
logger.info("age_model_weights.h5 will be downloaded...") logger.info("age_model_weights.h5 will be downloaded...")

View File

@ -2,7 +2,7 @@ import os
import gdown import gdown
import numpy as np import numpy as np
import cv2 import cv2
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography from deepface.models.Demography import Demography
@ -12,7 +12,7 @@ logger = Logger(module="extendedmodels.Emotion")
# pylint: disable=line-too-long # pylint: disable=line-too-long
# ------------------------------------------- # -------------------------------------------
# dependency configuration # dependency configuration
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Sequential from keras.models import Sequential
@ -88,7 +88,7 @@ def load_model(
# ---------------------------- # ----------------------------
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/facial_expression_model_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/facial_expression_model_weights.h5") != True:
logger.info("facial_expression_model_weights.h5 will be downloaded...") logger.info("facial_expression_model_weights.h5 will be downloaded...")

View File

@ -2,7 +2,7 @@ import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.basemodels import VGGFace from deepface.basemodels import VGGFace
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography from deepface.models.Demography import Demography
@ -13,7 +13,7 @@ logger = Logger(module="extendedmodels.Gender")
# ------------------------------------- # -------------------------------------
# dependency configurations # dependency configurations
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model, Sequential from keras.models import Model, Sequential
from keras.layers import Convolution2D, Flatten, Activation from keras.layers import Convolution2D, Flatten, Activation
@ -66,7 +66,7 @@ def load_model(
# load weights # load weights
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/gender_model_weights.h5") != True: if os.path.isfile(home + "/.deepface/weights/gender_model_weights.h5") != True:
logger.info("gender_model_weights.h5 will be downloaded...") logger.info("gender_model_weights.h5 will be downloaded...")

View File

@ -2,7 +2,7 @@ import os
import gdown import gdown
import numpy as np import numpy as np
from deepface.basemodels import VGGFace from deepface.basemodels import VGGFace
from deepface.commons import functions from deepface.commons import package_utils, folder_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.models.Demography import Demography from deepface.models.Demography import Demography
@ -12,7 +12,7 @@ logger = Logger(module="extendedmodels.Race")
# pylint: disable=line-too-long # pylint: disable=line-too-long
# -------------------------- # --------------------------
# dependency configurations # dependency configurations
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model, Sequential from keras.models import Model, Sequential
@ -63,7 +63,7 @@ def load_model(
# load weights # load weights
home = functions.get_deepface_home() home = folder_utils.get_deepface_home()
if os.path.isfile(home + "/.deepface/weights/race_model_single_batch.h5") != True: if os.path.isfile(home + "/.deepface/weights/race_model_single_batch.h5") != True:
logger.info("race_model_single_batch.h5 will be downloaded...") logger.info("race_model_single_batch.h5 will be downloaded...")

View File

@ -1,9 +1,9 @@
from typing import Union from typing import Union
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 1: if tf_version == 1:
from keras.models import Model from keras.models import Model
else: else:

View File

@ -1,9 +1,9 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any, Union, List, Tuple from typing import Any, Union, List, Tuple
import numpy as np import numpy as np
from deepface.commons import functions from deepface.commons import package_utils
tf_version = functions.get_tf_major_version() tf_version = package_utils.get_tf_major_version()
if tf_version == 2: if tf_version == 2:
from tensorflow.keras.models import Model from tensorflow.keras.models import Model
else: else:

View File

@ -10,7 +10,7 @@ from PIL import Image
from deepface.modules import preprocessing from deepface.modules import preprocessing
from deepface.models.Detector import DetectedFace, FacialAreaRegion from deepface.models.Detector import DetectedFace, FacialAreaRegion
from deepface.detectors import DetectorWrapper from deepface.detectors import DetectorWrapper
from deepface.commons import functions from deepface.commons import package_utils
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger(module="deepface/modules/detection.py") logger = Logger(module="deepface/modules/detection.py")
@ -18,7 +18,7 @@ logger = Logger(module="deepface/modules/detection.py")
# pylint: disable=no-else-raise # pylint: disable=no-else-raise
tf_major_version = functions.get_tf_major_version() tf_major_version = package_utils.get_tf_major_version()
if tf_major_version == 1: if tf_major_version == 1:
from keras.preprocessing import image from keras.preprocessing import image
elif tf_major_version == 2: elif tf_major_version == 2:
@ -63,8 +63,11 @@ def extract_faces(
Returns: Returns:
results (List[Dict[str, Any]]): A list of dictionaries, where each dictionary contains: results (List[Dict[str, Any]]): A list of dictionaries, where each dictionary contains:
- "face" (np.ndarray): The detected face as a NumPy array. - "face" (np.ndarray): The detected face as a NumPy array.
- "facial_area" (List[float]): The detected face's regions represented as a list of floats. - "facial_area" (List[float]): The detected face's regions represented as a list of floats.
- "confidence" (float): The confidence score associated with the detected face. - "confidence" (float): The confidence score associated with the detected face.
""" """

View File

@ -10,9 +10,8 @@ import pandas as pd
from tqdm import tqdm from tqdm import tqdm
# project dependencies # project dependencies
from deepface.commons import distance as dst
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
from deepface.modules import representation, detection, modeling from deepface.modules import representation, detection, modeling, verification
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
logger = Logger(module="deepface/modules/recognition.py") logger = Logger(module="deepface/modules/recognition.py")
@ -253,13 +252,17 @@ def find(
) )
if distance_metric == "cosine": if distance_metric == "cosine":
distance = dst.find_cosine_distance(source_representation, target_representation) distance = verification.find_cosine_distance(
source_representation, target_representation
)
elif distance_metric == "euclidean": elif distance_metric == "euclidean":
distance = dst.find_euclidean_distance(source_representation, target_representation) distance = verification.find_euclidean_distance(
source_representation, target_representation
)
elif distance_metric == "euclidean_l2": elif distance_metric == "euclidean_l2":
distance = dst.find_euclidean_distance( distance = verification.find_euclidean_distance(
dst.l2_normalize(source_representation), verification.l2_normalize(source_representation),
dst.l2_normalize(target_representation), verification.l2_normalize(target_representation),
) )
else: else:
raise ValueError(f"invalid distance metric passes - {distance_metric}") raise ValueError(f"invalid distance metric passes - {distance_metric}")
@ -267,7 +270,7 @@ def find(
distances.append(distance) distances.append(distance)
# --------------------------- # ---------------------------
target_threshold = threshold or dst.find_threshold(model_name, distance_metric) target_threshold = threshold or verification.find_threshold(model_name, distance_metric)
result_df["threshold"] = target_threshold result_df["threshold"] = target_threshold
result_df["distance"] = distances result_df["distance"] = distances

View File

@ -6,7 +6,6 @@ from typing import Any, Dict, Union
import numpy as np import numpy as np
# project dependencies # project dependencies
from deepface.commons import distance as dst
from deepface.modules import representation, detection, modeling from deepface.modules import representation, detection, modeling
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
@ -138,12 +137,12 @@ def verify(
img2_representation = img2_embedding_obj[0]["embedding"] img2_representation = img2_embedding_obj[0]["embedding"]
if distance_metric == "cosine": if distance_metric == "cosine":
distance = dst.find_cosine_distance(img1_representation, img2_representation) distance = find_cosine_distance(img1_representation, img2_representation)
elif distance_metric == "euclidean": elif distance_metric == "euclidean":
distance = dst.find_euclidean_distance(img1_representation, img2_representation) distance = find_euclidean_distance(img1_representation, img2_representation)
elif distance_metric == "euclidean_l2": elif distance_metric == "euclidean_l2":
distance = dst.find_euclidean_distance( distance = find_euclidean_distance(
dst.l2_normalize(img1_representation), dst.l2_normalize(img2_representation) l2_normalize(img1_representation), l2_normalize(img2_representation)
) )
else: else:
raise ValueError("Invalid distance_metric passed - ", distance_metric) raise ValueError("Invalid distance_metric passed - ", distance_metric)
@ -152,7 +151,7 @@ def verify(
regions.append((img1_region, img2_region)) regions.append((img1_region, img2_region))
# ------------------------------- # -------------------------------
threshold = dst.find_threshold(model_name, distance_metric) threshold = find_threshold(model_name, distance_metric)
distance = min(distances) # best distance distance = min(distances) # best distance
facial_areas = regions[np.argmin(distances)] facial_areas = regions[np.argmin(distances)]
@ -171,3 +170,65 @@ def verify(
} }
return resp_obj return resp_obj
def find_cosine_distance(
source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list]
) -> np.float64:
if isinstance(source_representation, list):
source_representation = np.array(source_representation)
if isinstance(test_representation, list):
test_representation = np.array(test_representation)
a = np.matmul(np.transpose(source_representation), test_representation)
b = np.sum(np.multiply(source_representation, source_representation))
c = np.sum(np.multiply(test_representation, test_representation))
return 1 - (a / (np.sqrt(b) * np.sqrt(c)))
def find_euclidean_distance(
source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list]
) -> np.float64:
if isinstance(source_representation, list):
source_representation = np.array(source_representation)
if isinstance(test_representation, list):
test_representation = np.array(test_representation)
euclidean_distance = source_representation - test_representation
euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
euclidean_distance = np.sqrt(euclidean_distance)
return euclidean_distance
def l2_normalize(x: Union[np.ndarray, list]) -> np.ndarray:
if isinstance(x, list):
x = np.array(x)
return x / np.sqrt(np.sum(np.multiply(x, x)))
def find_threshold(model_name: str, distance_metric: str) -> float:
base_threshold = {"cosine": 0.40, "euclidean": 0.55, "euclidean_l2": 0.75}
thresholds = {
# "VGG-Face": {"cosine": 0.40, "euclidean": 0.60, "euclidean_l2": 0.86}, # 2622d
"VGG-Face": {
"cosine": 0.68,
"euclidean": 1.17,
"euclidean_l2": 1.17,
}, # 4096d - tuned with LFW
"Facenet": {"cosine": 0.40, "euclidean": 10, "euclidean_l2": 0.80},
"Facenet512": {"cosine": 0.30, "euclidean": 23.56, "euclidean_l2": 1.04},
"ArcFace": {"cosine": 0.68, "euclidean": 4.15, "euclidean_l2": 1.13},
"Dlib": {"cosine": 0.07, "euclidean": 0.6, "euclidean_l2": 0.4},
"SFace": {"cosine": 0.593, "euclidean": 10.734, "euclidean_l2": 1.055},
"OpenFace": {"cosine": 0.10, "euclidean": 0.55, "euclidean_l2": 0.55},
"DeepFace": {"cosine": 0.23, "euclidean": 64, "euclidean_l2": 0.64},
"DeepID": {"cosine": 0.015, "euclidean": 45, "euclidean_l2": 0.17},
}
threshold = thresholds.get(model_name, base_threshold).get(distance_metric, 0.4)
return threshold

3
package_info.json Normal file
View File

@ -0,0 +1,3 @@
{
"version": "0.0.84"
}

2
scripts/service.sh Normal file → Executable file
View File

@ -1,3 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd ../api cd ../api/src
gunicorn --workers=1 --timeout=3600 --bind=0.0.0.0:5000 "app:create_app()" gunicorn --workers=1 --timeout=3600 --bind=0.0.0.0:5000 "app:create_app()"

View File

@ -1,3 +1,4 @@
import json
import setuptools import setuptools
with open("README.md", "r", encoding="utf-8") as fh: with open("README.md", "r", encoding="utf-8") as fh:
@ -6,13 +7,19 @@ with open("README.md", "r", encoding="utf-8") as fh:
with open("requirements.txt", "r", encoding="utf-8") as f: with open("requirements.txt", "r", encoding="utf-8") as f:
requirements = f.read().split("\n") requirements = f.read().split("\n")
with open("package_info.json", "r", encoding="utf-8") as f:
package_info = json.load(f)
setuptools.setup( setuptools.setup(
name="deepface", name="deepface",
version="0.0.84", version=package_info["version"],
author="Sefik Ilkin Serengil", author="Sefik Ilkin Serengil",
author_email="serengil@gmail.com", author_email="serengil@gmail.com",
description="A Lightweight Face Recognition and Facial Attribute Analysis Framework (Age, Gender, Emotion, Race) for Python", description=(
data_files=[("", ["README.md", "requirements.txt"])], "A Lightweight Face Recognition and Facial Attribute Analysis Framework"
" (Age, Gender, Emotion, Race) for Python"
),
data_files=[("", ["README.md", "requirements.txt", "package_info.json"])],
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/serengil/deepface", url="https://github.com/serengil/deepface",

View File

@ -1,7 +1,7 @@
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from deepface import DeepFace from deepface import DeepFace
from deepface.commons import distance from deepface.modules import verification
from deepface.models.FacialRecognition import FacialRecognition from deepface.models.FacialRecognition import FacialRecognition
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
@ -38,7 +38,7 @@ distance_vector = np.square(img1_representation - img2_representation)
current_distance = np.sqrt(distance_vector.sum()) current_distance = np.sqrt(distance_vector.sum())
logger.info(f"Euclidean distance: {current_distance}") logger.info(f"Euclidean distance: {current_distance}")
threshold = distance.find_threshold(model_name=model_name, distance_metric="euclidean") threshold = verification.find_threshold(model_name=model_name, distance_metric="euclidean")
logger.info(f"Threshold for {model_name}-euclidean pair is {threshold}") logger.info(f"Threshold for {model_name}-euclidean pair is {threshold}")
if current_distance < threshold: if current_distance < threshold:

View File

@ -1,12 +1,12 @@
import cv2 import cv2
import pandas as pd import pandas as pd
from deepface import DeepFace from deepface import DeepFace
from deepface.commons import distance from deepface.modules import verification
from deepface.commons.logger import Logger from deepface.commons.logger import Logger
logger = Logger("tests/test_find.py") logger = Logger("tests/test_find.py")
threshold = distance.find_threshold(model_name="VGG-Face", distance_metric="cosine") threshold = verification.find_threshold(model_name="VGG-Face", distance_metric="cosine")
def test_find_with_exact_path(): def test_find_with_exact_path():

View File

@ -20,7 +20,16 @@ model_names = [
"SFace", "SFace",
] ]
detector_backends = ["opencv", "ssd", "dlib", "mtcnn", "retinaface", "yunet", "yolov8"] detector_backends = [
"opencv",
"ssd",
"dlib",
"mtcnn",
# "mediapipe", # crashed in mac
"retinaface",
"yunet",
"yolov8",
]
# verification # verification
for model_name in model_names: for model_name in model_names: