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:
151
process_mp4_facial.py
Executable file
151
process_mp4_facial.py
Executable 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()
|
||||
Reference in New Issue
Block a user