1 Star 0 Fork 1

ayu6688/pymavlink

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
quaternion.py 18.43 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
#!/usr/bin/env python
"""
Quaternion implementation for use in pymavlink
"""
from __future__ import absolute_import, division, print_function
from builtins import object
import numpy as np
from .rotmat import Vector3, Matrix3
__author__ = "Thomas Gubler"
__copyright__ = "Copyright (C) 2014 Thomas Gubler"
__license__ = "GNU Lesser General Public License v3"
__email__ = "thomasgubler@gmail.com"
class QuaternionBase(object):
"""
Quaternion class, this is the version which supports numpy arrays
If you need support for Matrix3 look at the Quaternion class
Usage:
>>> from quaternion import QuaternionBase
>>> import numpy as np
>>> q = QuaternionBase([np.radians(20), np.radians(20), np.radians(20)])
>>> print(q)
[ 0.9603483 0.13871646 0.19810763 0.13871646]
>>> print(q.dcm)
[[ 0.88302222 -0.21147065 0.41898917]
[ 0.3213938 0.92303098 -0.21147065]
[-0.34202014 0.3213938 0.88302222]]
>>> q = QuaternionBase([1, 0, 0, 0])
>>> print(q.euler)
[ 0. -0. 0.]
>>> m = [[1, 0, 0], [0, 0, -1], [0, 1, 0]]
>>> q = QuaternionBase(m)
>>> vector = [0, 1, 0]
>>> vector2 = q.transform(vector)
"""
def __init__(self, attitude=[1, 0, 0, 0]):
"""
Construct a quaternion from an attitude
:param attitude: another QuaternionBase,
3 element list [roll, pitch, yaw],
4 element list [w, x, y ,z], DCM (3x3 array)
"""
if isinstance(attitude, QuaternionBase):
self.q = attitude.q
elif np.array(attitude).shape == (3, 3):
self.dcm = attitude
elif len(attitude) == 4:
self.q = attitude
elif len(attitude) == 3:
self.euler = attitude
else:
raise TypeError("attitude is not valid")
@property
def q(self):
"""
Get the quaternion
:returns: array containing the quaternion elements
"""
if self._q is None:
if self._euler is not None:
# get q from euler
self._q = self._euler_to_q(self.euler)
elif self._dcm is not None:
# get q from DCM
self._q = self._dcm_to_q(self.dcm)
return self._q
def __getitem__(self, index):
"""Returns the quaternion entry at index"""
return self.q[index]
@q.setter
def q(self, q):
"""
Set the quaternion
:param q: list or array of quaternion values [w, x, y, z]
"""
self._q = np.array(q)
# mark other representations as outdated, will get generated on next
# read
self._euler = None
self._dcm = None
@property
def euler(self):
"""
Get the euler angles.
The convention is Tait-Bryan (ZY'X'')
:returns: array containing the euler angles [roll, pitch, yaw]
"""
if self._euler is None:
if self._q is not None:
# try to get euler angles from q via DCM
self._dcm = self._q_to_dcm(self.q)
self._euler = self._dcm_to_euler(self.dcm)
elif self._dcm is not None:
# get euler angles from DCM
self._euler = self._dcm_to_euler(self.dcm)
return self._euler
@euler.setter
def euler(self, euler):
"""
Set the euler angles
:param euler: list or array of the euler angles [roll, pitch, yaw]
"""
assert(len(euler) == 3)
self._euler = np.array(euler)
# mark other representations as outdated, will get generated on next
# read
self._q = None
self._dcm = None
@property
def dcm(self):
"""
Get the DCM
:returns: 3x3 array
"""
if self._dcm is None:
if self._q is not None:
# try to get dcm from q
self._dcm = self._q_to_dcm(self.q)
elif self._euler is not None:
# try to get get dcm from euler
self._dcm = self._euler_to_dcm(self._euler)
return self._dcm
@dcm.setter
def dcm(self, dcm):
"""
Set the DCM
:param dcm: 3x3 array
"""
assert(len(dcm) == 3)
for sub in dcm:
assert(len(sub) == 3)
self._dcm = np.array(dcm)
# mark other representations as outdated, will get generated on next
# read
self._q = None
self._euler = None
def transform(self, v):
"""
Calculates the vector transformed by this quaternion
:param v: array with len 3 to be transformed
:returns: transformed vector
"""
assert(len(v) == 3)
assert(np.allclose(self.norm, 1))
# perform transformation t = q * [0, v] * q^-1 but avoid multiplication
# because terms cancel out
q0 = self.q[0]
qi = self.q[1:4]
ui = np.array(v)
a = q0 * ui + np.cross(qi, ui)
t = np.dot(qi, ui) * qi + q0 * a - np.cross(a, qi)
return t
@property
def norm(self):
"""
Returns norm of quaternion
:returns: norm (scalar)
"""
return QuaternionBase.norm_array(self.q)
def normalize(self):
"""Normalizes the quaternion"""
self.q = QuaternionBase.normalize_array(self.q)
@property
def inversed(self):
"""
Get inversed quaternion
:returns: inversed quaternion
"""
q_inv = self._q_inversed(self.q)
return QuaternionBase(q_inv)
def __eq__(self, other):
"""
Equality test (same orientation, not necessarily same rotation)
:param other: a QuaternionBase
:returns: true if the quaternions are equal
"""
if isinstance(other, QuaternionBase):
return abs(self.q.dot(other.q)) > 1 - np.finfo(float).eps
return NotImplemented
def close(self, other):
"""
Equality test with tolerance
(same orientation, not necessarily same rotation)
:param other: a QuaternionBase
:returns: true if the quaternions are almost equal
"""
if isinstance(other, QuaternionBase):
return np.allclose(self.q, other.q) or np.allclose(self.q, -other.q)
return NotImplemented
def __mul__(self, other):
"""
:param other: QuaternionBase
:returns: multiplaction of this Quaternion with other
"""
if isinstance(other, QuaternionBase):
o = other.q
elif len(other) == 4:
o = other
else:
return NotImplemented
return QuaternionBase(self._mul_array(self.q, o))
def __truediv__(self, other):
"""
:param other: QuaternionBase
:returns: division of this Quaternion with other
"""
if isinstance(other, QuaternionBase):
o = other
elif len(other) == 4:
o = QuaternionBase(other)
else:
return NotImplemented
return self * o.inversed
@staticmethod
def normalize_array(q):
"""
Normalizes the list with len 4 so that it can be used as quaternion
:param q: array of len 4
:returns: normalized array
"""
assert(len(q) == 4)
q = np.array(q)
n = QuaternionBase.norm_array(q)
return q / n
@staticmethod
def norm_array(q):
"""
Calculate quaternion norm on array q
:param quaternion: array of len 4
:returns: norm (scalar)
"""
assert(len(q) == 4)
return np.sqrt(np.dot(q, q))
def _mul_array(self, p, q):
"""
Performs multiplication of the 2 quaterniona arrays p and q
:param p: array of len 4
:param q: array of len 4
:returns: array of len, result of p * q (with p, q quaternions)
"""
assert(len(q) == len(p) == 4)
p0 = p[0]
pi = p[1:4]
q0 = q[0]
qi = q[1:4]
res = np.zeros(4)
res[0] = p0 * q0 - np.dot(pi, qi)
res[1:4] = p0 * qi + q0 * pi + np.cross(pi, qi)
return res
def _euler_to_q(self, euler):
"""
Create q array from euler angles
:param euler: array [roll, pitch, yaw] in rad
:returns: array q which represents a quaternion [w, x, y, z]
"""
assert(len(euler) == 3)
phi = euler[0]
theta = euler[1]
psi = euler[2]
c_phi_2 = np.cos(phi / 2)
s_phi_2 = np.sin(phi / 2)
c_theta_2 = np.cos(theta / 2)
s_theta_2 = np.sin(theta / 2)
c_psi_2 = np.cos(psi / 2)
s_psi_2 = np.sin(psi / 2)
q = np.zeros(4)
q[0] = (c_phi_2 * c_theta_2 * c_psi_2 +
s_phi_2 * s_theta_2 * s_psi_2)
q[1] = (s_phi_2 * c_theta_2 * c_psi_2 -
c_phi_2 * s_theta_2 * s_psi_2)
q[2] = (c_phi_2 * s_theta_2 * c_psi_2 +
s_phi_2 * c_theta_2 * s_psi_2)
q[3] = (c_phi_2 * c_theta_2 * s_psi_2 -
s_phi_2 * s_theta_2 * c_psi_2)
return q
def _q_to_dcm(self, q):
"""
Create DCM from q
:param q: array q which represents a quaternion [w, x, y, z]
:returns: 3x3 dcm array
"""
assert(len(q) == 4)
assert(np.allclose(QuaternionBase.norm_array(q), 1))
dcm = np.zeros([3, 3])
a = q[0]
b = q[1]
c = q[2]
d = q[3]
a_sq = a * a
b_sq = b * b
c_sq = c * c
d_sq = d * d
dcm[0][0] = a_sq + b_sq - c_sq - d_sq
dcm[0][1] = 2 * (b * c - a * d)
dcm[0][2] = 2 * (a * c + b * d)
dcm[1][0] = 2 * (b * c + a * d)
dcm[1][1] = a_sq - b_sq + c_sq - d_sq
dcm[1][2] = 2 * (c * d - a * b)
dcm[2][0] = 2 * (b * d - a * c)
dcm[2][1] = 2 * (a * b + c * d)
dcm[2][2] = a_sq - b_sq - c_sq + d_sq
return dcm
def _dcm_to_q(self, dcm):
"""
Create q from dcm
Reference:
- Shoemake, Quaternions,
http://www.cs.ucr.edu/~vbz/resources/quatut.pdf
:param dcm: 3x3 dcm array
returns: quaternion array
"""
assert(dcm.shape == (3, 3))
q = np.zeros(4)
tr = np.trace(dcm)
if tr > 0:
s = np.sqrt(tr + 1.0)
q[0] = s * 0.5
s = 0.5 / s
q[1] = (dcm[2][1] - dcm[1][2]) * s
q[2] = (dcm[0][2] - dcm[2][0]) * s
q[3] = (dcm[1][0] - dcm[0][1]) * s
else:
dcm_i = np.argmax(np.diag(dcm))
dcm_j = (dcm_i + 1) % 3
dcm_k = (dcm_i + 2) % 3
s = np.sqrt((dcm[dcm_i][dcm_i] - dcm[dcm_j][dcm_j] -
dcm[dcm_k][dcm_k]) + 1.0)
q[dcm_i + 1] = s * 0.5
s = 0.5 / s
q[dcm_j + 1] = (dcm[dcm_i][dcm_j] + dcm[dcm_j][dcm_i]) * s
q[dcm_k + 1] = (dcm[dcm_k][dcm_i] + dcm[dcm_i][dcm_k]) * s
q[0] = (dcm[dcm_k][dcm_j] - dcm[dcm_j][dcm_k]) * s
return q
def _euler_to_dcm(self, euler):
"""
Create DCM from euler angles
:param euler: array [roll, pitch, yaw] in rad
:returns: 3x3 dcm array
"""
assert(len(euler) == 3)
phi = euler[0]
theta = euler[1]
psi = euler[2]
dcm = np.zeros([3, 3])
c_phi = np.cos(phi)
s_phi = np.sin(phi)
c_theta = np.cos(theta)
s_theta = np.sin(theta)
c_psi = np.cos(psi)
s_psi = np.sin(psi)
dcm[0][0] = c_theta * c_psi
dcm[0][1] = -c_phi * s_psi + s_phi * s_theta * c_psi
dcm[0][2] = s_phi * s_psi + c_phi * s_theta * c_psi
dcm[1][0] = c_theta * s_psi
dcm[1][1] = c_phi * c_psi + s_phi * s_theta * s_psi
dcm[1][2] = -s_phi * c_psi + c_phi * s_theta * s_psi
dcm[2][0] = -s_theta
dcm[2][1] = s_phi * c_theta
dcm[2][2] = c_phi * c_theta
return dcm
def _dcm_to_euler(self, dcm):
"""
Create DCM from euler angles
:param dcm: 3x3 dcm array
:returns: array [roll, pitch, yaw] in rad
"""
assert(dcm.shape == (3, 3))
theta = np.arcsin(min(1, max(-1, -dcm[2][0])))
if abs(theta - np.pi/2) < 1.0e-3:
phi = 0.0
psi = (np.arctan2(dcm[1][2] - dcm[0][1],
dcm[0][2] + dcm[1][1]) + phi)
elif abs(theta + np.pi/2) < 1.0e-3:
phi = 0.0
psi = np.arctan2(dcm[1][2] - dcm[0][1],
dcm[0][2] + dcm[1][1] - phi)
else:
phi = np.arctan2(dcm[2][1], dcm[2][2])
psi = np.arctan2(dcm[1][0], dcm[0][0])
return np.array([phi, theta, psi])
def _q_inversed(self, q):
"""
Returns inversed quaternion q
:param q: array q which represents a quaternion [w, x, y, z]
:returns: inversed array q which is a quaternion [w, x, y ,z]
"""
assert(len(q) == 4)
return np.hstack([q[0], -q[1:4]])
def __str__(self):
"""String of quaternion values"""
return str(self.q)
class Quaternion(QuaternionBase):
"""
Quaternion class that supports pymavlink's Vector3 and Matrix3
Usage:
>>> from quaternion import Quaternion
>>> from rotmat import Vector3, Matrix3
>>> m = Matrix3()
>>> m.from_euler(45, 0, 0)
>>> print(m)
Matrix3((1.00, 0.00, 0.00), (0.00, 0.53, -0.85), (-0.00, 0.85, 0.53))
>>> q = Quaternion(m)
>>> print(q)
[ 0.87330464 0.48717451 0. 0. ]
>>> print(q.dcm)
Matrix3((1.00, 0.00, 0.00), (0.00, 0.53, -0.85), (-0.00, 0.85, 0.53))
>>> v = Vector3(0, 1, 0)
>>> v2 = q.transform(v)
>>> print(v2)
Vector3(0.00, 0.53, 0.85)
"""
def __init__(self, attitude):
"""
Construct a quaternion from an attitude
:param attitude: another Quaternion, QuaternionBase,
3 element list [roll, pitch, yaw],
4 element list [w, x, y ,z], DCM (3x3 array or Matrix3)
"""
if isinstance(attitude, Quaternion):
self.q = attitude.q
if isinstance(attitude, Matrix3):
self.dcm = attitude
elif np.array(attitude).shape == (3, 3):
# convert dcm array to Matrix3
self.dcm = self._dcm_array_to_matrix3(attitude)
elif isinstance(attitude, Vector3):
# provided euler angles
euler = [attitude.x, attitude.y, attitude.z]
super(Quaternion, self).__init__(euler)
else:
super(Quaternion, self).__init__(attitude)
@property
def dcm(self):
"""
Get the DCM
:returns: Matrix3
"""
if self._dcm is None:
if self._q is not None:
# try to get dcm from q
self._dcm = self._q_to_dcm(self.q)
elif self._euler is not None:
# try to get get dcm from euler
self._dcm = self._euler_to_dcm(self._euler)
return self._dcm
@dcm.setter
def dcm(self, dcm):
"""
Set the DCM
:param dcm: Matrix3
"""
assert(isinstance(dcm, Matrix3))
self._dcm = dcm.copy()
# mark other representations as outdated, will get generated on next
# read
self._q = None
self._euler = None
@property
def inversed(self):
"""
Get inversed quaternion
:returns: inversed quaternion
"""
return Quaternion(super(Quaternion, self).inversed)
def transform(self, v3):
"""
Calculates the vector transformed by this quaternion
:param v3: Vector3 to be transformed
:returns: transformed vector
"""
if isinstance(v3, Vector3):
t = super(Quaternion, self).transform([v3.x, v3.y, v3.z])
return Vector3(t[0], t[1], t[2])
elif len(v3) == 3:
return super(Quaternion, self).transform(v3)
else:
raise TypeError("param v3 is not a vector type")
def _dcm_array_to_matrix3(self, dcm):
"""
Converts dcm array into Matrix3
:param dcm: 3x3 dcm array
:returns: Matrix3
"""
assert(dcm.shape == (3, 3))
a = Vector3(dcm[0][0], dcm[0][1], dcm[0][2])
b = Vector3(dcm[1][0], dcm[1][1], dcm[1][2])
c = Vector3(dcm[2][0], dcm[2][1], dcm[2][2])
return Matrix3(a, b, c)
def _matrix3_to_dcm_array(self, m):
"""
Converts Matrix3 in an array
:param m: Matrix3
:returns: 3x3 array
"""
assert(isinstance(m, Matrix3))
return np.array([[m.a.x, m.a.y, m.a.z],
[m.b.x, m.b.y, m.b.z],
[m.c.x, m.c.y, m.c.z]])
def _q_to_dcm(self, q):
"""
Create DCM (Matrix3) from q
:param q: array q which represents a quaternion [w, x, y, z]
:returns: Matrix3
"""
assert(len(q) == 4)
arr = super(Quaternion, self)._q_to_dcm(q)
return self._dcm_array_to_matrix3(arr)
def _dcm_to_q(self, dcm):
"""
Create q from dcm (Matrix3)
:param dcm: Matrix3
:returns: array q which represents a quaternion [w, x, y, z]
"""
assert(isinstance(dcm, Matrix3))
arr = self._matrix3_to_dcm_array(dcm)
return super(Quaternion, self)._dcm_to_q(arr)
def _euler_to_dcm(self, euler):
"""
Create DCM (Matrix3) from euler angles
:param euler: array [roll, pitch, yaw] in rad
:returns: Matrix3
"""
assert(len(euler) == 3)
m = Matrix3()
m.from_euler(*euler)
return m
def _dcm_to_euler(self, dcm):
"""
Create DCM from euler angles
:param dcm: Matrix3
:returns: array [roll, pitch, yaw] in rad
"""
assert(isinstance(dcm, Matrix3))
return np.array(dcm.to_euler())
def __mul__(self, other):
"""
:param other: Quaternion
:returns: multiplaction of this Quaternion with other
"""
return Quaternion(super(Quaternion, self).__mul__(other))
def __truediv__(self, other):
"""
:param other: Quaternion
:returns: division of this Quaternion with other
"""
return Quaternion(super(Quaternion, self).__truediv__(other))
if __name__ == "__main__":
import doctest
doctest.testmod()
import unittest
unittest.main('quaterniontest')
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/ayu6688/pymavlink.git
git@gitee.com:ayu6688/pymavlink.git
ayu6688
pymavlink
pymavlink
master

搜索帮助