You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
8.8 KiB
295 lines
8.8 KiB
import numpy as np
|
|
from numpy import dot, inner, array, linalg
|
|
from common.transformations.coordinates import LocalCoord
|
|
|
|
|
|
'''
|
|
Vectorized functions that transform between
|
|
rotation matrices, euler angles and quaternions.
|
|
All support lists, array or array of arrays as inputs.
|
|
Supports both x2y and y_from_x format (y_from_x preferred!).
|
|
'''
|
|
|
|
def euler2quat(eulers):
|
|
eulers = array(eulers)
|
|
if len(eulers.shape) > 1:
|
|
output_shape = (-1, 4)
|
|
else:
|
|
output_shape = (4,)
|
|
eulers = np.atleast_2d(eulers)
|
|
gamma, theta, psi = eulers[:, 0], eulers[:, 1], eulers[:, 2]
|
|
|
|
q0 = np.cos(gamma / 2) * np.cos(theta / 2) * np.cos(psi / 2) + \
|
|
np.sin(gamma / 2) * np.sin(theta / 2) * np.sin(psi / 2)
|
|
q1 = np.sin(gamma / 2) * np.cos(theta / 2) * np.cos(psi / 2) - \
|
|
np.cos(gamma / 2) * np.sin(theta / 2) * np.sin(psi / 2)
|
|
q2 = np.cos(gamma / 2) * np.sin(theta / 2) * np.cos(psi / 2) + \
|
|
np.sin(gamma / 2) * np.cos(theta / 2) * np.sin(psi / 2)
|
|
q3 = np.cos(gamma / 2) * np.cos(theta / 2) * np.sin(psi / 2) - \
|
|
np.sin(gamma / 2) * np.sin(theta / 2) * np.cos(psi / 2)
|
|
|
|
quats = array([q0, q1, q2, q3]).T
|
|
for i in range(len(quats)):
|
|
if quats[i, 0] < 0:
|
|
quats[i] = -quats[i]
|
|
return quats.reshape(output_shape)
|
|
|
|
|
|
def quat2euler(quats):
|
|
quats = array(quats)
|
|
if len(quats.shape) > 1:
|
|
output_shape = (-1, 3)
|
|
else:
|
|
output_shape = (3,)
|
|
quats = np.atleast_2d(quats)
|
|
q0, q1, q2, q3 = quats[:, 0], quats[:, 1], quats[:, 2], quats[:, 3]
|
|
|
|
gamma = np.arctan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1**2 + q2**2))
|
|
theta = np.arcsin(2 * (q0 * q2 - q3 * q1))
|
|
psi = np.arctan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2**2 + q3**2))
|
|
|
|
eulers = array([gamma, theta, psi]).T
|
|
return eulers.reshape(output_shape)
|
|
|
|
|
|
def quat2rot(quats):
|
|
quats = array(quats)
|
|
input_shape = quats.shape
|
|
quats = np.atleast_2d(quats)
|
|
Rs = np.zeros((quats.shape[0], 3, 3))
|
|
q0 = quats[:, 0]
|
|
q1 = quats[:, 1]
|
|
q2 = quats[:, 2]
|
|
q3 = quats[:, 3]
|
|
Rs[:, 0, 0] = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3
|
|
Rs[:, 0, 1] = 2 * (q1 * q2 - q0 * q3)
|
|
Rs[:, 0, 2] = 2 * (q0 * q2 + q1 * q3)
|
|
Rs[:, 1, 0] = 2 * (q1 * q2 + q0 * q3)
|
|
Rs[:, 1, 1] = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3
|
|
Rs[:, 1, 2] = 2 * (q2 * q3 - q0 * q1)
|
|
Rs[:, 2, 0] = 2 * (q1 * q3 - q0 * q2)
|
|
Rs[:, 2, 1] = 2 * (q0 * q1 + q2 * q3)
|
|
Rs[:, 2, 2] = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3
|
|
|
|
if len(input_shape) < 2:
|
|
return Rs[0]
|
|
else:
|
|
return Rs
|
|
|
|
|
|
def rot2quat(rots):
|
|
input_shape = rots.shape
|
|
if len(input_shape) < 3:
|
|
rots = array([rots])
|
|
K3 = np.empty((len(rots), 4, 4))
|
|
K3[:, 0, 0] = (rots[:, 0, 0] - rots[:, 1, 1] - rots[:, 2, 2]) / 3.0
|
|
K3[:, 0, 1] = (rots[:, 1, 0] + rots[:, 0, 1]) / 3.0
|
|
K3[:, 0, 2] = (rots[:, 2, 0] + rots[:, 0, 2]) / 3.0
|
|
K3[:, 0, 3] = (rots[:, 1, 2] - rots[:, 2, 1]) / 3.0
|
|
K3[:, 1, 0] = K3[:, 0, 1]
|
|
K3[:, 1, 1] = (rots[:, 1, 1] - rots[:, 0, 0] - rots[:, 2, 2]) / 3.0
|
|
K3[:, 1, 2] = (rots[:, 2, 1] + rots[:, 1, 2]) / 3.0
|
|
K3[:, 1, 3] = (rots[:, 2, 0] - rots[:, 0, 2]) / 3.0
|
|
K3[:, 2, 0] = K3[:, 0, 2]
|
|
K3[:, 2, 1] = K3[:, 1, 2]
|
|
K3[:, 2, 2] = (rots[:, 2, 2] - rots[:, 0, 0] - rots[:, 1, 1]) / 3.0
|
|
K3[:, 2, 3] = (rots[:, 0, 1] - rots[:, 1, 0]) / 3.0
|
|
K3[:, 3, 0] = K3[:, 0, 3]
|
|
K3[:, 3, 1] = K3[:, 1, 3]
|
|
K3[:, 3, 2] = K3[:, 2, 3]
|
|
K3[:, 3, 3] = (rots[:, 0, 0] + rots[:, 1, 1] + rots[:, 2, 2]) / 3.0
|
|
q = np.empty((len(rots), 4))
|
|
for i in range(len(rots)):
|
|
_, eigvecs = linalg.eigh(K3[i].T)
|
|
eigvecs = eigvecs[:, 3:]
|
|
q[i, 0] = eigvecs[-1]
|
|
q[i, 1:] = -eigvecs[:-1].flatten()
|
|
if q[i, 0] < 0:
|
|
q[i] = -q[i]
|
|
|
|
if len(input_shape) < 3:
|
|
return q[0]
|
|
else:
|
|
return q
|
|
|
|
|
|
def euler2rot(eulers):
|
|
return rotations_from_quats(euler2quat(eulers))
|
|
|
|
|
|
def rot2euler(rots):
|
|
return quat2euler(quats_from_rotations(rots))
|
|
|
|
|
|
quats_from_rotations = rot2quat
|
|
quat_from_rot = rot2quat
|
|
rotations_from_quats = quat2rot
|
|
rot_from_quat = quat2rot
|
|
rot_from_quat = quat2rot
|
|
euler_from_rot = rot2euler
|
|
euler_from_quat = quat2euler
|
|
rot_from_euler = euler2rot
|
|
quat_from_euler = euler2quat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
Random helpers below
|
|
'''
|
|
|
|
|
|
def quat_product(q, r):
|
|
t = np.zeros(4)
|
|
t[0] = r[0] * q[0] - r[1] * q[1] - r[2] * q[2] - r[3] * q[3]
|
|
t[1] = r[0] * q[1] + r[1] * q[0] - r[2] * q[3] + r[3] * q[2]
|
|
t[2] = r[0] * q[2] + r[1] * q[3] + r[2] * q[0] - r[3] * q[1]
|
|
t[3] = r[0] * q[3] - r[1] * q[2] + r[2] * q[1] + r[3] * q[0]
|
|
return t
|
|
|
|
|
|
def rot_matrix(roll, pitch, yaw):
|
|
cr, sr = np.cos(roll), np.sin(roll)
|
|
cp, sp = np.cos(pitch), np.sin(pitch)
|
|
cy, sy = np.cos(yaw), np.sin(yaw)
|
|
rr = array([[1, 0, 0], [0, cr, -sr], [0, sr, cr]])
|
|
rp = array([[cp, 0, sp], [0, 1, 0], [-sp, 0, cp]])
|
|
ry = array([[cy, -sy, 0], [sy, cy, 0], [0, 0, 1]])
|
|
return ry.dot(rp.dot(rr))
|
|
|
|
|
|
def rot(axis, angle):
|
|
# Rotates around an arbitrary axis
|
|
ret_1 = (1 - np.cos(angle)) * array([[axis[0]**2, axis[0] * axis[1], axis[0] * axis[2]], [
|
|
axis[1] * axis[0], axis[1]**2, axis[1] * axis[2]
|
|
], [axis[2] * axis[0], axis[2] * axis[1], axis[2]**2]])
|
|
ret_2 = np.cos(angle) * np.eye(3)
|
|
ret_3 = np.sin(angle) * array([[0, -axis[2], axis[1]], [axis[2], 0, -axis[0]],
|
|
[-axis[1], axis[0], 0]])
|
|
return ret_1 + ret_2 + ret_3
|
|
|
|
|
|
def ecef_euler_from_ned(ned_ecef_init, ned_pose):
|
|
'''
|
|
Got it from here:
|
|
Using Rotations to Build Aerospace Coordinate Systems
|
|
-Don Koks
|
|
'''
|
|
converter = LocalCoord.from_ecef(ned_ecef_init)
|
|
x0 = converter.ned2ecef([1, 0, 0]) - converter.ned2ecef([0, 0, 0])
|
|
y0 = converter.ned2ecef([0, 1, 0]) - converter.ned2ecef([0, 0, 0])
|
|
z0 = converter.ned2ecef([0, 0, 1]) - converter.ned2ecef([0, 0, 0])
|
|
|
|
x1 = rot(z0, ned_pose[2]).dot(x0)
|
|
y1 = rot(z0, ned_pose[2]).dot(y0)
|
|
z1 = rot(z0, ned_pose[2]).dot(z0)
|
|
|
|
x2 = rot(y1, ned_pose[1]).dot(x1)
|
|
y2 = rot(y1, ned_pose[1]).dot(y1)
|
|
z2 = rot(y1, ned_pose[1]).dot(z1)
|
|
|
|
x3 = rot(x2, ned_pose[0]).dot(x2)
|
|
y3 = rot(x2, ned_pose[0]).dot(y2)
|
|
#z3 = rot(x2, ned_pose[0]).dot(z2)
|
|
|
|
x0 = array([1, 0, 0])
|
|
y0 = array([0, 1, 0])
|
|
z0 = array([0, 0, 1])
|
|
|
|
psi = np.arctan2(inner(x3, y0), inner(x3, x0))
|
|
theta = np.arctan2(-inner(x3, z0), np.sqrt(inner(x3, x0)**2 + inner(x3, y0)**2))
|
|
y2 = rot(z0, psi).dot(y0)
|
|
z2 = rot(y2, theta).dot(z0)
|
|
phi = np.arctan2(inner(y3, z2), inner(y3, y2))
|
|
|
|
ret = array([phi, theta, psi])
|
|
return ret
|
|
|
|
|
|
def ned_euler_from_ecef(ned_ecef_init, ecef_poses):
|
|
'''
|
|
Got the math from here:
|
|
Using Rotations to Build Aerospace Coordinate Systems
|
|
-Don Koks
|
|
|
|
Also accepts array of ecef_poses and array of ned_ecef_inits.
|
|
Where each row is a pose and an ecef_init.
|
|
'''
|
|
ned_ecef_init = array(ned_ecef_init)
|
|
ecef_poses = array(ecef_poses)
|
|
output_shape = ecef_poses.shape
|
|
ned_ecef_init = np.atleast_2d(ned_ecef_init)
|
|
if ned_ecef_init.shape[0] == 1:
|
|
ned_ecef_init = np.tile(ned_ecef_init[0], (output_shape[0], 1))
|
|
ecef_poses = np.atleast_2d(ecef_poses)
|
|
|
|
ned_poses = np.zeros(ecef_poses.shape)
|
|
for i, ecef_pose in enumerate(ecef_poses):
|
|
converter = LocalCoord.from_ecef(ned_ecef_init[i])
|
|
x0 = array([1, 0, 0])
|
|
y0 = array([0, 1, 0])
|
|
z0 = array([0, 0, 1])
|
|
|
|
x1 = rot(z0, ecef_pose[2]).dot(x0)
|
|
y1 = rot(z0, ecef_pose[2]).dot(y0)
|
|
z1 = rot(z0, ecef_pose[2]).dot(z0)
|
|
|
|
x2 = rot(y1, ecef_pose[1]).dot(x1)
|
|
y2 = rot(y1, ecef_pose[1]).dot(y1)
|
|
z2 = rot(y1, ecef_pose[1]).dot(z1)
|
|
|
|
x3 = rot(x2, ecef_pose[0]).dot(x2)
|
|
y3 = rot(x2, ecef_pose[0]).dot(y2)
|
|
#z3 = rot(x2, ecef_pose[0]).dot(z2)
|
|
|
|
x0 = converter.ned2ecef([1, 0, 0]) - converter.ned2ecef([0, 0, 0])
|
|
y0 = converter.ned2ecef([0, 1, 0]) - converter.ned2ecef([0, 0, 0])
|
|
z0 = converter.ned2ecef([0, 0, 1]) - converter.ned2ecef([0, 0, 0])
|
|
|
|
psi = np.arctan2(inner(x3, y0), inner(x3, x0))
|
|
theta = np.arctan2(-inner(x3, z0), np.sqrt(inner(x3, x0)**2 + inner(x3, y0)**2))
|
|
y2 = rot(z0, psi).dot(y0)
|
|
z2 = rot(y2, theta).dot(z0)
|
|
phi = np.arctan2(inner(y3, z2), inner(y3, y2))
|
|
ned_poses[i] = array([phi, theta, psi])
|
|
|
|
return ned_poses.reshape(output_shape)
|
|
|
|
|
|
def ecef2car(car_ecef, psi, theta, points_ecef, ned_converter):
|
|
"""
|
|
TODO: add roll rotation
|
|
Converts an array of points in ecef coordinates into
|
|
x-forward, y-left, z-up coordinates
|
|
Parameters
|
|
----------
|
|
psi: yaw, radian
|
|
theta: pitch, radian
|
|
Returns
|
|
-------
|
|
[x, y, z] coordinates in car frame
|
|
"""
|
|
|
|
# input is an array of points in ecef cocrdinates
|
|
# output is an array of points in car's coordinate (x-front, y-left, z-up)
|
|
|
|
# convert points to NED
|
|
points_ned = []
|
|
for p in points_ecef:
|
|
points_ned.append(ned_converter.ecef2ned_matrix.dot(array(p) - car_ecef))
|
|
|
|
points_ned = np.vstack(points_ned).T
|
|
|
|
# n, e, d -> x, y, z
|
|
# Calculate relative postions and rotate wrt to heading and pitch of car
|
|
invert_R = array([[1., 0., 0.], [0., -1., 0.], [0., 0., -1.]])
|
|
|
|
c, s = np.cos(psi), np.sin(psi)
|
|
yaw_R = array([[c, s, 0.], [-s, c, 0.], [0., 0., 1.]])
|
|
|
|
c, s = np.cos(theta), np.sin(theta)
|
|
pitch_R = array([[c, 0., -s], [0., 1., 0.], [s, 0., c]])
|
|
|
|
return dot(pitch_R, dot(yaw_R, dot(invert_R, points_ned)))
|
|
|