This commit is contained in:
galthran-wq 2025-02-16 14:09:05 +00:00
parent 8bfdcf139a
commit 7e59cdf05d
7 changed files with 167 additions and 104 deletions

View File

@ -11,13 +11,14 @@ class Detector(ABC):
@abstractmethod @abstractmethod
def detect_faces( def detect_faces(
self, self,
imgs: Union[np.ndarray, List[np.ndarray]] img: Union[np.ndarray, List[np.ndarray]]
) -> Union[List["FacialAreaRegion"], List[List["FacialAreaRegion"]]]: ) -> Union[List["FacialAreaRegion"], List[List["FacialAreaRegion"]]]:
""" """
Interface for detect and align faces in a batch of images Interface for detect and align faces in a batch of images
Args: Args:
imgs (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those img (Union[np.ndarray, List[np.ndarray]]):
Pre-loaded image as numpy array or a list of those
Returns: Returns:
results (Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]): results (Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]):

View File

@ -19,8 +19,7 @@ class MtCnnClient(Detector):
def detect_faces( def detect_faces(
self, self,
img: Union[np.ndarray, img: Union[np.ndarray, List[np.ndarray]]
List[np.ndarray]]
) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: ) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]:
""" """
Detect and align faces with mtcnn for a list of images Detect and align faces with mtcnn for a list of images
@ -31,7 +30,8 @@ class MtCnnClient(Detector):
Returns: Returns:
results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]):
A list of FacialAreaRegion objects for a single image or a list of lists of FacialAreaRegion objects for each image A list of FacialAreaRegion objects for a single image
or a list of lists of FacialAreaRegion objects for each image
""" """
if not isinstance(img, list): if not isinstance(img, list):

View File

@ -29,35 +29,42 @@ class OpenCvClient(Detector):
detector["eye_detector"] = self.__build_cascade("haarcascade_eye") detector["eye_detector"] = self.__build_cascade("haarcascade_eye")
return detector return detector
def detect_faces(self, imgs: Union[np.ndarray, List[np.ndarray]]) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: def detect_faces(
self,
img: Union[np.ndarray, List[np.ndarray]]
) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]:
""" """
Detect and align face with opencv Detect and align face with opencv
Args: Args:
imgs (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those img (Union[np.ndarray, List[np.ndarray]]):
Pre-loaded image as numpy array or a list of those
Returns: Returns:
results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): A list or a list of lists of FacialAreaRegion objects results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]):
A list or a list of lists of FacialAreaRegion objects
""" """
if isinstance(imgs, np.ndarray): if isinstance(img, np.ndarray):
imgs = [imgs] imgs = [img]
else:
imgs = img
batch_results = [] batch_results = []
for img in imgs: for single_img in imgs:
resp = [] resp = []
detected_face = None detected_face = None
faces = [] faces = []
try: try:
faces, _, scores = self.model["face_detector"].detectMultiScale3( faces, _, scores = self.model["face_detector"].detectMultiScale3(
img, 1.1, 10, outputRejectLevels=True single_img, 1.1, 10, outputRejectLevels=True
) )
except: except:
pass pass
if len(faces) > 0: if len(faces) > 0:
for (x, y, w, h), confidence in zip(faces, scores): for (x, y, w, h), confidence in zip(faces, scores):
detected_face = img[int(y):int(y + h), int(x):int(x + w)] detected_face = single_img[int(y):int(y + h), int(x):int(x + w)]
left_eye, right_eye = self.find_eyes(img=detected_face) left_eye, right_eye = self.find_eyes(img=detected_face)
if left_eye is not None: if left_eye is not None:

View File

@ -13,15 +13,20 @@ class RetinaFaceClient(Detector):
def __init__(self): def __init__(self):
self.model = rf.build_model() self.model = rf.build_model()
def detect_faces(self, img: Union[np.ndarray, List[np.ndarray]]) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]: def detect_faces(
self,
img: Union[np.ndarray, List[np.ndarray]]
) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]:
""" """
Detect and align faces with retinaface in an image or a list of images Detect and align faces with retinaface in a batch of images
Args: Args:
img (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those img (Union[np.ndarray, List[np.ndarray]]):
Pre-loaded image as numpy array or a list of those
Returns: Returns:
results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]): A list or a list of lists of FacialAreaRegion objects results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]):
A list or a list of lists of FacialAreaRegion objects
""" """
if isinstance(img, np.ndarray): if isinstance(img, np.ndarray):
imgs = [img] imgs = [img]
@ -30,9 +35,9 @@ class RetinaFaceClient(Detector):
batch_results = [] batch_results = []
for img in imgs: for single_img in imgs:
resp = [] resp = []
obj = rf.detect_faces(img, model=self.model, threshold=0.9) obj = rf.detect_faces(single_img, model=self.model, threshold=0.9)
if isinstance(obj, dict): if isinstance(obj, dict):
for face_idx in obj.keys(): for face_idx in obj.keys():

