import cv2 import itertools import numpy as np import mediapipe as mp import matplotlib.pyplot as plt import PIL import os import shutil class Aligner(mp.solutions.face_mesh.FaceMesh): """Inherits from MediaPipe Face Mesh.""" def __init__( self, static_image_mode: bool = True, max_num_faces: int = 1, refine_landmarks: bool = False, min_detection_confidence: float = 0.5, min_tracking_confidence: float = 0.5 ): """Initializes a Image Aligner object. Unlike MediaPipe Face Mesh we set `static_image_mode` to `True` as we only require image manipulation. Args: static_image_mode: Whether to treat the input images as a batch of static and possibly unrelated images, or a video stream. max_num_faces: Maximum number of faces to detect. refine_landmarks: Whether to further refine the landmark coordinates around the eyes and lips, and output additional landmarks around the irises. Default to False. min_detection_confidence: Minimum confidence value ([0.0, 1.0]) for face detection to be considered successful. min_tracking_confidence: Minimum confidence value ([0.0, 1.0]) for the face landmarks to be considered tracked successfully. """ super().__init__( static_image_mode=static_image_mode, max_num_faces=max_num_faces, refine_landmarks=refine_landmarks, min_detection_confidence=min_detection_confidence, min_tracking_confidence=min_tracking_confidence ) self._left_eye_idx = list( set(itertools.chain(*mp.solutions.face_mesh.FACEMESH_LEFT_EYE)) )[7] self._right_eye_idx = list( set(itertools.chain(*mp.solutions.face_mesh.FACEMESH_RIGHT_EYE)) )[4] def _aligner(self, /, img: np.ndarray) -> np.ndarray: """Private helper function to align the given image parallel to the x-axis. This function creates a line between the left and right eye points and tries to align that line parallel to the x-axis, thus aligning the complete image. Args: img: Image to align parallel to the x-axis. """ fm = self.process(img) if fm is None: return None points = [] h, w, _ = img.shape face_landmarks = fm.multi_face_landmarks[0] le_x_coord = int( np.clip(face_landmarks.landmark[self._left_eye_idx].x * w, 0, w) ) le_y_coord = int( np.clip(face_landmarks.landmark[self._left_eye_idx].y * h, 0, h) ) p0 = np.array((le_x_coord, le_y_coord), dtype=np.float64) re_x_coord = int( np.clip(face_landmarks.landmark[self._right_eye_idx].x * w, 0, w) ) re_y_coord = int( np.clip(face_landmarks.landmark[self._right_eye_idx].y * h, 0, h) ) p1 = np.array((re_x_coord, re_y_coord), dtype=np.float64) h = abs(p0[1] - p1[1]) w = abs(p0[0] - p1[0]) # Get the angle between the x-axis and the line joining the eye points. theta = np.arctan(h / w) angle = (theta * 180) / np.pi if p0[0] < p1[0]: direction = 1 if p0[1] < p1[1] else -1 else: direction = 1 if p1[1] < p0[1] else -1 angle *= direction img = PIL.Image.fromarray(img) return np.array(img.rotate(angle)) def align(self, /, imgs: tuple[np.ndarray]) -> list[np.ndarray]: """Aligns the given set of images parallel to the x-axis on the image plane. Args: imgs: Images to align parallel to the x-axis on the image place. """ return [self._aligner(img) for img in imgs]