Setting-up Smoothing Filters for MediaPipe Pose Estimation Pipeline using Python: A Practical Guide
Hands-on guide for applying Simple Moving Average filter to MediaPipe Pose Estimation Pipeline and help set up the development environment to test and compare effectiveness of various types of filters.
Table of Contents
- Introduction
- Prerequisites
- Version and Environment Details
- MediaPipe Pipeline Setup
- Class Design for Pose Estimation
- Simple Moving Average Filter
- What the future holds
Introduction
Accurately tracking human poses from video is a complex challenge, especially when body parts are obscured or move out of frame, causing jitter and unreliable data. This can be critical in applications ranging from sports analytics to AI surveillance. MediaPipe Pose Landmarker is a powerful tool for pose detection, but raw landmark data can be noisy. Applying smoothing filters like Kalman, Exponential Moving Average, and One-Euro helps reduce jitter and improve the stability of pose estimations.
Prerequisites
- Basic knowledge of Python programming.
- Familiarity with computer vision concepts.
- Working installation of Python (version 3.7 or above).
- Experience with NumPy and OpenCV is beneficial.
Version and Environment Details
This guide uses the latest MediaPipe Pose Landmarker models and APIs released in January 2025. Ensure you have:
- Python 3.7 or above
- MediaPipe installed via
python -m pip install mediapipe --upgrade - The appropriate pre-trained model from the Official Solutions Guide (recommended: Full model for demonstration)
MediaPipe Pipeline Setup
Installation
$ python -m pip install mediapipeDownload your preferred pose landmarker model from the official guide. For this demonstration, the full model is recommended. Also ensure you have OpenCV installed (pip install opencv-python).
Why?
mediapipe: Core pose detection library.opencv-python: Efficient video reading/writing and per-frame image manipulation.numpy: Fast numerical array operations for frame/image and landmark buffer handling.
Why the ‘full’ model?
The “Full” pose landmarker strikes a balance between accuracy and performance. The “Lite” version is fastest but can miss fine-grained landmarks in challenging scenarios; “Heavy” is most accurate but computationally expensive. For most prototyping and smoothing filter evaluation, “Full” is the optimal choice.
Landmark Structure in MediaPipe
Landmarks are represented as Protocol Buffers.
Landmark: Holdsx,y,z,visibility,presence.NormalizedLandmarkList: List of normalized landmarks (each in range).
These enable efficient serialization, manipulation, and extension for additional processing and filtering steps.
Landmark Data Structure: Protobuf Definitions
MediaPipe uses Protocol Buffers (protobuf) to define and serialize landmark data in a format that is lightweight and easily portable across languages and platforms.
Protocol Buffers, often called Protobuf, provide a method for serializing structured data in a language-neutral, platform-neutral, and extensible format. In Python, Protobuf is used to define data structures, generate Python classes from these definitions, and then serialize and de-serialize data using these classes.
You can see the protobuf definition here: mediapipe/mediapipe/framework/formats/landmark.proto
You can see something like:
message Landmark {
optional float x = 1;
optional float y = 2;
optional float z = 3;
// Landmark visibility. Should stay unset if not supported.
// Float score of whether landmark is visible or occluded by other objects.
// Landmark considered as invisible also if it is not present on the screen
// (out of scene bounds). Depending on the model, visibility value is either a
// sigmoid or an argument of sigmoid.
optional float visibility = 4;
// Landmark presence. Should stay unset if not supported.
// Float score of whether landmark is present on the scene (located within
// scene bounds). Depending on the model, presence value is either a result of
// sigmoid or an argument of sigmoid function to get landmark presence
// probability.
optional float presence = 5;
}
// Group of Landmark protos.
message LandmarkList {
repeated Landmark landmark = 1;
}
// Group of LandmarkList protos.
message LandmarkListCollection {
repeated LandmarkList landmark_list = 1;
}
// A normalized version of above Landmark proto. All coordinates should be
// within [0, 1].
message NormalizedLandmark {
optional float x = 1;
optional float y = 2;
optional float z = 3;
optional float visibility = 4;
optional float presence = 5;
}Visibility and presence scores allow you to down-weight, ignore, or treat noisy/occluded landmarks differently during smoothing. For example, a Kalman filter can set its gain lower or freeze movement for landmarks with low visibility.
When applying a smoothing filter, you copy the x, y, z, visibility, presence fields, apply your transform, and build a new NormalizedLandmarkList for downstream visualization or analysis.
Transformation and Filtering of Landmarks
Transformation functions are integrated into the PoseEstimator class. They operate on each landmark's x, y, z, visibility, presence and output modified landmark tuples that can be further processed or visualized.
Example placeholder transform:
def transform_func(self, x, y, z, visibility, presence, idx):
return x+0.1, y+0.1, z+0.1, visibility, presenceReal filtering algorithms (like Kalman, EMA, etc.) will replace this function to reduce jitter and output more stable landmark trajectories.
We have the basic pose estimation setup. Now we want a way to visualize our results. We can get just that from the solutions module. We will write this function inside the class scope in the next section.
We can process each frame through this function and convert the video frames to new frames annotated with the pose landmarks data. This will help us visualize the difference between our original and the filtered outputs.
Class Design for Pose Estimation
Having a clean, modular pipeline is essential for reliable pose smoothing experiments. In this section, we’ll organize the PoseEstimator class so readers can both understand and reuse every component. We'll rely on a configuration file for paths and hyper-parameters (tips on this below), and each code snippet will be self-contained, type-annotated, well-commented, and use idiomatic Python throughout.
Get Debasish Raut’s stories in your inbox
Join Medium for free to get updates from this writer.
Code is organized into a PoseEstimator class responsible for initializing the model, processing video frames, extracting and transforming pose landmarks, and managing buffers. Key methods handle model setup, video reading, frame processing, and landmark transformation.
1. Project-Wide Configuration
All settings and file paths are managed in config.py for easy tuning and reproducibility.
config.py
VIDEO_PATH = "../Assets/explosive.mp4"
VIDEO_OUT_PATH = "../out/mp_annot.mp4"
MODEL_PATH = "../models/pose_landmarker_full.task"2. Imports and Class Definition
from config import MODEL_PATH, VIDEO_OUT_PATH, VIDEO_PATH, SMA_WINDOW
import cv2
import mediapipe as mp
import numpy as np
from collections import deque
from mediapipe.framework.formats import landmark_pb2
from mediapipe import solutions3. PoseEstimator Initialization Block
What it does: Instantiates model, initializes buffers, prepares lists for results.
class PoseEstimator:
"""
A class to handle pose estimation from video files using MediaPipe.
"""
def __init__(self, model_path: str):
"""
Initialize the PoseEstimator with model path and smoothing window size.
Args:
model_path: Path to the MediaPipe pose landmarker model (.task file)
sma_window: Simple Moving Average window size for landmark smoothing
"""
self.model_path = model_path
self.pose_detector = self._init_pose_detector()
self.landmarks_list = []4. Model and Video IO Setup Methods
def _init_pose_detector(self) -> mp.tasks.vision.PoseLandmarker:
"""
Initialize MediaPipe PoseLandmarker with VIDEO running mode.
Returns:
PoseLandmarker: Configured pose detection model
"""
BaseOptions = mp.tasks.BaseOptions
PoseLandmarker = mp.tasks.vision.PoseLandmarker
PoseLandmarkerOptions = mp.tasks.vision.PoseLandmarkerOptions
VisionRunningMode = mp.tasks.vision.RunningMode
options = PoseLandmarkerOptions(
base_options=BaseOptions(model_asset_path=self.model_path),
running_mode=VisionRunningMode.VIDEO
)
return PoseLandmarker.create_from_options(options)
def _init_video_capture(self, video_path: str) -> tuple:
"""
Initialize video capture and extract video properties.
Args:
video_path: Path to the video file
Returns:
tuple: (VideoCapture object, fps, frame_width, frame_height)
"""
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
return cap, fps, frame_width, frame_height
def _init_writer(self, out_path, fourcc, fps, dimensions):
return cv2.VideoWriter(out_path, fourcc, fps, dimensions)5. Frame Processing and Landmark Extraction
Clear separation: Prepares and processes a single frame, making the main video loop readable and maintainable.
def _process_frame(self, image: np.ndarray, timestamp_ms: int) -> tuple:
"""
Process a single frame through the pose detector.
Args:
image: Frame in RGB format (numpy array)
timestamp_ms: Timestamp in milliseconds for video frame
Returns:
tuple: (detection_result, mp.Image)
"""
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=image)
detection_result = self.pose_detector.detect_for_video(mp_image, timestamp_ms)
return detection_result, mp_image6. Landmark Transformation and Smoothing
The transform_func is a placeholder where our filter functions will go. Currently, this is a simple transform function that does not do much other than add a small value to the incoming landmark values. The resultant output is a slightly shifted pose landmarks( you can test it out and play around with it).
The transform_landmarks function is used to process each landmark frame through our filter function one by one and storing them in one variable before returning the value to the concerned function.
def transform_func(self, x, y, z, visibility, presence, idx):
return x+0.25, y, z, visibility, presence
def _transform_landmarks(self, pose_landmarks_list: list, transform_func) -> list:
"""
Applies transform_func to each landmark in each detected pose.
Args:
pose_landmarks_list: list of lists of landmarks (detection_result.pose_landmarks)
transform_func: function that takes (x, y, z) and returns transformed (x, y, z)
Returns:
transformed_landmarks_list: list of lists of transformed landmarks as tuples (x, y, z)
"""
transformed_landmarks_list = []
if not pose_landmarks_list:
return transformed_landmarks_list
for pose_landmarks in pose_landmarks_list:
transformed_landmarks = []
for i,landmark in enumerate(pose_landmarks):
x, y, z, v, p = landmark.x, landmark.y, landmark.z, landmark.visibility, landmark.presence
x_t, y_t, z_t, v_t, p_t = transform_func(x, y, z, v, p, idx=i)
transformed_landmarks.append((x_t, y_t, z_t, v_t, p_t))
transformed_landmarks_list.append(transformed_landmarks)
self.landmarks_list.append(transformed_landmarks)
return transformed_landmarks_list7. Converting Processed Landmarks to MediaPipe’s NormalizedLandmarkList
After applying smoothing or filtering algorithms to your pose landmarks, you’ll need to convert your processed results back into MediaPipe’s NormalizedLandmarkList protobuf format. This step ensures compatibility with downstream MediaPipe utilities—such as visualization, analytics, and exporting—by serializing your transformed data into the original data structure expected by the framework.
def _create_landmark_proto(self, transformed_landmarks: list) -> landmark_pb2.NormalizedLandmarkList:
"""
Convert transformed landmarks to MediaPipe protobuf format.
Args:
transformed_landmarks: List of transformed landmark tuples
Returns:
landmark_pb2.NormalizedLandmarkList: Protobuf representation of landmarks
"""
proto_landmarks = landmark_pb2.NormalizedLandmarkList()
for pose_landmarks in transformed_landmarks:
proto_landmarks.landmark.extend([
landmark_pb2.NormalizedLandmark(
x=x, y=y, z=z, visibility=visibility, presence=presence
) for (x, y, z, visibility, presence) in pose_landmarks
])
return proto_landmarks8. Visualization / Annotation Utilities
def draw_landmarks_on_image(self, rgb_image, proto_landmarks):
"""
Draw pose landmarks on the image given protobuf NormalizedLandmarkList.
Args:
rgb_image: Input RGB image as a numpy array.
proto_landmarks: A landmark_pb2.NormalizedLandmarkList object of pose landmarks.
Returns:
annotated_image: Image with landmarks drawn.
"""
annotated_image = np.copy(rgb_image)
if proto_landmarks is None or len(proto_landmarks.landmark) == 0:
return annotated_image
solutions.drawing_utils.draw_landmarks(
annotated_image,
proto_landmarks,
solutions.pose.POSE_CONNECTIONS,
solutions.drawing_styles.get_default_pose_landmarks_style()
)
return annotated_image8. Main Video Loop
def process_video(self, video_path: str, out_path: str) -> landmark_pb2.NormalizedLandmarkList:
"""
Process an entire video file and extract pose landmarks.
Args:
video_path: Path to the video file
Returns:
All extracted landmarks data of a single frame
"""
cap, fps, width, height = self._init_video_capture(video_path)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = self._init_writer(out_path, fourcc, fps, (width, height))
frame_count = 0
try:
while cap.isOpened():
success, image = cap.read()
if not success:
break
# if(frame_count > 20):
# break
timestamp_ms = int(cap.get(cv2.CAP_PROP_POS_MSEC))
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
detection_result, mp_image = self._process_frame(image_rgb, timestamp_ms)
if detection_result.pose_landmarks:
transformed_points = self._transform_landmarks(detection_result.pose_landmarks, self.transform_func )
proto_landmarks = self._create_landmark_proto(transformed_points)
annotated_image = self.draw_landmarks_on_image(mp_image.numpy_view(), proto_landmarks)
annotated_image_bgr = cv2.cvtColor(annotated_image, cv2.COLOR_RGB2BGR)
out.write(annotated_image_bgr)
frame_count += 1
finally:
cap.release()
out.release()
return proto_landmarks
9. Accessing Processed Landmarks
def get_all_landmarks(self):
return self.landmarks_listTesting the base model without filtering
if __name__ == "__main__":
videoPath = "../Assets/mediapipe_female.mp4"
modelPath = "../models/pose_landmarker_full.task"
estimator = PoseEstimator(model_path=modelPath)
pose_landmarks_list = estimator.process_video(video_path=videoPath)
print("Pose estimation completed successfully!")
print(f"Extracted landmarks: {pose_landmarks_list}")Initialize the videoPath & modelPath and run the main function of the class. If all the assets and models are in place you will get a output video which is MediaPipe PoseLandmark annotated with a slightly transformed values.
SIMPLE MOVING AVERAGE FILTER
A simple moving average (SMA) is calculated by taking the arithmetic mean of a given set of values over a specified period. This is as simple as it gets, we basically take the mean points in the neighborhood in order to smooth out the jitter.
We should start with this function as an experiment, and we can gradually move to more complex filter functions and compare our results as we go.
Each pose from MediaPipe PoseEstimation consists of 33 landmarks. To apply SMA across a video, we maintain a buffer for each landmark, storing the last N sets of coordinates (where N = sma_window). Each buffer allows us to quickly compute the mean of each coordinate over the window.
class PoseEstimator:
. . .
self.pose_detector = self._init_pose_detector()
self.landmark_buffers = self._init_buffers()
self.landmarks_list = []
. . .These buffers will be updated very fast on each frame pose estimation cycle, so we need an apt data structure that is designed for fast appends and pop operations. That is why Python’s collections.deque (Double Ended Queue) is the best fit for it. By setting maxlen=sma_window, it automatically keeps only the N most recent items: when a new frame is added and the buffer is full, the oldest frame is dropped. Also appending and popping elements from a deque are O(1) operations (constant time), which is much faster than using a list.
So we write a new function within the class to initialize the buffers as collections.dequeue:
def _init_buffers(self) -> dict:
"""
Initialize deque buffers for each of the 33 pose landmarks.
Each buffer stores the last `sma_window` frames of landmark coordinates.
Returns:
dict: Dictionary mapping landmark index to deque buffer
"""
return {i: deque(maxlen=self.sma_window) for i in range(33)}
def reset_buffers(self):
"""Reset all landmark buffers. Call this before processing a new video."""
self.landmark_buffers = self._init_buffers()Next we add our transform function to calculate moving averages.
def sma_transform_func(self, x, y, z, visibility, presence, landmark_buffers, idx):
buffer = landmark_buffers[idx]
buffer.append([x, y, z])
if len(buffer) == buffer.maxlen:
mean_x, mean_y, mean_z = np.mean(buffer, axis=0)
return mean_x, mean_y, mean_z, visibility, presence
else:
return x, y, z, visibility, presenceWe simply find the mean of the pose landmark values(x,y, & z co-ordinate) for the Pose Landmark node based on the number idx.
In our transform_landmarks function, we will modify it to include the transformation properly:
def _transform_landmarks(self, pose_landmarks_list: list, transform_func, landmark_buffers) -> list:
"""
Applies transform_func to each landmark in each detected pose.
Args:
pose_landmarks_list: list of lists of landmarks (detection_result.pose_landmarks)
transform_func: function that takes (x, y, z) and returns transformed (x, y, z)
Returns:
transformed_landmarks_list: list of lists of transformed landmarks as tuples (x, y, z)
"""
transformed_landmarks_list = []
if not pose_landmarks_list:
return transformed_landmarks_list
for pose_landmarks in pose_landmarks_list:
transformed_landmarks = []
for i,landmark in enumerate(pose_landmarks):
x, y, z, v, p = landmark.x, landmark.y, landmark.z, landmark.visibility, landmark.presence
x_t, y_t, z_t, v_t, p_t = sma_transform_func(x, y, z, v, p, landmark_buffers, idx=i)
transformed_landmarks.append((x_t, y_t, z_t, v_t, p_t))
transformed_landmarks_list.append(transformed_landmarks)
self.landmarks_list.append(transformed_landmarks)
return transformed_landmarks_listIn process_video function, we include the new transform function instead of the old one. But we will also use the old shift transformation because it looked much more comprehensible that way.
Given below is the entire class definition with SMA filter applied with a X-Shift transformation applied to the resultant from SMA filter:
class PoseEstimator:
"""
A class to handle pose estimation from video files using MediaPipe.
"""
def __init__(self, model_path: str):
"""
Initialize the PoseEstimator with model path and smoothing window size.
Args:
model_path: Path to the MediaPipe pose landmarker model (.task file)
sma_window: Simple Moving Average window size for landmark smoothing
"""
self.model_path = model_path
self.pose_detector = self._init_pose_detector()
self.landmarks_list = []
def _init_buffers(self) -> dict:
"""
Initialize deque buffers for each of the 33 pose landmarks.
Each buffer stores the last `sma_window` frames of landmark coordinates.
Returns:
dict: Dictionary mapping landmark index to deque buffer
"""
return {i: deque(maxlen=SMA_WINDOW) for i in range(33)}
def _init_pose_detector(self) -> mp.tasks.vision.PoseLandmarker:
"""
Initialize MediaPipe PoseLandmarker with VIDEO running mode.
Returns:
PoseLandmarker: Configured pose detection model
"""
BaseOptions = mp.tasks.BaseOptions
PoseLandmarker = mp.tasks.vision.PoseLandmarker
PoseLandmarkerOptions = mp.tasks.vision.PoseLandmarkerOptions
VisionRunningMode = mp.tasks.vision.RunningMode
options = PoseLandmarkerOptions(
base_options=BaseOptions(model_asset_path=self.model_path),
running_mode=VisionRunningMode.VIDEO
)
return PoseLandmarker.create_from_options(options)
def _init_video_capture(self, video_path: str) -> tuple:
"""
Initialize video capture and extract video properties.
Args:
video_path: Path to the video file
Returns:
tuple: (VideoCapture object, fps, frame_width, frame_height)
"""
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
return cap, fps, frame_width, frame_height
def _init_writer(self, out_path, fourcc, fps, dimensions):
return cv2.VideoWriter(out_path, fourcc, fps, dimensions)
def _process_frame(self, image: np.ndarray, timestamp_ms: int) -> tuple:
"""
Process a single frame through the pose detector.
Args:
image: Frame in RGB format (numpy array)
timestamp_ms: Timestamp in milliseconds for video frame
Returns:
tuple: (detection_result, mp.Image)
"""
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=image)
detection_result = self.pose_detector.detect_for_video(mp_image, timestamp_ms)
return detection_result, mp_image
def transform_func(self, x, y, z, visibility, presence, idx):
return x+0.25, y, z, visibility, presence
def sma_transform_func(self, x, y, z, visibility, presence, landmark_buffers, idx):
buffer = landmark_buffers[idx]
buffer.append([x, y, z])
if len(buffer) == buffer.maxlen:
mean_x, mean_y, mean_z = np.mean(buffer, axis=0)
return mean_x, mean_y, mean_z, visibility, presence
else:
return x, y, z, visibility, presence
def _transform_landmarks(self, pose_landmarks_list: list, transform_func, landmark_buffers) -> list:
"""
Applies transform_func to each landmark in each detected pose.
Args:
pose_landmarks_list: list of lists of landmarks (detection_result.pose_landmarks)
transform_func: function that takes (x, y, z) and returns transformed (x, y, z)
Returns:
transformed_landmarks_list: list of lists of transformed landmarks as tuples (x, y, z)
"""
transformed_landmarks_list = []
if not pose_landmarks_list:
return transformed_landmarks_list
for pose_landmarks in pose_landmarks_list:
transformed_landmarks = []
for i,landmark in enumerate(pose_landmarks):
x, y, z, v, p = landmark.x, landmark.y, landmark.z, landmark.visibility, landmark.presence
x_t, y_t, z_t, v_t, p_t = transform_func(x, y, z, v, p, landmark_buffers, idx=i)
x_t1, y_t1, z_t1, v_t1, p_t1 = self.transform_func(x_t, y_t, z_t, v_t, p_t, idx=i)
transformed_landmarks.append((x_t1, y_t1, z_t1, v_t1, p_t1))
transformed_landmarks_list.append(transformed_landmarks)
self.landmarks_list.append(transformed_landmarks)
return transformed_landmarks_list
def _create_landmark_proto(self, transformed_landmarks: list) -> landmark_pb2.NormalizedLandmarkList:
"""
Convert transformed landmarks to MediaPipe protobuf format.
Args:
transformed_landmarks: List of transformed landmark tuples
Returns:
landmark_pb2.NormalizedLandmarkList: Protobuf representation of landmarks
"""
proto_landmarks = landmark_pb2.NormalizedLandmarkList()
for pose_landmarks in transformed_landmarks:
proto_landmarks.landmark.extend([
landmark_pb2.NormalizedLandmark(
x=x, y=y, z=z, visibility=visibility, presence=presence
) for (x, y, z, visibility, presence) in pose_landmarks
])
return proto_landmarks
def draw_landmarks_on_image(self, rgb_image, proto_landmarks):
"""
Draw pose landmarks on the image given protobuf NormalizedLandmarkList.
Args:
rgb_image: Input RGB image as a numpy array.
proto_landmarks: A landmark_pb2.NormalizedLandmarkList object of pose landmarks.
Returns:
annotated_image: Image with landmarks drawn.
"""
annotated_image = np.copy(rgb_image)
if proto_landmarks is None or len(proto_landmarks.landmark) == 0:
return annotated_image
solutions.drawing_utils.draw_landmarks(
annotated_image,
proto_landmarks,
solutions.pose.POSE_CONNECTIONS,
solutions.drawing_styles.get_default_pose_landmarks_style()
)
return annotated_image
def reset_buffers(self):
"""Reset all landmark buffers. Call this before processing a new video."""
self.landmark_buffers = self._init_buffers()
def process_video(self, video_path: str, out_path: str) -> landmark_pb2.NormalizedLandmarkList:
"""
Process an entire video file and extract pose landmarks.
Args:
video_path: Path to the video file
Returns:
All extracted landmarks data of a single frame
"""
cap, fps, width, height = self._init_video_capture(video_path)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = self._init_writer(out_path, fourcc, fps, (width, height))
frame_count = 0
buffers = self._init_buffers()
try:
while cap.isOpened():
success, image = cap.read()
if not success:
break
# if(frame_count > 20):
# break
timestamp_ms = int(cap.get(cv2.CAP_PROP_POS_MSEC))
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
detection_result, mp_image = self._process_frame(image_rgb, timestamp_ms)
if detection_result.pose_landmarks:
transformed_points = self._transform_landmarks(detection_result.pose_landmarks, self.sma_transform_func, buffers )
proto_landmarks = self._create_landmark_proto(transformed_points)
annotated_image = self.draw_landmarks_on_image(mp_image.numpy_view(), proto_landmarks)
annotated_image_bgr = cv2.cvtColor(annotated_image, cv2.COLOR_RGB2BGR)
out.write(annotated_image_bgr)
frame_count += 1
finally:
cap.release()
out.release()
return proto_landmarks
def get_all_landmarks(self):
return self.landmarks_listWhat the future holds
MediaPipe used to provide smoothing options in older versions but the new version does not let us smooth out the landmarks. A python module is needed to work with the MediaPipe solutions for faster implementation of various filter algorithms.
As the field of pose estimation matures, the pipeline is shifting toward more modular and customizable architectures. Developers who embrace and contribute to well-documented Python modules for pose landmark smoothing will drive both research and real-world adoption forward. With the right community, resources, and standards, the future holds far more robust, jitter-free human pose tracking — fueling the next generation of computer vision applications.
Ready to level up? Next, we’ll implement and benchmark advanced filters — including Kalman, EMA, and One-Euro — so you can choose the best fit for your mission-critical pose estimation workflows. Stay tuned for more.










