Rot.from_rotvec
下面这段代码展示了Rot.from_rotvec可以起到生成rodrigues_matrix的作用
rotvec使用一个向量表示旋转轴,使用向量的模长表示向量绕轴旋转的角度
def get_rodrigues_matrix(axis, angle):
axis = np.array(axis) / np.linalg.norm(axis) # 确保单位向量
identity = np.eye(3)
s1 = np.array([
[0, -axis[2], axis[1]],
[axis[2], 0, -axis[0]],
[-axis[1], axis[0], 0]
])
s2 = np.outer(axis, axis)
cos_angle = np.cos(angle)
sin_angle = np.sin(angle)
return cos_angle * identity + sin_angle * s1 + (1 - cos_angle) * s2
rotation = [1, 12, 13] # Z → X → Y 的度数
rotation_order = "ZXY"
rot_mat = {}
rot_mat["X"] = get_rodrigues_matrix([1, 0, 0], rotation[0])
rot_mat["Y"] = get_rodrigues_matrix([0, 1, 0], rotation[1])
rot_mat["Z"] = get_rodrigues_matrix([0, 0, 1], rotation[2])
# print(rot_mat)
new_rot_mat = {}
new_rot_mat["Z"] = Rot.from_rotvec(rotation[2] * np.array([0, 0, 1])).as_matrix()
new_rot_mat["X"] = Rot.from_rotvec(rotation[0] * np.array([1, 0, 0])).as_matrix()
new_rot_mat["Y"] = Rot.from_rotvec(rotation[1] * np.array([0, 1, 0])).as_matrix()
# print(rot_mat)
assert np.allclose(rot_mat["X"], new_rot_mat["X"])
assert np.allclose(rot_mat["Y"], new_rot_mat["Y"])
assert np.allclose(rot_mat["Z"], new_rot_mat["Z"])
Rot.from_euler
旋转顺序
大写字母是外在旋转,小写字母是内在旋转,字母顺序表示旋转顺序(下文会提到这个旋转顺序相当令人困惑)。内在旋转表示每次旋转都是在局部坐标系下进行,外在旋转表示旋转相对于绝对的坐标系进行。
以按照“XYZ”先后顺序旋转的欧拉角为例,内在(左乘):Rz@Ry@Rx@points,外在(右乘):Rx@Ry@Rz@points
对于同样的角度,内在旋转xyz顺序和外在旋转ZYX顺序,得到的结果一样
rotation = [1,2,3]# 默认弧度
# 二者相同
a_rot = Rot.from_euler("ZXY", rotation).as_matrix()
# 调整顺序
rotation = [rotation[2], rotation[1], rotation[0]]
b_rot = Rot.from_euler("yxz", rotation).as_matrix()
assert np.allclose(a_rot, b_rot)
旋转顺序解释
from_euler接受的参数“seq”顺序和我平时理解的是反着来的
根据下面这个例子可知,from_euler(“ZXY”)返回矩阵相当于"ZXY"顺序的右乘,也就是"YXZ"顺序的左乘,按道理来说应该写作欧拉角"Y->X->Z",最后执行的是z的旋转。
我将其解释为,from_euler接受的参数顺序是和人眼看到的一样,我们对一个坐标进行(我平时理解的)"YXZ"外在旋转变换,要先对坐标乘Rx,再乘Ry,最后乘Rz,写作Rz@Ry@Rx@points,这在我们的视觉中就是"ZXY"的顺序
# 原始参数(ZXY 顺序的弧度)
rotation = [1, 2, 3] # [Z_angle, X_angle, Y_angle]
rotation_order = "ZXY"
# 手动生成旋转矩阵(修正轴分配)
rot_mat = {
"Z": get_rodrigues_matrix([0, 0, 1], rotation[0]), # Z 轴
"X": get_rodrigues_matrix([1, 0, 0], rotation[1]), # X 轴
"Y": get_rodrigues_matrix([0, 1, 0], rotation[2]) # Y 轴
}
# 初始化单位矩阵
mat = np.eye(3, dtype=np.float64)
# 按 ZXY 顺序右乘旋转矩阵(修正顺序)
for s in rotation_order:
mat = mat @ rot_mat[s] # 右乘矩阵
# SciPy 生成旋转矩阵
grasp_rotation = Rot.from_euler("ZXY", rotation, degrees=False).as_matrix()
# 比较结果
print("Manual Matrix:\n", mat)
print("SciPy Matrix:\n", grasp_rotation)
assert np.allclose(mat, grasp_rotation, rtol=1e-5, atol=1e-8), "结果不一致"
参数顺序
接受一个长度为三的数组,而且这个数组的并不是三个位置依次对应“xyz”的角度,而是和seq参数一样。例如Rot.from_euler(“YXZ”)接受的数组的三个位置分别是"YXZ"的角度,可以看到rotation = [rotation[1], rotation[0], rotation[2]]
的操作。
下面的代码执行了一个旋转变换,可以看到第一个函数使用了"ZXY"顺序的欧拉角旋转。按照上一节的说法,第二个scipy函数使用正好与之相反的"YXZ"顺序作为参数
def apply_transformation(vertices, position, rotation, rotation_order="XYZ"):
"""
rotation: [rX, rY, rZ] in radian
"""
# process rotation
rot_mat = {}
rot_mat["X"] = get_rodrigues_matrix([1, 0, 0], rotation[0])
rot_mat["Y"] = get_rodrigues_matrix([0, 1, 0], rotation[1])
rot_mat["Z"] = get_rodrigues_matrix([0, 0, 1], rotation[2])
for s in rotation_order:
vertices = np.matmul(vertices, rot_mat[s].T)
# process position second
vertices = vertices + np.array(position)
return vertices
vertices = np.random.rand(3) # 10个点
position = [0, 0, 0] # 平移向量
rotation = [1, 1.5, 2] # 旋转角度(弧度)
# 第一段代码
result1 = apply_transformation(vertices, position, rotation, rotation_order="ZXY")
# 第二段代码
rotation = [rotation[1], rotation[0], rotation[2]]
grasp_rotation = Rot.from_euler("YXZ", rotation, degrees=False).as_matrix()
result2 = np.matmul(grasp_rotation, vertices)
# 比较结果
print("Result 1 (Manual):\n", result1)
print("Result 2 (SciPy):\n", result2)
print("Are results equal?", np.allclose(result1, result2))
as_euler
as_euler和from_euler相对应,as_euler输出什么,from_euler就接受什么,顺序一致,它们两个能互相转换。
import numpy as np
from scipy.spatial.transform import Rotation as Rot
import numpy as np
from scipy.spatial.transform import Rotation as R
# 定义一个旋转矩阵
rotation_matrix = np.array([
[ 0.14959902, -0.98768834 , 0.04573701 ],
[ 0.94453106, 0.15643447, 0.28877212 ],
[-0.2923717, 0. , 0.95630476]
])
# 将旋转矩阵转换为欧拉角(使用 ZXY 顺序)
euler_angles = R.from_matrix(rotation_matrix).as_euler('ZXY', degrees=True)
print("Euler angles (degrees):", euler_angles)
# 将欧拉角转换回旋转矩阵
reconstructed_rotation_matrix = R.from_euler('ZXY', euler_angles, degrees=True).as_matrix()
print("Reconstructed rotation matrix:\n", reconstructed_rotation_matrix)
# 比较原始旋转矩阵和重建的旋转矩阵
assert np.allclose(rotation_matrix, reconstructed_rotation_matrix, atol=1e-6), "Matrices do not match"
print("Matrices match successfully!")
参考
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.from_euler.html#scipy.spatial.transform.Rotation.from_euler
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.as_euler.html
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.as_rotvec.html