mediapipe_face_common.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from typing import Mapping
  2. import mediapipe as mp
  3. import numpy
  4. mp_drawing = mp.solutions.drawing_utils
  5. mp_drawing_styles = mp.solutions.drawing_styles
  6. mp_face_detection = mp.solutions.face_detection # Only for counting faces.
  7. mp_face_mesh = mp.solutions.face_mesh
  8. mp_face_connections = mp.solutions.face_mesh_connections.FACEMESH_TESSELATION
  9. mp_hand_connections = mp.solutions.hands_connections.HAND_CONNECTIONS
  10. mp_body_connections = mp.solutions.pose_connections.POSE_CONNECTIONS
  11. DrawingSpec = mp.solutions.drawing_styles.DrawingSpec
  12. PoseLandmark = mp.solutions.drawing_styles.PoseLandmark
  13. min_face_size_pixels: int = 64
  14. f_thick = 2
  15. f_rad = 1
  16. right_iris_draw = DrawingSpec(color=(10, 200, 250), thickness=f_thick, circle_radius=f_rad)
  17. right_eye_draw = DrawingSpec(color=(10, 200, 180), thickness=f_thick, circle_radius=f_rad)
  18. right_eyebrow_draw = DrawingSpec(color=(10, 220, 180), thickness=f_thick, circle_radius=f_rad)
  19. left_iris_draw = DrawingSpec(color=(250, 200, 10), thickness=f_thick, circle_radius=f_rad)
  20. left_eye_draw = DrawingSpec(color=(180, 200, 10), thickness=f_thick, circle_radius=f_rad)
  21. left_eyebrow_draw = DrawingSpec(color=(180, 220, 10), thickness=f_thick, circle_radius=f_rad)
  22. mouth_draw = DrawingSpec(color=(10, 180, 10), thickness=f_thick, circle_radius=f_rad)
  23. head_draw = DrawingSpec(color=(10, 200, 10), thickness=f_thick, circle_radius=f_rad)
  24. # mp_face_mesh.FACEMESH_CONTOURS has all the items we care about.
  25. face_connection_spec = {}
  26. for edge in mp_face_mesh.FACEMESH_FACE_OVAL:
  27. face_connection_spec[edge] = head_draw
  28. for edge in mp_face_mesh.FACEMESH_LEFT_EYE:
  29. face_connection_spec[edge] = left_eye_draw
  30. for edge in mp_face_mesh.FACEMESH_LEFT_EYEBROW:
  31. face_connection_spec[edge] = left_eyebrow_draw
  32. # for edge in mp_face_mesh.FACEMESH_LEFT_IRIS:
  33. # face_connection_spec[edge] = left_iris_draw
  34. for edge in mp_face_mesh.FACEMESH_RIGHT_EYE:
  35. face_connection_spec[edge] = right_eye_draw
  36. for edge in mp_face_mesh.FACEMESH_RIGHT_EYEBROW:
  37. face_connection_spec[edge] = right_eyebrow_draw
  38. # for edge in mp_face_mesh.FACEMESH_RIGHT_IRIS:
  39. # face_connection_spec[edge] = right_iris_draw
  40. for edge in mp_face_mesh.FACEMESH_LIPS:
  41. face_connection_spec[edge] = mouth_draw
  42. iris_landmark_spec = {468: right_iris_draw, 473: left_iris_draw}
  43. def draw_pupils(image, landmark_list, drawing_spec, halfwidth: int = 2):
  44. """We have a custom function to draw the pupils because the mp.draw_landmarks method requires a parameter for all
  45. landmarks. Until our PR is merged into mediapipe, we need this separate method."""
  46. if len(image.shape) != 3:
  47. raise ValueError("Input image must be H,W,C.")
  48. image_rows, image_cols, image_channels = image.shape
  49. if image_channels != 3: # BGR channels
  50. raise ValueError('Input image must contain three channel bgr data.')
  51. for idx, landmark in enumerate(landmark_list.landmark):
  52. if (
  53. (landmark.HasField('visibility') and landmark.visibility < 0.9) or
  54. (landmark.HasField('presence') and landmark.presence < 0.5)
  55. ):
  56. continue
  57. if landmark.x >= 1.0 or landmark.x < 0 or landmark.y >= 1.0 or landmark.y < 0:
  58. continue
  59. image_x = int(image_cols*landmark.x)
  60. image_y = int(image_rows*landmark.y)
  61. draw_color = None
  62. if isinstance(drawing_spec, Mapping):
  63. if drawing_spec.get(idx) is None:
  64. continue
  65. else:
  66. draw_color = drawing_spec[idx].color
  67. elif isinstance(drawing_spec, DrawingSpec):
  68. draw_color = drawing_spec.color
  69. image[image_y-halfwidth:image_y+halfwidth, image_x-halfwidth:image_x+halfwidth, :] = draw_color
  70. def reverse_channels(image):
  71. """Given a numpy array in RGB form, convert to BGR. Will also convert from BGR to RGB."""
  72. # im[:,:,::-1] is a neat hack to convert BGR to RGB by reversing the indexing order.
  73. # im[:,:,::[2,1,0]] would also work but makes a copy of the data.
  74. return image[:, :, ::-1]
  75. def generate_annotation(
  76. img_rgb,
  77. max_faces: int,
  78. min_confidence: float
  79. ):
  80. """
  81. Find up to 'max_faces' inside the provided input image.
  82. If min_face_size_pixels is provided and nonzero it will be used to filter faces that occupy less than this many
  83. pixels in the image.
  84. """
  85. with mp_face_mesh.FaceMesh(
  86. static_image_mode=True,
  87. max_num_faces=max_faces,
  88. refine_landmarks=True,
  89. min_detection_confidence=min_confidence,
  90. ) as facemesh:
  91. img_height, img_width, img_channels = img_rgb.shape
  92. assert(img_channels == 3)
  93. results = facemesh.process(img_rgb).multi_face_landmarks
  94. if results is None:
  95. print("No faces detected in controlnet image for Mediapipe face annotator.")
  96. return numpy.zeros_like(img_rgb)
  97. # Filter faces that are too small
  98. filtered_landmarks = []
  99. for lm in results:
  100. landmarks = lm.landmark
  101. face_rect = [
  102. landmarks[0].x,
  103. landmarks[0].y,
  104. landmarks[0].x,
  105. landmarks[0].y,
  106. ] # Left, up, right, down.
  107. for i in range(len(landmarks)):
  108. face_rect[0] = min(face_rect[0], landmarks[i].x)
  109. face_rect[1] = min(face_rect[1], landmarks[i].y)
  110. face_rect[2] = max(face_rect[2], landmarks[i].x)
  111. face_rect[3] = max(face_rect[3], landmarks[i].y)
  112. if min_face_size_pixels > 0:
  113. face_width = abs(face_rect[2] - face_rect[0])
  114. face_height = abs(face_rect[3] - face_rect[1])
  115. face_width_pixels = face_width * img_width
  116. face_height_pixels = face_height * img_height
  117. face_size = min(face_width_pixels, face_height_pixels)
  118. if face_size >= min_face_size_pixels:
  119. filtered_landmarks.append(lm)
  120. else:
  121. filtered_landmarks.append(lm)
  122. # Annotations are drawn in BGR for some reason, but we don't need to flip a zero-filled image at the start.
  123. empty = numpy.zeros_like(img_rgb)
  124. # Draw detected faces:
  125. for face_landmarks in filtered_landmarks:
  126. mp_drawing.draw_landmarks(
  127. empty,
  128. face_landmarks,
  129. connections=face_connection_spec.keys(),
  130. landmark_drawing_spec=None,
  131. connection_drawing_spec=face_connection_spec
  132. )
  133. draw_pupils(empty, face_landmarks, iris_landmark_spec, 2)
  134. # Flip BGR back to RGB.
  135. empty = reverse_channels(empty).copy()
  136. return empty