3D向量的旋转?

86
我有两个向量作为Python列表和一个角度。例如:
v = [3, 5, 0]
axis = [4, 4, 1]
theta = 1.2  # In radians.

什么是将向量 v 绕着 axis 旋转后得到的结果向量的最佳/最简单方法?
旋转应该对于一个观察者来说是逆时针的,这个观察者的视角是 axis 向量的指向。这被称为right hand rule

17
我觉得很惊讶,在SciPy(或类似易于使用的软件包)中居然没有这种功能;向量旋转并不算是很奇特的操作。 - Mads Skjern
11个回答

134

使用欧拉-罗德里格斯公式

import numpy as np
import math

def rotation_matrix(axis, theta):
    """
    Return the rotation matrix associated with counterclockwise rotation about
    the given axis by theta radians.
    """
    axis = np.asarray(axis)
    axis = axis / math.sqrt(np.dot(axis, axis))
    a = math.cos(theta / 2.0)
    b, c, d = -axis * math.sin(theta / 2.0)
    aa, bb, cc, dd = a * a, b * b, c * c, d * d
    bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
    return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
                     [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
                     [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]])

v = [3, 5, 0]
axis = [4, 4, 1]
theta = 1.2 

print(np.dot(rotation_matrix(axis, theta), v)) 
# [ 2.74911638  4.77180932  1.91629719]

8
@bougui:对我来说,使用np.linalg.norm而不是np.sqrt(np.dot(...))似乎是一个不错的改进,但是timeit测试表明,在我的机器上,np.sqrt(np.dot(...))np.linalg.norm快2.5倍,所以我还是坚持使用np.sqrt(np.dot(...)) - unutbu
3
Python的math模块中的sqrt函数在标量计算时甚至更快。scipy.linalg.norm可能比np.linalg.norm更快;我已经向NumPy提交了一个补丁,将linalg.norm改为使用dot,但尚未合并。 - Fred Foo
3
在操作标量时,我认为math.sqrt始终比np.sqrt更快,因为如果np.sqrt需要检查其输入是否是标量,它的整体性能会变慢。 - unutbu
1
这非常整洁,您能否添加2D的等效内容?我知道对于围绕OX轴旋转,我们可以计算新坐标:(x*np.cos(theta)-y*np.sin(theta), x*np.sin(theta)+y*np.cos(theta)),但是当旋转轴不再是OX时,应如何修改呢?感谢任何提示。 - user6039682
1
轴不应该是x、y或z吗?那个向量是什么? - Schütze
显示剩余4条评论

57

使用numpy/scipy函数的一行代码。

我们使用以下内容:

a为沿着axis方向的单位向量,即a = axis/norm(axis)
并且令A = I × a 为与a相关的斜对称矩阵,即标识矩阵与a的叉积

M = exp(θ A)是旋转矩阵。

from numpy import cross, eye, dot
from scipy.linalg import expm, norm

def M(axis, theta):
    return expm(cross(eye(3), axis/norm(axis)*theta))

v, axis, theta = [3,5,0], [4,4,1], 1.2
M0 = M(axis, theta)

print(dot(M0,v))
# [ 2.74911638  4.77180932  1.91629719]

expm (代码在此处) 计算指数的泰勒级数:
\sum_{k=0}^{20} \frac{1}{k!} (θ A)^k 因此它很耗时间,但易读且安全。如果需要进行少量旋转但大量向量操作,这可能是一个不错的选择。


引用“让a为...然后M = exp(θ A)是旋转矩阵”的参考文献是什么? - ximiki
谢谢。这个维基百科页面(https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula#Matrix_notation)也很有用。最后一个问题:您能解释一下如何使用 cross(eye(3), axis/norm(axis)*theta) 得到“叉积矩阵”吗? - ximiki

22

我只想提一下,如果需要速度,可以将unutbu的代码放入scipy的weave.inline中,并将已存在的矩阵作为参数传递,这样可以使运行时间减少20倍。

代码(在rotation_matrix_test.py中):

import numpy as np
import timeit

from math import cos, sin, sqrt
import numpy.random as nr

from scipy import weave

def rotation_matrix_weave(axis, theta, mat = None):
    if mat == None:
        mat = np.eye(3,3)

    support = "#include <math.h>"
    code = """
        double x = sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
        double a = cos(theta / 2.0);
        double b = -(axis[0] / x) * sin(theta / 2.0);
        double c = -(axis[1] / x) * sin(theta / 2.0);
        double d = -(axis[2] / x) * sin(theta / 2.0);

        mat[0] = a*a + b*b - c*c - d*d;
        mat[1] = 2 * (b*c - a*d);
        mat[2] = 2 * (b*d + a*c);

        mat[3*1 + 0] = 2*(b*c+a*d);
        mat[3*1 + 1] = a*a+c*c-b*b-d*d;
        mat[3*1 + 2] = 2*(c*d-a*b);

        mat[3*2 + 0] = 2*(b*d-a*c);
        mat[3*2 + 1] = 2*(c*d+a*b);
        mat[3*2 + 2] = a*a+d*d-b*b-c*c;
    """

    weave.inline(code, ['axis', 'theta', 'mat'], support_code = support, libraries = ['m'])

    return mat

def rotation_matrix_numpy(axis, theta):
    mat = np.eye(3,3)
    axis = axis/sqrt(np.dot(axis, axis))
    a = cos(theta/2.)
    b, c, d = -axis*sin(theta/2.)

    return np.array([[a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c)],
                  [2*(b*c+a*d), a*a+c*c-b*b-d*d, 2*(c*d-a*b)],
                  [2*(b*d-a*c), 2*(c*d+a*b), a*a+d*d-b*b-c*c]])
