opencv-gui / src /face_mesh_tracker.py
samuellimabraz's picture
Add initial project structure with OpenCV and Mediapipe integration
ae1809b unverified
raw
history blame
12.7 kB
import os
import urllib.request
import sys
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from mediapipe.framework.formats import landmark_pb2
import time
import numpy as np
# import autopy
class FaceMeshTracker:
# face bounder indices
FACE_OVAL = [
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
]
# lips indices for Landmarks
LIPS = [
61,
146,
91,
181,
84,
17,
314,
405,
321,
375,
291,
308,
324,
318,
402,
317,
14,
87,
178,
88,
95,
185,
40,
39,
37,
0,
267,
269,
270,
409,
415,
310,
311,
312,
13,
82,
81,
42,
183,
78,
]
LOWER_LIPS = [
61,
146,
91,
181,
84,
17,
314,
405,
321,
375,
291,
308,
324,
318,
402,
317,
14,
87,
178,
88,
95,
]
UPPER_LIPS = [
185,
40,
39,
37,
0,
267,
269,
270,
409,
415,
310,
311,
312,
13,
82,
81,
42,
183,
78,
]
# Left eyes indices
LEFT_EYE = [
362,
382,
381,
380,
374,
373,
390,
249,
263,
466,
388,
387,
386,
385,
384,
398,
]
LEFT_EYEBROW = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285]
LEFT_CENTER_EYE = [473]
# right eyes indices
RIGHT_EYE = [
33,
7,
163,
144,
145,
153,
154,
155,
133,
173,
157,
158,
159,
160,
161,
246,
]
RIGHT_EYEBROW = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46]
RIGHT_CENTER_EYE = [468]
def __init__(
self,
model: str = None,
num_faces: int = 1,
min_face_detection_confidence: float = 0.5,
min_face_presence_confidence: float = 0.5,
min_tracking_confidence: float = 0.5,
):
"""
Initialize a FaceTracker instance.
Args:
model (str): The path to the model for face tracking.
num_faces (int): Maximum number of faces to detect.
min_face_detection_confidence (float): Minimum confidence value ([0.0, 1.0]) for successful face detection.
min_face_presence_confidence (float): Minimum confidence value ([0.0, 1.0]) for presence of a face to be tracked.
min_tracking_confidence (float): Minimum confidence value ([0.0, 1.0]) for successful face landmark tracking.
"""
self.model = model
if self.model == None:
self.model = self.download_model()
if self.model == None:
self.model = self.download_model()
self.detector = self.initialize_detector(
num_faces,
min_face_detection_confidence,
min_face_presence_confidence,
min_tracking_confidence,
)
self.mp_face_mesh = mp.solutions.face_mesh
self.mp_drawing = mp.solutions.drawing_utils
self.mp_drawing_styles = mp.solutions.drawing_styles
self.DETECTION_RESULT = None
def save_result(
self,
result: vision.FaceLandmarkerResult,
unused_output_image,
timestamp_ms: int,
fps: bool = False,
):
"""
Saves the result of the face detection.
Args:
result (vision.FaceLandmarkerResult): Result of the face detection.
unused_output_image (mp.Image): Unused.
timestamp_ms (int): Timestamp of the detection.
Returns:
None
"""
self.DETECTION_RESULT = result
def initialize_detector(
self,
num_faces: int,
min_face_detection_confidence: float,
min_face_presence_confidence: float,
min_tracking_confidence: float,
):
"""
Initializes the FaceLandmarker instance.
Args:
num_faces (int): Maximum number of faces to detect.
min_face_detection_confidence (float): Minimum confidence value ([0.0, 1.0]) for face detection to be considered successful.
min_face_presence_confidence (float): Minimum confidence value ([0.0, 1.0]) for the presence of a face for the face landmarks to be considered tracked successfully.
min_tracking_confidence (float): Minimum confidence value ([0.0, 1.0]) for the face landmarks to be considered tracked successfully.
Returns:
vision.FaceLandmarker: FaceLandmarker instance.
"""
base_options = python.BaseOptions(model_asset_path=self.model)
options = vision.FaceLandmarkerOptions(
base_options=base_options,
running_mode=vision.RunningMode.LIVE_STREAM,
num_faces=num_faces,
min_face_detection_confidence=min_face_detection_confidence,
min_face_presence_confidence=min_face_presence_confidence,
min_tracking_confidence=min_tracking_confidence,
output_face_blendshapes=True,
result_callback=self.save_result,
)
return vision.FaceLandmarker.create_from_options(options)
def draw_landmarks(
self,
image: np.ndarray,
text_color: tuple = (0, 0, 0),
font_size: int = 1,
font_thickness: int = 1,
) -> np.ndarray:
"""
Draws the face landmarks on the image.
Args:
image (numpy.ndarray): Image on which to draw the landmarks.
text_color (tuple, optional): Color of the text. Defaults to (0, 0, 0).
font_size (int, optional): Size of the font. Defaults to 1.
font_thickness (int, optional): Thickness of the font. Defaults to 1.
Returns:
numpy.ndarray: Image with the landmarks drawn.
"""
if self.DETECTION_RESULT:
# Draw landmarks.
for face_landmarks in self.DETECTION_RESULT.face_landmarks:
face_landmarks_proto = landmark_pb2.NormalizedLandmarkList()
face_landmarks_proto.landmark.extend(
[
landmark_pb2.NormalizedLandmark(
x=landmark.x, y=landmark.y, z=landmark.z
)
for landmark in face_landmarks
]
)
self.mp_drawing.draw_landmarks(
image=image,
landmark_list=face_landmarks_proto,
connections=self.mp_face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=None,
connection_drawing_spec=self.mp_drawing_styles.get_default_face_mesh_tesselation_style(),
)
self.mp_drawing.draw_landmarks(
image=image,
landmark_list=face_landmarks_proto,
connections=self.mp_face_mesh.FACEMESH_CONTOURS,
landmark_drawing_spec=None,
connection_drawing_spec=self.mp_drawing_styles.get_default_face_mesh_contours_style(),
)
self.mp_drawing.draw_landmarks(
image=image,
landmark_list=face_landmarks_proto,
connections=self.mp_face_mesh.FACEMESH_IRISES,
landmark_drawing_spec=None,
connection_drawing_spec=self.mp_drawing_styles.get_default_face_mesh_iris_connections_style(),
)
return image
def draw_landmark_circles(
self,
image: np.ndarray,
landmark_indices: list,
circle_radius: int = 1,
circle_color: tuple = (0, 255, 0),
circle_thickness: int = 1,
) -> np.ndarray:
"""
Draws circles on the specified face landmarks on the image.
Args:
image (numpy.ndarray): Image on which to draw the landmarks.
landmark_indices (list of int): Indices of the landmarks to draw.
circle_radius (int, optional): Radius of the circles. Defaults to 1.
circle_color (tuple, optional): Color of the circles. Defaults to (0, 255, 0).
circle_thickness (int, optional): Thickness of the circles. Defaults to 1.
Returns:
numpy.ndarray: Image with the landmarks drawn.
"""
if self.DETECTION_RESULT:
# Draw landmarks.
for face_landmarks in self.DETECTION_RESULT.face_landmarks:
for i, landmark in enumerate(face_landmarks):
if i in landmark_indices:
# Convert the landmark position to image coordinates.
x = int(landmark.x * image.shape[1])
y = int(landmark.y * image.shape[0])
cv2.circle(
image,
(x, y),
circle_radius,
circle_color,
circle_thickness,
)
return image
def detect(self, frame: np.ndarray, draw: bool = False) -> np.ndarray:
"""
Detects the face landmarks in the frame.
Args:
frame (numpy.ndarray): Frame in which to detect the landmarks.
draw (bool, optional): Whether to draw the landmarks on the frame. Defaults to False.
Returns:
numpy.ndarray: Frame with the landmarks drawn.
"""
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_image)
self.detector.detect_async(mp_image, time.time_ns() // 1_000_000)
return self.draw_landmarks(frame) if draw else frame
def get_face_landmarks(self, face_idx: int = 0, idxs: list = None) -> list:
"""
Returns the face landmarks.
Args:
face_idx (int, optional): Index of the face for which to return the landmarks. Defaults to 0.
idxs (list, optional): List of indices of the landmarks to return. Defaults to None.
Returns:
list: List of face world landmarks.
"""
if self.DETECTION_RESULT is not None:
if idxs is None:
return self.DETECTION_RESULT.face_landmarks[face_idx]
else:
return [
self.DETECTION_RESULT.face_landmarks[face_idx][idx] for idx in idxs
]
else:
return []
@staticmethod
def download_model() -> str:
"""
Download the face_landmarker task model from the mediapipe repository.
https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task
Returns:
str: Path to the downloaded model.
"""
root = os.path.dirname(os.path.realpath(__file__))
# Unino to res folder
root = os.path.join(root, "..", "res")
filename = os.path.join(root, "face_landmarker.task")
if os.path.exists(filename):
print(f"O arquivo {filename} já existe, pulando o download.")
else:
base = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task"
urllib.request.urlretrieve(base, filename)
return filename