Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Authored by Souvik Mandal, Ph.D.

Project Leader & Instructor, Computational Behavioral Sciences, LS100, FAS, Harvard University | Linkedin ID: souvik-mandal-phd


Last updated: 2026-07-02

Open in Colab Open in GitHub Codespaces

This notebook is both a guided lesson and a working pipeline for detecting human pose landmarks from local video files or entire folders of videos using MediaPipe Tasks.

Goal

  1. Set up a clean Python 3.12 environment and verify required packages.

  2. Understand each step and terminologies.

  3. Download and select a Pose Landmarker model (lite / full / heavy) and understand accuracy–speed trade-offs.

  4. Read videos with OpenCV and run inference in RunningMode.VIDEO with correct timestamps.

  5. Export results as tidy CSVs for analysis: 2D image-normalized and 3D world landmarks.

  6. Create an annotated MP4 showing the skeleton overlay.

  7. Build intuition for visibility, image vs. world coordinates, and simple feature engineering (e.g., joint angles).

Built for learning: Along the way you’ll see short callouts explaining why each step exists (e.g., timestamps in VIDEO mode), how coordinate spaces differ, and how to tune speed vs. accuracy.

After completing this guide, you will be able to

  • Load one video—or loop through an entire folder—and extract the coordinates of the landmark bodypoints frame-by-frame.

  • Save two analysis-ready CSVs per video: one for 2D normalized landmarks and one for 3D world coordinates.

  • Produce an annotated MP4 with landmarks and connections overlaid.

  • Explain and adjust RunningMode.VIDEO, per-frame timestamps, visibility filtering, image vs. world coordinates, and model variants (lite/full/heavy).

Prerequisites

  • Python 3.12 virtual environment selected as the active Jupyter kernel. In case yo8u need help, please refer to the “LS100_Guide 3_Introduction to Pose Estimation Using MediaPipe.pdf” guide.

  • Installed packages: mediapipe opencv-python pandas numpy tqdm matplotlib seaborn

  • One or more local video files (e.g., .mp4) to test.

Ethics & consent

  • If processing videos of people, obtain consent and store data securely. Avoid uploading sensitive content to third-party services.

References for learners


0. Environment Setup and Verification (LS100 Standard)

Before running any code, make sure you’re using the LS100_PoseEstimation_MP kernel that was created in your Python 3.12 virtual environment. This section verifies your environment and installs all required packages.


What you should already have

✅ Python 3.12 installed

✅ Virtual environment activated ((MediaPipeEnv) should appear in your terminal)

✅ Kernel registered as LS100_PoseEstimation_MP

If you haven’t completed those steps, revisit the LS100_Guide 3_Introduction to Pose Estimation Using MediaPipe.pdf document.


Required packages

This notebook uses the following libraries:

  • mediapipe – pose landmark model and API

  • opencv-python – video I/O (input/output) and frame conversion

  • pandas & numpy – data handling and analysis

  • tqdm – progress bars for video processing

  • matplotlib & seaborn – visualization and data inspection

Run the next cell to ensure these are installed and to confirm the environment details.


Learning focus

  • Why virtual environments prevent version conflicts

  • Why we require Python 3.12 (MediaPipe Tasks currently supports Python 3.9–3.12 only)

  • How each library fits into the MediaPipe Pose pipeline


0. Environment setup

If running locally (VS Code/Jupyter), run the following cell once; it might take about a minute to run.

🧠 Python version: 3.12.12
✅ mediapipe already installed
⬇️ Installing opencv-python ...
✅ pandas already installed
✅ numpy already installed
✅ tqdm already installed
✅ matplotlib already installed
✅ seaborn already installed

📦 Package versions:
mediapipe      : 0.10.21
opencv-python  : 4.11.0
pandas         : 2.3.3
numpy          : 1.26.4
matplotlib     : 3.10.7
seaborn        : 0.13.2

✅ Environment is ready to proceed!

1. Imports & version checks


1. Imports and Version Verification

Now that your environment is ready, let’s import the main libraries used throughout this notebook.

This step helps confirm that:

  • The correct packages are installed inside your LS100 virtual environment

  • MediaPipe loads successfully (and we can access its Tasks API)

  • OpenCV, NumPy, and Pandas are working properly

If an import fails, it usually means you’re running the notebook in a different kernel (not the one you registered). You can fix that by selecting Kernel → Change Kernel → LS100_PoseEstimation_MP (or the name you chose).


✅ MediaPipe Tasks API imported successfully!

