I'm probably a bit late and zabop's answer already points to the right direction. I just want to clarify two things.
There are several ambiguities when we work with transformations that can make things more confusing. The two things that might make the code here a bit confusing are:
I'm starting from your example above:
import numpy as np
r_0 = np.array([[-0.02659679, -0.00281247, 0.99964229],
[ 0.76308514, -0.64603356, 0.01848528],
[ 0.64575048, 0.76330382, 0.01932857]])
r_1 = np.array([[ 0.05114056, -0.03815443, 0.99796237],
[-0.30594799, 0.95062582, 0.05202294],
[-0.95067369, -0.30798506, 0.03694226]])
The way I would calculate a rotation matrix that rotates r_0
to r_1
is the following (different from your code!):
r0_to_r1 = r_1.dot(r_0.T)
r0_to_r1
Result:
array([[ 0.99635252, 0.08212126, 0.0231898 ],
[ 0.05746796, -0.84663889, 0.52905579],
[ 0.06308011, -0.52579339, -0.84827012]])
I use the extrinsic convention for concatenation of rotation matrices, that is, r_1
is applied after r_0.T
. (If r_0
and r_1
were real numbers, we would write r_1 - r_0
to obtain a number that transforms r_0
to r_1
.)
You can verify that r0_to_r1
rotates from r_0
to r_1
:
from numpy.testing import assert_array_almost_equal
# verify correctness: apply r0_to_r1 after r_0
assert_array_almost_equal(r_1, r0_to_r1.dot(r_0))
# would raise an error if test fails
Anyway, the intrinsic convention would also work:
r0_to_r1_intrinsic = r_0.T.dot(r_1)
assert_array_almost_equal(r_1, r_0.dot(r0_to_r1_intrinsic))
Since zabop introduced pytransform3d, I would also like to clarify that scipy uses active rotation matrices and the rotation matrix that pytransform3d.rotations.euler_xyz_from_matrix
produces is a passive rotation matrix! This wasn't documented so clearly in previous versions. You can transform an active rotation matrix to a passive rotation matrix and vice versa with the matrix transpose. Both pytransform3d's function and scipy's Rotation.to_euler("xyz", ...)
use the intrinsic concatenation convention.
from scipy.spatial.transform import Rotation as R
r = R.from_matrix(r0_to_r1)
euler_xyz_intrinsic_active_degrees = r.as_euler('xyz', degrees=True)
euler_xyz_intrinsic_active_degrees
Result: array([-148.20762964, -3.6166255 , 3.30106818])
You can obtain the same result with pytransform3d (note that we obtain the passive rotation matrix by .T
):
import pytransform3d.rotations as pr
euler_xyz_intrinsic_active_radians = pr.euler_xyz_from_matrix(r0_to_r1.T)
np.rad2deg(euler_xyz_intrinsic_active_radians)
Result: array([-148.20762951, -3.61662542, 3.30106799])
You can also obtain the rotation matrix from euler angles with pytransform3d (note that we obtain the active rotation matrix by .T
):
r0_to_r1_from_euler = pr.matrix_from_euler_xyz(euler_xyz_intrinsic_active_radians).T
r0_to_r1_from_euler
Result:
array([[ 0.99635251, 0.08212125, 0.0231898 ],
[ 0.05746796, -0.84663889, 0.52905579],
[ 0.06308011, -0.52579339, -0.84827013]])
r_0
andr_1
the same asrot_mat_0
androt_mat_1
respectively? – Parsonagenp.degrees(r.as_euler('xyz', degrees=True)
doesn't look right. If you can getas_euler()
to return values in degrees, you shouldn't need to then convert the result to degrees! – Nika