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

Popular posts from this blog

AVR for Arduino/Atmel MCU

UART Communication

RS-485 Communication 구현