The Geometry of Movement: Analyzing Detected Human Pose Landmarks

The Geometry of Movement: Analyzing Detected Human Pose Landmarks

In the dynamic field of human movement, angles formed by various body parts play a pivotal role. Whether applied to sports science, animation, or medical diagnostics, these angular measurements are fundamental in analyzing posture, optimizing movement efficiency, and enhancing gesture recognition. Through precise quantification of motion mechanics, professionals across disciplines can evaluate performance, monitor progression, and diagnose potential health issues effectively.

In this article, we delve deep into the practical application of angle calculations in the study of human movement. MediaPipe, developed by Google, provides advanced solutions designed for real-time pose estimation, making it a valuable tool for researchers and professionals seeking deeper understanding of biomechanics. That's why I utilized it to demonstrate practical examples. However, let's first review the mathematical principles.

2D angle calculations

Calculating angles in 2D space can be approached in several ways, depending on the given data and the required precision. Traditionally, the cosine rule is employed, especially when dealing with clearly defined triangles formed by three points.

Cosine Rule Method

The cosine of the angle θ at any point A between points B and C can be determined if the lengths of the sides of the triangle they form are known. The formula is:

$$\cos(\theta) = \frac{b^2 + c^2 - a^2}{2bc}$$

where θ is the angle at point A, and a, b, and c are the lengths of the sides opposite to points A, B, and C respectively. This method directly relates to the physical dimensions and distances between points, making it intuitive and straightforward for manual calculations.

Here is the code snipped implementing the angle calculation using the cosine rule.

import math

def calculate_angle_cosine_rule(A, B, C):
    # Calculate the side lengths
    a = math.sqrt((B[0] - C[0])**2 + (B[1] - C[1])**2)
    b = math.sqrt((A[0] - C[0])**2 + (A[1] - C[1])**2)
    c = math.sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2)

    # Apply the cosine rule
    cos_theta = (b**2 + c**2 - a**2) / (2 * b * c)
    theta = math.acos(cos_theta)  # in radians
    return math.degrees(theta)  # Convert to degrees

# Example usage
angle_cosine = calculate_angle_cosine_rule((1, 0), (0, 0), (0, 1))
print(f"Angle using cosine rule: {angle_cosine} degrees")

Transition to Vector Approach

While the cosine rule provides a clear and educational view on angle calculation, the vector approach is often more practical in computational applications due to its efficiency and robustness against small numerical errors.

Mathematical Explanation

Vectors Formation: Vectors are formed from the points given. For points A, B, and C, two vectors are created, one from point B to point A and the other from point B to point C. These vectors are represented as:

$$\vec{BA} = (A_x - B_x, A_y - B_y)$$

$$\vec{BC} = (C_x - B_x, C_y - B_y)$$

Dot Product: The dot product of two vectors 𝑢^ and 𝑣^, which are defined as

$$\vec{u} = (u_x, u_y) \quad \text{and} \quad \vec{v} = (v_x, v_y)\text{,}$$

is calculated as:

$$\vec{u} \cdot \vec{v} = u_x \times v_x + u_y \times v_y$$

​Applying this to our vectors:

$$\vec{BA} \cdot \vec{BC} = (A_x - B_x) \times (C_x - B_x) + (A_y - B_y) \times (C_y - B_y)$$

Norm of Vectors: The norm (or magnitude) of a vector 𝑢^ is calculated using:

$$\|\vec{u}\| = \sqrt{u_x^2 + u_y^2}$$

For our vectors:

$$\|\vec{BA}\| = \sqrt{(A_x - B_x)^2 + (A_y - B_y)^2}$$

$$\|\vec{BC}\| = \sqrt{(C_x - B_x)^2 + (C_y - B_y)^2}$$

Cosine of the Angle: The cosine of the angle θ between 𝐵𝐴^ and 𝐵𝐶^ is found using the formula:

$$\cos(\theta) = \frac{\vec{BA} \cdot \vec{BC}}{\|\vec{BA}\| \times \|\vec{BC}\|}$$

This ratio gives the cosine of the angle based on the dot product and the magnitudes of the vectors.

