3D矢量旋转?


74

我有两个向量作为Python列表和一个角度。例如:

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

围绕轴旋转v向量时,最佳/最简单的方法是获得结果向量?

对于轴向量所指向的观察者,旋转应该看起来是逆时针方向。这称为右手法则


14
我感到非常惊讶,SciPy(或类似的易于访问的软件包)中没有此功能;向量旋转不是那么奇特。
Mads Skjern

Answers:



115

使用Euler-Rodrigues公式

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]

6
@bougui:使用np.linalg.norm代替np.sqrt(np.dot(...))似乎对我来说是一个不错的改进,但timeit测试表明,至少在我的机器上,它的np.sqrt(np.dot(...))速度比快2.5倍np.linalg.norm,所以我坚持使用np.sqrt(np.dot(...))
unutbu 2012年

3
sqrtmath在标量上,来自Python模块的速度甚至更快。scipy.linalg.norm可能比快np.linalg.norm; 我已经向NumPy提交了一个补丁,可以更改linalg.norm为use dot,但尚未被合并。
Fred Foo 2013年

3
我想它math.sqrt总是比np.sqrt在标量上运行时要快,因为np.sqrt如果必须检查其标量输入,它的整体性能将会降低。
unutbu 2013年

1
这非常整洁,您是否愿意为2D添加等效项?我知道,对于旋转wrt OX轴,我们可以将新坐标计算为:(x*np.cos(theta)-y*np.sin(theta), x*np.sin(theta)+y*np.cos(theta)),但是当旋转轴不再是OX时应如何修改?感谢您的任何提示。

1
轴不应该是x,y或z吗?那是什么向量
Schütze

47

具有numpy / scipy函数的单线。

我们使用以下内容:

a为沿的单位矢量,即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 (此处的代码)计算指数的taylor系列:
\sum_{k=0}^{20} \frac{1}{k!} (θ A)^k ,这虽然很费时,但可读性和安全性高。如果您只需要很少的旋转,但是向量很多,这可能是一个好方法。


引用“什么是...,则M = exp(θA)是旋转矩阵”的引用是什么。?
ximiki

谢谢。此Wikipedia页面(en.wikipedia.org/wiki/…)也很有用。最后一个问题:您能否解释一下如何cross(eye(3), axis/norm(axis)*theta)获得“跨产品矩阵”?
ximiki

20

我只是想提到,如果需要速度,可以将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]

16

这是一种使用非常快的四元数的优雅方法。我可以通过适当矢量化的numpy数组来计算每秒1000万转。它依赖于此处找到的对numpy的四元数扩展。

四元数论:四元数是一个具有一个实数和3个虚数维的数字,通常将其写为q = w + xi + yj + zk“ i”,“ j”,“ k”是虚数维。正如单位复数“ c”可以表示所有2d旋转c=exp(i * theta),单位四元数“ q”可以表示所有3d旋转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)

首先,构造一个由4个元素组成的numpy数组,其中要旋转的矢量vector和旋转轴的实数分量w = 0 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)似乎花费了比看起来更长的时间,np.exp(qlog)尽管这似乎只不过等于quat.quaternion(q.real, *(-q.imag))
user3622450

我知道这是一个旧线程,但是如果有人可以看一下,我对此方法的实现存在疑问:stackoverflow.com/questions/64988678/…–
Lucy

6

我为Python {2,3}制作了一个相当完整的3D数学库。它仍然不使用Cython,但是在很大程度上依赖于numpy的效率。您可以在此处找到pip:

python[3] -m pip install math3d

或者看看我的gitweb http://git.automatics.dyndns.dk/?p=pymath3d.git ,现在也可以在github上查看:https//github.com/mortlind/pymath3d

安装后,您可以在python中创建可以旋转矢量或成为变换对象一部分的方向对象。例如,以下代码段组成的方向表示围绕轴[1,2,3]旋转1 rad,将其应用于矢量[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)>

据我所知,这比使用上面BM发布的scipy的oneliner效率高大约四倍。但是,它需要安装我的math3d软件包。


我知道这很奇怪,但找不到其他与您联系的方式。是否可以使用math3d库更轻松地在任意轴上创建3D函数的2D投影?例如,假设从z轴在xy平面上投影正态分布。现在想象一下,以极角theta远离z轴移动(如球坐标符号),并将法线距离投影到现在也相对于xy旋转了theta的平面上吗?就像正交投影+积分。如果您愿意,我可以为此提出一个新问题。
ljetibo

嗨,ljetbo,我认为这听起来很困难,或者对math3d来说不是很容易。我猜该函数暗示一种解析函数,而math3d在点集上的效果更好。此外,您似乎在谈论平面上的标量场(R(2)),而math3d处理特殊欧几里得群(SE +(3))。可能可以做您想做的事,但是我还没有立即想到如何将解析函数与math3d混合使用。
Morten Lind

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数组,因此在使用此包时不会引入新的数据结构。


2

使用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根据有关轮换的数据,还有其他几种使用方式:


题外话注:一号线的代码是不是一定暗示的那样被一些用户更好的代码。


2

使用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)

1

我需要围绕嵌入该模型的三个轴{x,y,z}之一旋转3D模型,这是在numpy中搜索如何执行此操作的最高结果。我使用了以下简单功能:

def rotate(X, theta, axis='x'):
  '''Rotate multidimensional array `X` `theta` degrees around axis `axis`'''
  c, s = np.cos(theta), np.sin(theta)
  if axis == 'x': return np.dot(X, np.array([
    [1.,  0,  0],
    [0 ,  c, -s],
    [0 ,  s,  c]
  ]))
  elif axis == 'y': return np.dot(X, np.array([
    [c,  0,  -s],
    [0,  1,   0],
    [s,  0,   c]
  ]))
  elif axis == 'z': return np.dot(X, np.array([
    [c, -s,  0 ],
    [s,  c,  0 ],
    [0,  0,  1.],
  ]))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.