← Back to Blog

[Linear Algebra] Quaternion

math > linear algebra

2026-07-066 min read

#math #linear-algebra #quaternion #rotation #3d

Quaternion은 복소수를 확장한 4차원 수 체계이다. 한국어로는 사원수라고 부른다.

3D 회전에서는 Euler angle이나 rotation matrix 대신 quaternion을 자주 사용한다. 특히 robotics, drone, graphics, game engine에서 자세(attitude)나 orientation을 저장하고 보간할 때 유용하다.

기존 드론 시뮬레이션 글에서는 quaternion을 자세 업데이트 도구로 짧게 다뤘다. 이 글에서는 드론 동역학이 아니라, quaternion 자체의 수학적 구조와 3D 회전 적용 방법을 정리한다.


정의

Quaternion qq는 하나의 scalar part와 세 개의 vector part로 구성된다.

q=w+xi+yj+zkq = w + x\mathbf{i} + y\mathbf{j} + z\mathbf{k}

또는 ordered tuple로 쓸 수 있다.

q=(w,x,y,z)q = (w,x,y,z)

여기서 w,x,y,zRw,x,y,z\in\mathbb{R}이고, i,j,k\mathbf{i},\mathbf{j},\mathbf{k}는 다음 관계를 만족한다.

i2=j2=k2=ijk=1\mathbf{i}^2 = \mathbf{j}^2 = \mathbf{k}^2 = \mathbf{i}\mathbf{j}\mathbf{k} = -1

Scalar part와 vector part를 분리하면 다음처럼 쓸 수 있다.

q=(w,v)q = (w,\mathbf{v}) v=[xyz]\mathbf{v} = \begin{bmatrix} x\\y\\z \end{bmatrix}

이 표현은 quaternion 곱셈과 회전 공식을 이해할 때 편하다.


기본 연산

두 quaternion을 다음처럼 두자.

q1=(w1,x1,y1,z1)q_1=(w_1,x_1,y_1,z_1) q2=(w2,x2,y2,z2)q_2=(w_2,x_2,y_2,z_2)

덧셈은 성분별로 정의된다.

q1+q2=(w1+w2, x1+x2, y1+y2, z1+z2)q_1+q_2 = (w_1+w_2,\ x_1+x_2,\ y_1+y_2,\ z_1+z_2)

Scalar multiplication도 성분별로 정의된다.

αq=(αw,αx,αy,αz)\alpha q = (\alpha w,\alpha x,\alpha y,\alpha z)

중요한 것은 곱셈이다. Quaternion multiplication은 교환법칙을 만족하지 않는다.

q1q2q2q1q_1q_2 \neq q_2q_1

이는 3D 회전의 순서가 중요하다는 사실과 연결된다. Roll 다음 yaw를 적용하는 것과 yaw 다음 roll을 적용하는 것은 일반적으로 다른 결과를 만든다.


곱셈 규칙

허수 단위의 곱셈 규칙은 다음과 같다.

ij=k,jk=i,ki=j\mathbf{i}\mathbf{j}=\mathbf{k}, \qquad \mathbf{j}\mathbf{k}=\mathbf{i}, \qquad \mathbf{k}\mathbf{i}=\mathbf{j}

반대 순서로 곱하면 부호가 바뀐다.

ji=k,kj=i,ik=j\mathbf{j}\mathbf{i}=-\mathbf{k}, \qquad \mathbf{k}\mathbf{j}=-\mathbf{i}, \qquad \mathbf{i}\mathbf{k}=-\mathbf{j}

이를 이용해 q1q2q_1q_2를 전개하면 다음과 같다.

q1q2=(w1w2x1x2y1y2z1z2,w1x2+x1w2+y1z2z1y2,w1y2x1z2+y1w2+z1x2,w1z2+x1y2y1x2+z1w2)\begin{aligned} q_1q_2 =(& w_1w_2 - x_1x_2 - y_1y_2 - z_1z_2,\\ & w_1x_2 + x_1w_2 + y_1z_2 - z_1y_2,\\ & w_1y_2 - x_1z_2 + y_1w_2 + z_1x_2,\\ & w_1z_2 + x_1y_2 - y_1x_2 + z_1w_2 ) \end{aligned}

