Quaternion Base
쿼터니언 (4원수) 회전 정리
1. 3차원에서 Quaternion rotation을 할 때, 특정 벡터를 쿼터니언으로 표기를 하면 아래와 같다. 4차원 세상의 쿼터니언과 3차원의 벡터가 연결되는 다리를 만드는 과정이라고 생각하면 된다.
P = (Pv, Pw)P = (p, 0)
실수부가 0이고, 허수부가 p인 pure Quaternion이 된다.
[그림 1]
2. rotation axis u, rotation angle theta 를 통해 Quaternion rotation q를 정의해보자.
이 때, rotation Quaternion q의 크기는 1 (norm(q) == 1) 인 unit Quaternion이 되어야 한다. 이 unit Quaternion은 극형식으로 나타낸다.e^(u_q * theta) = u_q * sin(theta) + cos(theta)
-> Quaternion version of Euler formula
3. Ken Shoemaker라는 분의 Quaternion 관련된 논문에 의하면, Quaternion P를 축 u_q 중심으로 2theta 만큼 회전하는 것은 아래와 같이 표현이 된다. (Quaternion P is rotate 2*theta around u_q)
q * p * q^-1
q는 unit Quaternion이므로 다시 정리하면,
q * p * q^*
이 공식을 이용하여서 2 * theta 만큼 회전된 결과인 쿼터니언의 허수부를 구할 수 있다.
4. 연속으로 q에 대해서, 그리고 r에 대해서 회전을 할 경우 아래와 같다.
e^(u_q * theta) = u_q * sin(theta) + cos(theta)e^(u_r * theta) = u_r * sin(theta) + cos(theta)
r * (q * p * q^*) * r^* = (r * q) * p * (q^* * r^*)
5. 쿼터니언을 회전 행렬로 변환하는 방법을 알아보자.
[그림 2]
위의 그림을 참고해서 보자면, qpq*를 설명하기 위한 예제로 2개의 Quaternion의 곱셈 pq를 행렬로 위의 그림과 같이 표현 할 수 있다.
그리고 M_q * N_q * p 에 대해서 변환하여 최종 매트릭스를 구해낸다. (그림 2에서 2번에서 4번 과정을 참고)
이제 Quaternion rotation을 행렬의 형태로 표현하는 것이 가능하다.
6. 그럼 Quaternion rotation matrix로 부터 회전축 u_q 혹은 q를 구할 수 있을까? 가능하다.
그림 2의 5번 과정을 참고하자.결국 q_w(회전축의 실수부)을 알면, q_v인 (q_x, q_y, q_z)를 구할 수 있다.
그럼 q_w은 어떻게 구할 수 있을까?
그림 2의 6번 과정을 참고하자.
7. 최종적으로 Quaternion rotation matrix M_q는 4번을, 그리고 그의 역과정은 7번을 참고하면 된다.
8. 마지막으로 예전에 만들어놓은 Quaternion 관련 파이썬 코드들 덧붙인다.
- Euler to Quaternion- Quaternion to Euler
- Quaternion Rotation Matrix
def eul2quat(eulAngleList, seq):
mod_eulAng = np.dot((1/2), eulAngleList)
#Euler Angle = (phi, theta, psi) as X, Y, Z
if seq == 'ZYX': #Z_psi, 0 -> Y_theta, 1 -> X_phi, 2 / unit is RAD!!!!!
qList = [np.cos(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.cos(mod_eulAng[2]) + \
np.sin(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.cos(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.sin(mod_eulAng[2]) - \
np.sin(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.cos(mod_eulAng[2]), \
np.cos(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.cos(mod_eulAng[2]) + \
np.sin(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.sin(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.cos(mod_eulAng[2]) - \
np.cos(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.sin(mod_eulAng[2])
]
elif seq == 'XYZ': #X_phi 0 -> Y_theta, 1 -> Z_psi, 2 / unit is RAD!!!!!
qList = [np.cos(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.cos(mod_eulAng[2]) - \
np.sin(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.sin(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.cos(mod_eulAng[2]) + \
np.cos(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.cos(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.cos(mod_eulAng[2]) - \
np.sin(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.cos(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.sin(mod_eulAng[2]) + \
np.sin(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.cos(mod_eulAng[2])
]
elif seq == 'ZYZ': #Z_phi 0 -> Y_theta, 1 -> Z_psi, 2 / unit is RAD!!!!!
qList = [np.cos(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.cos(mod_eulAng[2]) - \
np.sin(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.cos(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.sin(mod_eulAng[2]) - \
np.sin(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.cos(mod_eulAng[2]), \
np.cos(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.cos(mod_eulAng[2]) + \
np.sin(mod_eulAng[0]) * np.sin(mod_eulAng[1]) * np.sin(mod_eulAng[2]), \
np.sin(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.cos(mod_eulAng[2]) + \
np.cos(mod_eulAng[0]) * np.cos(mod_eulAng[1]) * np.sin(mod_eulAng[2])
]
else:
print("there is an error on arguments")
return [None, None, None, None]
return qList
def eul2quat2(eulAngleList, seq):
# X_Roll, Y_Pitch, Z_Yaw: Rotation as ZYX, Yaw --> Pitch --> Roll
"""R(y) = [[cos_y, -sin_y, 0.], [sin_y, cos_y, 0.], [0., 0., 1.]]
R(p) = [[cos_p, 0., sin_p], [0., 1., 0.], [-sin_p, 0., cos_p]]
R(r) = [[1., 0., 0.], [0, cos_r, -sin_r], [0., sin_r, cos_r]]
dcm_bn = R(y)*R(p)*R(r) """
if seq == 'ZYX':
yaw = eulAngleList[0]
pitch = eulAngleList[1]
roll = eulAngleList[2]
elif seq == 'XYZ':
yaw = eulAngleList[2]
pitch = eulAngleList[1]
roll = eulAngleList[0]
elif seq == 'ZYZ':
yaw = eulAngleList[0]
pitch = eulAngleList[1]
roll = eulAngleList[2]
elif seq == 'XYX':
roll = eulAngleList[0]
pitch = eulAngleList[1]
yaw = eulAngleList[2]
cos_y, sin_y = np.cos(yaw), np.sin(yaw)
cos_p, sin_p = np.cos(pitch), np.sin(pitch)
cos_r, sin_r = np.cos(roll), np.sin(roll)
rot_y = np.array([[cos_y, -sin_y, 0.], [sin_y, cos_y, 0.], [0., 0., 1.]])
rot_p = np.array([[cos_p, 0., sin_p], [0., 1., 0.], [-sin_p, 0., cos_p]])
rot_r = np.array([[1., 0., 0.], [0, cos_r, -sin_r], [0., sin_r, cos_r]])
rot_yy = np.array([[cos_r, -sin_r, 0.], [sin_r, cos_r, 0.], [0., 0., 1.]])
rot_rr = np.array([[1., 0., 0.], [0, cos_y, -sin_y], [0., sin_y, cos_y]])
if seq == 'ZYX':
dcm_bn = rot_y @ rot_p @ rot_r
elif seq == 'XYZ':
dcm_bn = rot_r @ rot_p @ rot_y
elif seq == 'ZYZ':
dcm_bn = rot_y @ rot_p @ rot_yy
elif seq == 'XYX':
dcm_bn = rot_r @ rot_p @ rot_rr
c11, c12, c13 = dcm_bn[0, 0], dcm_bn[0, 1], dcm_bn[0, 2]
c21, c22, c23 = dcm_bn[1, 0], dcm_bn[1, 1], dcm_bn[1, 2]
c31, c32, c33 = dcm_bn[2, 0], dcm_bn[2, 1], dcm_bn[2, 2]
tr = c11 + c22 + c33
q1 = 0.5 * np.sqrt(1 + tr)
k2 = np.sqrt(0.25 * (2 * c11 + 1 - tr))
k3 = np.sqrt(0.25 * (2 * c22 + 1 - tr))
k4 = np.sqrt(0.25 * (2 * c33 + 1 - tr))
if k2 >= k3 and k2 >= k4:
q2 = k2 * np.sign(c32 - c23)
q3 = k3 * np.sign(q2 * (c21 + c12))
q4 = k4 * np.sign(q2 * (c31 + c13))
elif k3 >= k2 and k3 >= k4:
q3 = k3 * np.sign(c13 - c31)
q2 = k2 * np.sign(q3 * (c12 + c21))
q4 = k4 * np.sign(q3 * (c32 + c23))
else:
q4 = k4 * np.sign(c21 - c12)
q2 = k2 * np.sign(q4 * (c13 + c31))
q3 = k3 * np.sign(q4 * (c23 + c32))
quat = np.array([q1, q2, q3, q4])
return quat, dcm_bn
def quat2eul(qList, seq):
#Euler Angle = (phi, theta, psi) as X, Y, Z
qw = qList[0]
qx = qList[1]
qy = qList[2]
qz = qList[3]
if seq == 'ZYX': #Z_psi, 0 -> Y_theta, 1 -> X_phi, 2 / unit is RAD!!!!!
aSinInput = -2 * (qx * qz - qw * qy)
if aSinInput > 1:
aSinInput = 1. elif aSinInput < -1:
aSinInput = -1.
theta = np.arcsin(aSinInput)
if (theta <= np.pi/2) & (theta >= -np.pi/2):
eulList = np.array([np.arctan2(2 * (qx * qy + qw * qz), qw**2 + qx**2 - qy**2 - qz**2),\
np.arcsin(aSinInput), \
np.arctan2(2 * (qy * qz + qw * qx), qw**2 - qx**2 - qy**2 + qz**2)])
elif (theta >= np.pi/2) & (theta <= 3*np.pi/2):
eulList = np.array([np.arctan2(-2 * (qx * qy + qw * qz), -( qw**2 + qx**2 - qy**2 - qz**2 )),\
np.arcsin(aSinInput), \
np.arctan2(-2 * (qy * qz + qw * qx), -(qw**2 - qx**2 - qy**2 + qz**2))])
elif seq == 'XYZ': #X_phi 0 -> Y_theta, 1 -> Z_psi, 2 / unit is RAD!!!!! aSinInput = 2 * (qx * qz + qy * qw)
if aSinInput > 1:
aSinInput = 1. elif aSinInput < -1:
aSinInput = -1.
eulList = np.array([np.arctan2(-2 * (qy * qz - qx * qw), qw**2 - qx**2 - qy**2 + qz**2),\
np.arcsin(aSinInput),\
np.arctan2(-2 * (qx * qy - qz * qw), qw**2 + qx**2 - qy**2 - qz**2)])
#elif seq == 'ZYZ': #Z_phi 0 -> Y_theta, 1 -> Z_psi, 2 / unit is RAD!!!!!
else:
print("there is an error on arguments")
return [None, None, None]
eulList = np.real(eulList)
return eulList
def RotMatQuat(quatList): #3 by 3
#w: quatList[0], x: quatList[1], y: quatList[2], z: quatList[3]
w = quatList[0]
x = quatList[1]
y = quatList[2]
z = quatList[3]
RMat = [[w**2 + x**2 - y**2 - z**2, 2 * (x*y - w*z), 2*(x*z + w*y)],\
[2*(x*y + w*z), w**2 - x**2 + y**2 - z**2, 2*(y*z -w*x)],\
[2*(x*z - w*y), 2*(y*z +w*x), w**2 - x**2 - y**2 + z**2]]
return RMat
def RotMatQuat2(q):
dcm = [[1 - 2 * (q[2] ** 2 + q[3] ** 2), 2 * (q[1] * q[2] - q[0] * q[3]),
2 * (q[1] * q[3] + q[0] * q[2])],
[2 * (q[1] * q[2] + q[0] * q[3]), 1 - 2 * (q[1] ** 2 + q[3] ** 2),
2 * (q[2] * q[3] - q[0] * q[1])],
[2 * (q[1] * q[3] - q[0] * q[2]), 2 * (q[2] * q[3] + q[0] * q[1]),
1 - 2 * (q[1] ** 2 + q[2] ** 2)]]
return np.array(dcm)
참고:
- https://blog.naver.com/jidon333/220889264929
- https://blog.naver.com/jidon333/220889335389
Comments
Post a Comment