Skip to content

Commit 9768abc

Browse files
authored
Merge pull request #1309 from kremnik/verification
Verification
2 parents 8365f14 + 3416aa9 commit 9768abc

File tree

2 files changed

+96
-84
lines changed

2 files changed

+96
-84
lines changed

deepface/modules/verification.py

Lines changed: 76 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -105,80 +105,6 @@ def verify(
105105
)
106106
dims = model.output_shape
107107

108-
# extract faces from img1
109-
if isinstance(img1_path, list):
110-
# given image is already pre-calculated embedding
111-
if not all(isinstance(dim, float) for dim in img1_path):
112-
raise ValueError(
113-
"When passing img1_path as a list, ensure that all its items are of type float."
114-
)
115-
116-
if silent is False:
117-
logger.warn(
118-
"You passed 1st image as pre-calculated embeddings."
119-
f"Please ensure that embeddings have been calculated for the {model_name} model."
120-
)
121-
122-
if len(img1_path) != dims:
123-
raise ValueError(
124-
f"embeddings of {model_name} should have {dims} dimensions,"
125-
f" but it has {len(img1_path)} dimensions input"
126-
)
127-
128-
img1_embeddings = [img1_path]
129-
img1_facial_areas = [None]
130-
else:
131-
try:
132-
img1_embeddings, img1_facial_areas = __extract_faces_and_embeddings(
133-
img_path=img1_path,
134-
model_name=model_name,
135-
detector_backend=detector_backend,
136-
enforce_detection=enforce_detection,
137-
align=align,
138-
expand_percentage=expand_percentage,
139-
normalization=normalization,
140-
anti_spoofing=anti_spoofing,
141-
)
142-
except ValueError as err:
143-
raise ValueError("Exception while processing img1_path") from err
144-
145-
# extract faces from img2
146-
if isinstance(img2_path, list):
147-
# given image is already pre-calculated embedding
148-
if not all(isinstance(dim, float) for dim in img2_path):
149-
raise ValueError(
150-
"When passing img2_path as a list, ensure that all its items are of type float."
151-
)
152-
153-
if silent is False:
154-
logger.warn(
155-
"You passed 2nd image as pre-calculated embeddings."
156-
f"Please ensure that embeddings have been calculated for the {model_name} model."
157-
)
158-
159-
if len(img2_path) != dims:
160-
raise ValueError(
161-
f"embeddings of {model_name} should have {dims} dimensions,"
162-
f" but it has {len(img2_path)} dimensions input"
163-
)
164-
165-
img2_embeddings = [img2_path]
166-
img2_facial_areas = [None]
167-
else:
168-
try:
169-
img2_embeddings, img2_facial_areas = __extract_faces_and_embeddings(
170-
img_path=img2_path,
171-
model_name=model_name,
172-
detector_backend=detector_backend,
173-
enforce_detection=enforce_detection,
174-
align=align,
175-
expand_percentage=expand_percentage,
176-
normalization=normalization,
177-
anti_spoofing=anti_spoofing,
178-
)
179-
except ValueError as err:
180-
raise ValueError("Exception while processing img2_path") from err
181-
182108
no_facial_area = {
183109
"x": None,
184110
"y": None,
@@ -188,21 +114,88 @@ def verify(
188114
"right_eye": None,
189115
}
190116

191-
distances = []
192-
facial_areas = []
117+
def extract_embeddings_and_facial_areas(
118+
img_path: Union[str, np.ndarray, List[float]],
119+
index: int
120+
) -> Tuple[List[List[float]], List[dict]]:
121+
"""
122+
Extracts facial embeddings and corresponding facial areas from an
123+
image or returns pre-calculated embeddings.
124+
125+
Depending on the type of img_path, the function either extracts
126+
facial embeddings from the provided image
127+
(via a path or NumPy array) or verifies that the input is a list of
128+
pre-calculated embeddings and validates them.
129+
130+
Args:
131+
img_path (Union[str, np.ndarray, List[float]]):
132+
- A string representing the file path to an image,
133+
- A NumPy array containing the image data,
134+
- Or a list of pre-calculated embedding values (of type `float`).
135+
index (int): An index value used in error messages and logging
136+
to identify the number of the image.
137+
138+
Returns:
139+
Tuple[List[List[float]], List[dict]]:
140+
- A list containing lists of facial embeddings for each detected face.
141+
- A list of dictionaries where each dictionary contains facial area information.
142+
"""
143+
if isinstance(img_path, list):
144+
# given image is already pre-calculated embedding
145+
if not all(isinstance(dim, float) for dim in img_path):
146+
raise ValueError(
147+
f"When passing img{index}_path as a list,"
148+
" ensure that all its items are of type float."
149+
)
150+
151+
if silent is False:
152+
logger.warn(
153+
"You passed 1st image as pre-calculated embeddings."
154+
"Please ensure that embeddings have been calculated"
155+
f" for the {model_name} model."
156+
)
157+
158+
if len(img_path) != dims:
159+
raise ValueError(
160+
f"embeddings of {model_name} should have {dims} dimensions,"
161+
f" but it has {len(img_path)} dimensions input"
162+
)
163+
164+
img_embeddings = [img_path]
165+
img_facial_areas = [no_facial_area]
166+
else:
167+
try:
168+
img_embeddings, img_facial_areas = __extract_faces_and_embeddings(
169+
img_path=img_path,
170+
model_name=model_name,
171+
detector_backend=detector_backend,
172+
enforce_detection=enforce_detection,
173+
align=align,
174+
expand_percentage=expand_percentage,
175+
normalization=normalization,
176+
anti_spoofing=anti_spoofing,
177+
)
178+
except ValueError as err:
179+
raise ValueError(f"Exception while processing img{index}_path") from err
180+
return img_embeddings, img_facial_areas
181+
182+
img1_embeddings, img1_facial_areas = extract_embeddings_and_facial_areas(img1_path, 1)
183+
img2_embeddings, img2_facial_areas = extract_embeddings_and_facial_areas(img2_path, 2)
184+
185+
min_distance, min_idx, min_idy = float("inf"), None, None
193186
for idx, img1_embedding in enumerate(img1_embeddings):
194187
for idy, img2_embedding in enumerate(img2_embeddings):
195188
distance = find_distance(img1_embedding, img2_embedding, distance_metric)
196-
distances.append(distance)
197-
facial_areas.append(
198-
(img1_facial_areas[idx] or no_facial_area, img2_facial_areas[idy] or no_facial_area)
199-
)
189+
if distance < min_distance:
190+
min_distance, min_idx, min_idy = distance, idx, idy
200191

201192
# find the face pair with minimum distance
202193
threshold = threshold or find_threshold(model_name, distance_metric)
203-
min_index = np.argmin(distances)
204-
distance = float(distances[min_index]) # best distance
205-
facial_areas = facial_areas[min_index]
194+
distance = float(min_distance)
195+
facial_areas = (
196+
no_facial_area if min_idx is None else img1_facial_areas[min_idx],
197+
no_facial_area if min_idy is None else img2_facial_areas[min_idy],
198+
)
206199

207200
toc = time.time()
208201

tests/test_verify.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,25 @@ def test_verify_for_precalculated_embeddings():
121121
assert result["verified"] is True
122122
assert result["distance"] < result["threshold"]
123123
assert result["model"] == model_name
124+
assert result["facial_areas"]["img1"] is not None
125+
assert result["facial_areas"]["img2"] is not None
126+
127+
assert isinstance(result["facial_areas"]["img1"], dict)
128+
assert isinstance(result["facial_areas"]["img2"], dict)
129+
130+
assert "x" in result["facial_areas"]["img1"].keys()
131+
assert "y" in result["facial_areas"]["img1"].keys()
132+
assert "w" in result["facial_areas"]["img1"].keys()
133+
assert "h" in result["facial_areas"]["img1"].keys()
134+
assert "left_eye" in result["facial_areas"]["img1"].keys()
135+
assert "right_eye" in result["facial_areas"]["img1"].keys()
136+
137+
assert "x" in result["facial_areas"]["img2"].keys()
138+
assert "y" in result["facial_areas"]["img2"].keys()
139+
assert "w" in result["facial_areas"]["img2"].keys()
140+
assert "h" in result["facial_areas"]["img2"].keys()
141+
assert "left_eye" in result["facial_areas"]["img2"].keys()
142+
assert "right_eye" in result["facial_areas"]["img2"].keys()
124143

125144
logger.info("✅ test verify for pre-calculated embeddings done")
126145

@@ -152,4 +171,4 @@ def test_verify_for_broken_embeddings():
152171
match="When passing img1_path as a list, ensure that all its items are of type float.",
153172
):
154173
_ = DeepFace.verify(img1_path=img1_embeddings, img2_path=img2_embeddings)
155-
logger.info("✅ test verify for broken embeddings content is done")
174+
logger.info("✅ test verify for broken embeddings content is done")

0 commit comments

Comments
 (0)