Initial commit: MediaPipe landmarks demo

HTML demos for face, hand, gesture, and posture tracking using MediaPipe.
Includes Python CLI tools for processing video files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-19 22:38:40 -05:00
commit 8bcc62b045
22 changed files with 2347 additions and 0 deletions

151
process_mp4_facial.py Executable file
View File

@@ -0,0 +1,151 @@
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import numpy as np
from mediapipe.framework.formats import landmark_pb2
import argparse
import os
import csv
# --- NEW: Helper function to create the landmark-to-feature map ---
def create_landmark_map():
"""Creates a mapping from landmark index to facial feature name."""
landmark_map = {}
# Define the connection groups from MediaPipe's face_mesh solutions
connection_groups = {
'lips': mp.solutions.face_mesh.FACEMESH_LIPS,
'left_eye': mp.solutions.face_mesh.FACEMESH_LEFT_EYE,
'right_eye': mp.solutions.face_mesh.FACEMESH_RIGHT_EYE,
'left_eyebrow': mp.solutions.face_mesh.FACEMESH_LEFT_EYEBROW,
'right_eyebrow': mp.solutions.face_mesh.FACEMESH_RIGHT_EYEBROW,
'face_oval': mp.solutions.face_mesh.FACEMESH_FACE_OVAL,
'left_iris': mp.solutions.face_mesh.FACEMESH_LEFT_IRIS,
'right_iris': mp.solutions.face_mesh.FACEMESH_RIGHT_IRIS,
}
# Populate the map by iterating through the connection groups
for part_name, connections in connection_groups.items():
for connection in connections:
landmark_map[connection[0]] = part_name
landmark_map[connection[1]] = part_name
return landmark_map
# --- Helper Function to Draw Landmarks ---
def draw_landmarks_on_image(rgb_image, detection_result):
"""Draws face landmarks on a single image frame."""
face_landmarks_list = detection_result.face_landmarks
annotated_image = np.copy(rgb_image)
# Loop through the detected faces to visualize.
for face_landmarks in face_landmarks_list:
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
])
mp.solutions.drawing_utils.draw_landmarks(
image=annotated_image,
landmark_list=face_landmarks_proto,
connections=mp.solutions.face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=None,
connection_drawing_spec=mp.solutions.drawing_styles
.get_default_face_mesh_tesselation_style())
mp.solutions.drawing_utils.draw_landmarks(
image=annotated_image,
landmark_list=face_landmarks_proto,
connections=mp.solutions.face_mesh.FACEMESH_CONTOURS,
landmark_drawing_spec=None,
connection_drawing_spec=mp.solutions.drawing_styles
.get_default_face_mesh_contours_style())
mp.solutions.drawing_utils.draw_landmarks(
image=annotated_image,
landmark_list=face_landmarks_proto,
connections=mp.solutions.face_mesh.FACEMESH_IRISES,
landmark_drawing_spec=None,
connection_drawing_spec=mp.solutions.drawing_styles
.get_default_face_mesh_iris_connections_style())
return annotated_image
def main():
parser = argparse.ArgumentParser(description='Process a video to detect and draw face landmarks.')
parser.add_argument('input_video', help='The path to the input video file.')
args = parser.parse_args()
input_video_path = args.input_video
base_name, extension = os.path.splitext(input_video_path)
output_video_path = f"{base_name}_annotated{extension}"
output_csv_path = f"{base_name}_landmarks.csv"
# --- NEW: Create the landmark map ---
landmark_to_part_map = create_landmark_map()
# --- Configuration & Setup ---
model_path = 'face_landmarker.task'
base_options = python.BaseOptions(model_asset_path=model_path)
options = vision.FaceLandmarkerOptions(base_options=base_options,
output_face_blendshapes=True,
output_facial_transformation_matrixes=True,
num_faces=1)
detector = vision.FaceLandmarker.create_from_options(options)
# --- Video and CSV Setup ---
cap = cv2.VideoCapture(input_video_path)
if not cap.isOpened():
print(f"Error: Could not open video file {input_video_path}")
return
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height))
# Open CSV file for writing
with open(output_csv_path, 'w', newline='') as csvfile:
csv_writer = csv.writer(csvfile)
# NEW: Write the updated header row
csv_writer.writerow(['frame', 'face', 'landmark_index', 'face_part', 'x', 'y', 'z'])
print(f"Processing video: {input_video_path} 📹")
frame_number = 0
while(cap.isOpened()):
ret, frame = cap.read()
if not ret:
break
frame_number += 1
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_frame)
detection_result = detector.detect(mp_image)
# Write landmark data to CSV
if detection_result.face_landmarks:
for face_index, face_landmarks in enumerate(detection_result.face_landmarks):
for landmark_index, landmark in enumerate(face_landmarks):
# NEW: Look up the face part name from the map
face_part = landmark_to_part_map.get(landmark_index, 'unknown')
# NEW: Write the new column to the CSV row
csv_writer.writerow([frame_number, face_index, landmark_index, face_part, landmark.x, landmark.y, landmark.z])
# Draw landmarks on the frame for the video
annotated_frame = draw_landmarks_on_image(rgb_frame, detection_result)
bgr_annotated_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_RGB2BGR)
out.write(bgr_annotated_frame)
# Release everything when the job is finished
cap.release()
out.release()
cv2.destroyAllWindows()
print(f"\n✅ Processing complete.")
print(f"Annotated video saved to: {output_video_path}")
print(f"Landmarks CSV saved to: {output_csv_path}")
if __name__ == '__main__':
main()