View File

@ -1,5 +1,5 @@
# built-in dependencies # built-in dependencies
from typing import List from typing import List, Union
from enum import IntEnum from enum import IntEnum
# 3rd party dependencies # 3rd party dependencies
@ -54,28 +54,38 @@ class SsdClient(Detector):
return {"face_detector": face_detector, "opencv_module": OpenCv.OpenCvClient()} return {"face_detector": face_detector, "opencv_module": OpenCv.OpenCvClient()}
def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]: def detect_faces(
self,
img: Union[np.ndarray, List[np.ndarray]]
) -> Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]:
""" """
Detect and align face with ssd Detect and align faces with ssd in a batch of images
Args: Args:
img (np.ndarray): pre-loaded image as numpy array img (Union[np.ndarray, List[np.ndarray]]):
Pre-loaded image as numpy array or a list of those
Returns: Returns:
results (List[FacialAreaRegion]): A list of FacialAreaRegion objects results (Union[List[FacialAreaRegion], List[List[FacialAreaRegion]]]):
A list or a list of lists of FacialAreaRegion objects
""" """
if isinstance(img, np.ndarray):
imgs = [img]
else:
imgs = img
batch_results = []
for single_img in imgs:
# Because cv2.dnn.blobFromImage expects CV_8U (8-bit unsigned integer) values # Because cv2.dnn.blobFromImage expects CV_8U (8-bit unsigned integer) values
if img.dtype != np.uint8: if single_img.dtype != np.uint8:
img = img.astype(np.uint8) single_img = single_img.astype(np.uint8)
opencv_module: OpenCv.OpenCvClient = self.model["opencv_module"] opencv_module: OpenCv.OpenCvClient = self.model["opencv_module"]
target_size = (300, 300) target_size = (300, 300)
original_size = single_img.shape
original_size = img.shape current_img = cv2.resize(single_img, target_size)
current_img = cv2.resize(img, target_size)
aspect_ratio_x = original_size[1] / target_size[1] aspect_ratio_x = original_size[1] / target_size[1]
aspect_ratio_y = original_size[0] / target_size[0] aspect_ratio_y = original_size[0] / target_size[0]
@ -112,7 +122,7 @@ class SsdClient(Detector):
for face in faces: for face in faces:
confidence = float(face[ssd_labels.confidence]) confidence = float(face[ssd_labels.confidence])
x, y, w, h = map(int, face[margins]) x, y, w, h = map(int, face[margins])
detected_face = img[y : y + h, x : x + w] detected_face = single_img[y : y + h, x : x + w]
left_eye, right_eye = opencv_module.find_eyes(detected_face) left_eye, right_eye = opencv_module.find_eyes(detected_face)
@ -133,4 +143,7 @@ class SsdClient(Detector):
confidence=confidence, confidence=confidence,
) )
resp.append(facial_area) resp.append(facial_area)
return resp
batch_results.append(resp)
return batch_results if len(batch_results) > 1 else batch_results[0]

View File

