Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verification #1309

Merged
merged 4 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 76 additions & 83 deletions deepface/modules/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,80 +105,6 @@ def verify(
)
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 = {
"x": None,
"y": None,
Expand All @@ -188,21 +114,88 @@ def verify(
"right_eye": None,
}

distances = []
facial_areas = []
def extract_embeddings_and_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."
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st image term should not be static. Would you please get it from index arg?

"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"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we mention the index here as well?

)

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 idy, img2_embedding in enumerate(img2_embeddings):
distance = find_distance(img1_embedding, img2_embedding, distance_metric)
distances.append(distance)
facial_areas.append(
(img1_facial_areas[idx] or no_facial_area, img2_facial_areas[idy] or no_facial_area)
)
if distance < min_distance:
min_distance, min_idx, min_idy = distance, idx, idy

# find the face pair with minimum distance
threshold = threshold or find_threshold(model_name, distance_metric)
min_index = np.argmin(distances)
distance = float(distances[min_index]) # best distance
facial_areas = facial_areas[min_index]
distance = float(min_distance)
facial_areas = (
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()

Expand Down
21 changes: 20 additions & 1 deletion tests/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ def test_verify_for_precalculated_embeddings():
assert result["verified"] is True
assert result["distance"] < result["threshold"]
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")

Expand Down Expand Up @@ -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.",
):
_ = 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")