Vector form으로 쓰면 더 구조가 잘 보인다. q1=(a,u)q_1=(a,\mathbf{u}), q2=(b,v)q_2=(b,\mathbf{v})라고 하자.

q1q2=(abuTv,av+bu+u×v)q_1q_2 = \left( ab-\mathbf{u}^{T}\mathbf{v}, \quad a\mathbf{v}+b\mathbf{u}+\mathbf{u}\times\mathbf{v} \right)

즉 scalar part에는 dot product가 들어가고, vector part에는 cross product가 들어간다.


Conjugate, Norm, Inverse

Quaternion의 conjugate는 vector part의 부호를 바꾼 것이다.

q=(w,x,y,z)q^* = (w,-x,-y,-z)

Norm은 다음과 같다.

q=w2+x2+y2+z2\|q\| = \sqrt{w^2+x^2+y^2+z^2}

Quaternion과 conjugate를 곱하면 norm square가 나온다.

qq=qq=q2qq^* = q^*q = \|q\|^2

따라서 inverse는 다음과 같다.

q1=qq2q^{-1} = \frac{q^*}{\|q\|^2}

만약 qq가 unit quaternion이면 q=1\|q\|=1이므로 inverse가 conjugate와 같다.

q1=qq^{-1}=q^*

3D 회전을 표현할 때는 보통 unit quaternion을 사용한다.

q=1\|q\|=1

Axis-Angle에서 Quaternion 만들기

3D 회전은 회전축과 회전각으로 표현할 수 있다. 단위 회전축을 u\mathbf{u}라고 하자.

u=[uxuyuz],u=1\mathbf{u} = \begin{bmatrix} u_x\\u_y\\u_z \end{bmatrix}, \qquad \|\mathbf{u}\|=1

이 축을 기준으로 θ\theta만큼 회전하는 unit quaternion은 다음과 같다.

q=(cosθ2,uxsinθ2,uysinθ2,uzsinθ2)q = \left( \cos\frac{\theta}{2}, \quad u_x\sin\frac{\theta}{2}, \quad u_y\sin\frac{\theta}{2}, \quad u_z\sin\frac{\theta}{2} \right)

Vector form으로 쓰면,

q=(cosθ2,usinθ2)q = \left( \cos\frac{\theta}{2}, \quad \mathbf{u}\sin\frac{\theta}{2} \right)

여기서 각도가 θ\theta가 아니라 θ/2\theta/2로 들어가는 점이 중요하다. Quaternion 회전은 qvq1qvq^{-1} 형태로 양쪽에서 곱해지므로 half-angle이 나타난다.


Vector 회전

3D vector pR3\mathbf{p}\in\mathbb{R}^3를 quaternion으로 회전하려면 먼저 scalar part가 0인 pure quaternion으로 만든다.

p=(0,p)=0+pxi+pyj+pzkp = (0,\mathbf{p}) = 0+p_x\mathbf{i}+p_y\mathbf{j}+p_z\mathbf{k}

Unit quaternion qq가 회전을 나타낼 때, 회전된 vector는 다음과 같다.

p=qpq1p' = q p q^{-1}

그리고 pp'의 vector part가 실제 회전된 3D vector이다.

p=vec(p)\mathbf{p}' = \operatorname{vec}(p')

Unit quaternion이면 q1=qq^{-1}=q^*이므로 계산은 다음처럼 단순해진다.

p=qpqp' = q p q^*

이 식은 quaternion 회전의 핵심이다.

