HybrIK icon indicating copy to clipboard operation
HybrIK copied to clipboard

Great job! A quick question about SMPL joint angles?

Open brainleacher opened this issue 2 years ago • 3 comments

When running inferences in the wild, how do I extract the joint angles from pose_output? I see the joint positions in the object, but not the rotation angles? Is it embedded in the SMPL object? I would love to evaluate the accuracy of the joint twist angles as your paper states that you are able to decompose.

Again, great job! This is the first paper that I have seen that can decompose all 3 joint angles using inference.

Thanks!

brainleacher avatar Jun 02 '22 00:06 brainleacher

@brainleacher Maybe you need quaternion to angle conversion? https://github.com/Jeff-sjtu/HybrIK/blob/8a798c9d427f1867032d063b5647d6705f4a1265/scripts/demo_image.py#L108

So hopefully this helps:

#!/usr/bin/env python3

from math import radians, degrees, cos, sin, atan2, asin, pow, floor

class Quaternion:
    def __init__(self, x: float, y: float, z: float, w: float) -> None:
        if round(pow(x, 2) + pow(y, 2) + pow(z, 2) + pow(w, 2), 1) == 1:
            self.x = x
            self.y = y
            self.z = z
            self.w = w
        else:
            raise ValueError(
                "Invalid quaternion values: x={}, y={}, z={}, w={}".format(
                    x,
                    y,
                    z,
                    w
                )
            )
    
    @classmethod
    def from_euler(cls, 
                   phi: float, theta: float, psi: float,
                   precision: int) -> 'Quaternion':
        """Creates an quaternion by euler angles and returns it

        Args:
            phi (float): Rotation angle around the X axis
            theta (float): Rotation angle around the Y axis
            psi (float): Rotation angle around the Y axis
            precision (int): Round the results to 'precision' digits after the decimal point

        Returns:
            Quaternion: Created quaternion (x, y, z, w)

        >>> str(Quaternion.from_euler(-90, -180, 90, 12))
        '(0.5, -0.5, -0.5, 0.5)'

        .. _Source:
            https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python

        """
        # x axis rotation angle
        cos_phi_half = cos(radians(phi) / 2)
        sin_phi_half = sin(radians(phi) / 2)
        # y axis rotation angle
        cos_theta_half = cos(radians(theta) / 2)
        sin_theta_half = sin(radians(theta) / 2)
        # z axis rotation angle
        cos_psi_half = cos(radians(psi) / 2)
        sin_psi_half = sin(radians(psi) / 2)
        # Calculation
        return cls(
            x = float(round(sin_phi_half * cos_theta_half * cos_psi_half - cos_phi_half * sin_theta_half * sin_psi_half, precision)),
            y = float(round(cos_phi_half * sin_theta_half * cos_psi_half + sin_phi_half * cos_theta_half * sin_psi_half, precision)),
            z = float(round(cos_phi_half * cos_theta_half * sin_psi_half - sin_phi_half * sin_theta_half * cos_psi_half, precision)),
            w = float(round(cos_phi_half * cos_theta_half * cos_psi_half + sin_phi_half * sin_theta_half * sin_psi_half, precision))
        )

    def to_euler(self) -> tuple[float, float, float]:
        """Exports an quaternion as an euler angle tuple

        Returns:
            tuple[float, float, float]: (x, y, z)

        >>> str(Quaternion.from_euler(-90, -180, 90, 12).to_euler())
        (90.0, 0.0, -90.0)

        .. _Source:
            https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python

        """
        # x axis rotation angle
        t0 = 2 * (self.w * self.x + self.y * self.z)
        t1 = 1 - 2 * (self.x * self.x + self.y * self.y)
        x = atan2(t0, t1)
        # y axis rotation angle
        t2 = 2 * (self.w * self.y - self.z * self.x)
        t2 = 1 if t2 > 1 else t2
        t2 = -1 if t2 < -1 else t2
        y = asin(t2)
        # y axis rotation angle
        t3 = 2 * (self.w * self.z + self.x * self.y)
        t4 = 1 - 2 * (self.y * self.y + self.z * self.z)
        z = atan2(t3, t4)
        return degrees(x), degrees(y), degrees(z)

    def __str__(self) -> str:
        return "(" + ", ".join(
            (
                str(self.x), 
                str(self.y), 
                str(self.z), 
                str(self.w)
            )
        ) + ")"

Edit: Fixed the check in Quaternion.__init__() and added rounding parameter to Quaternion.from_euler()

Example usage:

q = Quaternion(float(-0.000002366145), float(-0.000047797843), float(0.000008500623), float(-1))
print(q)
print(q.to_euler())

q = Quaternion.from_euler(-90, -180, 90, precision = 12)
print(q)
print(q.to_euler())

'Euler' means roatations by angles instead of quaternions.

vivi90 avatar Jun 04 '22 12:06 vivi90

Thanks @vivi90 , I actually prefer to look at the quaternions rather than the Euler angles. Where are the quaternions burried in the pose_output object (either in rotational axis and rotation angle form, or the 4x4 matrix?) Thanks!

brainleacher avatar Jun 06 '22 00:06 brainleacher

Hi @brainleacher, you can refer to this line to obtain the output quaternions.

Jeff-sjtu avatar Jun 06 '22 02:06 Jeff-sjtu