mediapipe version : 0.10.21
opencv version    : 4.11.0
pandas version    : 2.3.3
numpy version     : 1.26.4
⚙️ Running on CPU

 MediaPipe Tasks API is available:
- BaseOptions           : True
- PoseLandmarker        : True
- PoseLandmarkerOptions : True
- RunningMode           : True

Notes

  • Why this matters: ensures that the environment is truly isolated and reproducible.

  • Discussion prompt: Can you tell why we check MediaPipe imports before running the pipeline? (to confirm the Tasks API is available and working).

  • TASK: Print mp.__file__ to confirm MediaPipe’s path. This helps you understand where packages live inside the venv.


2. How Pose Landmarker works

  • Running modes: IMAGE, VIDEO, LIVE_STREAM. For offline videos we use VIDEO and must pass a timestamp (ms) for each frame; the task uses tracking to avoid re-running the full model on every frame (reduces latency at the same accuracy settings).

  • Outputs:

    • 2D normalized landmarks in image coordinates (x,y in [0,1] relative to width/height; z is a depth-like value; visibility in [0,1]).

    • 3D world landmarks (meters, origin near hip center; handy for biomechanical features).

  • Variants: lite / full / heavy. Heavier models = more accurate, slower (see model card).

  • Accuracy vs speed knobs: num_poses (usually 1 for single-person), min_pose_detection_confidence, min_pose_presence_confidence, min_tracking_confidence, and frame stride (e.g., analyze every 2nd/3rd frame).

We’ll expose all of these transparently in helper functions below.

3. Download a Pose Landmarker model (.task bundle)

Choose one of: "lite", "full", "heavy" (default).
URLs follow Google’s published pattern; we try latest/… first and then fall back to version 1/….

You only need to download once; it will be cached under models/.

✔ Model already present: models/pose_landmarker_heavy.task
✅ PoseLandmarker initialized successfully (VIDEO mode).
   Model: heavy → models/pose_landmarker_heavy.task
I0000 00:00:1761584076.985338 53872720 gl_context.cc:369] GL version: 2.1 (2.1 Metal - 88.1), renderer: Apple M2 Max
W0000 00:00:1761584077.082610 53919524 inference_feedback_manager.cc:114] Feedback manager requires a model with a single signature inference. Disabling support for feedback tensors.
W0000 00:00:1761584077.170321 53919532 inference_feedback_manager.cc:114] Feedback manager requires a model with a single signature inference. Disabling support for feedback tensors.

4. VIDEO mode: timestamps & inference loop

For offline videos, we must use RunningMode.VIDEO and pass a monotonic timestamp (ms) for each frame:

  • We read frames with OpenCV, compute timestamp_ms = int((frame_idx / fps) * 1000), and call landmarker.detect_for_video(mp_image, timestamp_ms).

  • The Task returns normalized 2D landmarks (x, y ∈ [0,1], z depth-like, plus visibility) and world 3D landmarks (x_m, y_m, z_m in meters).

  • We’ll save tidy CSV files for 2D and 3D landmarks.

  • We’ll also write an annotated MP4 by drawing a simple skeleton over each frame.

Parameters you can tune
  • MODEL_VARIANT (lite/full/heavy), num_poses (usually 1), frame_stride (skip frames for speed),

  • min_pose_detection_confidence, min_pose_presence_confidence, min_tracking_confidence.

5. Choose Your Parameters

Before running extraction, set the tunable parameters in the next cell.
These control model accuracy, processing speed, output organization, and post-processing filters (anti-jitter smoothing).


Model Variant

  • MODEL_VARIANT — choose one of:

    • lite → fastest but least accurate

    • full → balanced (medium accuracy & speed)

    • heavy → most accurate (default; recommended for LS100 on modern hardware)

Changing MODEL_VARIANT automatically downloads the correct .task file to your local models/ folder if needed.


Inference Settings

  • frame_stride — process every k-th frame

    • 1 = every frame (maximum precision)

    • 2 = every other frame (faster)

    • 3+ = skip more frames (fastest, least temporal detail)

  • num_poses — number of people to detect per frame

    • Use 1 for single-person videos (default in LS100)

  • Confidence thresholds

    • min_pose_detection_confidence — confidence for detecting a pose

    • min_pose_presence_confidence — confidence that a person is visible

    • min_tracking_confidence — confidence for stable tracking across frames


Output Settings

  • make_annotated_video — if True, saves an annotated .mp4 showing the skeleton overlay.

  • outputs_subdir_name — defines where outputs are saved:

    • All CSVs and optional annotated MP4s are written to an outputs/ folder placed next to each input video (same directory).


Post-Processing Filters (Anti-Jitter)

