From 2d5b56ec8608b5872dc199a54458f52f426e8d30 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sat, 10 Feb 2024 21:10:16 +0000 Subject: [PATCH] some cosmetic changes --- retinaface/RetinaFace.py | 133 +++++++++++------------------- retinaface/commons/postprocess.py | 54 ++++++++++-- tests/test_expand_face_area.py | 4 +- 3 files changed, 98 insertions(+), 93 deletions(-) diff --git a/retinaface/RetinaFace.py b/retinaface/RetinaFace.py index bc2d487..f8c109b 100644 --- a/retinaface/RetinaFace.py +++ b/retinaface/RetinaFace.py @@ -1,7 +1,7 @@ import os import warnings import logging -from typing import Union, Any, Optional, Dict, Tuple +from typing import Union, Any, Optional, Dict import numpy as np import tensorflow as tf @@ -243,89 +243,52 @@ def extract_faces( "Align first functionality can be applied only if there is single face in the input" ) - if isinstance(obj, dict): - for _, identity in obj.items(): - facial_area = identity["facial_area"] - - x = facial_area[0] - y = facial_area[1] - w = facial_area[2] - h = facial_area[3] - - # expand the facial area to be extracted and stay within img.shape limits - x1 = max(0, x - int((w * expand_face_area) / 100)) # expand left - y1 = max(0, y - int((h * expand_face_area) / 100)) # expand top - x2 = min(img.shape[1], w + int((w * expand_face_area) / 100)) # expand right - y2 = min(img.shape[0], h + int((h * expand_face_area) / 100)) # expand bottom - - if align_first is False or (align_first is True and len(obj) > 1): - facial_img = img[y1:y2, x1:x2] - else: - facial_img = img.copy() - - if align is True: - landmarks = identity["landmarks"] - left_eye = landmarks["left_eye"] - right_eye = landmarks["right_eye"] - nose = landmarks["nose"] - # mouth_right = landmarks["mouth_right"] - # mouth_left = landmarks["mouth_left"] - facial_img, rotate_angle, rotate_direction = postprocess.alignment_procedure( - facial_img, right_eye, left_eye, nose - ) - - if align_first is True and len(obj) == 1: - facial_area = rotate_facial_area( - facial_area, rotate_angle, rotate_direction, img.shape - ) - # Expand the facial area to be extracted and stay within img.shape limits - x1 = max(0, facial_area[0] - int((facial_area[2] * expand_face_area) / 100)) - y1 = max(0, facial_area[1] - int((facial_area[3] * expand_face_area) / 100)) - x2 = min( - img.shape[1], facial_area[2] + int((facial_area[2] * expand_face_area) / 100) - ) - y2 = min( - img.shape[0], facial_area[3] + int((facial_area[3] * expand_face_area) / 100) - ) - facial_img = facial_img[y1:y2, x1:x2] - - resp.append(facial_img[:, :, ::-1]) - - return resp - -def rotate_facial_area(facial_area: Tuple[int, int, int, int], angle: float, direction: - int, size: Tuple[int, int]) -> Tuple[int, int, int, int]: - """ - Rotate the facial area around its center. - - Args: - facial_area (tuple of int): Representing the (x1, y1, x2, y2) of the facial area. - angle (float): Angle of rotation in degrees. - direction (int): Direction of rotation (-1 for clockwise, 1 for counterclockwise). - size (tuple of int): Tuple representing the size of the image (width, height). - - Returns: - tuple of int: Representing the new coordinates (x1, y1, x2, y2) of the rotated facial area. - """ - # Angle in radians - angle = angle * np.pi / 180 - - # Translate the facial area to the center of the image - x = (facial_area[0] + facial_area[2]) / 2 - size[1] / 2 - y = (facial_area[1] + facial_area[3]) / 2 - size[0] / 2 - - # Rotate the facial area - x_new = x * np.cos(angle) + y * direction * np.sin(angle) - y_new = -x * direction * np.sin(angle) + y * np.cos(angle) - - # Translate the facial area back to the original position - x_new = x_new + size[1] / 2 - y_new = y_new + size[0] / 2 + if not isinstance(obj, dict): + return resp - # Calculate the new facial area - x1 = x_new - (facial_area[2] - facial_area[0]) / 2 - y1 = y_new - (facial_area[3] - facial_area[1]) / 2 - x2 = x_new + (facial_area[2] - facial_area[0]) / 2 - y2 = y_new + (facial_area[3] - facial_area[1]) / 2 + for _, identity in obj.items(): + facial_area = identity["facial_area"] + rotate_angle = 0 + rotate_direction = 1 + + x = facial_area[0] + y = facial_area[1] + w = facial_area[2] + h = facial_area[3] + + # expand the facial area to be extracted and stay within img.shape limits + x1 = max(0, x - int((w * expand_face_area) / 100)) # expand left + y1 = max(0, y - int((h * expand_face_area) / 100)) # expand top + x2 = min(img.shape[1], w + int((w * expand_face_area) / 100)) # expand right + y2 = min(img.shape[0], h + int((h * expand_face_area) / 100)) # expand bottom + + if align_first is False or (align_first is True and len(obj) > 1): + facial_img = img[y1:y2, x1:x2] + else: + facial_img = img.copy() + + if align is True: + landmarks = identity["landmarks"] + left_eye = landmarks["left_eye"] + right_eye = landmarks["right_eye"] + nose = landmarks["nose"] + # mouth_right = landmarks["mouth_right"] + # mouth_left = landmarks["mouth_left"] + facial_img, rotate_angle, rotate_direction = postprocess.alignment_procedure( + facial_img, right_eye, left_eye, nose + ) + + if align_first is True and len(obj) == 1: + facial_area = postprocess.rotate_facial_area( + facial_area, rotate_angle, rotate_direction, img.shape + ) + # Expand the facial area to be extracted and stay within img.shape limits + x1 = max(0, facial_area[0] - int((facial_area[2] * expand_face_area) / 100)) + y1 = max(0, facial_area[1] - int((facial_area[3] * expand_face_area) / 100)) + x2 = min(img.shape[1], facial_area[2] + int((facial_area[2] * expand_face_area) / 100)) + y2 = min(img.shape[0], facial_area[3] + int((facial_area[3] * expand_face_area) / 100)) + facial_img = facial_img[y1:y2, x1:x2] + + resp.append(facial_img[:, :, ::-1]) - return (int(x1), int(y1), int(x2), int(y2)) + return resp diff --git a/retinaface/commons/postprocess.py b/retinaface/commons/postprocess.py index c150137..c223508 100644 --- a/retinaface/commons/postprocess.py +++ b/retinaface/commons/postprocess.py @@ -1,5 +1,5 @@ import math -from typing import Union +from typing import Union, Tuple import numpy as np from PIL import Image @@ -7,7 +7,7 @@ # pylint: disable=unused-argument -def findEuclideanDistance( +def find_euclidean_distance( source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list] ) -> float: """ @@ -30,7 +30,9 @@ def findEuclideanDistance( return euclidean_distance -def alignment_procedure(img: np.ndarray, left_eye: tuple, right_eye: tuple, nose: tuple): +def alignment_procedure( + img: np.ndarray, left_eye: tuple, right_eye: tuple, nose: tuple +) -> Tuple[np.ndarray, float, int]: """ Alignma given face with respect to the left and right eye coordinates. Left eye is the eye appearing on the left (right eye of the person). Left top point is (0, 0) @@ -58,9 +60,9 @@ def alignment_procedure(img: np.ndarray, left_eye: tuple, right_eye: tuple, nose # ----------------------- # find length of triangle edges - a = findEuclideanDistance(np.array(left_eye), np.array(point_3rd)) - b = findEuclideanDistance(np.array(right_eye), np.array(point_3rd)) - c = findEuclideanDistance(np.array(right_eye), np.array(left_eye)) + a = find_euclidean_distance(np.array(left_eye), np.array(point_3rd)) + b = find_euclidean_distance(np.array(right_eye), np.array(point_3rd)) + c = find_euclidean_distance(np.array(right_eye), np.array(left_eye)) # ----------------------- # apply cosine rule @@ -91,6 +93,46 @@ def alignment_procedure(img: np.ndarray, left_eye: tuple, right_eye: tuple, nose return img, angle, direction +def rotate_facial_area( + facial_area: Tuple[int, int, int, int], angle: float, direction: int, size: Tuple[int, int] +) -> Tuple[int, int, int, int]: + """ + Rotate the facial area around its center. + + Args: + facial_area (tuple of int): Representing the (x1, y1, x2, y2) of the facial area. + angle (float): Angle of rotation in degrees. + direction (int): Direction of rotation (-1 for clockwise, 1 for counterclockwise). + size (tuple of int): Tuple representing the size of the image (width, height). + + Returns: + rotated_facial_area (tuple of int): Representing the new coordinates + (x1, y1, x2, y2) of the rotated facial area. + """ + # Angle in radians + angle = angle * np.pi / 180 + + # Translate the facial area to the center of the image + x = (facial_area[0] + facial_area[2]) / 2 - size[1] / 2 + y = (facial_area[1] + facial_area[3]) / 2 - size[0] / 2 + + # Rotate the facial area + x_new = x * np.cos(angle) + y * direction * np.sin(angle) + y_new = -x * direction * np.sin(angle) + y * np.cos(angle) + + # Translate the facial area back to the original position + x_new = x_new + size[1] / 2 + y_new = y_new + size[0] / 2 + + # Calculate the new facial area + x1 = x_new - (facial_area[2] - facial_area[0]) / 2 + y1 = y_new - (facial_area[3] - facial_area[1]) / 2 + x2 = x_new + (facial_area[2] - facial_area[0]) / 2 + y2 = y_new + (facial_area[3] - facial_area[1]) / 2 + + return (int(x1), int(y1), int(x2), int(y2)) + + def bbox_pred(boxes, box_deltas): """ This function is copied from the following code snippet: diff --git a/tests/test_expand_face_area.py b/tests/test_expand_face_area.py index 8e18161..a6fb928 100644 --- a/tests/test_expand_face_area.py +++ b/tests/test_expand_face_area.py @@ -25,8 +25,8 @@ def test_expand_face_area(): landmarks1 = obj1["face_1"]["landmarks"] landmarks2 = obj2["face_1"]["landmarks"] - distance1 = postprocess.findEuclideanDistance(landmarks1["right_eye"], landmarks1["left_eye"]) - distance2 = postprocess.findEuclideanDistance(landmarks2["right_eye"], landmarks2["left_eye"]) + distance1 = postprocess.find_euclidean_distance(landmarks1["right_eye"], landmarks1["left_eye"]) + distance2 = postprocess.find_euclidean_distance(landmarks2["right_eye"], landmarks2["left_eye"]) # 2nd one's expand ratio is higher. so, it should be smaller. assert distance2 < distance1