mirror of
https://github.com/serengil/deepface.git
synced 2025-06-06 11:35:21 +00:00
support embedding input for verify
This commit is contained in:
parent
d7c2998e1b
commit
6eced68e69
@ -62,6 +62,7 @@ def verify(
|
||||
align: bool = True,
|
||||
expand_percentage: int = 0,
|
||||
normalization: str = "base",
|
||||
silent: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify if an image pair represents the same person or different persons.
|
||||
@ -91,6 +92,9 @@ def verify(
|
||||
normalization (string): Normalize the input image before feeding it to the model.
|
||||
Options: base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace (default is base)
|
||||
|
||||
silent (boolean): Suppress or allow some log messages for a quieter analysis process
|
||||
(default is False).
|
||||
|
||||
Returns:
|
||||
result (dict): A dictionary containing verification results with following keys.
|
||||
|
||||
@ -126,6 +130,7 @@ def verify(
|
||||
align=align,
|
||||
expand_percentage=expand_percentage,
|
||||
normalization=normalization,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# built-in dependencies
|
||||
import time
|
||||
from typing import Any, Dict, Union
|
||||
from typing import Any, Dict, Union, List, Tuple
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
@ -8,11 +8,14 @@ import numpy as np
|
||||
# project dependencies
|
||||
from deepface.modules import representation, detection, modeling
|
||||
from deepface.models.FacialRecognition import FacialRecognition
|
||||
from deepface.commons.logger import Logger
|
||||
|
||||
logger = Logger(module="deepface/modules/verification.py")
|
||||
|
||||
|
||||
def verify(
|
||||
img1_path: Union[str, np.ndarray],
|
||||
img2_path: Union[str, np.ndarray],
|
||||
img1_path: Union[str, np.ndarray, List[float]],
|
||||
img2_path: Union[str, np.ndarray, List[float]],
|
||||
model_name: str = "VGG-Face",
|
||||
detector_backend: str = "opencv",
|
||||
distance_metric: str = "cosine",
|
||||
@ -20,6 +23,7 @@ def verify(
|
||||
align: bool = True,
|
||||
expand_percentage: int = 0,
|
||||
normalization: str = "base",
|
||||
silent: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify if an image pair represents the same person or different persons.
|
||||
@ -30,10 +34,10 @@ def verify(
|
||||
|
||||
Args:
|
||||
img1_path (str or np.ndarray): Path to the first image. Accepts exact image path
|
||||
as a string, numpy array (BGR), or base64 encoded images.
|
||||
as a string, numpy array (BGR), base64 encoded images or pre-calculated embeddings.
|
||||
|
||||
img2_path (str or np.ndarray): Path to the second image. Accepts exact image path
|
||||
as a string, numpy array (BGR), or base64 encoded images.
|
||||
as a string, numpy array (BGR), base64 encoded images or pre-calculated embeddings.
|
||||
|
||||
model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512,
|
||||
OpenFace, DeepFace, DeepID, Dlib, ArcFace and SFace (default is VGG-Face).
|
||||
@ -54,6 +58,9 @@ def verify(
|
||||
normalization (string): Normalize the input image before feeding it to the model.
|
||||
Options: base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace (default is base)
|
||||
|
||||
silent (boolean): Suppress or allow some log messages for a quieter analysis process
|
||||
(default is False).
|
||||
|
||||
Returns:
|
||||
result (dict): A dictionary containing verification results.
|
||||
|
||||
@ -81,74 +88,96 @@ def verify(
|
||||
|
||||
tic = time.time()
|
||||
|
||||
# --------------------------------
|
||||
model: FacialRecognition = modeling.build_model(model_name)
|
||||
target_size = model.input_shape
|
||||
dims = model.output_shape
|
||||
|
||||
try:
|
||||
img1_objs = detection.extract_faces(
|
||||
if isinstance(img1_path, list):
|
||||
# given image is already pre-calculated embedding
|
||||
if not all(isinstance(dim, float) for dim in img1_path):
|
||||
raise ValueError(
|
||||
"When passing img1_path as a list, ensure that all its items are of type float."
|
||||
)
|
||||
|
||||
if silent is False:
|
||||
logger.warn(
|
||||
"You passed 1st image as pre-calculated embeddings."
|
||||
f"Please ensure that embeddings have been calculated for the {model_name} model."
|
||||
)
|
||||
|
||||
if len(img1_path) != dims:
|
||||
raise ValueError(
|
||||
f"embeddings of {model_name} should have {dims} dimensions,"
|
||||
f" but it has {len(img1_path)} dimensions input"
|
||||
)
|
||||
|
||||
img1_embeddings = [img1_path]
|
||||
img1_facial_areas = [None]
|
||||
else:
|
||||
img1_embeddings, img1_facial_areas = __extract_faces_and_embeddings(
|
||||
img_path=img1_path,
|
||||
target_size=target_size,
|
||||
model_name=model_name,
|
||||
detector_backend=detector_backend,
|
||||
grayscale=False,
|
||||
enforce_detection=enforce_detection,
|
||||
align=align,
|
||||
expand_percentage=expand_percentage,
|
||||
normalization=normalization,
|
||||
)
|
||||
except ValueError as err:
|
||||
raise ValueError("Exception while processing img1_path") from err
|
||||
|
||||
try:
|
||||
img2_objs = detection.extract_faces(
|
||||
if isinstance(img2_path, list):
|
||||
# given image is already pre-calculated embedding
|
||||
if not all(isinstance(dim, float) for dim in img2_path):
|
||||
raise ValueError(
|
||||
"When passing img2_path as a list, ensure that all its items are of type float."
|
||||
)
|
||||
|
||||
if silent is False:
|
||||
logger.warn(
|
||||
"You passed 2nd image as pre-calculated embeddings."
|
||||
f"Please ensure that embeddings have been calculated for the {model_name} model."
|
||||
)
|
||||
|
||||
if len(img2_path) != dims:
|
||||
raise ValueError(
|
||||
f"embeddings of {model_name} should have {dims} dimensions,"
|
||||
f" but it has {len(img2_path)} dimensions input"
|
||||
)
|
||||
|
||||
img2_embeddings = [img2_path]
|
||||
img2_facial_areas = [None]
|
||||
else:
|
||||
img2_embeddings, img2_facial_areas = __extract_faces_and_embeddings(
|
||||
img_path=img2_path,
|
||||
target_size=target_size,
|
||||
model_name=model_name,
|
||||
detector_backend=detector_backend,
|
||||
grayscale=False,
|
||||
enforce_detection=enforce_detection,
|
||||
align=align,
|
||||
expand_percentage=expand_percentage,
|
||||
)
|
||||
except ValueError as err:
|
||||
raise ValueError("Exception while processing img2_path") from err
|
||||
|
||||
img1_embeddings = []
|
||||
for img1_obj in img1_objs:
|
||||
img1_embedding_obj = representation.represent(
|
||||
img_path=img1_obj["face"],
|
||||
model_name=model_name,
|
||||
enforce_detection=enforce_detection,
|
||||
detector_backend="skip",
|
||||
align=align,
|
||||
normalization=normalization,
|
||||
)
|
||||
img1_embedding = img1_embedding_obj[0]["embedding"]
|
||||
img1_embeddings.append(img1_embedding)
|
||||
|
||||
img2_embeddings = []
|
||||
for img2_obj in img2_objs:
|
||||
img2_embedding_obj = representation.represent(
|
||||
img_path=img2_obj["face"],
|
||||
model_name=model_name,
|
||||
enforce_detection=enforce_detection,
|
||||
detector_backend="skip",
|
||||
align=align,
|
||||
normalization=normalization,
|
||||
)
|
||||
img2_embedding = img2_embedding_obj[0]["embedding"]
|
||||
img2_embeddings.append(img2_embedding)
|
||||
no_facial_area = {
|
||||
"x": None,
|
||||
"y": None,
|
||||
"w": None,
|
||||
"h": None,
|
||||
"left_eye": None,
|
||||
"right_eye": None,
|
||||
}
|
||||
|
||||
distances = []
|
||||
regions = []
|
||||
facial_areas = []
|
||||
for idx, img1_embedding in enumerate(img1_embeddings):
|
||||
for idy, img2_embedding in enumerate(img2_embeddings):
|
||||
distance = find_distance(img1_embedding, img2_embedding, distance_metric)
|
||||
distances.append(distance)
|
||||
regions.append((img1_objs[idx]["facial_area"], img2_objs[idy]["facial_area"]))
|
||||
facial_areas.append(
|
||||
(img1_facial_areas[idx] or no_facial_area, img2_facial_areas[idy] or no_facial_area)
|
||||
)
|
||||
|
||||
# find the face pair with minimum distance
|
||||
threshold = find_threshold(model_name, distance_metric)
|
||||
distance = float(min(distances)) # best distance
|
||||
facial_areas = regions[np.argmin(distances)]
|
||||
facial_areas = facial_areas[np.argmin(distances)]
|
||||
|
||||
toc = time.time()
|
||||
|
||||
@ -166,6 +195,58 @@ def verify(
|
||||
return resp_obj
|
||||
|
||||
|
||||
def __extract_faces_and_embeddings(
|
||||
img_path: Union[str, np.ndarray],
|
||||
model_name: str = "VGG-Face",
|
||||
detector_backend: str = "opencv",
|
||||
enforce_detection: bool = True,
|
||||
align: bool = True,
|
||||
expand_percentage: int = 0,
|
||||
normalization: str = "base",
|
||||
) -> Tuple[List[List[float]], List[dict]]:
|
||||
"""
|
||||
Extract facial areas and find corresponding embeddings for given image
|
||||
Returns:
|
||||
embeddings (List[float])
|
||||
facial areas (List[dict])
|
||||
"""
|
||||
embeddings = []
|
||||
facial_areas = []
|
||||
|
||||
model: FacialRecognition = modeling.build_model(model_name)
|
||||
target_size = model.input_shape
|
||||
|
||||
try:
|
||||
img_objs = detection.extract_faces(
|
||||
img_path=img_path,
|
||||
target_size=target_size,
|
||||
detector_backend=detector_backend,
|
||||
grayscale=False,
|
||||
enforce_detection=enforce_detection,
|
||||
align=align,
|
||||
expand_percentage=expand_percentage,
|
||||
)
|
||||
except ValueError as err:
|
||||
raise ValueError("Exception while processing img1_path") from err
|
||||
|
||||
# find embeddings for each face
|
||||
for img_obj in img_objs:
|
||||
img_embedding_obj = representation.represent(
|
||||
img_path=img_obj["face"],
|
||||
model_name=model_name,
|
||||
enforce_detection=enforce_detection,
|
||||
detector_backend="skip",
|
||||
align=align,
|
||||
normalization=normalization,
|
||||
)
|
||||
# already extracted face given, safe to access its 1st item
|
||||
img_embedding = img_embedding_obj[0]["embedding"]
|
||||
embeddings.append(img_embedding)
|
||||
facial_areas.append(img_obj["facial_area"])
|
||||
|
||||
return embeddings, facial_areas
|
||||
|
||||
|
||||
def find_cosine_distance(
|
||||
source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list]
|
||||
) -> np.float64:
|
||||
|
@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
import cv2
|
||||
from deepface import DeepFace
|
||||
from deepface.commons.logger import Logger
|
||||
@ -100,3 +101,53 @@ def test_verify_for_preloaded_image():
|
||||
res = DeepFace.verify(img1, img2)
|
||||
assert res["verified"] is True
|
||||
logger.info("✅ test verify for pre-loaded image done")
|
||||
|
||||
|
||||
def test_verify_for_precalculated_embeddings():
|
||||
model_name = "Facenet"
|
||||
|
||||
img1_path = "dataset/img1.jpg"
|
||||
img2_path = "dataset/img2.jpg"
|
||||
|
||||
img1_embedding = DeepFace.represent(img_path=img1_path, model_name=model_name)[0]["embedding"]
|
||||
img2_embedding = DeepFace.represent(img_path=img2_path, model_name=model_name)[0]["embedding"]
|
||||
|
||||
result = DeepFace.verify(
|
||||
img1_path=img1_embedding, img2_path=img2_embedding, model_name=model_name, silent=True
|
||||
)
|
||||
|
||||
assert result["verified"] is True
|
||||
assert result["distance"] < result["threshold"]
|
||||
assert result["model"] == model_name
|
||||
|
||||
logger.info("✅ test verify for pre-calculated embeddings done")
|
||||
|
||||
|
||||
def test_verify_with_precalculated_embeddings_for_incorrect_model():
|
||||
# generate embeddings with VGG (default)
|
||||
img1_path = "dataset/img1.jpg"
|
||||
img2_path = "dataset/img2.jpg"
|
||||
img1_embedding = DeepFace.represent(img_path=img1_path)[0]["embedding"]
|
||||
img2_embedding = DeepFace.represent(img_path=img2_path)[0]["embedding"]
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="embeddings of Facenet should have 128 dimensions, but it has 4096 dimensions input",
|
||||
):
|
||||
_ = DeepFace.verify(
|
||||
img1_path=img1_embedding, img2_path=img2_embedding, model_name="Facenet", silent=True
|
||||
)
|
||||
|
||||
logger.info("✅ test verify with pre-calculated embeddings for incorrect model done")
|
||||
|
||||
|
||||
def test_verify_for_broken_embeddings():
|
||||
img1_embeddings = ["a", "b", "c"]
|
||||
img2_embeddings = [1, 2, 3]
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="When passing img1_path as a list, ensure that all its items are of type float.",
|
||||
):
|
||||
_ = DeepFace.verify(img1_path=img1_embeddings, img2_path=img2_embeddings)
|
||||
logger.info("✅ test verify for broken embeddings content is done")
|
||||
|
Loading…
x
Reference in New Issue
Block a user