From 84976821711cb8746183c3042db6028472cdd520 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Fri, 2 Feb 2024 17:33:31 +0000 Subject: [PATCH] open issues --- CITATION.md | 41 ++++++++++ Dockerfile | 8 +- README.md | 8 +- .../deepface-api.postman_collection.json | 0 api/{ => src}/api.py | 0 api/{ => src}/app.py | 0 api/{ => src}/routes.py | 0 api/{ => src}/service.py | 0 deepface/DeepFace.py | 12 ++- deepface/basemodels/ArcFace.py | 6 +- deepface/basemodels/DeepID.py | 6 +- deepface/basemodels/Dlib.py | 4 +- deepface/basemodels/Facenet.py | 8 +- deepface/basemodels/FbDeepFace.py | 6 +- deepface/basemodels/OpenFace.py | 6 +- deepface/basemodels/SFace.py | 4 +- deepface/basemodels/VGGFace.py | 9 ++- deepface/commons/constant.py | 4 + deepface/commons/distance.py | 64 --------------- deepface/commons/folder_utils.py | 35 +++++++++ deepface/commons/functions.py | 42 ---------- deepface/commons/package_utils.py | 36 +++++++++ deepface/detectors/Dlib.py | 4 +- deepface/detectors/RetinaFace.py | 78 ++++++++++--------- deepface/detectors/Ssd.py | 4 +- deepface/detectors/Yolo.py | 5 +- deepface/detectors/YuNet.py | 4 +- deepface/extendedmodels/Age.py | 6 +- deepface/extendedmodels/Emotion.py | 6 +- deepface/extendedmodels/Gender.py | 6 +- deepface/extendedmodels/Race.py | 6 +- deepface/models/Demography.py | 4 +- deepface/models/FacialRecognition.py | 4 +- deepface/modules/detection.py | 7 +- deepface/modules/recognition.py | 19 +++-- deepface/modules/verification.py | 73 +++++++++++++++-- package_info.json | 3 + scripts/service.sh | 2 +- setup.py | 13 +++- tests/face-recognition-how.py | 4 +- tests/test_find.py | 4 +- tests/visual-test.py | 11 ++- 42 files changed, 332 insertions(+), 230 deletions(-) create mode 100644 CITATION.md rename api/{ => postman}/deepface-api.postman_collection.json (100%) rename api/{ => src}/api.py (100%) rename api/{ => src}/app.py (100%) rename api/{ => src}/routes.py (100%) rename api/{ => src}/service.py (100%) create mode 100644 deepface/commons/constant.py delete mode 100644 deepface/commons/distance.py create mode 100644 deepface/commons/folder_utils.py delete mode 100644 deepface/commons/functions.py create mode 100644 deepface/commons/package_utils.py create mode 100644 package_info.json mode change 100644 => 100755 scripts/service.sh diff --git a/CITATION.md b/CITATION.md new file mode 100644 index 00000000..4384442c --- /dev/null +++ b/CITATION.md @@ -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). \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 911df26d..70614723 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,10 +19,10 @@ RUN apt-get install ffmpeg libsm6 libxext6 -y # ----------------------------------- # Copy required files from repo into image COPY ./deepface /app/deepface -COPY ./api/app.py /app/ -COPY ./api/api.py /app/ -COPY ./api/routes.py /app/ -COPY ./api/service.py /app/ +COPY ./api/src/app.py /app/ +COPY ./api/src/api.py /app/ +COPY ./api/src/routes.py /app/ +COPY ./api/src/service.py /app/ COPY ./requirements.txt /app/ COPY ./setup.py /app/ COPY ./README.md /app/ diff --git a/README.md b/README.md index d9e0ef90..3ed75f2c 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ cd scripts

