|
from typing import Mapping |
|
|
|
import mediapipe as mp |
|
import numpy |
|
from PIL import Image |
|
|
|
|
|
mp_drawing = mp.solutions.drawing_utils |
|
mp_drawing_styles = mp.solutions.drawing_styles |
|
mp_face_detection = mp.solutions.face_detection |
|
mp_face_mesh = mp.solutions.face_mesh |
|
mp_face_connections = mp.solutions.face_mesh_connections.FACEMESH_TESSELATION |
|
mp_hand_connections = mp.solutions.hands_connections.HAND_CONNECTIONS |
|
mp_body_connections = mp.solutions.pose_connections.POSE_CONNECTIONS |
|
|
|
DrawingSpec = mp.solutions.drawing_styles.DrawingSpec |
|
PoseLandmark = mp.solutions.drawing_styles.PoseLandmark |
|
|
|
f_thick = 2 |
|
f_rad = 1 |
|
right_iris_draw = DrawingSpec(color=(10, 200, 250), thickness=f_thick, circle_radius=f_rad) |
|
right_eye_draw = DrawingSpec(color=(10, 200, 180), thickness=f_thick, circle_radius=f_rad) |
|
right_eyebrow_draw = DrawingSpec(color=(10, 220, 180), thickness=f_thick, circle_radius=f_rad) |
|
left_iris_draw = DrawingSpec(color=(250, 200, 10), thickness=f_thick, circle_radius=f_rad) |
|
left_eye_draw = DrawingSpec(color=(180, 200, 10), thickness=f_thick, circle_radius=f_rad) |
|
left_eyebrow_draw = DrawingSpec(color=(180, 220, 10), thickness=f_thick, circle_radius=f_rad) |
|
mouth_draw = DrawingSpec(color=(10, 180, 10), thickness=f_thick, circle_radius=f_rad) |
|
head_draw = DrawingSpec(color=(10, 200, 10), thickness=f_thick, circle_radius=f_rad) |
|
|
|
|
|
face_connection_spec = {} |
|
for edge in mp_face_mesh.FACEMESH_FACE_OVAL: |
|
face_connection_spec[edge] = head_draw |
|
for edge in mp_face_mesh.FACEMESH_LEFT_EYE: |
|
face_connection_spec[edge] = left_eye_draw |
|
for edge in mp_face_mesh.FACEMESH_LEFT_EYEBROW: |
|
face_connection_spec[edge] = left_eyebrow_draw |
|
|
|
|
|
for edge in mp_face_mesh.FACEMESH_RIGHT_EYE: |
|
face_connection_spec[edge] = right_eye_draw |
|
for edge in mp_face_mesh.FACEMESH_RIGHT_EYEBROW: |
|
face_connection_spec[edge] = right_eyebrow_draw |
|
|
|
|
|
for edge in mp_face_mesh.FACEMESH_LIPS: |
|
face_connection_spec[edge] = mouth_draw |
|
iris_landmark_spec = {468: right_iris_draw, 473: left_iris_draw} |
|
|
|
|
|
def draw_pupils(image, landmark_list, drawing_spec, halfwidth: int = 2): |
|
"""We have a custom function to draw the pupils because the mp.draw_landmarks method requires a parameter for all |
|
landmarks. Until our PR is merged into mediapipe, we need this separate method.""" |
|
if len(image.shape) != 3: |
|
raise ValueError("Input image must be H,W,C.") |
|
image_rows, image_cols, image_channels = image.shape |
|
if image_channels != 3: |
|
raise ValueError('Input image must contain three channel bgr data.') |
|
for idx, landmark in enumerate(landmark_list.landmark): |
|
if ( |
|
(landmark.HasField('visibility') and landmark.visibility < 0.9) or |
|
(landmark.HasField('presence') and landmark.presence < 0.5) |
|
): |
|
continue |
|
if landmark.x >= 1.0 or landmark.x < 0 or landmark.y >= 1.0 or landmark.y < 0: |
|
continue |
|
image_x = int(image_cols*landmark.x) |
|
image_y = int(image_rows*landmark.y) |
|
draw_color = None |
|
if isinstance(drawing_spec, Mapping): |
|
if drawing_spec.get(idx) is None: |
|
continue |
|
else: |
|
draw_color = drawing_spec[idx].color |
|
elif isinstance(drawing_spec, DrawingSpec): |
|
draw_color = drawing_spec.color |
|
image[image_y-halfwidth:image_y+halfwidth, image_x-halfwidth:image_x+halfwidth, :] = draw_color |
|
|
|
|
|
def reverse_channels(image): |
|
"""Given a numpy array in RGB form, convert to BGR. Will also convert from BGR to RGB.""" |
|
|
|
|
|
return image[:, :, ::-1] |
|
|
|
|
|
def generate_annotation( |
|
input_image: Image.Image, |
|
max_faces: int, |
|
min_face_size_pixels: int = 0, |
|
return_annotation_data: bool = False |
|
): |
|
""" |
|
Find up to 'max_faces' inside the provided input image. |
|
If min_face_size_pixels is provided and nonzero it will be used to filter faces that occupy less than this many |
|
pixels in the image. |
|
If return_annotation_data is TRUE (default: false) then in addition to returning the 'detected face' image, three |
|
additional parameters will be returned: faces before filtering, faces after filtering, and an annotation image. |
|
The faces_before_filtering return value is the number of faces detected in an image with no filtering. |
|
faces_after_filtering is the number of faces remaining after filtering small faces. |
|
|
|
:return: |
|
If 'return_annotation_data==True', returns (numpy array, numpy array, int, int). |
|
If 'return_annotation_data==False' (default), returns a numpy array. |
|
""" |
|
with mp_face_mesh.FaceMesh( |
|
static_image_mode=True, |
|
max_num_faces=max_faces, |
|
refine_landmarks=True, |
|
min_detection_confidence=0.5, |
|
) as facemesh: |
|
img_rgb = numpy.asarray(input_image) |
|
results = facemesh.process(img_rgb).multi_face_landmarks |
|
|
|
faces_found_before_filtering = len(results) |
|
|
|
|
|
filtered_landmarks = [] |
|
for lm in results: |
|
landmarks = lm.landmark |
|
face_rect = [ |
|
landmarks[0].x, |
|
landmarks[0].y, |
|
landmarks[0].x, |
|
landmarks[0].y, |
|
] |
|
for i in range(len(landmarks)): |
|
face_rect[0] = min(face_rect[0], landmarks[i].x) |
|
face_rect[1] = min(face_rect[1], landmarks[i].y) |
|
face_rect[2] = max(face_rect[2], landmarks[i].x) |
|
face_rect[3] = max(face_rect[3], landmarks[i].y) |
|
if min_face_size_pixels > 0: |
|
face_width = abs(face_rect[2] - face_rect[0]) |
|
face_height = abs(face_rect[3] - face_rect[1]) |
|
face_width_pixels = face_width * input_image.size[0] |
|
face_height_pixels = face_height * input_image.size[1] |
|
face_size = min(face_width_pixels, face_height_pixels) |
|
if face_size >= min_face_size_pixels: |
|
filtered_landmarks.append(lm) |
|
else: |
|
filtered_landmarks.append(lm) |
|
|
|
faces_remaining_after_filtering = len(filtered_landmarks) |
|
|
|
|
|
empty = numpy.zeros_like(img_rgb) |
|
|
|
|
|
for face_landmarks in filtered_landmarks: |
|
mp_drawing.draw_landmarks( |
|
empty, |
|
face_landmarks, |
|
connections=face_connection_spec.keys(), |
|
landmark_drawing_spec=None, |
|
connection_drawing_spec=face_connection_spec |
|
) |
|
draw_pupils(empty, face_landmarks, iris_landmark_spec, 2) |
|
|
|
|
|
empty = reverse_channels(empty) |
|
|
|
|
|
if return_annotation_data: |
|
|
|
annotated = reverse_channels(numpy.asarray(input_image)).copy() |
|
for face_landmarks in filtered_landmarks: |
|
mp_drawing.draw_landmarks( |
|
empty, |
|
face_landmarks, |
|
connections=face_connection_spec.keys(), |
|
landmark_drawing_spec=None, |
|
connection_drawing_spec=face_connection_spec |
|
) |
|
draw_pupils(empty, face_landmarks, iris_landmark_spec, 2) |
|
annotated = reverse_channels(annotated) |
|
|
|
if not return_annotation_data: |
|
return empty |
|
else: |
|
return empty, annotated, faces_found_before_filtering, faces_remaining_after_filtering |
|
|