J'ai deux vecteurs comme Python et un angle. E.g .:
v = [3,5,0]
axis = [4,4,1]
theta = 1.2 #radian
Quelle est la meilleure façon/la plus simple d’obtenir le vecteur résultant lors de la rotation du vecteur v autour de l’axe?
La rotation devrait apparaître dans le sens contraire des aiguilles d'une montre pour un observateur vers lequel le vecteur d'axe est dirigé. C'est ce qu'on appelle le règle de la main droite
Jetez un oeil à http://vpython.org/contents/docs/visual/VisualIntro.html .
Il fournit une classe vector
qui a une méthode A.rotate(theta,B)
. Il fournit également une fonction d'assistance rotate(A,theta,B)
si vous ne souhaitez pas appeler la méthode sur A
.
En utilisant le formule d'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]
Une ligne, avec des fonctions numpy/scipy.
Nous utilisons les éléments suivants:
soit a le vecteur unitaire le long de axe , c’est-à-dire a = axe/norme (axe)
et A = I × a soit la matrice asymétrique associée à a , c'est-à-dire le produit croisé de la matrice d'identité avec aalors M = exp (θ A) est la matrice de rotation.
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
(code here) calcule la série taylor de l'exponentielle:\sum_{k=0}^{20} \frac{1}{k!} (θ A)^k
, il est donc coûteux en temps, mais lisible et sécurisé. Ce peut être un bon moyen si vous avez peu de rotations à faire mais beaucoup de vecteurs.
Je voulais juste mentionner que si la vitesse est requise, en enveloppant le code de unutbu dans weave.inline de scipy et en passant une matrice déjà existante en tant que paramètre, le temps d'exécution sera 20 fois plus court.
Le code (dans 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]])
Le timing:
>>> 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]
Voici une méthode élégante utilisant des quaternions extrêmement rapides. Je peux calculer 10 millions de rotations par seconde avec des tableaux numpy correctement vectorisés. Il s’appuie sur l’extension quaternion de numpy trouvée ici .
Quaternion Theory: Un quaternion est un nombre avec une dimension réelle et 3 dimensions imaginaires, généralement écrit sous la forme q = w + xi + yj + zk
, Où 'i', 'j', 'k' sont des dimensions imaginaires. Tout comme un nombre unitaire complexe 'c' peut représenter toutes les rotations en 2D par c=exp(i * theta)
, un quaternion unitaire 'q' peut représenter toutes les rotations en 3D par q=exp(p)
, où 'p' est pur quaternion imaginaire défini par votre axe et angle.
Nous commençons par convertir votre axe et votre angle en un quaternion dont les dimensions imaginaires sont données par votre axe de rotation et dont la grandeur est donnée par la moitié de l’angle de rotation en radians. Les 4 éléments vecteurs (w, x, y, z)
Sont construits comme suit:
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)
Tout d'abord, un tableau numpy de 4 éléments est construit avec le composant réel w = 0 pour le vecteur à faire pivoter vector
et l'axe de rotation rot_axis
. La représentation de l'angle des axes est ensuite construite en normalisant puis en multipliant par la moitié de l'angle souhaité theta
. Voir ici pour savoir pourquoi la moitié de l'angle est requise.
Créez maintenant les quaternions v
et qlog
à l'aide de la bibliothèque, puis récupérez le quaternion à rotation d'unité q
en prenant l'exponentielle.
vec = quat.quaternion(*v)
qlog = quat.quaternion(*axis_angle)
q = np.exp(qlog)
Enfin, la rotation du vecteur est calculée par l'opération suivante.
v_prime = q * vec * np.conjugate(q)
print(v_prime) # quaternion(0.0, 2.7491163, 4.7718093, 1.9162971)
Maintenant, supprimez le véritable élément et vous avez votre vecteur pivoté!
v_prime_vec = v_prime.imag # [2.74911638 4.77180932 1.91629719] as a numpy array
Notez que cette méthode est particulièrement efficace si vous devez faire pivoter un vecteur de nombreuses rotations séquentielles, car le produit quaternion peut simplement être calculé comme suit: q = q1 * q2 * q3 * q4 * ... * qn et le vecteur ne fait alors que pivoter. par 'q' à la toute fin en utilisant v '= q * v * conj (q).
Cette méthode vous donne une transformation transparente entre les angles d'axe <---> de l'opérateur de rotation 3D simplement par exp
et log
fonctions (yes log(q)
) renvoie simplement la représentation de l'axe-angle !) Pour plus de précisions sur le fonctionnement de la multiplication de quaternion, etc., voir ici
J'ai réalisé une bibliothèque assez complète de mathématiques 3D pour Python {2,3}. Il n'utilise toujours pas Cython, mais s'appuie fortement sur l'efficacité de numpy. Vous pouvez le trouver ici avec pip:
python[3] -m pip install math3d
Ou jetez un oeil à mon gitweb http://git.automatics.dyndns.dk/?p=pymath3d.git et maintenant aussi sur github: https://github.com/ mortlind/pymath3d .
Une fois installé, dans python, vous pouvez créer un objet d’orientation pouvant faire pivoter des vecteurs ou faire partie d’un objet de transformation. Par exemple, l’extrait de code suivant compose une orientation représentant une rotation de 1 rad autour de l’axe [1,2,3], l'applique au vecteur [4,5,6] et affiche le résultat:
import math3d as m3d
r = m3d.Orientation.new_axis_angle([1,2,3], 1)
v = m3d.Vector(4,5,6)
print(r * v)
La sortie serait
<Vector: (2.53727, 6.15234, 5.71935)>
C’est plus efficace, dans la mesure du possible, d’un facteur environ quatre fois supérieur à celui de l’élément à l’aide de Scipy posté par B. M. ci-dessus. Cependant, cela nécessite l'installation de mon paquet math3d.
Disclaimer: Je suis l'auteur de ce paquet
Des classes spéciales pour les rotations peuvent être pratiques, mais dans certains cas, vous avez besoin de matrices de rotation (par exemple, pour travailler avec d'autres bibliothèques telles que les fonctions affine_transform de scipy). Pour éviter que chacun n’implémente ses propres petites fonctions génératrices de matrice, il existe un minuscule paquet python) qui ne fournit rien de plus que de fournir des fonctions pratiques de génération de matrice de rotation. Le paquet est sur github ( mgen ) et peut être installé via pip:
pip install mgen
Exemple d'utilisation copié du fichier Lisez-moi:
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.])
Notez que les matrices ne sont que des tableaux numpy ordinaires. Par conséquent, aucune nouvelle structure de données n'est introduite lors de l'utilisation de ce paquet.
Il peut également être résolu en utilisant la théorie des quaternions:
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]
Utiliser pyquaternion est extrêmement simple. pour l'installer (toujours en python), lancez dans votre console:
import pip;
pip.main(['install','pyquaternion'])
Une fois installé:
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)
J'avais besoin de faire pivoter un modèle 3D autour de l'un des trois axes {x, y, z} dans lesquels ce modèle était intégré. Il s'agissait du meilleur résultat pour une recherche de la manière de le faire numpy. J'ai utilisé la fonction simple suivante:
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.],
]))
Utilisez le Rotation.from_rotvec()
de scipy. L'argument est le vecteur de rotation (un vecteur unitaire) multiplié par l'angle de rotation en rads.
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]
Il existe plusieurs façons d'utiliser Rotation
en fonction des données dont vous disposez sur la rotation:
from_quat
Initialisé à partir de quaternions.
from_dcm
Initialisé à partir des matrices de cosinus de direction.
from_euler
Initialisé sous un angle d'Euler.
Remarque hors sujet: Le code à une ligne est pas nécessairement un meilleur code, comme l'impliquent certains utilisateurs.