Calculating the Angle: Finally, the angle 𝜃 is obtained by taking the arccos (inverse cosine) of cos(θ):

$$\theta = \arccos\left(\cos(\theta)\right)$$

This vector-based approach to calculating angles in 2D is not only mathematically elegant but also computationally efficient, particularly when using libraries that support vectorized operations. Using vectors simplifies the calculation process and can handle scenarios where the points do not form a perfect triangle due to practical data collection issues. This method employs the dot product and the norms (magnitudes) of vectors, making it highly adaptable to high-performance computing environments such as numerical libraries in Python.

Here is the code snipped with the Python implementation using the numpy library.

import numpy as np

def calculate_2d_angle(A, B, C):
    """Calculate the angle between three 2D points based on their X and Y coordinates."""
    BA = np.array([A[0] - B[0], A[1] - B[1]])
    BC = np.array([C[0] - B[0], C[1] - B[1]])
    cosine_angle = np.dot(BA, BC) / (np.linalg.norm(BA) * np.linalg.norm(BC))
    angle = np.arccos(np.clip(cosine_angle, -1.0, 1.0))
    return np.degrees(angle)

# Example usage
angle_vector = calculate_2d_angle((1, 0), (0, 0), (0, 1))
print(f"Angle using vector approach: {angle_vector} degrees")

Limitations of 2D Angle Calculations

While 2D angle calculations are useful in many contexts, they come with significant limitations:

  • Dependence on Camera Angle:

    The accuracy of 2D measurements heavily relies on the camera's position relative to the subject. Incorrect positioning can lead to erroneous angle calculations, which may misrepresent the actual posture or movement mechanics.

  • Lack of Depth Perception:

    2D calculations do not account for movements along the z-axis (depth), which can be critical in fully understanding complex motions. This limitation can lead to oversimplified conclusions that might not be effective in applications requiring detailed spatial awareness, such as advanced physical therapy or biomechanical analysis.

  • Perspective Distortion:

    When using 2D angle calculations, perspective distortion can occur, especially when parts of the body are closer to the camera. This can artificially inflate or reduce the perceived angles, leading to inaccurate assessments.

3D angle calculations

Transitioning from 2D to 3D angle calculations enhances our ability to model and understand real-world phenomena. This shift involves embracing concepts from linear algebra that allow us to extend the vector-based methods used in 2D into the richer, more complex three-dimensional space. By adding a third coordinate, the z-axis, we gain a more complete understanding of spatial relationships and dynamics, essential in fields such as physics, engineering, and computer graphics.

Mathematical Explanation

Calculating the angle between three 3D points is done using the vector approach, which expands the 2D vector method into three dimensions. The calculations are similar to those in 2D calculations, but with an additional z coordinate.

Vectors Formation: Again, vectors are formed from the points given. They are represented as:

$$\vec{BA} = (A_x - B_x, A_y - B_y, A_z - B_z)$$

$$\vec{BC} = (C_x - B_x, C_y - B_y, C_z - B_z)$$

Dot Product: The dot product formula is expanded to accommodate the z coordinate:

$$\vec{BA} \cdot \vec{BC} = (A_x - B_x) \times (C_x - B_x) + (A_y - B_y) \times (C_y - B_y) + (A_z - B_z) \times (C_z - B_z)$$

Norm of Vectors: The same goes for vector magnitude calculation:

$$\|\vec{BA}\| = \sqrt{(A_x - B_x)^2 + (A_y - B_y)^2 + (A_z - B_z)^2}$$

$$\|\vec{BC}\| = \sqrt{(C_x - B_x)^2 + (C_y - B_y)^2 + (C_z - B_z)^2}$$

Cosine of the Angle: The cosine formula remains unchanged:

$$\cos(\theta) = \frac{\vec{BA} \cdot \vec{BC}}{\|\vec{BA}\| \times \|\vec{BC}\|}\text{,}$$

as does the angle 𝜃 calculation which is again obtained by taking the inverse cosine of cos(θ):

$$\theta = \arccos\left(\cos(\theta)\right)$$

Here is a python code snipped implementing the 3D angle calculation.

import numpy as np

