Spaces:
Sleeping
Sleeping
Commit
·
64f11f5
1
Parent(s):
143c297
Refactor `Aligner` to support multiple cropped images at once
Browse files- face_detection/helper.py +1 -1
- face_recognition/aligner.py +102 -69
- face_recognition/inference.py +1 -1
face_detection/helper.py
CHANGED
|
@@ -17,7 +17,7 @@ def get_crops(img,objs_found,aligner=None,resize:tuple=None):
|
|
| 17 |
|
| 18 |
crop=img[ymin:ymax,xmin:xmax]
|
| 19 |
if aligner is not None:
|
| 20 |
-
crop=aligner.
|
| 21 |
if crop is None: continue
|
| 22 |
if resize is not None:
|
| 23 |
crop=square_maker(crop)
|
|
|
|
| 17 |
|
| 18 |
crop=img[ymin:ymax,xmin:xmax]
|
| 19 |
if aligner is not None:
|
| 20 |
+
crop=aligner.align((crop,))
|
| 21 |
if crop is None: continue
|
| 22 |
if resize is not None:
|
| 23 |
crop=square_maker(crop)
|
face_recognition/aligner.py
CHANGED
|
@@ -9,72 +9,105 @@ import shutil
|
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
-
class
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
+
class Aligner(mp.solutions.face_mesh.FaceMesh):
|
| 13 |
+
"""Inherits from MediaPipe Face Mesh."""
|
| 14 |
+
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
static_image_mode: bool = True,
|
| 18 |
+
max_num_faces: int = 1,
|
| 19 |
+
refine_landmarks: bool = False,
|
| 20 |
+
min_detection_confidence: float = 0.5,
|
| 21 |
+
min_tracking_confidence: float = 0.5
|
| 22 |
+
):
|
| 23 |
+
"""Initializes a Image Aligner object.
|
| 24 |
+
|
| 25 |
+
Unlike MediaPipe Face Mesh we set `static_image_mode` to `True` as we only
|
| 26 |
+
require image manipulation.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
static_image_mode: Whether to treat the input images as a batch of static
|
| 30 |
+
and possibly unrelated images, or a video stream.
|
| 31 |
+
max_num_faces: Maximum number of faces to detect.
|
| 32 |
+
refine_landmarks: Whether to further refine the landmark coordinates
|
| 33 |
+
around the eyes and lips, and output additional landmarks around the
|
| 34 |
+
irises. Default to False.
|
| 35 |
+
min_detection_confidence: Minimum confidence value ([0.0, 1.0]) for face
|
| 36 |
+
detection to be considered successful.
|
| 37 |
+
min_tracking_confidence: Minimum confidence value ([0.0, 1.0]) for the
|
| 38 |
+
face landmarks to be considered tracked successfully.
|
| 39 |
+
"""
|
| 40 |
+
super().__init__(
|
| 41 |
+
static_image_mode=static_image_mode,
|
| 42 |
+
max_num_faces=max_num_faces,
|
| 43 |
+
refine_landmarks=refine_landmarks,
|
| 44 |
+
min_detection_confidence=min_detection_confidence,
|
| 45 |
+
min_tracking_confidence=min_tracking_confidence
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
self._left_eye_idx = list(
|
| 49 |
+
set(itertools.chain(*mp.solutions.face_mesh.FACEMESH_LEFT_EYE))
|
| 50 |
+
)[7]
|
| 51 |
+
self._right_eye_idx = list(
|
| 52 |
+
set(itertools.chain(*mp.solutions.face_mesh.FACEMESH_RIGHT_EYE))
|
| 53 |
+
)[4]
|
| 54 |
+
|
| 55 |
+
def _aligner(self, /, img: np.ndarray) -> np.ndarray:
|
| 56 |
+
"""Private helper function to align the given image parallel to the x-axis.
|
| 57 |
+
|
| 58 |
+
This function creates a line between the left and right eye points and tries
|
| 59 |
+
to align that line parallel to the x-axis, thus aligning the complete image.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
img: Image to align parallel to the x-axis.
|
| 63 |
+
"""
|
| 64 |
+
fm = self.process(img)
|
| 65 |
+
if fm is None:
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
points = []
|
| 69 |
+
h, w, _ = img.shape
|
| 70 |
+
|
| 71 |
+
face_landmarks = fm.multi_face_landmarks[0]
|
| 72 |
+
|
| 73 |
+
le_x_coord = int(
|
| 74 |
+
np.clip(face_landmarks.landmark[self._left_eye_idx].x * w, 0, w)
|
| 75 |
+
)
|
| 76 |
+
le_y_coord = int(
|
| 77 |
+
np.clip(face_landmarks.landmark[self._left_eye_idx].y * h, 0, h)
|
| 78 |
+
)
|
| 79 |
+
p0 = np.array((le_x_coord, le_y_coord), dtype=np.float64)
|
| 80 |
+
|
| 81 |
+
re_x_coord = int(
|
| 82 |
+
np.clip(face_landmarks.landmark[self._right_eye_idx].x * w, 0, w)
|
| 83 |
+
)
|
| 84 |
+
re_y_coord = int(
|
| 85 |
+
np.clip(face_landmarks.landmark[self._right_eye_idx].y * h, 0, h)
|
| 86 |
+
)
|
| 87 |
+
p1 = np.array((re_x_coord, re_y_coord), dtype=np.float64)
|
| 88 |
+
|
| 89 |
+
h = abs(p0[1] - p1[1])
|
| 90 |
+
w = abs(p0[0] - p1[0])
|
| 91 |
+
|
| 92 |
+
# Get the angle between the x-axis and the line joining the eye points.
|
| 93 |
+
theta = np.arctan(h / w)
|
| 94 |
+
|
| 95 |
+
angle = (theta * 180) / np.pi
|
| 96 |
+
|
| 97 |
+
if p0[0] < p1[0]:
|
| 98 |
+
direction = 1 if p0[1] < p1[1] else -1
|
| 99 |
+
else:
|
| 100 |
+
direction = 1 if p1[1] < p0[1] else -1
|
| 101 |
+
|
| 102 |
+
angle *= direction
|
| 103 |
+
|
| 104 |
+
img = PIL.fromarray(img)
|
| 105 |
+
return np.array(img.rotate(angle))
|
| 106 |
+
|
| 107 |
+
def align(self, /, imgs: tuple[np.ndarray]) -> list[np.ndarray]:
|
| 108 |
+
"""Aligns the given set of images parallel to the x-axis on the image plane.
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
imgs: Images to align parallel to the x-axis on the image place.
|
| 112 |
+
"""
|
| 113 |
+
return [self._aligner(img) for img in imgs]
|
face_recognition/inference.py
CHANGED
|
@@ -139,7 +139,7 @@ class face_recognition:
|
|
| 139 |
|
| 140 |
crop_img=img[ymin:ymax,xmin:xmax]
|
| 141 |
crop_img=cv2.resize(crop_img,[self.model_config.input_size,self.model_config.input_size])
|
| 142 |
-
crop_img=self.aligner.
|
| 143 |
|
| 144 |
|
| 145 |
if crop_img is not None:
|
|
|
|
| 139 |
|
| 140 |
crop_img=img[ymin:ymax,xmin:xmax]
|
| 141 |
crop_img=cv2.resize(crop_img,[self.model_config.input_size,self.model_config.input_size])
|
| 142 |
+
crop_img=self.aligner.align((crop_img,))
|
| 143 |
|
| 144 |
|
| 145 |
if crop_img is not None:
|