From 0ef420bc10c8a863501f23235d1d4c7fa35ef1ba Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 13:01:29 +0000 Subject: [PATCH 01/13] batched inputs in representation --- deepface/models/FacialRecognition.py | 10 +- deepface/modules/representation.py | 138 ++++++++++++++++----------- 2 files changed, 88 insertions(+), 60 deletions(-) diff --git a/deepface/models/FacialRecognition.py b/deepface/models/FacialRecognition.py index a6ee7b5..ae9958e 100644 --- a/deepface/models/FacialRecognition.py +++ b/deepface/models/FacialRecognition.py @@ -18,7 +18,7 @@ class FacialRecognition(ABC): input_shape: Tuple[int, int] output_shape: int - def forward(self, img: np.ndarray) -> List[float]: + def forward(self, img: np.ndarray) -> Union[List[float], List[List[float]]]: if not isinstance(self.model, Model): raise ValueError( "You must overwrite forward method if it is not a keras model," @@ -26,4 +26,10 @@ class FacialRecognition(ABC): ) # model.predict causes memory issue when it is called in a for loop # embedding = model.predict(img, verbose=0)[0].tolist() - return self.model(img, training=False).numpy()[0].tolist() + if img.shape == 4 and img.shape[0] == 1: + img = img[0] + embeddings = self.model(img, training=False).numpy() + if embeddings.shape[0] == 1: + return embeddings[0].tolist() + else: + return embeddings.tolist() diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index d880645..f0fb1db 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -11,7 +11,7 @@ from deepface.models.FacialRecognition import FacialRecognition def represent( - img_path: Union[str, np.ndarray], + img_path: Union[str, np.ndarray, List[Union[str, np.ndarray]]], model_name: str = "VGG-Face", enforce_detection: bool = True, detector_backend: str = "opencv", @@ -25,9 +25,9 @@ def represent( Represent facial images as multi-dimensional vector embeddings. 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, or list): The exact path to the image, a numpy array in BGR format, + a base64 encoded image, or a list of these. If the source image contains multiple faces, + the result will include information for each detected face. model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512, OpenFace, DeepFace, DeepID, Dlib, ArcFace, SFace and GhostFaceNet @@ -70,70 +70,92 @@ def represent( task="facial_recognition", model_name=model_name ) - # --------------------------------- - # we have run pre-process in verification. so, this can be skipped if it is coming from verify. - target_size = model.input_shape - if detector_backend != "skip": - img_objs = detection.extract_faces( - img_path=img_path, - detector_backend=detector_backend, - grayscale=False, - enforce_detection=enforce_detection, - align=align, - expand_percentage=expand_percentage, - anti_spoofing=anti_spoofing, - max_faces=max_faces, - ) - else: # skip - # Try load. If load error, will raise exception internal - img, _ = image_utils.load_image(img_path) + # Handle list of image paths or 4D numpy array + if isinstance(img_path, list): + images = img_path + elif isinstance(img_path, np.ndarray) and img_path.ndim == 4: + images = [img_path[i] for i in range(img_path.shape[0])] + else: + images = [img_path] - if len(img.shape) != 3: - raise ValueError(f"Input img must be 3 dimensional but it is {img.shape}") + batch_images = [] + batch_regions = [] + batch_confidences = [] - # make dummy region and confidence to keep compatibility with `extract_faces` - img_objs = [ - { - "face": img, - "facial_area": {"x": 0, "y": 0, "w": img.shape[0], "h": img.shape[1]}, - "confidence": 0, - } - ] - # --------------------------------- + for single_img_path in images: + # --------------------------------- + # we have run pre-process in verification. so, this can be skipped if it is coming from verify. + target_size = model.input_shape + if detector_backend != "skip": + img_objs = detection.extract_faces( + img_path=single_img_path, + detector_backend=detector_backend, + grayscale=False, + enforce_detection=enforce_detection, + align=align, + expand_percentage=expand_percentage, + anti_spoofing=anti_spoofing, + max_faces=max_faces, + ) + else: # skip + # Try load. If load error, will raise exception internal + img, _ = image_utils.load_image(single_img_path) - if max_faces is not None and max_faces < len(img_objs): - # sort as largest facial areas come first - img_objs = sorted( - img_objs, - key=lambda img_obj: img_obj["facial_area"]["w"] * img_obj["facial_area"]["h"], - reverse=True, - ) - # discard rest of the items - img_objs = img_objs[0:max_faces] + if len(img.shape) != 3: + raise ValueError(f"Input img must be 3 dimensional but it is {img.shape}") - for img_obj in img_objs: - if anti_spoofing is True and img_obj.get("is_real", True) is False: - raise ValueError("Spoof detected in the given image.") - img = img_obj["face"] + # make dummy region and confidence to keep compatibility with `extract_faces` + img_objs = [ + { + "face": img, + "facial_area": {"x": 0, "y": 0, "w": img.shape[0], "h": img.shape[1]}, + "confidence": 0, + } + ] + # --------------------------------- - # bgr to rgb - img = img[:, :, ::-1] + if max_faces is not None and max_faces < len(img_objs): + # sort as largest facial areas come first + img_objs = sorted( + img_objs, + key=lambda img_obj: img_obj["facial_area"]["w"] * img_obj["facial_area"]["h"], + reverse=True, + ) + # discard rest of the items + img_objs = img_objs[0:max_faces] - region = img_obj["facial_area"] - confidence = img_obj["confidence"] + for img_obj in img_objs: + if anti_spoofing is True and img_obj.get("is_real", True) is False: + raise ValueError("Spoof detected in the given image.") + img = img_obj["face"] - # resize to expected shape of ml model - img = preprocessing.resize_image( - img=img, - # thanks to DeepId (!) - target_size=(target_size[1], target_size[0]), - ) + # bgr to rgb + img = img[:, :, ::-1] - # custom normalization - img = preprocessing.normalize_input(img=img, normalization=normalization) + region = img_obj["facial_area"] + confidence = img_obj["confidence"] - embedding = model.forward(img) + # resize to expected shape of ml model + img = preprocessing.resize_image( + img=img, + # thanks to DeepId (!) + target_size=(target_size[1], target_size[0]), + ) + # custom normalization + img = preprocessing.normalize_input(img=img, normalization=normalization) + + batch_images.append(img) + batch_regions.append(region) + batch_confidences.append(confidence) + + # Convert list of images to a numpy array for batch processing + batch_images = np.concat(batch_images) + + # Forward pass through the model for the entire batch + embeddings = model.forward(batch_images) + + for embedding, region, confidence in zip(embeddings, batch_regions, batch_confidences): resp_objs.append( { "embedding": embedding, From 72919d95f416e2e501166a8194542bd764ca5111 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 13:27:13 +0000 Subject: [PATCH 02/13] typo --- deepface/modules/representation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index f0fb1db..a017114 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -150,7 +150,7 @@ def represent( batch_confidences.append(confidence) # Convert list of images to a numpy array for batch processing - batch_images = np.concat(batch_images) + batch_images = np.concatenate(batch_images, axis=0) # Forward pass through the model for the entire batch embeddings = model.forward(batch_images) From d7a985bf128db24761477894b0bedb95190c1f87 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 13:34:50 +0000 Subject: [PATCH 03/13] update DeepFace represent method --- deepface/DeepFace.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 3abe6db..51cc8e2 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -373,7 +373,7 @@ def find( def represent( - img_path: Union[str, np.ndarray, IO[bytes]], + img_path: Union[str, np.ndarray, IO[bytes], List[Union[str, np.ndarray]]], model_name: str = "VGG-Face", enforce_detection: bool = True, detector_backend: str = "opencv", @@ -387,10 +387,12 @@ def represent( Represent facial images as multi-dimensional vector embeddings. 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], or List[Union[str, np.ndarray]]): 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. + the result will include information for each detected face. If a list is provided, + each element should be a string or numpy array representing an image, and the function + will process images in batch. model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512, OpenFace, DeepFace, DeepID, Dlib, ArcFace, SFace and GhostFaceNet From bb134b25d2110886667ac550009168b75c6dad04 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 13:56:09 +0000 Subject: [PATCH 04/13] compatibility --- deepface/modules/representation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index a017114..6bdbf1b 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -154,6 +154,8 @@ def represent( # Forward pass through the model for the entire batch embeddings = model.forward(batch_images) + if len(batch_images) == 1: + embeddings = [embeddings] for embedding, region, confidence in zip(embeddings, batch_regions, batch_confidences): resp_objs.append( From c60152e9a55fb137b313def615ef95d6d828acf6 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 16:58:44 +0000 Subject: [PATCH 05/13] batched represent --- tests/test_represent.py | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_represent.py b/tests/test_represent.py index b33def7..f09834e 100644 --- a/tests/test_represent.py +++ b/tests/test_represent.py @@ -2,6 +2,7 @@ import io import cv2 import pytest +import numpy as np # project dependencies from deepface import DeepFace @@ -81,3 +82,49 @@ def test_max_faces(): max_faces = 1 results = DeepFace.represent(img_path="dataset/couple.jpg", max_faces=max_faces) assert len(results) == max_faces + + +def test_batched_represent(): + img_paths = [ + "dataset/img1.jpg", + "dataset/img2.jpg", + "dataset/img3.jpg", + "dataset/img4.jpg", + "dataset/img5.jpg", + ] + + def _test_for_model(model_name: str): + embedding_objs = DeepFace.represent(img_path=img_paths, model_name=model_name) + assert len(embedding_objs) == len(img_paths) + if model_name == "VGG-Face": + for embedding_obj in embedding_objs: + embedding = embedding_obj["embedding"] + logger.debug(f"Function returned {len(embedding)} dimensional vector") + assert len(embedding) == 4096 + embedding_objs_one_by_one = [ + embedding_obj + for img_path in img_paths + for embedding_obj in DeepFace.represent(img_path=img_path, model_name=model_name) + ] + for embedding_obj_one_by_one, embedding_obj in zip(embedding_objs_one_by_one, embedding_objs): + assert np.allclose( + embedding_obj_one_by_one["embedding"], + embedding_obj["embedding"], + rtol=1e-2, + atol=1e-2 + ) + + for model_name in [ + "VGG-Face", + "Facenet", + "Facenet512", + "OpenFace", + # "DeepFace", + "DeepID", + # "Dlib", + "ArcFace", + "SFace", + "GhostFaceNet" + ]: + _test_for_model(model_name) + logger.info("✅ test batch represent function done") From 8fb70eb43fd4c0002718f661dca8234a18253632 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:00:59 +0000 Subject: [PATCH 06/13] VGGFace batched inference --- deepface/models/facial_recognition/VGGFace.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/deepface/models/facial_recognition/VGGFace.py b/deepface/models/facial_recognition/VGGFace.py index bfcbcad..a3ea2e3 100644 --- a/deepface/models/facial_recognition/VGGFace.py +++ b/deepface/models/facial_recognition/VGGFace.py @@ -57,8 +57,7 @@ class VggFaceClient(FacialRecognition): def forward(self, img: np.ndarray) -> List[float]: """ Generates embeddings using the VGG-Face model. - This method incorporates an additional normalization layer, - necessitating the override of the forward method. + This method incorporates an additional normalization layer. Args: img (np.ndarray): pre-loaded image in BGR @@ -70,8 +69,14 @@ 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 = self.model(img, training=False).numpy()[0].tolist() - embedding = verification.l2_normalize(embedding) + embedding = super().forward(img) + if ( + isinstance(embedding, list) and + isinstance(embedding[0], list) + ): + embedding = verification.l2_normalize(embedding, axis=1) + else: + embedding = verification.l2_normalize(embedding) return embedding.tolist() From 035d3c8ba8bd17d31b76e07d8cb020238cf334b8 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:01:24 +0000 Subject: [PATCH 07/13] SFace pseudo-batched inference --- deepface/models/facial_recognition/SFace.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/deepface/models/facial_recognition/SFace.py b/deepface/models/facial_recognition/SFace.py index f6a01ca..53dfb86 100644 --- a/deepface/models/facial_recognition/SFace.py +++ b/deepface/models/facial_recognition/SFace.py @@ -1,5 +1,5 @@ # built-in dependencies -from typing import Any, List +from typing import Any, List, Union # 3rd party dependencies import numpy as np @@ -27,7 +27,7 @@ class SFaceClient(FacialRecognition): self.input_shape = (112, 112) self.output_shape = 128 - def forward(self, img: np.ndarray) -> List[float]: + def forward(self, img: np.ndarray) -> Union[List[float], List[List[float]]]: """ Find embeddings with SFace model This model necessitates the override of the forward method @@ -37,14 +37,18 @@ class SFaceClient(FacialRecognition): Returns embeddings (list): multi-dimensional vector """ - # return self.model.predict(img)[0].tolist() + input_blob = (img * 255).astype(np.uint8) - # revert the image to original format and preprocess using the model - input_blob = (img[0] * 255).astype(np.uint8) + embeddings = [] + for i in range(input_blob.shape[0]): + embedding = self.model.model.feature(input_blob[i]) + embeddings.append(embedding) + embeddings = np.concatenate(embeddings, axis=0) - embeddings = self.model.model.feature(input_blob) - - return embeddings[0].tolist() + if embeddings.shape[0] == 1: + return embeddings[0].tolist() + else: + return embeddings.tolist() def load_model( From 3a9385fad8cd3027889ac41463339ad64cdaaf85 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:03:00 +0000 Subject: [PATCH 08/13] List->Sequence typing --- deepface/DeepFace.py | 8 ++++---- deepface/modules/representation.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 51cc8e2..9f3ff1e 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -2,7 +2,7 @@ import os import warnings import logging -from typing import Any, Dict, IO, List, Union, Optional +from typing import Any, Dict, IO, List, Union, Optional, Sequence # this has to be set before importing tensorflow os.environ["TF_USE_LEGACY_KERAS"] = "1" @@ -373,7 +373,7 @@ def find( def represent( - img_path: Union[str, np.ndarray, IO[bytes], List[Union[str, np.ndarray]]], + img_path: Union[str, np.ndarray, IO[bytes], Sequence[Union[str, np.ndarray, IO[bytes]]]], model_name: str = "VGG-Face", enforce_detection: bool = True, detector_backend: str = "opencv", @@ -387,10 +387,10 @@ def represent( Represent facial images as multi-dimensional vector embeddings. Args: - img_path (str, np.ndarray, IO[bytes], or List[Union[str, np.ndarray]]): The exact path to the image, a numpy array + img_path (str, np.ndarray, IO[bytes], or Sequence[Union[str, np.ndarray, IO[bytes]]]): 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. If a list is provided, + the result will include information for each detected face. If a sequence is provided, each element should be a string or numpy array representing an image, and the function will process images in batch. diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index 6bdbf1b..d36f5bc 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -1,5 +1,5 @@ # built-in dependencies -from typing import Any, Dict, List, Union, Optional +from typing import Any, Dict, List, Union, Optional, Sequence, IO # 3rd party dependencies import numpy as np @@ -11,7 +11,7 @@ from deepface.models.FacialRecognition import FacialRecognition def represent( - img_path: Union[str, np.ndarray, List[Union[str, np.ndarray]]], + img_path: Union[str, IO[bytes], np.ndarray, Sequence[Union[str, np.ndarray, IO[bytes]]]], model_name: str = "VGG-Face", enforce_detection: bool = True, detector_backend: str = "opencv", @@ -25,8 +25,8 @@ def represent( Represent facial images as multi-dimensional vector embeddings. Args: - img_path (str, np.ndarray, or list): The exact path to the image, a numpy array in BGR format, - a base64 encoded image, or a list of these. If the source image contains multiple faces, + img_path (str, np.ndarray, or Sequence[Union[str, np.ndarray]]): The exact path to the image, a numpy array in BGR format, + a base64 encoded image, or a sequence of these. If the source image contains multiple faces, the result will include information for each detected face. model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512, From a4a579e5eb0913029a9debae3a480aac01012b98 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:21:01 +0000 Subject: [PATCH 09/13] dlib pseudo-batched forward --- deepface/models/facial_recognition/Dlib.py | 42 ++++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/deepface/models/facial_recognition/Dlib.py b/deepface/models/facial_recognition/Dlib.py index 7b29dec..0d58bb8 100644 --- a/deepface/models/facial_recognition/Dlib.py +++ b/deepface/models/facial_recognition/Dlib.py @@ -1,5 +1,5 @@ # built-in dependencies -from typing import List +from typing import List, Union # 3rd party dependencies import numpy as np @@ -26,35 +26,39 @@ class DlibClient(FacialRecognition): self.input_shape = (150, 150) self.output_shape = 128 - def forward(self, img: np.ndarray) -> List[float]: + def forward(self, img: np.ndarray) -> Union[List[float], List[List[float]]]: """ Find embeddings with Dlib model. This model necessitates the override of the forward method because it is not a keras model. Args: - img (np.ndarray): pre-loaded image in BGR + img (np.ndarray): pre-loaded image(s) in BGR Returns - embeddings (list): multi-dimensional vector + embeddings (list of lists or list of floats): multi-dimensional vectors """ - # return self.model.predict(img)[0].tolist() + # Handle single image case + if len(img.shape) == 3: + img = np.expand_dims(img, axis=0) - # extract_faces returns 4 dimensional images - if len(img.shape) == 4: - img = img[0] + embeddings = [] + for single_img in img: + # bgr to rgb + single_img = single_img[:, :, ::-1] # bgr to rgb - # bgr to rgb - img = img[:, :, ::-1] # bgr to rgb + # img is in scale of [0, 1] but expected [0, 255] + if single_img.max() <= 1: + single_img = single_img * 255 - # img is in scale of [0, 1] but expected [0, 255] - if img.max() <= 1: - img = img * 255 + single_img = single_img.astype(np.uint8) - img = img.astype(np.uint8) - - img_representation = self.model.model.compute_face_descriptor(img) - img_representation = np.array(img_representation) - img_representation = np.expand_dims(img_representation, axis=0) - return img_representation[0].tolist() + img_representation = self.model.model.compute_face_descriptor(single_img) + img_representation = np.array(img_representation) + embeddings.append(img_representation.tolist()) + + if len(embeddings) == 1: + return embeddings[0] + else: + return embeddings class DlibResNet: From 8becc975123a63c7398be0353ab2d31353447e9f Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:25:32 +0000 Subject: [PATCH 10/13] dlib true-batched forward --- deepface/models/facial_recognition/Dlib.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/deepface/models/facial_recognition/Dlib.py b/deepface/models/facial_recognition/Dlib.py index 0d58bb8..cb95f08 100644 --- a/deepface/models/facial_recognition/Dlib.py +++ b/deepface/models/facial_recognition/Dlib.py @@ -40,21 +40,17 @@ class DlibClient(FacialRecognition): if len(img.shape) == 3: img = np.expand_dims(img, axis=0) - embeddings = [] - for single_img in img: - # bgr to rgb - single_img = single_img[:, :, ::-1] # bgr to rgb + # bgr to rgb + img = img[:, :, :, ::-1] # bgr to rgb - # img is in scale of [0, 1] but expected [0, 255] - if single_img.max() <= 1: - single_img = single_img * 255 + # img is in scale of [0, 1] but expected [0, 255] + if img.max() <= 1: + img = img * 255 - single_img = single_img.astype(np.uint8) + img = img.astype(np.uint8) - img_representation = self.model.model.compute_face_descriptor(single_img) - img_representation = np.array(img_representation) - embeddings.append(img_representation.tolist()) - + embeddings = self.model.model.compute_face_descriptor(img) + embeddings = [np.array(embedding).tolist() for embedding in embeddings] if len(embeddings) == 1: return embeddings[0] else: From 9e12c92d8a00623019d8587d236acdfa2f7527d2 Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:35:53 +0000 Subject: [PATCH 11/13] refactor test --- tests/test_represent.py | 71 +++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/tests/test_represent.py b/tests/test_represent.py index f09834e..3ac65fa 100644 --- a/tests/test_represent.py +++ b/tests/test_represent.py @@ -3,6 +3,7 @@ import io import cv2 import pytest import numpy as np +import pytest # project dependencies from deepface import DeepFace @@ -84,7 +85,19 @@ def test_max_faces(): assert len(results) == max_faces -def test_batched_represent(): +@pytest.mark.parametrize("model_name", [ + "VGG-Face", + "Facenet", + "Facenet512", + "OpenFace", + "DeepFace", + "DeepID", + "Dlib", + "ArcFace", + "SFace", + "GhostFaceNet" +]) +def test_batched_represent(model_name): img_paths = [ "dataset/img1.jpg", "dataset/img2.jpg", @@ -93,38 +106,26 @@ def test_batched_represent(): "dataset/img5.jpg", ] - def _test_for_model(model_name: str): - embedding_objs = DeepFace.represent(img_path=img_paths, model_name=model_name) - assert len(embedding_objs) == len(img_paths) - if model_name == "VGG-Face": - for embedding_obj in embedding_objs: - embedding = embedding_obj["embedding"] - logger.debug(f"Function returned {len(embedding)} dimensional vector") - assert len(embedding) == 4096 - embedding_objs_one_by_one = [ - embedding_obj - for img_path in img_paths - for embedding_obj in DeepFace.represent(img_path=img_path, model_name=model_name) - ] - for embedding_obj_one_by_one, embedding_obj in zip(embedding_objs_one_by_one, embedding_objs): - assert np.allclose( - embedding_obj_one_by_one["embedding"], - embedding_obj["embedding"], - rtol=1e-2, - atol=1e-2 - ) + embedding_objs = DeepFace.represent(img_path=img_paths, model_name=model_name) + assert len(embedding_objs) == len(img_paths), f"Expected {len(img_paths)} embeddings, got {len(embedding_objs)}" - for model_name in [ - "VGG-Face", - "Facenet", - "Facenet512", - "OpenFace", - # "DeepFace", - "DeepID", - # "Dlib", - "ArcFace", - "SFace", - "GhostFaceNet" - ]: - _test_for_model(model_name) - logger.info("✅ test batch represent function done") + if model_name == "VGG-Face": + for embedding_obj in embedding_objs: + embedding = embedding_obj["embedding"] + logger.debug(f"Function returned {len(embedding)} dimensional vector") + assert len(embedding) == 4096, f"Expected embedding of length 4096, got {len(embedding)}" + + embedding_objs_one_by_one = [ + embedding_obj + for img_path in img_paths + for embedding_obj in DeepFace.represent(img_path=img_path, model_name=model_name) + ] + for embedding_obj_one_by_one, embedding_obj in zip(embedding_objs_one_by_one, embedding_objs): + assert np.allclose( + embedding_obj_one_by_one["embedding"], + embedding_obj["embedding"], + rtol=1e-2, + atol=1e-2 + ), "Embeddings do not match within tolerance" + + logger.info(f"✅ test batch represent function for model {model_name} done") From da03b479d89d2f650b20234c884f7b3da69b76cb Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 17:52:48 +0000 Subject: [PATCH 12/13] remove unnecessary models from the test --- tests/test_represent.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/test_represent.py b/tests/test_represent.py index 3ac65fa..bc83a4e 100644 --- a/tests/test_represent.py +++ b/tests/test_represent.py @@ -88,14 +88,7 @@ def test_max_faces(): @pytest.mark.parametrize("model_name", [ "VGG-Face", "Facenet", - "Facenet512", - "OpenFace", - "DeepFace", - "DeepID", - "Dlib", - "ArcFace", "SFace", - "GhostFaceNet" ]) def test_batched_represent(model_name): img_paths = [ From f1734b23675f397fa7e89334cbaeb8353ba24b4b Mon Sep 17 00:00:00 2001 From: galthran-wq Date: Tue, 11 Feb 2025 20:05:23 +0000 Subject: [PATCH 13/13] linting --- deepface/DeepFace.py | 3 ++- deepface/models/FacialRecognition.py | 3 +-- deepface/models/facial_recognition/Dlib.py | 3 +-- deepface/models/facial_recognition/SFace.py | 3 +-- deepface/models/facial_recognition/VGGFace.py | 2 +- deepface/modules/representation.py | 9 ++++++--- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 9f3ff1e..02dcf3f 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -387,7 +387,8 @@ def represent( Represent facial images as multi-dimensional vector embeddings. Args: - img_path (str, np.ndarray, IO[bytes], or Sequence[Union[str, np.ndarray, IO[bytes]]]): The exact path to the image, a numpy array + img_path (str, np.ndarray, IO[bytes], or Sequence[Union[str, np.ndarray, IO[bytes]]]): + 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. If a sequence is provided, diff --git a/deepface/models/FacialRecognition.py b/deepface/models/FacialRecognition.py index ae9958e..410a033 100644 --- a/deepface/models/FacialRecognition.py +++ b/deepface/models/FacialRecognition.py @@ -31,5 +31,4 @@ class FacialRecognition(ABC): embeddings = self.model(img, training=False).numpy() if embeddings.shape[0] == 1: return embeddings[0].tolist() - else: - return embeddings.tolist() + return embeddings.tolist() diff --git a/deepface/models/facial_recognition/Dlib.py b/deepface/models/facial_recognition/Dlib.py index cb95f08..a2e5ca6 100644 --- a/deepface/models/facial_recognition/Dlib.py +++ b/deepface/models/facial_recognition/Dlib.py @@ -53,8 +53,7 @@ class DlibClient(FacialRecognition): embeddings = [np.array(embedding).tolist() for embedding in embeddings] if len(embeddings) == 1: return embeddings[0] - else: - return embeddings + return embeddings class DlibResNet: diff --git a/deepface/models/facial_recognition/SFace.py b/deepface/models/facial_recognition/SFace.py index 53dfb86..eeebbe3 100644 --- a/deepface/models/facial_recognition/SFace.py +++ b/deepface/models/facial_recognition/SFace.py @@ -47,8 +47,7 @@ class SFaceClient(FacialRecognition): if embeddings.shape[0] == 1: return embeddings[0].tolist() - else: - return embeddings.tolist() + return embeddings.tolist() def load_model( diff --git a/deepface/models/facial_recognition/VGGFace.py b/deepface/models/facial_recognition/VGGFace.py index a3ea2e3..bffd0d6 100644 --- a/deepface/models/facial_recognition/VGGFace.py +++ b/deepface/models/facial_recognition/VGGFace.py @@ -71,7 +71,7 @@ class VggFaceClient(FacialRecognition): # instead we are now calculating it with traditional way not with keras backend embedding = super().forward(img) if ( - isinstance(embedding, list) and + isinstance(embedding, list) and isinstance(embedding[0], list) ): embedding = verification.l2_normalize(embedding, axis=1) diff --git a/deepface/modules/representation.py b/deepface/modules/representation.py index d36f5bc..56eaef2 100644 --- a/deepface/modules/representation.py +++ b/deepface/modules/representation.py @@ -25,8 +25,10 @@ def represent( Represent facial images as multi-dimensional vector embeddings. Args: - img_path (str, np.ndarray, or Sequence[Union[str, np.ndarray]]): The exact path to the image, a numpy array in BGR format, - a base64 encoded image, or a sequence of these. If the source image contains multiple faces, + img_path (str, np.ndarray, or Sequence[Union[str, np.ndarray]]): + The exact path to the image, a numpy array in BGR format, + a base64 encoded image, or a sequence of these. + If the source image contains multiple faces, the result will include information for each detected face. model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512, @@ -84,7 +86,8 @@ def represent( for single_img_path in images: # --------------------------------- - # we have run pre-process in verification. so, this can be skipped if it is coming from verify. + # we have run pre-process in verification. + # so, this can be skipped if it is coming from verify. target_size = model.input_shape if detector_backend != "skip": img_objs = detection.extract_faces(