def calculate_3d_angle(A, B, C):
    """Calculate the angle between three 3D points."""
    AB = np.array(B) - np.array(A)
    BC = np.array(C) - np.array(B)
    dot_product = np.dot(AB, BC)
    magnitude_AB = np.linalg.norm(AB)
    magnitude_BC = np.linalg.norm(BC)
    angle_rad = np.arccos(np.clip(dot_product / (magnitude_AB * magnitude_BC), -1.0, 1.0))
    return np.degrees(angle_rad)

# Example usage
angle_vector_3d = calculate_3d_angle([1, 0, 0], [0, 0, 0], [0, 1, 0])
print(f"Angle using vector approach in 3D: {angle_vector_3d} degrees")

The Impact of Perspective on Angle Calculations

The perspective from which motion is observed significantly influences angle calculations, affecting the interpretation and analysis of movements. In 2D motion analysis, the angle observed can vary dramatically with changes in the camera's position, leading to different interpretations of the same motion. This variation arises because 2D calculations cannot account for the depth component, which often results in a loss of accuracy when translating real-world movements into a two-dimensional plane. Conversely, 3D angle calculations, which include depth, aim to provide consistent results regardless of the observer's perspective.

Experimental Setup

To investigate how perspective affects angle calculations, I conducted an experiment. I performed a series of exercises captured from three different camera angles simultaneously. This setup aimed to keep the captures consistent and guarantee that each camera recorded the same motion from a distinct viewpoint. For illustration, the angle we aim to calculate is the right knee angle, determined by landmarks: right ankle, right knee, and right hip.

The images provided showcase the same exercise performed and captured from three distinct angles.

The images display the detected landmarks and connections overlaid on the original images. Detection was carried out using MediaPipe, and the overlay was created using Python's cv2 library.

After normalizing the coordinates but preserving the aspect ratio, I plotted the landmark coordinates using Python's MatPlotLib library. As observed, the calculated 2D knee angles vary significantly across these different perspectives:

Frontal View: The knee angle appears significantly different, often misleading due to depth compression and overlapping body parts. This view flattens depth cues and can misrepresent the true knee bend, making it unreliable for accurate angle assessments.

Diagonal View: The knee angle from this perspective offers a compromise, providing a broader view of the knee's movement than the frontal view but with less accuracy than the side view due to some remaining depth distortion.

Side View: This view typically offers a more accurate depiction of knee flexion by eliminating depth compression. It is valuable for precise biomechanical analysis as it clearly shows the joint's angle without the interference of overlapping body structures.

While the frontal view may not be ideal for accurately assessing knee angles, it could still be more reliable for other joint angles, such as those of the elbow or shoulder. The suitability of a particular perspective depends greatly on the specific joints and movements being analyzed. Each angle and body part may have a view that offers the most accurate visual and analytical insight, highlighting the importance of choosing the right perspective for each biomechanical evaluation.

Transitioning into the third dimension, the perspective from which a movement is observed should not influence the calculated 3D angle. This is because 3D motion analysis captures spatial relationships in their entirety, including depth, eliminating the distortions and limitations inherent in 2D projections. Unlike 2D views, where perspective can drastically alter the perceived angles of joints like knees, elbows, or shoulders, 3D modeling offers a consistent view that remains true regardless of the observer's position. This ensures that every measurement reflects the actual physical arrangement and movement of body parts in three-dimensional space.

By leveraging a 3D rotating plot, we can visually demonstrate this principle. This type of visualization allows us to observe the same motion from multiple angles, providing a comprehensive understanding of the dynamics at play.

This plot is constructed from a single image, specifically the front view.

In theory, all perspectives should yield roughly the same 3D plot and resulting angle calculations, as 3D motion analysis is designed to be impervious to changes in viewpoint. However, in practical applications, the 3D angles calculated can still show variation due to differences in detection capabilities and the challenges inherent in capturing three-dimensional data accurately.

Calculated 3D angles: front view = 94°, diagonal view = 84°, side view = 80°.