时间:
>>> import timeit
>>> 
>>> setup = """
... import numpy as np
... import numpy.random as nr
... 
... from rotation_matrix_test import rotation_matrix_weave
... from rotation_matrix_test import rotation_matrix_numpy
... 
... mat1 = np.eye(3,3)
... theta = nr.random()
... axis = nr.random(3)
... """
>>> 
>>> timeit.repeat("rotation_matrix_weave(axis, theta, mat1)", setup=setup, number=100000)
[0.36641597747802734, 0.34883809089660645, 0.3459300994873047]
>>> timeit.repeat("rotation_matrix_numpy(axis, theta)", setup=setup, number=100000)
[7.180983066558838, 7.172032117843628, 7.180462837219238]

17

这里有一种优雅的方法,使用四元数进行旋转计算非常快速;我可以使用适当矢量化的numpy数组每秒计算1000万次旋转。它依赖于numpy的四元数扩展程序,可以在这里找到。

四元数理论: 四元数是一个带有一个实数和三个虚数维度的数字,通常写成q = w + xi + yj + zk,其中“i”、“j”、“k”是虚数维度。就像单位复数“c”可以表示所有二维旋转一样,c=exp(i * theta),单位四元数“q”可以通过q=exp(p)表示所有三维旋转,其中“p”是由您的轴和角度设置的纯虚四元数。

我们首先将您的轴和角度转换为四元数,其虚数维度由您的旋转轴给出,其大小由旋转角的一半以弧度表示。以下是构建4个元素向量(w, x, y, z)的方法:

import numpy as np
import quaternion as quat

v = [3,5,0]
axis = [4,4,1]
theta = 1.2 #radian

vector = np.array([0.] + v)
rot_axis = np.array([0.] + axis)
axis_angle = (theta*0.5) * rot_axis/np.linalg.norm(rot_axis)

首先,使用实部为0构建一个由4个元素组成的numpy数组,分别用于需要旋转的向量vector和旋转轴rot_axis。然后通过归一化并乘以所需角度的一半theta来构造轴角表示。查看这里了解为什么需要使用一半的角度。

现在使用库创建四元数vqlog,通过取指数得到单位旋转四元数q

vec = quat.quaternion(*v)
qlog = quat.quaternion(*axis_angle)
q = np.exp(qlog)

最后,通过以下操作计算向量的旋转。

v_prime = q * vec * np.conjugate(q)

print(v_prime) # quaternion(0.0, 2.7491163, 4.7718093, 1.9162971)

现在只需放弃实际元素,您就可以得到旋转后的向量!

v_prime_vec = v_prime.imag # [2.74911638 4.77180932 1.91629719] as a numpy array

请注意,如果您需要通过多个连续旋转来旋转向量,则此方法特别高效,因为四元数乘积可以计算为 q = q1 * q2 * q3 * q4 * ... * qn,然后仅在最后使用 v' = q * v * conj(q) 通过 'q' 旋转向量。

此方法通过 explog 函数,为您提供了轴角和 3D 旋转运算符之间的无缝转换(是的,log(q) 只返回轴角表示!)。有关四元数乘法等工作原理的进一步说明,请参见此处


令人惊讶的是,尽管np.conjugate(q)看起来等同于quat.quaternion(q.real, *(-q.imag)),但似乎比np.exp(qlog)慢。 - user3622450
我知道这是一个旧的帖子,但如果有人能够看一下这里的方法实现,我有一个关于它的问题:https://stackoverflow.com/questions/64988678/applying-quaternion-rotation-to-a-vector-time-series - Lucy

13

12

使用scipy的Rotation.from_rotvec()方法。该方法的参数是旋转向量(一个单位向量)与旋转角度(弧度制)的乘积。

from scipy.spatial.transform import Rotation
from numpy.linalg import norm


v = [3, 5, 0]
axis = [4, 4, 1]
theta = 1.2

axis = axis / norm(axis)  # normalize the rotation vector first
rot = Rotation.from_rotvec(theta * axis)

new_v = rot.apply(v)  
print(new_v)    # results in [2.74911638 4.77180932 1.91629719]

根据旋转数据的不同,可以有几种使用 Rotation 的方法:


离题笔记:仅仅因为某些用户暗示一行代码更好,并不意味着它就是更好的代码。


9