@ -1,6 +1,6 @@
# built-in dependencies # built-in dependencies
import os import os
from typing import List, Any, Union, Tuple from typing import List, Any, Union
from enum import Enum from enum import Enum
# 3rd party dependencies # 3rd party dependencies
@ -62,25 +62,30 @@ class YoloDetectorClient(Detector):
# Return face_detector # Return face_detector
return YOLO(weight_file) return YOLO(weight_file)
def detect_faces(self, imgs: Union[np.ndarray, List[np.ndarray]]) -> Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]: def detect_faces(
self,
img: Union[np.ndarray, List[np.ndarray]]
) -> Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]:
""" """
Detect and align faces in an image or a list of images with yolo Detect and align faces in a batch of images with yolo
Args: Args:
imgs (Union[np.ndarray, List[np.ndarray]]): pre-loaded image as numpy array or a list of those img (Union[np.ndarray, List[np.ndarray]]):
Pre-loaded image as numpy array or a list of those
Returns: Returns:
results (Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]): results (Union[List[List[FacialAreaRegion]], List[FacialAreaRegion]]):
A list of lists of FacialAreaRegion objects for each image or a list of FacialAreaRegion objects A list of lists of FacialAreaRegion objects
for each image or a list of FacialAreaRegion objects
""" """
if not isinstance(imgs, list): if not isinstance(img, list):
imgs = [imgs] img = [img]
all_results = [] all_results = []
# Detect faces for all images # Detect faces for all images
results_list = self.model.predict( results_list = self.model.predict(
imgs, img,
verbose=False, verbose=False,
show=False, show=False,
conf=float(os.getenv("YOLO_MIN_DETECTION_CONFIDENCE", "0.25")), conf=float(os.getenv("YOLO_MIN_DETECTION_CONFIDENCE", "0.25")),
@ -113,9 +118,16 @@ class YoloDetectorClient(Detector):
# eyes are list of float, need to cast them tuple of int # eyes are list of float, need to cast them tuple of int
# Ensure eyes are tuples of exactly two integers or None # Ensure eyes are tuples of exactly two integers or None
left_eye = tuple(map(int, left_eye[:2])) if left_eye and len(left_eye) == 2 else None left_eye = (
right_eye = tuple(map(int, right_eye[:2])) if right_eye and len(right_eye) == 2 else None tuple(map(int, left_eye[:2]))
if left_eye and len(left_eye) == 2
else None
)
right_eye = (
tuple(map(int, right_eye[:2]))
if right_eye and len(right_eye) == 2
else None
)
x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h) x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h)
facial_area = FacialAreaRegion( facial_area = FacialAreaRegion(
x=x, x=x,

View File

@ -34,8 +34,9 @@ def extract_faces(
Extract faces from a given image or list of images Extract faces from a given image or list of images
Args: Args:
img_paths (List[str or np.ndarray or IO[bytes]] or str or np.ndarray or IO[bytes]): Path(s) to the image(s). Accepts exact image path img_paths (List[str or np.ndarray or IO[bytes]] or str or np.ndarray or IO[bytes]):
as a string, numpy array (BGR), a file object that supports at least `.read` and is Path(s) to the image(s) as a string,
numpy array (BGR), a file object that supports at least `.read` and is
opened in binary mode, or base64 encoded images. opened in binary mode, or base64 encoded images.
detector_backend (string): face detector backend. Options: 'opencv', 'retinaface', detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
@ -148,7 +149,10 @@ def extract_faces(
elif color_face == "gray": elif color_face == "gray":
current_img = cv2.cvtColor(current_img, cv2.COLOR_BGR2GRAY) current_img = cv2.cvtColor(current_img, cv2.COLOR_BGR2GRAY)
else: else:
raise ValueError(f"The color_face can be rgb, bgr or gray, but it is {color_face}.") raise ValueError(
f"The color_face can be rgb, bgr or gray, "
f"but it is {color_face}."
)
if normalize_face: if normalize_face:
current_img = current_img / 255 # normalize input in [0, 1] current_img = current_img / 255 # normalize input in [0, 1]
@ -184,7 +188,10 @@ def extract_faces(
if anti_spoofing is True: if anti_spoofing is True:
antispoof_model = modeling.build_model(task="spoofing", model_name="Fasnet") antispoof_model = modeling.build_model(task="spoofing", model_name="Fasnet")
is_real, antispoof_score = antispoof_model.analyze(img=img, facial_area=(x, y, w, h)) is_real, antispoof_score = antispoof_model.analyze(
img=img,
facial_area=(x, y, w, h)
)
resp_obj["is_real"] = is_real resp_obj["is_real"] = is_real
resp_obj["antispoof_score"] = antispoof_score resp_obj["antispoof_score"] = antispoof_score
@ -229,7 +236,15 @@ def detect_faces(
if detector_backend == "skip": if detector_backend == "skip":
all_face_objs = [ all_face_objs = [
[DetectedFace(img=single_img, facial_area=FacialAreaRegion(x=0, y=0, w=single_img.shape[1], h=single_img.shape[0]), confidence=0)] [
DetectedFace(
img=single_img,
facial_area=FacialAreaRegion(
x=0, y=0, w=single_img.shape[1], h=single_img.shape[0]
),
confidence=0,
)
]
for single_img in img for single_img in img
] ]
if len(img) == 1: if len(img) == 1:
@ -280,7 +295,17 @@ def detect_faces(
all_facial_areas = [all_facial_areas] all_facial_areas = [all_facial_areas]
all_detected_faces = [] all_detected_faces = []
for single_img, facial_areas, width_border, height_border in zip(preprocessed_images, all_facial_areas, width_borders, height_borders): for (
single_img,
facial_areas,
width_border,
height_border
) in zip(
preprocessed_images,
all_facial_areas,
width_borders,
height_borders
):
if not isinstance(facial_areas, list): if not isinstance(facial_areas, list):
facial_areas = [facial_areas] facial_areas = [facial_areas]