scipy中三维旋转/欧拉角相关的函数介绍(含代码)

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!")

参考

  1. https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.from_euler.html#scipy.spatial.transform.Rotation.from_euler
  2. https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.as_euler.html
  3. https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.as_rotvec.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值