TKK_E32232028/.venv/lib/python3.10/site-packages/retinaface/commons/postprocess.py

280 lines
9.3 KiB
Python

import math
from typing import Union, Tuple
import numpy as np
from PIL import Image
# pylint: disable=unused-argument
def find_euclidean_distance(
source_representation: Union[np.ndarray, list], test_representation: Union[np.ndarray, list]
) -> float:
"""
Find euclidean distance between 2 vectors
Args:
source_representation (numpy array or list)
test_representation (numpy array or list)
Returns
distance
"""
if isinstance(source_representation, list):
source_representation = np.array(source_representation)
if isinstance(test_representation, list):
test_representation = np.array(test_representation)
euclidean_distance = source_representation - test_representation
euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
euclidean_distance = np.sqrt(euclidean_distance)
return euclidean_distance
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)
Args:
img (numpy array): given image
left_eye (tuple): left eye coordinates.
Left eye is appearing on the left of image (right eye of the person)
right_eye (tuple): right eye coordinates.
Right eye is appearing on the right of image (left eye of the person)
nose (tuple): coordinates of nose
"""
left_eye_x, left_eye_y = left_eye
right_eye_x, right_eye_y = right_eye
# -----------------------
# find rotation direction
if left_eye_y > right_eye_y:
point_3rd = (right_eye_x, left_eye_y)
direction = -1 # rotate same direction to clock
else:
point_3rd = (left_eye_x, right_eye_y)
direction = 1 # rotate inverse direction of clock
# -----------------------
# find length of triangle edges
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
if b != 0 and c != 0: # this multiplication causes division by zero in cos_a calculation
cos_a = (b * b + c * c - a * a) / (2 * b * c)
# PR15: While mathematically cos_a must be within the closed range [-1.0, 1.0],
# floating point errors would produce cases violating this
# In fact, we did come across a case where cos_a took the value 1.0000000169176173
# which lead to a NaN from the following np.arccos step
cos_a = min(1.0, max(-1.0, cos_a))
angle = np.arccos(cos_a) # angle in radian
angle = (angle * 180) / math.pi # radian to degree
# -----------------------
# rotate base image
if direction == -1:
angle = 90 - angle
img = Image.fromarray(img)
img = np.array(img.rotate(direction * angle))
else:
angle = 0.0 # Dummy value for undefined angle
# -----------------------
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
height, weight = size
# Translate the facial area to the center of the image
x = (facial_area[0] + facial_area[2]) / 2 - weight / 2
y = (facial_area[1] + facial_area[3]) / 2 - height / 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 + weight / 2
y_new = y_new + height / 2
# Calculate projected coordinates after alignment
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
# validate projected coordinates are in image's boundaries
x1 = max(int(x1), 0)
y1 = max(int(y1), 0)
x2 = min(int(x2), weight)
y2 = min(int(y2), height)
return (x1, y1, x2, y2)
def bbox_pred(boxes, box_deltas):
"""
This function is copied from the following code snippet:
https://github.com/StanislasBertrand/RetinaFace-tf2/blob/master/retinaface.py
"""
if boxes.shape[0] == 0:
return np.zeros((0, box_deltas.shape[1]))
boxes = boxes.astype(float, copy=False)
widths = boxes[:, 2] - boxes[:, 0] + 1.0
heights = boxes[:, 3] - boxes[:, 1] + 1.0
ctr_x = boxes[:, 0] + 0.5 * (widths - 1.0)
ctr_y = boxes[:, 1] + 0.5 * (heights - 1.0)
dx = box_deltas[:, 0:1]
dy = box_deltas[:, 1:2]
dw = box_deltas[:, 2:3]
dh = box_deltas[:, 3:4]
pred_ctr_x = dx * widths[:, np.newaxis] + ctr_x[:, np.newaxis]
pred_ctr_y = dy * heights[:, np.newaxis] + ctr_y[:, np.newaxis]
pred_w = np.exp(dw) * widths[:, np.newaxis]
pred_h = np.exp(dh) * heights[:, np.newaxis]
pred_boxes = np.zeros(box_deltas.shape)
# x1
pred_boxes[:, 0:1] = pred_ctr_x - 0.5 * (pred_w - 1.0)
# y1
pred_boxes[:, 1:2] = pred_ctr_y - 0.5 * (pred_h - 1.0)
# x2
pred_boxes[:, 2:3] = pred_ctr_x + 0.5 * (pred_w - 1.0)
# y2
pred_boxes[:, 3:4] = pred_ctr_y + 0.5 * (pred_h - 1.0)
if box_deltas.shape[1] > 4:
pred_boxes[:, 4:] = box_deltas[:, 4:]
return pred_boxes
def landmark_pred(boxes, landmark_deltas):
"""
This function copied from the following code snippet
https://github.com/StanislasBertrand/RetinaFace-tf2/blob/master/retinaface.py
"""
if boxes.shape[0] == 0:
return np.zeros((0, landmark_deltas.shape[1]))
boxes = boxes.astype(float, copy=False)
widths = boxes[:, 2] - boxes[:, 0] + 1.0
heights = boxes[:, 3] - boxes[:, 1] + 1.0
ctr_x = boxes[:, 0] + 0.5 * (widths - 1.0)
ctr_y = boxes[:, 1] + 0.5 * (heights - 1.0)
pred = landmark_deltas.copy()
for i in range(5):
pred[:, i, 0] = landmark_deltas[:, i, 0] * widths + ctr_x
pred[:, i, 1] = landmark_deltas[:, i, 1] * heights + ctr_y
return pred
def clip_boxes(boxes, im_shape):
"""
This function copied from rcnn module of retinaface-tf2 project
https://github.com/StanislasBertrand/RetinaFace-tf2/blob/master/rcnn/processing/bbox_transform.py
"""
# x1 >= 0
boxes[:, 0::4] = np.maximum(np.minimum(boxes[:, 0::4], im_shape[1] - 1), 0)
# y1 >= 0
boxes[:, 1::4] = np.maximum(np.minimum(boxes[:, 1::4], im_shape[0] - 1), 0)
# x2 < im_shape[1]
boxes[:, 2::4] = np.maximum(np.minimum(boxes[:, 2::4], im_shape[1] - 1), 0)
# y2 < im_shape[0]
boxes[:, 3::4] = np.maximum(np.minimum(boxes[:, 3::4], im_shape[0] - 1), 0)
return boxes
def anchors_plane(height, width, stride, base_anchors):
"""
This function is mainly based on the following code snippet
https://github.com/StanislasBertrand/RetinaFace-tf2/blob/master/rcnn/cython/anchors.pyx
"""
A = base_anchors.shape[0]
c_0_2 = np.tile(np.arange(0, width)[np.newaxis, :, np.newaxis, np.newaxis], (height, 1, A, 1))
c_1_3 = np.tile(np.arange(0, height)[:, np.newaxis, np.newaxis, np.newaxis], (1, width, A, 1))
all_anchors = np.concatenate([c_0_2, c_1_3, c_0_2, c_1_3], axis=-1) * stride + np.tile(
base_anchors[np.newaxis, np.newaxis, :, :], (height, width, 1, 1)
)
return all_anchors
def cpu_nms(dets, threshold):
"""
This function is mainly based on the following code snippet
https://github.com/StanislasBertrand/RetinaFace-tf2/blob/master/rcnn/cython/cpu_nms.pyx
Fast R-CNN by Ross Girshick
"""
x1 = dets[:, 0]
y1 = dets[:, 1]
x2 = dets[:, 2]
y2 = dets[:, 3]
scores = dets[:, 4]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort()[::-1]
ndets = dets.shape[0]
suppressed = np.zeros((ndets), dtype=int)
keep = []
for _i in range(ndets):
i = order[_i]
if suppressed[i] == 1:
continue
keep.append(i)
ix1 = x1[i]
iy1 = y1[i]
ix2 = x2[i]
iy2 = y2[i]
iarea = areas[i]
for _j in range(_i + 1, ndets):
j = order[_j]
if suppressed[j] == 1:
continue
xx1 = max(ix1, x1[j])
yy1 = max(iy1, y1[j])
xx2 = min(ix2, x2[j])
yy2 = min(iy2, y2[j])
w = max(0.0, xx2 - xx1 + 1)
h = max(0.0, yy2 - yy1 + 1)
inter = w * h
ovr = inter / (iarea + areas[j] - inter)
if ovr >= threshold:
suppressed[j] = 1
return keep