diff --git a/CITATION.md b/CITATION.md
new file mode 100644
index 000000000..4384442cf
--- /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 911df26df..70614723b 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 d9e0ef905..3ed75f2cb 100644
--- a/README.md
+++ b/README.md
@@ -289,7 +289,7 @@ cd scripts
![](https://raw.githubusercontent.com/serengil/deepface/master/icon/deepface-api.jpg)
-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 f3d4f693b..11058acfa 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 cacacd4f1..3c8d6f0ab 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 0933c276e..66e31f963 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 8336ca6f3..174fda10c 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 31811a3e2..646579dd5 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 1a47685ef..11b49860c 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 8ee2d95d2..9b32bd6bc 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 f8ec19216..816b65fd7 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 c4516a8c5..e2221acc6 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 000000000..22f63499f
--- /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 950a7a23e..000000000
--- 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 000000000..51b77390b
--- /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 c7b317ac4..000000000
--- 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 000000000..6251d4adc
--- /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 9a66b06c2..9709401d3 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 632d5e3f0..0e054a9d1 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 0c6ee9c13..0c39c40eb 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 e23d3c177..3423988f2 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 f7728b6d1..d345c613c 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 1d5e6cd50..4cb8ef219 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 8a04c33f1..983045b84 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 191e315d1..a7b914cc6 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 9c907f1c1..eb2c29833 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 36a01c874..ad9392029 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 c7aff6b4c..4e636a573 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 97763b34a..406563a77 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 04fbb6739..0fea20c51 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 e014ed39e..133dc453e 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 000000000..8d6e00f09
--- /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 c1444f5e3..2076648a3
--- 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 dc730dc4d..f56a292db 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 09ad8cf25..c08476773 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 1d3faa728..fd0902a3b 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 dbf861c86..27d416949 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: