v = [3, 5, 0]
axis = [4, 4, 1]
theta = 1.2 # In radians.
什么是将向量
v
绕着 axis
旋转后得到的结果向量的最佳/最简单方法?旋转应该对于一个观察者来说是逆时针的,这个观察者的视角是
axis
向量的指向。这被称为right hand rule。v = [3, 5, 0]
axis = [4, 4, 1]
theta = 1.2 # In radians.
v
绕着 axis
旋转后得到的结果向量的最佳/最简单方法?axis
向量的指向。这被称为right hand rule。使用欧拉-罗德里格斯公式:
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]
np.linalg.norm
而不是np.sqrt(np.dot(...))
似乎是一个不错的改进,但是timeit
测试表明,在我的机器上,np.sqrt(np.dot(...))
比np.linalg.norm
快2.5倍,所以我还是坚持使用np.sqrt(np.dot(...))
。 - unutbumath
模块中的sqrt
函数在标量计算时甚至更快。scipy.linalg.norm
可能比np.linalg.norm
更快;我已经向NumPy提交了一个补丁,将linalg.norm
改为使用dot
,但尚未合并。 - Fred Foomath.sqrt
始终比np.sqrt
更快,因为如果np.sqrt
需要检查其输入是否是标量,它的整体性能会变慢。 - unutbu(x*np.cos(theta)-y*np.sin(theta), x*np.sin(theta)+y*np.cos(theta))
,但是当旋转轴不再是OX时,应如何修改呢?感谢任何提示。 - user6039682使用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
因此它很耗时间,但易读且安全。如果需要进行少量旋转但大量向量操作,这可能是一个不错的选择。
cross(eye(3), axis/norm(axis)*theta)
得到“叉积矩阵”吗? - ximiki我只想提一下,如果需要速度,可以将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]
这里有一种优雅的方法,使用四元数进行旋转计算非常快速;我可以使用适当矢量化的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
来构造轴角表示。查看这里了解为什么需要使用一半的角度。
现在使用库创建四元数v
和qlog
,通过取指数得到单位旋转四元数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' 旋转向量。
此方法通过 exp
和 log
函数,为您提供了轴角和 3D 旋转运算符之间的无缝转换(是的,log(q)
只返回轴角表示!)。有关四元数乘法等工作原理的进一步说明,请参见此处。
np.conjugate(q)
看起来等同于quat.quaternion(q.real, *(-q.imag))
,但似乎比np.exp(qlog)
慢。 - user3622450vector
的类,其中包含一个方法A.rotate(theta,B)
。如果您不想在A
上调用该方法,它还提供了一个辅助函数rotate(A,theta,B)
。使用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
的方法:
from_quat
:从四元数初始化。
from_dcm
:从方向余弦矩阵初始化。
from_euler
:从欧拉角初始化。
离题笔记:仅仅因为某些用户暗示一行代码更好,并不意味着它就是更好的代码。
我为Python{2,3}制作了一个相当完整的三维数学库。它仍然没有使用Cython,但是依赖于numpy的高效性。您可以在此处使用pip找到它:
python[3] -m pip install math3d
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软件包。
使用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)
也可以使用四元数理论来解决:
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]
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.])