Depth (in)accuracy in MediaPipe's 3D pose detection

  • Sensor Accuracy and Depth Component Calculation: When utilizing systems like MediaPipe for 3D motion analysis, depth is typically inferred using monocular cues from a single camera. Unlike systems with dedicated depth sensors (like stereo cameras or LiDAR), MediaPipe uses machine learning models to estimate the three-dimensional positions of keypoints by analyzing visual patterns and how objects typically move in three-dimensional space. This estimation is based on previously learned data from diverse datasets but lacks the direct measurement of depth, which can introduce errors or less precise calculations compared to methods using actual depth sensors.

  • Software and Algorithm Limitations with MediaPipe: MediaPipe, while robust in handling real-time video data and efficient in processing, relies heavily on its underlying models' accuracy and the quality of the input data. The models are trained to predict 3D landmarks from 2D images, but this process can be susceptible to various issues:

    • Occlusion and Complex Movements: When parts of the body are obscured or when movements are highly dynamic or complex, the accuracy of the predicted key points can decrease. This is due to the model's reliance on visible cues, which can be incomplete in such scenarios.

    • Generalization and Environmental Variability: The pre-trained models in MediaPipe are designed to generalize across a wide range of human shapes, sizes, and environments. However, variations in lighting, background, and camera quality can affect the model's performance, leading to less reliable depth and angle estimations.

Other useful 3D calculations

Vertical angles

Vertical angles offer a specialized method for measuring the orientation of body segments relative to a vertical reference line, akin to how traditional joint angles are determined using the relationships between three landmarks. However, vertical angles focus on the alignment of a single vector with respect to gravity, making them particularly useful for evaluating posture and alignment.

Mathematical Explanation

The calculation of a vertical angle involves comparing a body segment's vector with a purely vertical reference vector. This vertical vector is typically defined to be perfectly upright relative to gravity, which means it has no deviation along the x and z axes and extends positively along the y-axis. The process is mathematically the same as regular 3D angle calculation, which was already explained in the previous section.

Practical example

Again, the calculation is illustrated with same exercise from 3 different angles. This time, the angle in question is the vertical trunk angle. Hip and shoulder midpoints are calculated by averaging the left and the right respective landmark coordinates.

Once again, the front view is not reliable for this purpose. The calculated angle actually shows the side lean angle, which is useful but not what we need at the moment. As anticipated, there is a significant difference in the calculated values, because having the right perspective is essential.

Calculated 2D angles: front view = 4°, diagonal view = 20°, side view = 27°.

The 3D plot shows the vertical trunk angle. Calculating in 3D is more consistent regardless of the viewpoint change.

Angle Between Non-Connected Vectors in 3D

When analyzing non-connected vectors, the goal is to grasp the directional relationship between different body parts that, although not connected by a joint, play a role in posture and movement. For example, the vectors from the heels to the toes describe the orientation of each foot. Calculating the angle between these vectors helps in evaluating how parallel the feet are in relation to each other, which is crucial for assessing stance in activities such as standing, walking, or specific athletic stances in sports.

Mathematical explanation

The process for calculating the angle between two non-connected vectors in 3D space is fundamentally the same as when calculating the angle between two vectors that are formed by three connected points. In both scenarios, the vectors are already defined or are established by connecting points. Here’s a brief overview of the steps involved, assuming that the vectors are explicitly provided:

  1. Dot Product: Calculate the dot product of the two vectors.

  2. Norm: Compute the magnitude (norm) of each vector.

  3. Cosine of the Angle: Use the dot product and magnitudes to find the cosine of the angle between the vectors.

  4. Angle Calculation: Determine the angle using the arccosine of the cosine value.

  5. Conversion to Degrees: Convert the result from radians to degrees for easier interpretation.

Here is a code snipped implementing the angle calculation:

import numpy as np

def calculate_vector_angle(vec1, vec2):
    # Convert lists to numpy arrays if not already np.array
    vec1 = np.array(vec1)
    vec2 = np.array(vec2)

    # Dot product of vec1 and vec2
    dot = np.dot(vec1, vec2)

    # Magnitudes of vec1 and vec2
    mag_vec1 = np.linalg.norm(vec1)
    mag_vec2 = np.linalg.norm(vec2)

    # Cosine of the angle
    cos_angle = dot / (mag_vec1 * mag_vec2)

    # Angle in radians, clamping the cosine value to avoid NaN due to floating point errors
    angle = np.arccos(np.clip(cos_angle, -1, 1))

    # Convert to degrees
    return np.degrees(angle)

