mirror of
https://github.com/serengil/deepface.git
synced 2025-06-04 02:20:06 +00:00
Merge pull request #1440 from serengil/feat-task-2002-post-batch-changes
post batch changes
This commit is contained in:
commit
c272befce1
@ -166,7 +166,7 @@ def verify(
|
||||
|
||||
|
||||
def analyze(
|
||||
img_path: Union[str, np.ndarray, IO[bytes]],
|
||||
img_path: Union[str, np.ndarray, IO[bytes], List[str], List[np.ndarray], List[IO[bytes]]],
|
||||
actions: Union[tuple, list] = ("emotion", "age", "gender", "race"),
|
||||
enforce_detection: bool = True,
|
||||
detector_backend: str = "opencv",
|
||||
@ -178,7 +178,7 @@ def analyze(
|
||||
"""
|
||||
Analyze facial attributes such as age, gender, emotion, and race in the provided image.
|
||||
Args:
|
||||
img_path (str or np.ndarray or IO[bytes]): The exact path to the image, a numpy array
|
||||
img_path (str, np.ndarray, IO[bytes], list): The exact path to the image, a numpy array
|
||||
in BGR format, a file object that supports at least `.read` and is opened in binary
|
||||
mode, or a base64 encoded image. If the source image contains multiple faces,
|
||||
the result will include information for each detected face.
|
||||
|
@ -11,6 +11,7 @@ else:
|
||||
|
||||
# Notice that all facial recognition models must be inherited from this class
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class FacialRecognition(ABC):
|
||||
model: Union[Model, Any]
|
||||
@ -24,11 +25,24 @@ class FacialRecognition(ABC):
|
||||
"You must overwrite forward method if it is not a keras model,"
|
||||
f"but {self.model_name} not overwritten!"
|
||||
)
|
||||
# model.predict causes memory issue when it is called in a for loop
|
||||
# embedding = model.predict(img, verbose=0)[0].tolist()
|
||||
if img.shape == 4 and img.shape[0] == 1:
|
||||
img = img[0]
|
||||
embeddings = self.model(img, training=False).numpy()
|
||||
|
||||
# predict expexts e.g. (1, 224, 224, 3) shaped inputs
|
||||
if img.ndim == 3:
|
||||
img = np.expand_dims(img, axis=0)
|
||||
|
||||
if img.ndim == 4 and img.shape[0] == 1:
|
||||
# model.predict causes memory issue when it is called in a for loop
|
||||
# embedding = model.predict(img, verbose=0)[0].tolist()
|
||||
embeddings = self.model(img, training=False).numpy()
|
||||
elif img.ndim == 4 and img.shape[0] > 1:
|
||||
embeddings = self.model.predict_on_batch(img)
|
||||
else:
|
||||
raise ValueError(f"Input image must be (1, X, X, 3) shaped but it is {img.shape}")
|
||||
|
||||
assert isinstance(
|
||||
embeddings, np.ndarray
|
||||
), f"Embeddings must be numpy array but it is {type(embeddings)}"
|
||||
|
||||
if embeddings.shape[0] == 1:
|
||||
return embeddings[0].tolist()
|
||||
return embeddings.tolist()
|
||||
|
@ -42,6 +42,7 @@ WEIGHTS_URL = (
|
||||
"https://github.com/serengil/deepface_models/releases/download/v1.0/vgg_face_weights.h5"
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class VggFaceClient(FacialRecognition):
|
||||
"""
|
||||
@ -70,10 +71,7 @@ class VggFaceClient(FacialRecognition):
|
||||
# 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 = super().forward(img)
|
||||
if (
|
||||
isinstance(embedding, list) and
|
||||
isinstance(embedding[0], list)
|
||||
):
|
||||
if isinstance(embedding, list) and len(embedding) > 0 and isinstance(embedding[0], list):
|
||||
embedding = verification.l2_normalize(embedding, axis=1)
|
||||
else:
|
||||
embedding = verification.l2_normalize(embedding)
|
||||
|
@ -1,5 +1,5 @@
|
||||
# built-in dependencies
|
||||
from typing import Any, Dict, List, Union
|
||||
from typing import Any, Dict, List, Union, IO
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
@ -11,7 +11,7 @@ from deepface.models.demography import Gender, Race, Emotion
|
||||
|
||||
|
||||
def analyze(
|
||||
img_path: Union[str, np.ndarray],
|
||||
img_path: Union[str, np.ndarray, IO[bytes], List[str], List[np.ndarray], List[IO[bytes]]],
|
||||
actions: Union[tuple, list] = ("emotion", "age", "gender", "race"),
|
||||
enforce_detection: bool = True,
|
||||
detector_backend: str = "opencv",
|
||||
@ -19,14 +19,14 @@ def analyze(
|
||||
expand_percentage: int = 0,
|
||||
silent: bool = False,
|
||||
anti_spoofing: bool = False,
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> Union[List[Dict[str, Any]], List[List[Dict[str, Any]]]]:
|
||||
"""
|
||||
Analyze facial attributes such as age, gender, emotion, and race in the provided image.
|
||||
|
||||
Args:
|
||||
img_path (str or np.ndarray): The exact path to the image, a numpy array in BGR format,
|
||||
or a base64 encoded image. If the source image contains multiple faces, the result will
|
||||
include information for each detected face.
|
||||
img_path (str, np.ndarray, IO[bytes], list): The exact path to the image,
|
||||
a numpy array in BGR format, or a base64 encoded image. If the source image
|
||||
contains multiple faces, the result will include information for each detected face.
|
||||
|
||||
actions (tuple): Attributes to analyze. The default is ('age', 'gender', 'emotion', 'race').
|
||||
You can exclude some of these attributes from the analysis if needed.
|
||||
@ -100,28 +100,28 @@ def analyze(
|
||||
- 'white': Confidence score for White ethnicity.
|
||||
"""
|
||||
|
||||
if isinstance(img_path, np.ndarray) and len(img_path.shape) == 4:
|
||||
# Received 4-D array, which means image batch.
|
||||
# Check batch dimension and process each image separately.
|
||||
if img_path.shape[0] > 1:
|
||||
batch_resp_obj = []
|
||||
# Execute analysis for each image in the batch.
|
||||
for single_img in img_path:
|
||||
# Call the analyze function for each image in the batch.
|
||||
resp_obj = analyze(
|
||||
img_path=single_img,
|
||||
actions=actions,
|
||||
enforce_detection=enforce_detection,
|
||||
detector_backend=detector_backend,
|
||||
align=align,
|
||||
expand_percentage=expand_percentage,
|
||||
silent=silent,
|
||||
anti_spoofing=anti_spoofing,
|
||||
)
|
||||
# batch input
|
||||
if (isinstance(img_path, np.ndarray) and img_path.ndim == 4 and img_path.shape[0] > 1) or (
|
||||
isinstance(img_path, list)
|
||||
):
|
||||
batch_resp_obj = []
|
||||
# Execute analysis for each image in the batch.
|
||||
for single_img in img_path:
|
||||
# Call the analyze function for each image in the batch.
|
||||
resp_obj = analyze(
|
||||
img_path=single_img,
|
||||
actions=actions,
|
||||
enforce_detection=enforce_detection,
|
||||
detector_backend=detector_backend,
|
||||
align=align,
|
||||
expand_percentage=expand_percentage,
|
||||
silent=silent,
|
||||
anti_spoofing=anti_spoofing,
|
||||
)
|
||||
|
||||
# Append the response object to the batch response list.
|
||||
batch_resp_obj.append(resp_obj)
|
||||
return batch_resp_obj
|
||||
# Append the response object to the batch response list.
|
||||
batch_resp_obj.append(resp_obj)
|
||||
return batch_resp_obj
|
||||
|
||||
# if actions is passed as tuple with single item, interestingly it becomes str here
|
||||
if isinstance(actions, str):
|
||||
|
@ -1,5 +1,6 @@
|
||||
# built-in dependencies
|
||||
from typing import Any, Dict, List, Union, Optional, Sequence, IO
|
||||
from collections import defaultdict
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
@ -161,17 +162,16 @@ def represent(
|
||||
# Forward pass through the model for the entire batch
|
||||
embeddings = model.forward(batch_images)
|
||||
|
||||
for idx in range(0, len(images)):
|
||||
resp_obj = []
|
||||
for idy, batch_index in enumerate(batch_indexes):
|
||||
if idx == batch_index:
|
||||
resp_obj.append(
|
||||
{
|
||||
"embedding": embeddings if len(batch_images) == 1 else embeddings[idy],
|
||||
"facial_area": batch_regions[idy],
|
||||
"face_confidence": batch_confidences[idy],
|
||||
}
|
||||
)
|
||||
resp_objs.append(resp_obj)
|
||||
resp_objs_dict = defaultdict(list)
|
||||
for idy, batch_index in enumerate(batch_indexes):
|
||||
resp_objs_dict[batch_index].append(
|
||||
{
|
||||
"embedding": embeddings if len(batch_images) == 1 else embeddings[idy],
|
||||
"facial_area": batch_regions[idy],
|
||||
"face_confidence": batch_confidences[idy],
|
||||
}
|
||||
)
|
||||
|
||||
resp_objs = [resp_objs_dict[idx] for idx in range(len(images))]
|
||||
|
||||
return resp_objs[0] if len(images) == 1 else resp_objs
|
||||
|
@ -16,9 +16,13 @@ detectors = ["opencv", "mtcnn"]
|
||||
def test_standard_analyze():
|
||||
img = "dataset/img4.jpg"
|
||||
demography_objs = DeepFace.analyze(img, silent=True)
|
||||
|
||||
# return type should be list of dict for non batch input
|
||||
assert isinstance(demography_objs, list)
|
||||
|
||||
for demography in demography_objs:
|
||||
assert isinstance(demography, dict)
|
||||
logger.debug(demography)
|
||||
assert type(demography) == dict
|
||||
assert demography["age"] > 20 and demography["age"] < 40
|
||||
assert demography["dominant_gender"] == "Woman"
|
||||
logger.info("✅ test standard analyze done")
|
||||
@ -99,9 +103,13 @@ def test_analyze_for_some_actions():
|
||||
def test_analyze_for_preloaded_image():
|
||||
img = cv2.imread("dataset/img1.jpg")
|
||||
resp_objs = DeepFace.analyze(img, silent=True)
|
||||
|
||||
# return type should be list of dict for non batch input
|
||||
assert isinstance(resp_objs, list)
|
||||
|
||||
for resp_obj in resp_objs:
|
||||
assert isinstance(resp_obj, dict)
|
||||
logger.debug(resp_obj)
|
||||
assert type(resp_obj) == dict
|
||||
assert resp_obj["age"] > 20 and resp_obj["age"] < 40
|
||||
assert resp_obj["dominant_gender"] == "Woman"
|
||||
|
||||
@ -127,7 +135,10 @@ def test_analyze_for_different_detectors():
|
||||
results = DeepFace.analyze(
|
||||
img_path, actions=("gender",), detector_backend=detector, enforce_detection=False
|
||||
)
|
||||
# return type should be list of dict for non batch input
|
||||
assert isinstance(results, list)
|
||||
for result in results:
|
||||
assert isinstance(result, dict)
|
||||
logger.debug(result)
|
||||
|
||||
# validate keys
|
||||
@ -138,13 +149,63 @@ def test_analyze_for_different_detectors():
|
||||
]
|
||||
|
||||
# validate probabilities
|
||||
assert type(result) == dict
|
||||
if result["dominant_gender"] == "Man":
|
||||
assert result["gender"]["Man"] > result["gender"]["Woman"]
|
||||
else:
|
||||
assert result["gender"]["Man"] < result["gender"]["Woman"]
|
||||
|
||||
|
||||
def test_analyze_for_batched_image_as_list_of_string():
|
||||
img_paths = ["dataset/img1.jpg", "dataset/img2.jpg", "dataset/couple.jpg"]
|
||||
expected_faces = [1, 1, 2]
|
||||
|
||||
demography_batch = DeepFace.analyze(img_path=img_paths, silent=True)
|
||||
# return type should be list of list of dict for batch input
|
||||
assert isinstance(demography_batch, list)
|
||||
|
||||
# 3 image in batch, so 3 demography objects
|
||||
assert len(demography_batch) == len(img_paths)
|
||||
|
||||
for idx, demography_objs in enumerate(demography_batch):
|
||||
assert isinstance(demography_objs, list)
|
||||
assert len(demography_objs) == expected_faces[idx]
|
||||
for demography_obj in demography_objs:
|
||||
assert isinstance(demography_obj, dict)
|
||||
|
||||
assert demography_obj["age"] > 20 and demography_obj["age"] < 40
|
||||
assert demography_obj["dominant_gender"] in ["Woman", "Man"]
|
||||
|
||||
logger.info("✅ test analyze for batched image as list of string done")
|
||||
|
||||
|
||||
def test_analyze_for_batched_image_as_list_of_numpy():
|
||||
img_paths = ["dataset/img1.jpg", "dataset/img2.jpg", "dataset/couple.jpg"]
|
||||
expected_faces = [1, 1, 2]
|
||||
|
||||
imgs = []
|
||||
for img_path in img_paths:
|
||||
img = cv2.imread(img_path)
|
||||
imgs.append(img)
|
||||
|
||||
demography_batch = DeepFace.analyze(img_path=imgs, silent=True)
|
||||
# return type should be list of list of dict for batch input
|
||||
assert isinstance(demography_batch, list)
|
||||
|
||||
# 3 image in batch, so 3 demography objects
|
||||
assert len(demography_batch) == len(img_paths)
|
||||
|
||||
for idx, demography_objs in enumerate(demography_batch):
|
||||
assert isinstance(demography_objs, list)
|
||||
assert len(demography_objs) == expected_faces[idx]
|
||||
for demography_obj in demography_objs:
|
||||
assert isinstance(demography_obj, dict)
|
||||
|
||||
assert demography_obj["age"] > 20 and demography_obj["age"] < 40
|
||||
assert demography_obj["dominant_gender"] in ["Woman", "Man"]
|
||||
|
||||
logger.info("✅ test analyze for batched image as list of numpy done")
|
||||
|
||||
|
||||
def test_analyze_for_numpy_batched_image():
|
||||
img1_path = "dataset/img4.jpg"
|
||||
img2_path = "dataset/couple.jpg"
|
||||
@ -163,14 +224,20 @@ def test_analyze_for_numpy_batched_image():
|
||||
assert img.shape[0] == 2 # Check batch size.
|
||||
|
||||
demography_batch = DeepFace.analyze(img, silent=True)
|
||||
# return type should be list of list of dict for batch input
|
||||
|
||||
assert isinstance(demography_batch, list)
|
||||
|
||||
# 2 image in batch, so 2 demography objects.
|
||||
assert len(demography_batch) == 2
|
||||
|
||||
for i, demography_objs in enumerate(demography_batch):
|
||||
assert isinstance(demography_objs, list)
|
||||
|
||||
assert len(demography_objs) == expected_num_faces[i]
|
||||
for demography in demography_objs: # Iterate over faces
|
||||
assert isinstance(demography, dict) # Check type
|
||||
assert isinstance(demography, dict)
|
||||
|
||||
assert demography["age"] > 20 and demography["age"] < 40
|
||||
assert demography["dominant_gender"] in ["Woman", "Man"]
|
||||
|
||||
|
@ -183,6 +183,8 @@ def test_batched_represent_for_list_input(model_name):
|
||||
assert len(single_embedding_objs) == len(batched_embedding_objs[idx])
|
||||
|
||||
for alpha, beta in zip(single_embedding_objs, batched_embedding_objs[idx]):
|
||||
assert isinstance(alpha, dict)
|
||||
assert isinstance(beta, dict)
|
||||
assert np.allclose(
|
||||
alpha["embedding"], beta["embedding"], rtol=1e-2, atol=1e-2
|
||||
), "Embeddings do not match within tolerance"
|
||||
|
Loading…
x
Reference in New Issue
Block a user