Merge pull request #1309 from kremnik/verification

Verification
This commit is contained in:
Sefik Ilkin Serengil 2024-08-16 20:59:36 +01:00 committed by GitHub
commit 9768abc720
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 96 additions and 84 deletions

View File

@ -105,80 +105,6 @@ def verify(
) )
dims = model.output_shape dims = model.output_shape
# extract faces from img1
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:
try:
img1_embeddings, img1_facial_areas = __extract_faces_and_embeddings(
img_path=img1_path,
model_name=model_name,
detector_backend=detector_backend,
enforce_detection=enforce_detection,
align=align,
expand_percentage=expand_percentage,
normalization=normalization,
anti_spoofing=anti_spoofing,
)
except ValueError as err:
raise ValueError("Exception while processing img1_path") from err
# extract faces from img2
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:
try:
img2_embeddings, img2_facial_areas = __extract_faces_and_embeddings(
img_path=img2_path,
model_name=model_name,
detector_backend=detector_backend,
enforce_detection=enforce_detection,
align=align,
expand_percentage=expand_percentage,
normalization=normalization,
anti_spoofing=anti_spoofing,
)
except ValueError as err:
raise ValueError("Exception while processing img2_path") from err
no_facial_area = { no_facial_area = {
"x": None, "x": None,
"y": None, "y": None,
@ -188,21 +114,88 @@ def verify(
"right_eye": None, "right_eye": None,
} }
distances = [] def extract_embeddings_and_facial_areas(
facial_areas = [] img_path: Union[str, np.ndarray, List[float]],
index: int
) -> Tuple[List[List[float]], List[dict]]:
"""
Extracts facial embeddings and corresponding facial areas from an
image or returns pre-calculated embeddings.
Depending on the type of img_path, the function either extracts
facial embeddings from the provided image
(via a path or NumPy array) or verifies that the input is a list of
pre-calculated embeddings and validates them.
Args:
img_path (Union[str, np.ndarray, List[float]]):
- A string representing the file path to an image,
- A NumPy array containing the image data,
- Or a list of pre-calculated embedding values (of type `float`).
index (int): An index value used in error messages and logging
to identify the number of the image.
Returns:
Tuple[List[List[float]], List[dict]]:
- A list containing lists of facial embeddings for each detected face.
- A list of dictionaries where each dictionary contains facial area information.
"""
if isinstance(img_path, list):
# given image is already pre-calculated embedding
if not all(isinstance(dim, float) for dim in img_path):
raise ValueError(
f"When passing img{index}_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."
"Please ensure that embeddings have been calculated"
f" for the {model_name} model."
)
if len(img_path) != dims:
raise ValueError(
f"embeddings of {model_name} should have {dims} dimensions,"
f" but it has {len(img_path)} dimensions input"
)
img_embeddings = [img_path]
img_facial_areas = [no_facial_area]
else:
try:
img_embeddings, img_facial_areas = __extract_faces_and_embeddings(
img_path=img_path,
model_name=model_name,
detector_backend=detector_backend,
enforce_detection=enforce_detection,
align=align,
expand_percentage=expand_percentage,
normalization=normalization,
anti_spoofing=anti_spoofing,
)
except ValueError as err:
raise ValueError(f"Exception while processing img{index}_path") from err
return img_embeddings, img_facial_areas
img1_embeddings, img1_facial_areas = extract_embeddings_and_facial_areas(img1_path, 1)
img2_embeddings, img2_facial_areas = extract_embeddings_and_facial_areas(img2_path, 2)
min_distance, min_idx, min_idy = float("inf"), None, None
for idx, img1_embedding in enumerate(img1_embeddings): for idx, img1_embedding in enumerate(img1_embeddings):
for idy, img2_embedding in enumerate(img2_embeddings): for idy, img2_embedding in enumerate(img2_embeddings):
distance = find_distance(img1_embedding, img2_embedding, distance_metric) distance = find_distance(img1_embedding, img2_embedding, distance_metric)
distances.append(distance) if distance < min_distance:
facial_areas.append( min_distance, min_idx, min_idy = distance, idx, idy
(img1_facial_areas[idx] or no_facial_area, img2_facial_areas[idy] or no_facial_area)
)
# find the face pair with minimum distance # find the face pair with minimum distance
threshold = threshold or find_threshold(model_name, distance_metric) threshold = threshold or find_threshold(model_name, distance_metric)
min_index = np.argmin(distances) distance = float(min_distance)
distance = float(distances[min_index]) # best distance facial_areas = (
facial_areas = facial_areas[min_index] no_facial_area if min_idx is None else img1_facial_areas[min_idx],
no_facial_area if min_idy is None else img2_facial_areas[min_idy],
)
toc = time.time() toc = time.time()

View File

@ -121,6 +121,25 @@ def test_verify_for_precalculated_embeddings():
assert result["verified"] is True assert result["verified"] is True
assert result["distance"] < result["threshold"] assert result["distance"] < result["threshold"]
assert result["model"] == model_name assert result["model"] == model_name
assert result["facial_areas"]["img1"] is not None
assert result["facial_areas"]["img2"] is not None
assert isinstance(result["facial_areas"]["img1"], dict)
assert isinstance(result["facial_areas"]["img2"], dict)
assert "x" in result["facial_areas"]["img1"].keys()
assert "y" in result["facial_areas"]["img1"].keys()
assert "w" in result["facial_areas"]["img1"].keys()
assert "h" in result["facial_areas"]["img1"].keys()
assert "left_eye" in result["facial_areas"]["img1"].keys()
assert "right_eye" in result["facial_areas"]["img1"].keys()
assert "x" in result["facial_areas"]["img2"].keys()
assert "y" in result["facial_areas"]["img2"].keys()
assert "w" in result["facial_areas"]["img2"].keys()
assert "h" in result["facial_areas"]["img2"].keys()
assert "left_eye" in result["facial_areas"]["img2"].keys()
assert "right_eye" in result["facial_areas"]["img2"].keys()
logger.info("✅ test verify for pre-calculated embeddings done") logger.info("✅ test verify for pre-calculated embeddings done")
@ -152,4 +171,4 @@ def test_verify_for_broken_embeddings():
match="When passing img1_path as a list, ensure that all its items are of type float.", 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) _ = DeepFace.verify(img1_path=img1_embeddings, img2_path=img2_embeddings)
logger.info("✅ test verify for broken embeddings content is done") logger.info("✅ test verify for broken embeddings content is done")