# Example usage:
vec1 = [1, 0, 0]
vec2 = [0, 1, 0]
angle = calculate_vector_angle(vec1, vec2)
print(f"The angle between the vectors is {angle} degrees.")

Practical example

The angle is calculated using the function in the code snippet. It provides a crucial insight into a person's stance.

The colored lines in the rotating plot represent the vectors, between which the angle is calculated.

Point to plane distance

This can be particularly useful in biomechanics for understanding spatial relationships and alignments, for example, assessing how far a limb is deviating from a reference plane during movement, which can be essential for postural analysis or ergonomic assessments.

Mathematical explanation

To define a plane in 3D space, you need a point on the plane and a normal vector perpendicular to the plane. Given two vectors on the plane, the cross product of these vectors gives a vector that is perpendicular to both, thus providing a normal to the plane.

Normal Vector Calculation: Given two vectors vec1 and vec2 that lie on the plane, the normal of the plane can be calculated using the cross product of these vectors, which is then normalized to have a unit length.

Point-to-Plane Distance: The distance d from a point to a plane defined by a normal vector and a point on the plane is calculated using the formula:

$$d = \frac{\left| (\vec{p} - \vec{p_0}) \cdot \vec{n} \right|}{\|\vec{n}\|}\text{,}$$

where:

  • 𝑝^ is the point for which the distance to the plane is being calculated.

  • 𝑝(0)^ is a point on the plane.

  • 𝑛^ is the normalized normal vector of the plane.

  • ⋅ denotes the dot product.

The absolute value is used because distance is a scalar quantity and cannot be negative. Here is a simple code snipped implementing this procedure:

import numpy as np

def calculate_plane_normal(vec1, vec2):
    """Calculate the normalized normal vector of the plane defined by two vectors."""
    normal = np.cross(vec1, vec2)
    normal = normal / np.linalg.norm(normal)
    return normal

def point_to_plane_distance(point, plane_point, normal):
    """Calculate the distance from a point to a plane defined by a point on the plane and a normal vector."""
    vec = np.array(point) - np.array(plane_point)
    dot_product = np.dot(vec, normal)
    return abs(dot_product)

# Example usage:
vec1 = np.array([1, 0, 0])  # Vector from right shoulder to left shoulder
vec2 = np.array([0, 1, 0])  # Vector from right shoulder to right hip
normal = calculate_plane_normal(vec1, vec2)

plane_point = np.array([0, 0, 0])  # Assume right shoulder as a point on the plane
point = np.array([1, 1, 1])  # Some arbitrary point, e.g., elbow midpoint
distance = point_to_plane_distance(point, plane_point, normal)
print(f"The distance from the point to the plane is {distance:.2f} units.")

Practical example

In the provided example, two vectors are used to define the torso plane during a push-up, offering a clear demonstration of how 3D geometric calculations can be applied in biomechanics. The vectors from the right shoulder to the left shoulder and from the right shoulder to the right hip create a plane that ideally represents the orientation of the upper body or torso during the exercise.

Green and blue arrows represent the two vectors defining the plane, and the dotted red line shows the shortest distance from the point of interest to the plane. In this example, the midpoint of the elbow serves as the point of interest. Measuring its distance to the defined torso plane can indicate how much the arm deviates from an ideal alignment. This information is crucial in exercises like push-ups, where the arm position relative to the torso can indicate effectiveness.

Calculated distance: 0.23.

Since the values are normalized, the distance measured is not expressed in standardized units like meters or inches. However, the relative distance is still highly valuable for monitoring changes or trends in alignment over time. This approach allows for consistent comparative analysis, making it useful for tracking improvements in technique, progression in physical therapy, or changes in body mechanics during training or rehabilitation.

Conclusion

By employing MediaPipe, I have illustrated not just the theory but also the practical application of these concepts, bringing to life the complex dynamics of human posture and motion.

For those interested in diving deeper into the practical aspects, all the code used in my examples is available in my GitHub repository. You can access and explore these resources to implement and experiment with the techniques discussed here: mp3d_visualization.