-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** @@ -332,9 +332,9 @@ You can also support this work on [Patreon](https://www.patreon.com/serengil?rep ## 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 @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 @inproceedings{serengil2021lightface, diff --git a/api/deepface-api.postman_collection.json b/api/postman/deepface-api.postman_collection.json similarity index 100% rename from api/deepface-api.postman_collection.json rename to api/postman/deepface-api.postman_collection.json diff --git a/api/api.py b/api/src/api.py similarity index 100% rename from api/api.py rename to api/src/api.py diff --git a/api/app.py b/api/src/app.py similarity index 100% rename from api/app.py rename to api/src/app.py diff --git a/api/routes.py b/api/src/routes.py similarity index 100% rename from api/routes.py rename to api/src/routes.py diff --git a/api/service.py b/api/src/service.py similarity index 100% rename from api/service.py rename to api/src/service.py diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index f3d4f693..11058acf 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -10,7 +10,7 @@ import tensorflow as tf # package dependencies -from deepface.commons import functions +from deepface.commons import package_utils, folder_utils from deepface.commons.logger import Logger from deepface.modules import ( modeling, @@ -24,17 +24,21 @@ logger = Logger(module="DeepFace") +# current package version of deepface +__version__ = package_utils.find_package_version() + # ----------------------------------- # configurations for dependencies warnings.filterwarnings("ignore") 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: 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: @@ -511,7 +515,7 @@ def detectFace( align (bool): Flag to enable face alignment (default is True). 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.") face_objs = extract_faces( diff --git a/deepface/basemodels/ArcFace.py b/deepface/basemodels/ArcFace.py index cacacd4f..3c8d6f0a 100644 --- a/deepface/basemodels/ArcFace.py +++ b/deepface/basemodels/ArcFace.py @@ -2,7 +2,7 @@ import os import gdown 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.models.FacialRecognition import FacialRecognition @@ -13,7 +13,7 @@ # -------------------------------- # dependency configuration -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: from keras.models import Model @@ -94,7 +94,7 @@ def load_model( # --------------------------------------- # check the availability of pre-trained weights - home = functions.get_deepface_home() + home = folder_utils.get_deepface_home() file_name = "arcface_weights.h5" output = home + "/.deepface/weights/" + file_name diff --git a/deepface/basemodels/DeepID.py b/deepface/basemodels/DeepID.py index 0933c276..66e31f96 100644 --- a/deepface/basemodels/DeepID.py +++ b/deepface/basemodels/DeepID.py @@ -2,13 +2,13 @@ import os import gdown 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.models.FacialRecognition import FacialRecognition logger = Logger(module="basemodels.DeepID") -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: 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: logger.info("deepid_keras_weights.h5 will be downloaded...") diff --git a/deepface/basemodels/Dlib.py b/deepface/basemodels/Dlib.py index 8336ca6f..174fda10 100644 --- a/deepface/basemodels/Dlib.py +++ b/deepface/basemodels/Dlib.py @@ -3,7 +3,7 @@ import bz2 import gdown import numpy as np -from deepface.commons import functions +from deepface.commons import folder_utils from deepface.commons.logger import Logger from deepface.models.FacialRecognition import FacialRecognition @@ -68,7 +68,7 @@ def __init__(self): # --------------------- - home = functions.get_deepface_home() + home = folder_utils.get_deepface_home() weight_file = home + "/.deepface/weights/dlib_face_recognition_resnet_model_v1.dat" # --------------------- diff --git a/deepface/basemodels/Facenet.py b/deepface/basemodels/Facenet.py index 31811a3e..646579dd 100644 --- a/deepface/basemodels/Facenet.py +++ b/deepface/basemodels/Facenet.py @@ -2,7 +2,7 @@ import os import gdown 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.models.FacialRecognition import FacialRecognition @@ -11,7 +11,7 @@ # -------------------------------- # dependency configuration -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: 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: 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: logger.info("facenet512_weights.h5 will be downloaded...") diff --git a/deepface/basemodels/FbDeepFace.py b/deepface/basemodels/FbDeepFace.py index 1a47685e..11b49860 100644 --- a/deepface/basemodels/FbDeepFace.py +++ b/deepface/basemodels/FbDeepFace.py @@ -3,7 +3,7 @@ import zipfile import gdown 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.models.FacialRecognition import FacialRecognition @@ -12,7 +12,7 @@ # -------------------------------- # dependency configuration -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: 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: logger.info("VGGFace2_DeepFace_weights_val-0.9034.h5 will be downloaded...") diff --git a/deepface/basemodels/OpenFace.py b/deepface/basemodels/OpenFace.py index 8ee2d95d..9b32bd6b 100644 --- a/deepface/basemodels/OpenFace.py +++ b/deepface/basemodels/OpenFace.py @@ -3,13 +3,13 @@ import gdown import tensorflow as tf 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.models.FacialRecognition import FacialRecognition logger = Logger(module="basemodels.OpenFace") -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: from keras.models import Model 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: logger.info("openface_weights.h5 will be downloaded...") diff --git a/deepface/basemodels/SFace.py b/deepface/basemodels/SFace.py index f8ec1921..816b65fd 100644 --- a/deepface/basemodels/SFace.py +++ b/deepface/basemodels/SFace.py @@ -5,7 +5,7 @@ import cv2 as cv import gdown -from deepface.commons import functions +from deepface.commons import folder_utils from deepface.commons.logger import Logger from deepface.models.FacialRecognition import FacialRecognition @@ -50,7 +50,7 @@ def load_model( 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" diff --git a/deepface/basemodels/VGGFace.py b/deepface/basemodels/VGGFace.py index c4516a8c..e2221acc 100644 --- a/deepface/basemodels/VGGFace.py +++ b/deepface/basemodels/VGGFace.py @@ -2,7 +2,8 @@ import os import gdown 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.commons.logger import Logger @@ -10,7 +11,7 @@ # --------------------------------------- -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: from keras.models import Model, Sequential from keras.layers import ( @@ -59,7 +60,7 @@ def find_embeddings(self, img: np.ndarray) -> List[float]: # 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 embedding = self.model(img, training=False).numpy()[0].tolist() - embedding = distance.l2_normalize(embedding) + embedding = verification.l2_normalize(embedding) return embedding.tolist() @@ -128,7 +129,7 @@ def load_model( model = base_model() - home = functions.get_deepface_home() + home = folder_utils.get_deepface_home() output = home + "/.deepface/weights/vgg_face_weights.h5" if os.path.isfile(output) != True: diff --git a/deepface/commons/constant.py b/deepface/commons/constant.py new file mode 100644 index 00000000..22f63499 --- /dev/null +++ b/deepface/commons/constant.py @@ -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) diff --git a/deepface/commons/distance.py b/deepface/commons/distance.py deleted file mode 100644 index 950a7a23..00000000 --- a/deepface/commons/distance.py +++ /dev/null @@ -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 diff --git a/deepface/commons/folder_utils.py b/deepface/commons/folder_utils.py new file mode 100644 index 00000000..51b77390 --- /dev/null +++ b/deepface/commons/folder_utils.py @@ -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()))) diff --git a/deepface/commons/functions.py b/deepface/commons/functions.py deleted file mode 100644 index c7b317ac..00000000 --- a/deepface/commons/functions.py +++ /dev/null @@ -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()))) diff --git a/deepface/commons/package_utils.py b/deepface/commons/package_utils.py new file mode 100644 index 00000000..6251d4ad --- /dev/null +++ b/deepface/commons/package_utils.py @@ -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 diff --git a/deepface/detectors/Dlib.py b/deepface/detectors/Dlib.py index 9a66b06c..9709401d 100644 --- a/deepface/detectors/Dlib.py +++ b/deepface/detectors/Dlib.py @@ -3,7 +3,7 @@ import bz2 import gdown 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.commons.logger import Logger @@ -20,7 +20,7 @@ def build_model(self) -> dict: Returns: 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. try: diff --git a/deepface/detectors/RetinaFace.py b/deepface/detectors/RetinaFace.py index 632d5e3f..0e054a9d 100644 --- a/deepface/detectors/RetinaFace.py +++ b/deepface/detectors/RetinaFace.py @@ -36,45 +36,47 @@ def detect_faces( obj = rf.detect_faces(img, model=self.model, threshold=0.9) - 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 = 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 - 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 - - # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] - detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] - - 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_obj = DetectedFace( - img=detected_face, - facial_area=img_region, - confidence=confidence, + if not isinstance(obj, dict): + return resp + + 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 = 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 + 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 + + # detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] + detected_face = img[int(y2) : int(y2 + h2), int(x2) : int(x2 + w2)] + + 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 ) - 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 diff --git a/deepface/detectors/Ssd.py b/deepface/detectors/Ssd.py index 0c6ee9c1..0c39c40e 100644 --- a/deepface/detectors/Ssd.py +++ b/deepface/detectors/Ssd.py @@ -5,7 +5,7 @@ import pandas as pd import numpy as np 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.modules import detection from deepface.commons.logger import Logger @@ -26,7 +26,7 @@ def build_model(self) -> dict: model (dict) """ - home = functions.get_deepface_home() + home = folder_utils.get_deepface_home() # model structure if os.path.isfile(home + "/.deepface/weights/deploy.prototxt") != True: diff --git a/deepface/detectors/Yolo.py b/deepface/detectors/Yolo.py index e23d3c17..3423988f 100644 --- a/deepface/detectors/Yolo.py +++ b/deepface/detectors/Yolo.py @@ -4,6 +4,7 @@ import gdown from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion from deepface.modules import detection +from deepface.commons import folder_utils from deepface.commons.logger import Logger logger = Logger() @@ -39,9 +40,7 @@ def build_model(self) -> Any: Please install using 'pip install ultralytics' " ) from e - from deepface.commons.functions import get_deepface_home - - weight_path = f"{get_deepface_home()}{PATH}" + weight_path = f"{folder_utils.get_deepface_home()}{PATH}" # Download the model's weights if they don't exist if not os.path.isfile(weight_path): diff --git a/deepface/detectors/YuNet.py b/deepface/detectors/YuNet.py index f7728b6d..d345c613 100644 --- a/deepface/detectors/YuNet.py +++ b/deepface/detectors/YuNet.py @@ -3,7 +3,7 @@ import cv2 import numpy as np import gdown -from deepface.commons import functions +from deepface.commons import folder_utils from deepface.models.Detector import Detector, DetectedFace, FacialAreaRegion from deepface.modules import detection from deepface.commons.logger import Logger @@ -31,7 +31,7 @@ def build_model(self) -> 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() + home = folder_utils.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}" diff --git a/deepface/extendedmodels/Age.py b/deepface/extendedmodels/Age.py index 1d5e6cd5..4cb8ef21 100644 --- a/deepface/extendedmodels/Age.py +++ b/deepface/extendedmodels/Age.py @@ -2,7 +2,7 @@ import gdown import numpy as np 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.models.Demography import Demography @@ -11,7 +11,7 @@ # ---------------------------------------- # dependency configurations -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: from keras.models import Model, Sequential @@ -64,7 +64,7 @@ def load_model( # 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: logger.info("age_model_weights.h5 will be downloaded...") diff --git a/deepface/extendedmodels/Emotion.py b/deepface/extendedmodels/Emotion.py index 8a04c33f..983045b8 100644 --- a/deepface/extendedmodels/Emotion.py +++ b/deepface/extendedmodels/Emotion.py @@ -2,7 +2,7 @@ import gdown import numpy as np import cv2 -from deepface.commons import functions +from deepface.commons import package_utils, folder_utils from deepface.commons.logger import Logger from deepface.models.Demography import Demography @@ -12,7 +12,7 @@ # pylint: disable=line-too-long # ------------------------------------------- # dependency configuration -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: 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: logger.info("facial_expression_model_weights.h5 will be downloaded...") diff --git a/deepface/extendedmodels/Gender.py b/deepface/extendedmodels/Gender.py index 191e315d..a7b914cc 100644 --- a/deepface/extendedmodels/Gender.py +++ b/deepface/extendedmodels/Gender.py @@ -2,7 +2,7 @@ import gdown import numpy as np 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.models.Demography import Demography @@ -13,7 +13,7 @@ # ------------------------------------- # dependency configurations -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: from keras.models import Model, Sequential from keras.layers import Convolution2D, Flatten, Activation @@ -66,7 +66,7 @@ def load_model( # 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: logger.info("gender_model_weights.h5 will be downloaded...") diff --git a/deepface/extendedmodels/Race.py b/deepface/extendedmodels/Race.py index 9c907f1c..eb2c2983 100644 --- a/deepface/extendedmodels/Race.py +++ b/deepface/extendedmodels/Race.py @@ -2,7 +2,7 @@ import gdown import numpy as np 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.models.Demography import Demography @@ -12,7 +12,7 @@ # pylint: disable=line-too-long # -------------------------- # dependency configurations -tf_version = functions.get_tf_major_version() +tf_version = package_utils.get_tf_major_version() if tf_version == 1: from keras.models import Model, Sequential @@ -63,7 +63,7 @@ def load_model( # 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: logger.info("race_model_single_batch.h5 will be downloaded...") diff --git a/deepface/models/Demography.py b/deepface/models/Demography.py index 36a01c87..ad939202 100644 --- a/deepface/models/Demography.py +++ b/deepface/models/Demography.py @@ -1,9 +1,9 @@ from typing import Union from abc import ABC, abstractmethod 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: from keras.models import Model else: diff --git a/deepface/models/FacialRecognition.py b/deepface/models/FacialRecognition.py index c7aff6b4..4e636a57 100644 --- a/deepface/models/FacialRecognition.py +++ b/deepface/models/FacialRecognition.py @@ -1,9 +1,9 @@ from abc import ABC, abstractmethod from typing import Any, Union, List, Tuple 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: from tensorflow.keras.models import Model else: diff --git a/deepface/modules/detection.py b/deepface/modules/detection.py index 97763b34..406563a7 100644 --- a/deepface/modules/detection.py +++ b/deepface/modules/detection.py @@ -10,7 +10,7 @@ from deepface.modules import preprocessing from deepface.models.Detector import DetectedFace, FacialAreaRegion from deepface.detectors import DetectorWrapper -from deepface.commons import functions +from deepface.commons import package_utils from deepface.commons.logger import Logger logger = Logger(module="deepface/modules/detection.py") @@ -18,7 +18,7 @@ # 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: from keras.preprocessing import image elif tf_major_version == 2: @@ -63,8 +63,11 @@ def extract_faces( Returns: results (List[Dict[str, Any]]): A list of dictionaries, where each dictionary contains: + - "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. + - "confidence" (float): The confidence score associated with the detected face. """ diff --git a/deepface/modules/recognition.py b/deepface/modules/recognition.py index 04fbb673..0fea20c5 100644 --- a/deepface/modules/recognition.py +++ b/deepface/modules/recognition.py @@ -10,9 +10,8 @@ from tqdm import tqdm # project dependencies -from deepface.commons import distance as dst 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 logger = Logger(module="deepface/modules/recognition.py") @@ -253,13 +252,17 @@ def find( ) 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": - distance = dst.find_euclidean_distance(source_representation, target_representation) + distance = verification.find_euclidean_distance( + source_representation, target_representation + ) elif distance_metric == "euclidean_l2": - distance = dst.find_euclidean_distance( - dst.l2_normalize(source_representation), - dst.l2_normalize(target_representation), + distance = verification.find_euclidean_distance( + verification.l2_normalize(source_representation), + verification.l2_normalize(target_representation), ) else: raise ValueError(f"invalid distance metric passes - {distance_metric}") @@ -267,7 +270,7 @@ def find( 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["distance"] = distances diff --git a/deepface/modules/verification.py b/deepface/modules/verification.py index e014ed39..133dc453 100644 --- a/deepface/modules/verification.py +++ b/deepface/modules/verification.py @@ -6,7 +6,6 @@ import numpy as np # project dependencies -from deepface.commons import distance as dst from deepface.modules import representation, detection, modeling from deepface.models.FacialRecognition import FacialRecognition @@ -138,12 +137,12 @@ def verify( img2_representation = img2_embedding_obj[0]["embedding"] 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": - distance = dst.find_euclidean_distance(img1_representation, img2_representation) + distance = find_euclidean_distance(img1_representation, img2_representation) elif distance_metric == "euclidean_l2": - distance = dst.find_euclidean_distance( - dst.l2_normalize(img1_representation), dst.l2_normalize(img2_representation) + distance = find_euclidean_distance( + l2_normalize(img1_representation), l2_normalize(img2_representation) ) else: raise ValueError("Invalid distance_metric passed - ", distance_metric) @@ -152,7 +151,7 @@ def verify( 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 facial_areas = regions[np.argmin(distances)] @@ -171,3 +170,65 @@ def verify( } 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 diff --git a/package_info.json b/package_info.json new file mode 100644 index 00000000..8d6e00f0 --- /dev/null +++ b/package_info.json @@ -0,0 +1,3 @@ +{ + "version": "0.0.84" +} \ No newline at end of file diff --git a/scripts/service.sh b/scripts/service.sh old mode 100644 new mode 100755 index c1444f5e..2076648a --- a/scripts/service.sh +++ b/scripts/service.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -cd ../api +cd ../api/src gunicorn --workers=1 --timeout=3600 --bind=0.0.0.0:5000 "app:create_app()" \ No newline at end of file diff --git a/setup.py b/setup.py index dc730dc4..f56a292d 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +import json import setuptools with open("README.md", "r", encoding="utf-8") as fh: @@ -6,13 +7,19 @@ with open("requirements.txt", "r", encoding="utf-8") as f: requirements = f.read().split("\n") +with open("package_info.json", "r", encoding="utf-8") as f: + package_info = json.load(f) + setuptools.setup( name="deepface", - version="0.0.84", + version=package_info["version"], author="Sefik Ilkin Serengil", author_email="serengil@gmail.com", - description="A Lightweight Face Recognition and Facial Attribute Analysis Framework (Age, Gender, Emotion, Race) for Python", - data_files=[("", ["README.md", "requirements.txt"])], + description=( + "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_content_type="text/markdown", url="https://github.com/serengil/deepface", diff --git a/tests/face-recognition-how.py b/tests/face-recognition-how.py index 09ad8cf2..c0847677 100644 --- a/tests/face-recognition-how.py +++ b/tests/face-recognition-how.py @@ -1,7 +1,7 @@ import matplotlib.pyplot as plt import numpy as np from deepface import DeepFace -from deepface.commons import distance +from deepface.modules import verification from deepface.models.FacialRecognition import FacialRecognition from deepface.commons.logger import Logger @@ -38,7 +38,7 @@ current_distance = np.sqrt(distance_vector.sum()) 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}") if current_distance < threshold: diff --git a/tests/test_find.py b/tests/test_find.py index 1d3faa72..fd0902a3 100644 --- a/tests/test_find.py +++ b/tests/test_find.py @@ -1,12 +1,12 @@ import cv2 import pandas as pd from deepface import DeepFace -from deepface.commons import distance +from deepface.modules import verification from deepface.commons.logger import Logger 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(): diff --git a/tests/visual-test.py b/tests/visual-test.py index dbf861c8..27d41694 100644 --- a/tests/visual-test.py +++ b/tests/visual-test.py @@ -20,7 +20,16 @@ "SFace", ] -detector_backends = ["opencv", "ssd", "dlib", "mtcnn", "retinaface", "yunet", "yolov8"] +detector_backends = [ + "opencv", + "ssd", + "dlib", + "mtcnn", + # "mediapipe", # crashed in mac + "retinaface", + "yunet", + "yolov8", +] # verification for model_name in model_names: