keysync-demo / sgm /modules /utils /draw_utils.py
Antoni Bigata
first commit
b5ce381
import cv2
import mediapipe as mp
import numpy as np
from mediapipe.framework.formats import landmark_pb2
class FaceMeshVisualizer:
def __init__(
self,
forehead_edge=False,
upface_only=False,
draw_eye=True,
draw_head=False,
draw_iris=True,
draw_eyebrow=True,
draw_mouse=True,
draw_nose=True,
draw_pupil=True,
):
self.mp_drawing = mp.solutions.drawing_utils
mp_face_mesh = mp.solutions.face_mesh
self.mp_face_mesh = mp_face_mesh
self.forehead_edge = forehead_edge
DrawingSpec = mp.solutions.drawing_styles.DrawingSpec
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)
head_draw = DrawingSpec(color=(10, 200, 10), thickness=f_thick, circle_radius=f_rad)
nose_draw = DrawingSpec(color=(200, 200, 200), thickness=f_thick, circle_radius=f_rad)
mouth_draw_obl = DrawingSpec(color=(10, 180, 20), thickness=f_thick, circle_radius=f_rad)
mouth_draw_obr = DrawingSpec(color=(20, 10, 180), thickness=f_thick, circle_radius=f_rad)
mouth_draw_ibl = DrawingSpec(color=(100, 100, 30), thickness=f_thick, circle_radius=f_rad)
mouth_draw_ibr = DrawingSpec(color=(100, 150, 50), thickness=f_thick, circle_radius=f_rad)
mouth_draw_otl = DrawingSpec(color=(20, 80, 100), thickness=f_thick, circle_radius=f_rad)
mouth_draw_otr = DrawingSpec(color=(80, 100, 20), thickness=f_thick, circle_radius=f_rad)
mouth_draw_itl = DrawingSpec(color=(120, 100, 200), thickness=f_thick, circle_radius=f_rad)
mouth_draw_itr = DrawingSpec(color=(150, 120, 100), thickness=f_thick, circle_radius=f_rad)
FACEMESH_LIPS_OUTER_BOTTOM_LEFT = [(61, 146), (146, 91), (91, 181), (181, 84), (84, 17)]
FACEMESH_LIPS_OUTER_BOTTOM_RIGHT = [(17, 314), (314, 405), (405, 321), (321, 375), (375, 291)]
FACEMESH_LIPS_INNER_BOTTOM_LEFT = [(78, 95), (95, 88), (88, 178), (178, 87), (87, 14)]
FACEMESH_LIPS_INNER_BOTTOM_RIGHT = [(14, 317), (317, 402), (402, 318), (318, 324), (324, 308)]
FACEMESH_LIPS_OUTER_TOP_LEFT = [(61, 185), (185, 40), (40, 39), (39, 37), (37, 0)]
FACEMESH_LIPS_OUTER_TOP_RIGHT = [(0, 267), (267, 269), (269, 270), (270, 409), (409, 291)]
FACEMESH_LIPS_INNER_TOP_LEFT = [(78, 191), (191, 80), (80, 81), (81, 82), (82, 13)]
FACEMESH_LIPS_INNER_TOP_RIGHT = [(13, 312), (312, 311), (311, 310), (310, 415), (415, 308)]
FACEMESH_CUSTOM_FACE_OVAL = [
(176, 149),
(150, 136),
(356, 454),
(58, 132),
(152, 148),
(361, 288),
(251, 389),
(132, 93),
(389, 356),
(400, 377),
(136, 172),
(377, 152),
(323, 361),
(172, 58),
(454, 323),
(365, 379),
(379, 378),
(148, 176),
(93, 234),
(397, 365),
(149, 150),
(288, 397),
(234, 127),
(378, 400),
(127, 162),
(162, 21),
]
# mp_face_mesh.FACEMESH_CONTOURS has all the items we care about.
face_connection_spec = {}
# from IPython import embed
# embed()
if self.forehead_edge:
for edge in mp_face_mesh.FACEMESH_FACE_OVAL:
face_connection_spec[edge] = head_draw
else:
if draw_head:
FACEMESH_CUSTOM_FACE_OVAL_sorted = sorted(FACEMESH_CUSTOM_FACE_OVAL)
if upface_only:
for edge in [
FACEMESH_CUSTOM_FACE_OVAL_sorted[edge_idx] for edge_idx in [1, 2, 9, 12, 13, 16, 22, 25]
]:
face_connection_spec[edge] = head_draw
else:
for edge in FACEMESH_CUSTOM_FACE_OVAL_sorted:
face_connection_spec[edge] = head_draw
if draw_eye:
for edge in mp_face_mesh.FACEMESH_LEFT_EYE:
face_connection_spec[edge] = left_eye_draw
for edge in mp_face_mesh.FACEMESH_RIGHT_EYE:
face_connection_spec[edge] = right_eye_draw
if draw_eyebrow:
for edge in mp_face_mesh.FACEMESH_LEFT_EYEBROW:
face_connection_spec[edge] = left_eyebrow_draw
for edge in mp_face_mesh.FACEMESH_RIGHT_EYEBROW:
face_connection_spec[edge] = right_eyebrow_draw
if draw_iris:
for edge in mp_face_mesh.FACEMESH_LEFT_IRIS:
face_connection_spec[edge] = left_iris_draw
for edge in mp_face_mesh.FACEMESH_RIGHT_IRIS:
face_connection_spec[edge] = right_iris_draw
# for edge in mp_face_mesh.FACEMESH_RIGHT_EYEBROW:
# face_connection_spec[edge] = right_eyebrow_draw
# for edge in mp_face_mesh.FACEMESH_RIGHT_IRIS:
# face_connection_spec[edge] = right_iris_draw
# for edge in mp_face_mesh.FACEMESH_LIPS:
# face_connection_spec[edge] = mouth_draw
if draw_mouse:
for edge in FACEMESH_LIPS_OUTER_BOTTOM_LEFT:
face_connection_spec[edge] = mouth_draw_obl
for edge in FACEMESH_LIPS_OUTER_BOTTOM_RIGHT:
face_connection_spec[edge] = mouth_draw_obr
for edge in FACEMESH_LIPS_INNER_BOTTOM_LEFT:
face_connection_spec[edge] = mouth_draw_ibl
for edge in FACEMESH_LIPS_INNER_BOTTOM_RIGHT:
face_connection_spec[edge] = mouth_draw_ibr
for edge in FACEMESH_LIPS_OUTER_TOP_LEFT:
face_connection_spec[edge] = mouth_draw_otl
for edge in FACEMESH_LIPS_OUTER_TOP_RIGHT:
face_connection_spec[edge] = mouth_draw_otr
for edge in FACEMESH_LIPS_INNER_TOP_LEFT:
face_connection_spec[edge] = mouth_draw_itl
for edge in FACEMESH_LIPS_INNER_TOP_RIGHT:
face_connection_spec[edge] = mouth_draw_itr
self.face_connection_spec = face_connection_spec
self.pupil_landmark_spec = {468: right_iris_draw, 473: left_iris_draw}
self.nose_landmark_spec = {4: nose_draw}
self.draw_pupil = draw_pupil
self.draw_nose = draw_nose
def draw_points(self, 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: # BGR channels
raise ValueError("Input image must contain three channel bgr data.")
for idx, landmark in enumerate(landmark_list.landmark):
if idx not in drawing_spec:
continue
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 = drawing_spec[idx].color
image[image_y - halfwidth : image_y + halfwidth, image_x - halfwidth : image_x + halfwidth, :] = draw_color
def draw_landmarks(self, image_size, keypoints, normed=False, ini_size=[512, 512]):
# ini_size = [512, 512]
image = np.zeros([ini_size[1], ini_size[0], 3], dtype=np.uint8)
if keypoints is not None:
new_landmarks = landmark_pb2.NormalizedLandmarkList()
for i in range(keypoints.shape[0]):
landmark = new_landmarks.landmark.add()
if normed:
landmark.x = keypoints[i, 0]
landmark.y = keypoints[i, 1]
else:
landmark.x = keypoints[i, 0] / image_size[0]
landmark.y = keypoints[i, 1] / image_size[1]
landmark.z = 1.0
self.mp_drawing.draw_landmarks(
image=image,
landmark_list=new_landmarks,
connections=self.face_connection_spec.keys(),
landmark_drawing_spec=None,
connection_drawing_spec=self.face_connection_spec,
)
if self.draw_pupil:
self.draw_points(image, new_landmarks, self.pupil_landmark_spec, 3)
if self.draw_nose:
self.draw_points(image, new_landmarks, self.nose_landmark_spec, 3)
image = cv2.resize(image, (image_size[0], image_size[1]))
return image