After landmark extraction, you can smooth or clean the data:

  • visibility_thresh — discard landmarks with confidence below threshold

  • hampel_window / hampel_nsigmas — outlier removal using a Hampel filter

    • Removes sudden jumps and replaces them with local medians

  • rolling_window — rolling average smoother (reduces frame-to-frame jitter)

💡 Tip:

  • If you have a slow computer, yo can choose MODEL_VARIANT = "lite" or frame_stride = 2 to reduce load.

  • After extraction, apply filtering to clean up the 2D CSV before using it in analysis.


The Pose Landmarker returns:

  • 2D normalized landmarks: (x, y ∈ [0,1]), z (depth-like, unitless), visibility (0–1 confidence).

  • 3D world landmarks: (x_m, y_m, z_m) in meters.

Outputs:

  • CSV files for both 2D and 3D landmarks.

  • Optional annotated MP4 with the skeleton overlay.


===== Parameter Summary =====
MODEL_VARIANT                  : heavy
MODEL_PATH                     : models/pose_landmarker_heavy.task
frame_stride                   : 1
num_poses                      : 1
confidences (detect,pres,track): 0.5, 0.5, 0.5
outputs_subdir_name            : outputs
make_annotated_video           : True
enable_filtering               : False
   ↳ Filtering is OFF (fast mode: no smoothing or visibility threshold applied)
=============================

Input the path of the video or the folder containing videos

Now, run the coe blcok below.

Pleae note that if you set the enable_filtering (the jitter filter) to True, it will take loger time.

Single video detected.
Outputs will be saved to: /Users/souvikmandal/Documents/06_Teaching_Mentoring/LS100_comp_etho/2025/media/video/outputs
[extract] enable_filtering = False (filtering runs after extraction)
I0000 00:00:1761594301.579539 53872720 gl_context.cc:369] GL version: 2.1 (2.1 Metal - 88.1), renderer: Apple M2 Max
W0000 00:00:1761594301.656664 54157257 inference_feedback_manager.cc:114] Feedback manager requires a model with a single signature inference. Disabling support for feedback tensors.
W0000 00:00:1761594301.732664 54157256 inference_feedback_manager.cc:114] Feedback manager requires a model with a single signature inference. Disabling support for feedback tensors.

✔ Done.
2D CSV : /Users/souvikmandal/Documents/06_Teaching_Mentoring/LS100_comp_etho/2025/media/video/outputs/Kevin_2022_Day5_CRNCH_pose2d.csv
3D CSV : /Users/souvikmandal/Documents/06_Teaching_Mentoring/LS100_comp_etho/2025/media/video/outputs/Kevin_2022_Day5_CRNCH_pose3d.csv
MP4    : /Users/souvikmandal/Documents/06_Teaching_Mentoring/LS100_comp_etho/2025/media/video/outputs/Kevin_2022_Day5_CRNCH_annotated.mp4

Notes

  • 2D normalized coordinates: x,y∈[0,1] relative to image width/height (values can be outside the range if the estimated point is out-of-frame). z is depthlike (negative is closer).

  • 3D world coordinates: x,y,z are in meters in a world coordinate space centered near the hips.

  • visibility: confidence for each landmark’s presence in the frame.

7. Optional: compute simple joint angles

We will explore this in he next guide

Once you have landmarks, you can compute feature engineering targets like elbow or knee angles. Below is a tiny utility to compute an angle between three named landmarks per frame.

8. Notes & best practices

  • Timestamps matter: in VIDEO mode you must pass timestamp_ms that increases with frames; we compute it from frame index and FPS.

  • Tracking saves compute: in VIDEO/LIVE_STREAM the task performs pose tracking so the full model isn’t re-run every frame (helps latency).

  • Out-of-frame landmarks: 2D normalized x,y can be outside [0,1] if a joint is off‑screen; use visibility to filter.

  • Model choice: start with full, switch to lite for underpowered laptops or large batches, use heavy when you need the highest accuracy and can afford the speed.

  • Stride: a cheap speedup is frame_stride=2 (½ the frames) or higher.

  • Ethics & consent: if students process videos of people, teach consent, privacy, and secure storage.


Troubleshooting

  • If you see NoneType for results, ensure the model path exists and your video actually contains a person.

  • If you get slowdowns or memory pressure, try frame_stride=2 or the "lite" model.

  • On some platforms OpenCV MP4 writing may need codecs; if a saved video is empty, try a different fourcc (e.g., cv2.VideoWriter_fourcc(*"avc1")) or install opencv-python-headless alternatives.

Happy exploring!