我为Python{2,3}制作了一个相当完整的三维数学库。它仍然没有使用Cython,但是依赖于numpy的高效性。您可以在此处使用pip找到它:

python[3] -m pip install math3d

请查看我的gitwebhttp://git.automatics.dyndns.dk/?p=pymath3d.git,现在也在github上:https://github.com/mortlind/pymath3d。安装完成后,在Python中,您可以创建旋转向量的方向对象,或将其作为变换对象的一部分。例如,以下代码段组成了一个方向,表示绕轴[1,2,3]旋转1弧度,将其应用于向量[4,5,6],并打印结果:
import math3d as m3d
r = m3d.Orientation.new_axis_angle([1,2,3], 1)
v = m3d.Vector(4,5,6)
print(r * v)

输出结果将会是:
<Vector: (2.53727, 6.15234, 5.71935)>

根据我的测试,相比B. M.上面发布的使用scipy的单行代码,这种方法更加高效,大约快了四倍。不过,它需要安装我的math3d软件包。


我知道这很奇怪,但我无法找到其他联系你的方式。是否可以使用math3d库更轻松地创建沿任意轴的三维函数的二维投影?例如,想象一下从z轴投影xy平面上的正态分布。现在想象一下沿极角theta(如球坐标符号)远离z轴并将正常分布投影到该平面,该平面现在也旋转了theta与xy的参考。这就像正交投影+积分。如果您愿意,我可以为此打开一个新问题。 - ljetibo
嗨,ljetbo,我认为这听起来很困难,或者用math3d并不容易。我猜这个函数会涉及到一个解析函数,而math3d更适合处理点集。此外,你似乎在谈论平面上的标量场(R(2)),而math3d处理的是特殊欧几里得群(SE +(3))。也许可以实现你想要的功能,但我暂时不知道如何将解析函数与math3d混合使用。 - Morten Lind

3

使用pyquaternion非常简单;要安装它(在Python中),请在控制台中运行:

import pip;
pip.main(['install','pyquaternion'])

安装完成后:

  from pyquaternion import Quaternion
  v = [3,5,0]
  axis = [4,4,1]
  theta = 1.2 #radian
  rotated_v = Quaternion(axis=axis,angle=theta).rotate(v)

3

也可以使用四元数理论来解决:

def angle_axis_quat(theta, axis):
    """
    Given an angle and an axis, it returns a quaternion.
    """
    axis = np.array(axis) / np.linalg.norm(axis)
    return np.append([np.cos(theta/2)],np.sin(theta/2) * axis)

def mult_quat(q1, q2):
    """
    Quaternion multiplication.
    """
    q3 = np.copy(q1)
    q3[0] = q1[0]*q2[0] - q1[1]*q2[1] - q1[2]*q2[2] - q1[3]*q2[3]
    q3[1] = q1[0]*q2[1] + q1[1]*q2[0] + q1[2]*q2[3] - q1[3]*q2[2]
    q3[2] = q1[0]*q2[2] - q1[1]*q2[3] + q1[2]*q2[0] + q1[3]*q2[1]
    q3[3] = q1[0]*q2[3] + q1[1]*q2[2] - q1[2]*q2[1] + q1[3]*q2[0]
    return q3

def rotate_quat(quat, vect):
    """
    Rotate a vector with the rotation defined by a quaternion.
    """
    # Transfrom vect into an quaternion 
    vect = np.append([0],vect)
    # Normalize it
    norm_vect = np.linalg.norm(vect)
    vect = vect/norm_vect
    # Computes the conjugate of quat
    quat_ = np.append(quat[0],-quat[1:])
    # The result is given by: quat * vect * quat_
    res = mult_quat(quat, mult_quat(vect,quat_)) * norm_vect
    return res[1:]

v = [3, 5, 0]
axis = [4, 4, 1]
theta = 1.2 

print(rotate_quat(angle_axis_quat(theta, axis), v))
# [2.74911638 4.77180932 1.91629719]

2
免责声明:我是这个软件包的作者。
虽然为旋转提供特殊类可能很方便,但在某些情况下,人们需要旋转矩阵(例如,与像scipy中的affine_transform函数等其他库一起使用)。为了避免每个人都实现自己的小矩阵生成函数,存在一个微小的纯Python软件包,仅提供方便的旋转矩阵生成函数。该软件包位于github上(mgen),可以通过pip安装:
pip install mgen

从自述文件中复制的示例用法:
import numpy as np
np.set_printoptions(suppress=True)

from mgen import rotation_around_axis
from mgen import rotation_from_angles
from mgen import rotation_around_x

matrix = rotation_from_angles([np.pi/2, 0, 0], 'XYX')
matrix.dot([0, 1, 0])
# array([0., 0., 1.])

matrix = rotation_around_axis([1, 0, 0], np.pi/2)
matrix.dot([0, 1, 0])
# array([0., 0., 1.])

matrix = rotation_around_x(np.pi/2)
matrix.dot([0, 1, 0])
# array([0., 0., 1.])

请注意,这些矩阵只是普通的numpy数组,因此在使用该软件包时不会引入新的数据结构。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接