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