p=vec(q(0,p)q1)\boxed{ \mathbf{p}' = \operatorname{vec} \left( q(0,\mathbf{p})q^{-1} \right) }

예시: Z축 90도 회전

Z축을 기준으로 9090^\circ 회전한다고 하자.

u=[001],θ=π2\mathbf{u} = \begin{bmatrix} 0\\0\\1 \end{bmatrix}, \qquad \theta=\frac{\pi}{2}

Quaternion은 다음과 같다.

q=(cosπ4,0,0,sinπ4)q = \left( \cos\frac{\pi}{4}, 0, 0, \sin\frac{\pi}{4} \right)

q=(22,0,0,22)q = \left( \frac{\sqrt{2}}{2}, 0, 0, \frac{\sqrt{2}}{2} \right)

이 회전을 xx축 방향 vector에 적용하자.

p=[100]\mathbf{p} = \begin{bmatrix} 1\\0\\0 \end{bmatrix}

그러면 결과는 yy축 방향이 된다.

p=[010]\mathbf{p}' = \begin{bmatrix} 0\\1\\0 \end{bmatrix}

즉 오른손 좌표계에서 Z축 기준 9090^\circ 회전과 일치한다.


Rotation Matrix로 변환

Unit quaternion q=(w,x,y,z)q=(w,x,y,z)는 rotation matrix로 변환할 수 있다.

R(q)=[12(y2+z2)2(xywz)2(xz+wy)2(xy+wz)12(x2+z2)2(yzwx)2(xzwy)2(yz+wx)12(x2+y2)]R(q) = \begin{bmatrix} 1-2(y^2+z^2) & 2(xy-wz) & 2(xz+wy)\\ 2(xy+wz) & 1-2(x^2+z^2) & 2(yz-wx)\\ 2(xz-wy) & 2(yz+wx) & 1-2(x^2+y^2) \end{bmatrix}

그러면 vector 회전은 다음처럼 쓸 수 있다.

p=R(q)p\mathbf{p}' = R(q)\mathbf{p}

Quaternion 회전과 matrix 회전은 같은 변환을 나타낸다.

vec(q(0,p)q1)=R(q)p\operatorname{vec} \left( q(0,\mathbf{p})q^{-1} \right) = R(q)\mathbf{p}

실제 구현에서는 자세를 quaternion으로 저장하고, force나 vector를 변환할 때 rotation matrix로 바꿔 쓰는 경우가 많다.


Euler Angle과의 차이

Euler angle은 roll, pitch, yaw처럼 세 각도로 회전을 표현한다. 직관적이지만 문제가 있다.

표현장점단점
Euler angle사람이 이해하기 쉽다.gimbal lock이 발생할 수 있고 회전 순서가 중요하다.
Rotation matrixvector 변환이 바로 가능하다.9개 원소를 저장하고 orthogonality를 유지해야 한다.
Quaternioncompact하고 보간이 좋으며 gimbal lock이 없다.직관적으로 읽기 어렵다.

Euler angle의 가장 유명한 문제는 gimbal lock이다. 특정 자세에서 두 회전축이 겹치면 자유도 하나를 잃는다. Quaternion은 orientation을 unit sphere in 4D 위의 점으로 표현하기 때문에 이런 singularity를 피할 수 있다.

다만 quaternion도 완전히 "편한" 표현은 아니다. 예를 들어 qqq-q는 같은 회전을 나타낸다.

q(0,p)q1=(q)(0,p)(q)1q(0,\mathbf{p})q^{-1} = (-q)(0,\mathbf{p})(-q)^{-1}

따라서 interpolation이나 logging에서는 sign continuity를 신경써야 한다.


Composition of Rotations

두 회전 q1q_1, q2q_2가 있을 때, 회전을 연속 적용하는 것은 quaternion 곱셈으로 표현된다.

먼저 q1q_1을 적용하고, 그 다음 q2q_2를 적용한다고 하자.

p=q1pq11p' = q_1 p q_1^{-1}

다음 회전은

p=q2pq21p'' = q_2 p' q_2^{-1}

대입하면,

p=q2(q1pq11)q21p'' = q_2(q_1 p q_1^{-1})q_2^{-1}

정리하면,

p=(q2q1)p(q2q1)1p'' = (q_2q_1)p(q_2q_1)^{-1}

따라서 combined rotation은 다음이다.

qcombined=q2q1q_{\text{combined}} = q_2q_1

순서가 중요하다.

q2q1q1q2q_2q_1 \neq q_1q_2

SLERP

Quaternion은 두 orientation 사이를 부드럽게 보간할 때도 유용하다. 단순 선형 보간은 unit norm을 깨뜨릴 수 있다.

(1t)q0+tq1(1-t)q_0+tq_1

그래서 unit quaternion sphere 위에서 보간하는 SLERP(Spherical Linear Interpolation) 를 사용한다.

두 unit quaternion q0q_0, q1q_1 사이의 각도를 Ω\Omega라 하자.

cosΩ=q0Tq1\cos\Omega = q_0^Tq_1

SLERP는 다음과 같다.

slerp(q0,q1;t)=sin((1t)Ω)sinΩq0+sin(tΩ)sinΩq1\operatorname{slerp}(q_0,q_1;t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}q_0 + \frac{\sin(t\Omega)}{\sin\Omega}q_1

여기서 t[0,1]t\in[0,1]이다.

slerp(q0,q1;0)=q0\operatorname{slerp}(q_0,q_1;0)=q_0 slerp(q0,q1;1)=q1\operatorname{slerp}(q_0,q_1;1)=q_1

SLERP는 회전 속도가 일정한 보간을 제공하므로 animation, camera movement, robot trajectory에서 자주 쓰인다.


Python 구현

외부 library 없이 핵심 연산만 구현하면 다음과 같다.

import math
from dataclasses import dataclass


@dataclass
class Quaternion:
    w: float
    x: float
    y: float
    z: float

    def __mul__(self, other):
        return Quaternion(
            self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z,
            self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y,
            self.w * other.y - self.x * other.z + self.y * other.w + self.z * other.x,
            self.w * other.z + self.x * other.y - self.y * other.x + self.z * other.w,
        )

    def conjugate(self):
        return Quaternion(self.w, -self.x, -self.y, -self.z)

    def norm(self):
        return math.sqrt(self.w**2 + self.x**2 + self.y**2 + self.z**2)

    def normalized(self):
        n = self.norm()
        return Quaternion(self.w / n, self.x / n, self.y / n, self.z / n)

    def inverse(self):
        n2 = self.norm() ** 2
        c = self.conjugate()
        return Quaternion(c.w / n2, c.x / n2, c.y / n2, c.z / n2)

    @staticmethod
    def from_axis_angle(axis, angle_rad):
        ax, ay, az = axis
        length = math.sqrt(ax * ax + ay * ay + az * az)
        if length == 0:
            raise ValueError("axis must be non-zero")

        ax, ay, az = ax / length, ay / length, az / length
        half = angle_rad / 2
        s = math.sin(half)
        return Quaternion(math.cos(half), ax * s, ay * s, az * s)

    def rotate_vector(self, vector):
        p = Quaternion(0, vector[0], vector[1], vector[2])
        rotated = self * p * self.inverse()
        return [rotated.x, rotated.y, rotated.z]

Z축 기준 9090^\circ 회전을 테스트하면 다음과 같다.

q = Quaternion.from_axis_angle([0, 0, 1], math.pi / 2)
v = [1, 0, 0]

print(q.rotate_vector(v))
# approximately [0, 1, 0]

실무에서는 직접 구현하기보다 검증된 library를 사용하는 것이 좋다. Python에서는 scipy.spatial.transform.Rotation을 사용할 수 있다.

from scipy.spatial.transform import Rotation

rot = Rotation.from_euler("z", 90, degrees=True)
print(rot.as_quat())   # x, y, z, w order
print(rot.apply([1, 0, 0]))

주의할 점은 library마다 quaternion component order가 다르다는 것이다.

Convention순서
수학/일부 robotics 문헌(w,x,y,z)(w,x,y,z)
SciPy as_quat()(x,y,z,w)(x,y,z,w)

Component order를 혼동하면 회전 결과가 완전히 달라진다.


정리

Quaternion은 3D 회전을 표현하기 위한 compact한 수학적 도구이다.

핵심 공식은 다음이다.

q=(cosθ2,usinθ2)\boxed{ q = \left( \cos\frac{\theta}{2}, \mathbf{u}\sin\frac{\theta}{2} \right) }

그리고 vector 회전은 다음처럼 적용한다.

p=qpq1\boxed{ p' = q p q^{-1} }

Quaternion의 장점은 다음과 같다.

장점의미
No gimbal lockEuler angle singularity를 피한다.
Compact4개 값으로 orientation을 저장한다.
Efficient composition회전 합성이 quaternion 곱셈으로 된다.
Smooth interpolationSLERP로 일정한 회전 보간이 가능하다.

반면 사람이 읽기에는 Euler angle보다 직관적이지 않다. 그래서 실제 시스템에서는 내부 상태는 quaternion으로 유지하고, UI나 log에서는 roll-pitch-yaw로 변환해 보여주는 경우가 많다.