
회전 변환 행렬 (2D, 3D)
2020, Jan 02
- 참조 : https://en.wikipedia.org/wiki/Rotation_matrix
- 참조 : https://ko.wikipedia.org/wiki/회전변환행렬
- 이번 글에서는 2D와 3D 상태에서의 좌표의 회전 변환하는 방법에 대하여 알아보도록 하겠습니다.
- 다른 방식의 회전 변환 개념은 아래 링크에서 확인하실 수 있습니다. 다음 내용은 본 글에서 다루는
euler rotation
의 단점을 개선한 내용입니다.
목차
-
2D에서의 회전 변환
-
회전 변환 행렬 유도
-
임의의 점을 중심으로 회전 변환
-
3D에서의 회전 변환
-
회전 변환 행렬의 구성 요소
-
회전 변환 행렬의 직교성
-
Roll, Pitch, Yaw와 Rotation 행렬의 변환
2D에서의 회전 변환
- 2D 좌표계에서 회전 변환을 할 때 사용하는 변환 행렬은 다음과 같습니다.
- R(θ)=[cosθ−sinθsinθcosθ]R(θ)=[cosθ−sinθsinθcosθ]
- 여기서 θθ는 각도에 해당합니다. 반시계 방향으로 회전하는 방향이 + 각도가 됩니다.
- 위 회전 행렬을 이용하여 (x,y)(x,y) 좌표를 회전 변환을 하면 다음과 같습니다.
- [cosθ−sinθsinθcosθ][xy]=[xcosθ−ysinθxsinθ+ycosθ]=[x′y′][cosθ−sinθsinθcosθ][xy]=[xcosθ−ysinθxsinθ+ycosθ]=[x′y′]
- 위 식을 이용하여 회전 변환한 좌표를 구하면 다음과 같습니다.

- 자주 사용하는 회전인 90도 회전 / 180도 회전 / 270도 회전은 다음과 같습니다.
- R(π2)=[0−110]R(π2)=[0−110]
- R(π2)=[−100−1]R(π2)=[−100−1]
- R(3π2)=[01−10]R(3π2)=[01−10]
회전 변환 행렬 유도
- 회전 변환을 다루는 방법에 대해서는 위 글에서 다루었습니다. 그러면 왜 저런 형태의 행렬식이 유도되었는 지에 대하여 다루어 보겠습니다.

- 먼저 앞에서 다룬 회전 변환은 원점을 기준으로 회전을 하게 됩니다. 따라서 위 그림에서도 원점을 중심으로
P
가P'
로 어떻게 변환되는 지 다루어 보도록 하겠습니다. - 아래 식에서 P,¯OP,cos(α),sin(α)P,¯¯¯¯¯¯¯¯OP,cos(α),sin(α)를 정의해 보겠습니다.
- P=(x,y)P=(x,y)
- ¯OP=l=√(x−0)2+(y−0)2)=√x2+y2¯¯¯¯¯¯¯¯OP=l=√(x−0)2+(y−0)2)=√x2+y2
- cos(α)=x¯OP=x√x2+y2cos(α)=x¯¯¯¯¯¯¯¯OP=x√x2+y2
- sin(α)=y¯OP=y√x2+y2sin(α)=y¯¯¯¯¯¯¯¯OP=y√x2+y2
- 위 식을 그대로 이용하여 P′P′에 적용해 보도록 하겠습니다. P′=(x′,y′)P′=(x′,y′)는 P=(x,y)P=(x,y)를 +θ+θ 만큼 회전 시킨 것이므로 회전 각도 만큼 반영해여 식을 적어보겠습니다.
- x′=√x2+y2cos(α+θ)x′=√x2+y2cos(α+θ)
- y′=√x2+y2sin(α+θ)y′=√x2+y2sin(α+θ)

- 삼각함수의 덧셈 정리를 이용하여 식을 풀어보도록 하겠습니다.
- x′=√x2+y2(cos(α)cos(θ)−sin(α)sin(θ))=(√x2+y2x√x2+y2cos(θ)−√x2+y2y√x2+y2sin(θ))=xcos(θ)−ysin(θ)x′=√x2+y2(cos(α)cos(θ)−sin(α)sin(θ))=(√x2+y2x√x2+y2cos(θ)−√x2+y2y√x2+y2sin(θ))=xcos(θ)−ysin(θ)
- y′=√x2+y2sin(α+θ)=√x2+y2(sin(α)cos(θ)+cos(α)sin(θ))=(√x2+y2y√x2+y2cos(θ)+√x2+y2x√x2+y2sin(θ))=ycos(θ)+xsin(θ)y′=√x2+y2sin(α+θ)=√x2+y2(sin(α)cos(θ)+cos(α)sin(θ))=(√x2+y2y√x2+y2cos(θ)+√x2+y2x√x2+y2sin(θ))=ycos(θ)+xsin(θ)
- 위에서 유도한 식을 정리하면 다음과 같습니다.
- (x′y′)=(cosθ−sinθsinθcosθ)(xy)(x′y′)=(cosθ−sinθsinθcosθ)(xy)
임의의 점을 중심으로 회전 변환
- 앞에서 다룬 내용은 모두
원점
을 기준으로 회전한 것입니다. 좀 더 일반적인 케이스를 적용하기 위해 기준이 원점이 아니라 특정 좌표를 기준으로 회전 시켜보도록 하겠습니다.

- 위 그림을 보면 원점을 기준으로 30도 회전한 것을 알 수 있습니다.

- 반면에 위 그림에서는 회전한 기준을 보면 (1, 0)임을 알 수 있습니다.

- 지금부터 해야할 작업은 위 그림 처럼 기준점에서 각 점 방향으로의 벡터를 회전하는 것입니다. (물론 반시계 방향 회전이 + 회전 각도 입니다.)

- ① 기준점을 v0=(xbase,ybase)v0=(xbase,ybase) 이라고 하면 각 점 방향으로의 벡터는 vi−v0=(xi−xbase,yi−ybase)vi−v0=(xi−xbase,yi−ybase)이 됩니다.
- ② 이 벡터를 앞에서 알아본 변환 행렬을 이용하여 회전 시키면 됩니다.
- (x′y′)=(cosθ−sinθsinθcosθ)(x−xbasey−ybase)(x′y′)=(cosθ−sinθsinθcosθ)(x−xbasey−ybase)
- 여기 까지 계산 하면 ((0,0)(0,0) → (x−xbase,y−ybase)(x−xbase,y−ybase)) 방향과 크기의 벡터가 θθ 만큼 회전하여 (x′,y′)(x′,y′)가 되었습니다.
- ③ 벡터의 시작점을 회전 기준인 (xbase,ybase)(xbase,ybase)으로 옮겨줍니다.
- ①, ②, ③ 과정을 식으로 옮기면 다음과 같습니다.
- (x′y′)=(cosθ−sinθsinθcosθ)(x−xbasey−ybase)+(xbaseybase)(x′y′)=(cosθ−sinθsinθcosθ)(x−xbasey−ybase)+(xbaseybase)
3D에서의 회전 변환
- 3D에서의 회전 변환은 2차원에서 사용한 회전 변환 행렬을 유사하게 사용합니다. 다만 이 때, 3차원에 맞춰서 행렬의 차원이 늘어나게 되고 각 차원별로 회전을 고려해 주어야 합니다.
- 예를 들어서 Rx(θ)Rx(θ)는 x축을 중심으로 회전하는 행렬 변환이고 Ry(θ)Ry(θ)는 y축을 중심으로 Rz(θ)Rz(θ)는 z축을 중심으로 회전하는 행렬 변환입니다.
- Rx(θ)=[1000cosθ−sinθ0sinθcosθ]Rx(θ)=⎡⎢⎣1000cosθ−sinθ0sinθcosθ⎤⎥⎦
- Ry(θ)=[cosθ0sinθ010−sinθ0cosθ]
- Rz(θ)=[cosθ−sinθ0sinθcosθ0001]
- 이 행렬을 정리해 보려고 하는데, 그 전에
roll
,yaw
,pitch
에 대하여 알아보겠습니다.

- 위 회전축 기준으로
roll
은 x축을 기준으로 회전한 양을 뜻하고pitch
는 y축을 기준으로 회전한 양 그리고yaw
는 z축을 기준으로 회전한 양을 뜻합니다. 위 그림처럼 생각하시면 됩니다.- 예를 들어 자동차가 좌회전 또는 우회전을 한다면 z축을 기준으로 회전을 하는 것이므로
yaw
의 변화가 있게 됩니다.
- 예를 들어 자동차가 좌회전 또는 우회전을 한다면 z축을 기준으로 회전을 하는 것이므로
- 그러면 Rx(θ), Ry(θ) 그리고 Rz(θ) 각각 x축, y축, z축을 기준으로 회전하는 회전 변환 행렬이 됩니다.
- x축을 기준으로 회전한
roll angle
을 α, y축을 기준으로 회전한pitch angle
을 β 마지막으로 z축을 기준으로 회전한yaw angle
을 γ로 두겠습니다.
- R=Rz(γ)Ry(β)Rx(α)=[cosγ−sinγ0sinγcosγ0001][cosβ0sinβ010−sinβ0cosβ][1000cosα−sinα0sinαcosα]
- 위 변환 행렬을 모두 곱하면 roll, pitch, yaw angle을 모두 고려한 회전을 나타낼 수 있습니다.
- 위 식을 풀어서 나타내면 다음과 같습니다.
- R=[cosγ cosβcosγ sinβ sinα−sinγ cosαcosγ sinβ cosα+sinγ sinαsinγ cosβsinγ sinβ sinα+cosγ cosαsinγ sinβ cosα−cosγ sinα−sinβcosβ sinαcosβ cosα]
- 위 식을 파이썬 코드로 나타내면 다음과 같습니다.
import numpy as np
def euler_to_rotation_matrix(roll, pitch, yaw):
# Convert angles to radians
roll = np.radians(roll)
pitch = np.radians(pitch)
yaw = np.radians(yaw)
# Define individual rotation matrices
Rx = np.array([[1, 0, 0],
[0, np.cos(roll), -np.sin(roll)],
[0, np.sin(roll), np.cos(roll)]])
Ry = np.array([[np.cos(pitch), 0, np.sin(pitch)],
[0, 1, 0],
[-np.sin(pitch), 0, np.cos(pitch)]])
Rz = np.array([[np.cos(yaw), -np.sin(yaw), 0],
[np.sin(yaw), np.cos(yaw), 0],
[0, 0, 1]])
# Combine the rotations
R = np.dot(Rz, np.dot(Ry, Rx))
return R
회전 변환 행렬의 구성 요소
- 회전 변환 행렬의 경우 각 열의 성분이 각 축의
기저 벡터 (basis vector)
가 회전 되었을 때의 벡터를 의미합니다. 3차원 행렬의 경우 X,Y,Z 순서로 축의 의미를 가진다면 회전 변환 행렬의 첫번째 열은 X 축의 기저 벡터를 회전 변환하였을 때의 벡터, 두번째 열은 Y 축의 기저 벡터를 회전 변환하였을 때의 벡터, 세번째 열은 Z 축의 기저 벡터를 회전 변환하였을 때의 벡터를 의미합니다. - 각 축의 기저 벡터를 회전하는 예시를 확인하면 그 뜻을 쉽게 알 수 있습니다.
- [R11R12R13R21R22R23R31R32R33][100]=[R11R21R31]
- [R11R12R13R21R22R23R31R32R33][010]=[R12R22R32]
- [R11R12R13R21R22R23R31R32R33][001]=[R13R23R33]
- 첫번째 예시인 X 축의 예시를 살펴보면 기저 벡터가 [1,0,0]T→[R11,R21,R31]T 로 변환된 것을 알 수 있습니다. 두번째, 세번째 예시를 통하여 Y,Z 축이 어떻게 변하는 지 또한 확인할 수 있습니다.
- 다음 예시를 통하여 간단하게 회전 변환의 역할에 대하여 알아보도록 하겠습니다.
- [0−1000−1100]
- 먼저 첫번째 열인 [0,0,1]T 을 통해 X 축의 기저 벡터에 회전 변환 행렬을 적용하면 [1,0,0]→[0,0,1]T 이 된 것을 알 수 있습니다. 즉, Z 축의 기저 벡터 처럼 변경 되었습니다.
- 두번째 열인 [−1,0,0]T 을 통해 Y 축의 기저 벡터에 회전 변환 행렬을 적용하면 [0,1,0]→[−1,0,0]T 이 된 것을 알 수 있습니다. 즉, 음의 방향의 X 축 기저 벡터 처럼 변경 되었습니다.
- 세번째 열인 [0,−1,0]T 을 통해 Z 축의 기저 벡터에 회전 변환 행렬을 적용하면 [0,0,1]→[0,−1,0]T 이 된 것을 알 수 있습니다. 즉, 음의 방향의 Y 축 기저 벡터 처럼 변경 되었습니다.
- 따라서
R[:, 0]
,R[:, 1]
,R[:, 2]
성분을 이용하면 기저 벡터가 어떻게 회전 변환 되는 지 알 수 있습니다.
- 임의의 회전 행렬을 적용하였을 때, 현재 좌표축 기준으로 변환된 좌표축에서의 기저 벡터를 구하고 싶다면 각 열에서 절대값이 가장 큰 축을 기저 벡터로 간주하여 기저 벡터를 구성하면 됩니다. 이 때, 부호 또한 반영하면 현재 기저 벡터 기준으로 축의 방향 또한 고려하여 확인할 수 있습니다.
R = np.array([
0.00463, -0.99998, 0.00385,
-0.01405, -0.00391, -0.99989,
0.99989, 0.00457, -0.01407]
).reshape(3, 3)
basis_rotation = np.zeros((3, 3), dtype=np.float32)
for i in range(3):
axis = np.argmax(np.abs(R[:, i]))
if R[axis, i] > 0:
basis_rotation[axis, i] = 1.0
elif R[axis, i] < 0:
basis_rotation[axis, i] = -1.0
else:
basis_rotation[axis, i] = 0.0
print(basis_rotation)
# [[ 0. -1. 0.]
# [ 0. 0. -1.]
# [ 1. 0. 0.]]
basis_rotation
이 앞에서 해석한 내용과 동일한 값을 가집니다. 이와 같은 방법으로 현재 좌표계 기준의 기저 벡터가 회전 변환을 거쳤을 때, 어떻게 바뀌는 지 확인할 수 있습니다.
회전 변환 행렬의 직교성
- 지금까지 살펴본
rotation
행렬은orthogonal
행렬이며 다음과 같은 성질을 따릅니다.
- RT=R−1
- RTR=I
orthogonal
또는orthonormal
인 행렬 Q 가 있을 때, QQT=QTQ=I 임은 필요충분 조건임이 알려져 있습니다.- 앞에서 살펴본
2D
,3D
회전 변환 행렬의 경우도 RRT=RTR=I 를 만족하며 일반적으로orthogonal
형태이므로orthogonal
하다고 말할 수 있습니다. - 또한
orthogonal
한 경우 determinant가 1을 만족하는데 이 조건에도 만족하게 됩니다.

- 위 계산 결과와 같이 2D 회전 변환 행렬 R 의 RRT=I 임을 확인할 수 있습니다.
- 3D 회전 변환 행렬의 경우 간단히
det(R) = I
임을 통해orthogonal
임을 확인해 보겠습니다. -
아래 식과 같이
- R=Rz(α)Ry(β)Rx(γ)
- RTR=(RzRyRx)T(RzRyRx)=RTxRTyRTzRzRyRx
- RTxRX=I
- RTyRy=I
- RTzRz=I
- ∴RTR=I
- plus, det(R)=det(Rz)det(Ry)det(Rx)=1×1×1=1
- 아래와 같이 3D 회전 변환 행렬의 각 방향의 Rx,Ry,Rz 의 determinant는 1임을 확인할 수 있습니다.

Roll, Pitch, Yaw와 Rotation 행렬의 변환
- 참조 : https://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf
- 참조 : atan과 atan2 비교
- 지금까지 살펴본 방식은
Roll
,Pitch
,Yaw
를 이용하여Rotation
행렬을 구하는 방법에 대하여 살펴보았습니다. - 이번에는 임의의
Rotation
행렬이 주어졌을 때, 이 행렬을Roll
,Pitch
,Yaw
로 분해하는 방법을 알아보도록 하겠습니다. 3차원 좌표축의 기준은 앞에서 다룬바와 같이 X 축이 전방, Y 축이 왼쪽, Z 축이 위로 향하는Forward-Left-Up
순서의 좌표축을 의미합니다. 만약 다른 좌표축 기준에서Roll
,Pitch
,Yaw
성분을 분해하려면 아래 내용을 이해한 다음에 분해 순서 및 방식을 일부 수정하여 적용해야 합니다.
- 앞에서
Roll
,Pitch
,Yaw
각각은 다음과 같은 형태로 표현할 수 있었습니다. 아래 식에서 ψ 는 x 축의 회전인Roll
의 각도를 의미하며 θ 는 y 축의 회전인Pitch
의 각도를 의미하고 마지막으로 ϕ 는 z 축의 회전인Yaw
의 각도를 의미합니다.
- Rx(ψ)=[1000cosψ−sinψ0sinψcosψ]
- Ry(θ)=[cosθ0sinθ010−sinθ0cosθ]
- Rz(ϕ)=[cosϕ−sinϕ0sinϕcosϕ0001]
-
앞에서 다룬 바와 같이
Rotation
행렬은 다음과 같이Roll
,Pitch
,Yaw
를 이용하여 표현할 수 있었습니다. 회전 순서는 Rx,Ry,Rz 순서로 곱해지기 때문에Roll
,Pitch
,Yaw
순서로 회전 됩니다. - R=Rz(ϕ)Ry(θ)Rx(ψ)=[cosθcosϕsinψsinθcosϕ−cosψsinϕcosψsinθcosϕ+sinψsinϕcosθsinϕsinψsinθsinϕ+cosψcosϕcosψsinθsinϕ−sinψcosϕ−sinθsinψcosθcosψcosθ]=[R11R12R13R21R22R23R31R32R33]
- 현재 알고 싶은 정보는 위
Rotation
행렬 R 을 이용하여 ψ,θ,ϕ 를 구하고 싶은 것입니다.
-
2개의 θ 구하기
- 앞에서 R 을 구하였을 때, R31 은 다음과 같습니다.
- R31=−sin(θ)
- 따라서 θ 를 쉽게 구할 수 있습니다.
- θ=sin−1(−R31)=−sin−1(R31)
- 추가적으로 sin(π−θ)=sinθ 를 만족하므로 다음과 같이 2개의 식을 만족하는 θ 를 구할 수 있습니다. 단, R31≠±1 인 경우를 가정하며 이유는 글의 뒷부분에서 이어서 설명하도록 하겠습니다.
- θ1=−sin−1(R31)
- θ2=π−θ1=π+sin−1(R31)
- 위 식의 전개와 같이 θ 가 2 종류가 나오기 때문에 최종 해 또한 2 종류로 도출됩니다.
-
대응되는 ψ 구하기
- 위 식에서 R32,R33 을 이용하면 ψ 를 구할 수 있습니다.
- R32R33=sin(ψ)cos(θ)cos(ψ)cos(θ)=sin(ψ)cos(ψ)=tan(ψ)
- ψ=atan2(R32,R33)
- 위 식의 atan2 는 수치적 안정성을 위해 사용합니다. 일반적인 atan 은 1사분면과 4사분면만 표현할 수 있습니다. 즉, 분자, 분모가 모두 양수이거나 모두 음수인 경우만 atan 을 연산할 수 있습니다. 반면에 4개의 사분면에서 모두 atan 을 적용하기 위해 함수화한 것이 atan2 라고 말할 수 있습니다. 상세 내용은 아래 링크를 참조하시기 바랍니다.
- 참조 : atan과 atan2 비교
- 따라서 atan2 를 적용할 때에는 부호를 잘 고려해서 함수에 적용하면 위 링크에서 예외 처리한 내용을 고민하지 않고 계산할 수 있습니다.
- 따라서 앞선 식 R32R33=sin(ψ)cos(θ)cos(ψ)cos(θ)=sin(ψ)cos(ψ) 에서 단순히 cos(θ) 를 소거하지 않고 다음과 같이 연산해 줍니다. 다음과 같이 연산하면 cos(θ)≠0 일 때에 연산이 모두 유효합니다.
- ψ=atan2(R32cos(θ),R33cos(θ))
- 위 식에서 cos(θ) 는 θ 의 값이 1, 4 사분면에 있으면 양수이고 2, 3 사분면에 있으면 음수이기 때문에 위 식과 같이 부호를 고려한다면 θ 의 값에 따라 ψ 를 구할 수 있습니다.
- 앞선 식에서 θ 의 후보로 θ1,θ2 가 있었기 때문에 다음과 같이 2개의 ψ1,ψ2 를 구할 수 있습니다. cos(θ)=0 인 경우는 글 뒷부분에서 한번에 예외처리 하도록 하겠습니다.
- ψ1=atan2(R32cos(θ1),R33cos(θ1))
- ψ2=atan2(R32cos(θ2),R33cos(θ2))
-
대응되는 ϕ 구하기
- 앞에서 ψ1,ψ2 를 구할 때에는 R32,R33 을 이용하였습니다. ϕ 를 구할 때에는 R21,R11 을 이용해 보도록 하겠습니다. 방식은 완전히 동일합니다.
- ϕ=atan2(R21cos(θ),R11cos(θ))
- 이 경우에도 cos(θ)≠0 일 때, 계산이 모두 유효하며 아래와 같이 2개의 ϕ1,ϕ2 를 구할 수 있습니다.
- ϕ1=atan2(R21cos(θ1),R11cos(θ1))
- ϕ2=atan2(R21cos(θ2),R11cos(θ2))
-
cos(θ)≠0 일 때의 Euler Angles
- 지금까지 살펴본 방법으로 cos(θ)≠0 일 때, 다음과 같이 2가지
Euler Angles
를 구할 수 있습니다.
- (ψ1,θ1,ϕ1)=(Roll1,Pitch1,Yaw1)
- (ψ2,θ2,ϕ2)=(Roll2,Pitch2,Yaw2)
-
cos(θ)=0 일 때의 Euler Angles
- 다음과 같이 cos(θ)=0 인 경우는 θ=π2 또는 θ=−π2 입니다. 그리고 R31=sin(θ)=±1 인 경우를 의미하기도 합니다.
- 이 경우에도 앞에서 살펴본 방식과 동일하게 수식을 전개한다면 R11,R12,R32,R33 이 0이기 때문에 다음과 같은 문제가 발생합니다.
- ψ=atan2(00,00)
- ϕ=atan2(00,00)
- 즉, ψ,ϕ 의 값을 정할 수 없게 됩니다. 이와 같은 현상을
Gimbal Lock
이라고 하며 다음 링크에서 그 현상의 기하학적인 의미를 살펴볼 수 있습니다.- 링크 : https://gaussian37.github.io/vision-concept-axis_angle_rotation/
- 이러한 문제를 회피하여
Euler Angles
를 구하기 위해 다음과 같이 식을 전개해 보도록 하겠습니다.
- 먼저 θ=π2 인 경우 부터 살펴보도록 하겠습니다. cos(θ)≠0 일 때에는 R32,R33 을 이용하여 ψ 를 구하고 R11,R21 을 이용하여 ϕ 를 구하였습니다. 반면 cos(θ)=0 와 같은 예외 상황을 처리하기 위해서는 R12,R13,R22,R23 을 이용합니다.
삼각함수의 덧셈 공식
을 이용하면 다음과 같이 식을 전개할 수 있습니다.
Case 1: θ=π2
- R12=sin(ψ)cos(ϕ)−cos(ψ)sin(ϕ)=sin(ψ−ϕ)
- R13=cos(ψ)cos(ϕ)+sin(ψ)sin(ϕ)=cos(ψ−ϕ)
- R22=sin(ψ)sin(ϕ)−cos(ψ)cos(ϕ)=cos(ψ−ϕ)=R13
- R23=cos(ψ)sin(ϕ)−sin(ψ)cos(ϕ)=−sin(ψ−ϕ)=−R12
- R12R13=sin(ψ−ϕ)cos(ψ−ϕ)=tan(ψ−ϕ)
- (ψ−ϕ)=atan2(R12,R13)
- ψ=ϕ+atan2(R12,R13)
Case 2: θ=−π2
- R12=−sin(ψ)cos(ϕ)−cos(ψ)sin(ϕ)=−sin(ψ+ϕ)
- R13=−cos(ψ)cos(ϕ)+sin(ψ)sin(ϕ)=−cos(ψ+ϕ)
- R22=−sin(ψ)sin(ϕ)+cos(ψ)cos(ϕ)=cos(ψ+ϕ)=−R13
- R23=−cos(ψ)sin(ϕ)−sin(ψ)cos(ϕ)=−sin(ψ+ϕ)=R12
- −R12−R13=sin(ψ+ϕ)cos(ψ+ϕ)=tan(ψ+ϕ)
- (ψ+ϕ)=atan2(−R12,−R13)
- ψ=−ϕ+atan2(−R12,−R13)
- 지금까지 내용을 살펴보면 θ=±π2 인
Gimbal Lock
상황에서는 ψ 와 ϕ 가 서로 연결되어 있어 특정 해를 구할 수 없습니다. 따라서 특정 해를 지정하고 싶으면 ϕ=0 과 같이 임의의 값을 지정해 주어야 합니다.
- 이 조건들을 모두 이용하면 다음과 같이 의사 코드를 작성할 수 있습니다.

- 첫 조건문의 R31≠±1 은 θ≠±π2 을 의미합니다.
- 첫 조건문에서
else
로 분기되면 ϕ 는 어떤 값이 되어도 되므로 임의로 0으로 지정할 수 있습니다.
- 정리하면 임의의
Rotation
행렬을Euler Angle
의Roll
,Pitch
,Yaw
로 분해할 수 있습니다. - 이 때, 특정 한 축의 회전각이 ±π2 인 경우가 아니라면 2개의 해를 가지게 되고 회전 각도의 범위를 고려하여 1개의 해를 선택하여 사용할 수 있습니다. 반면 한 축의 회전각이 ±π2 인 경우에는 무한히 많은 해를 가지게 되며
Gimbal Lock
현상이 발생합니다.
python code
- 먼저 앞에서 설명한 기호를 이용하여 코드를 작성하면 다음과 같습니다.
import numpy as np
# Define the conversion function
def rotation_matrix_to_euler_angles(R):
assert(R.shape == (3, 3))
if R[2, 0] != 1 and R[2, 0] != -1:
theta1 = -np.arcsin(R[2, 0])
theta2 = np.pi - theta1
psi1 = np.arctan2(R[2, 1] / np.cos(theta1), R[2, 2] / np.cos(theta1))
psi2 = np.arctan2(R[2, 1] / np.cos(theta2), R[2, 2] / np.cos(theta2))
phi1 = np.arctan2(R[1, 0] / np.cos(theta1), R[0, 0] / np.cos(theta1))
phi2 = np.arctan2(R[1, 0] / np.cos(theta2), R[0, 0] / np.cos(theta2))
return (psi1, theta1, phi1), (psi2, theta2, phi2)
else:
phi = 0 # can set to anything, it's the gimbal lock case
if R[2, 0] == -1:
theta = np.pi / 2
psi = phi + np.arctan2(R[0, 1], R[0, 2])
else:
theta = -np.pi / 2
psi = -phi + np.arctan2(-R[0, 1], -R[0, 2])
return (psi, theta, phi), (None, None, None)
# Example rotation matrix
R_example = np.array([
[0.5, -0.1464, 0.8536],
[0.5, 0.8536, -0.1464],
[-0.7071, 0.5, 0.5]
])
# Get the Euler angles
euler_angles_set_1, euler_angles_set_2 = rotation_matrix_to_euler_angles(R_example)
print("euler_angles_set_1:")
print(f"psi: {euler_angles_set_1[0]} radian.({euler_angles_set_1[0] * 180 / np.pi} deg).")
print(f"theta: {euler_angles_set_1[1]} radian.({euler_angles_set_1[1] * 180 / np.pi} deg).")
print(f"phi: {euler_angles_set_1[2]} radian.({euler_angles_set_1[2] * 180 / np.pi} deg).")
print("\neuler_angles_set_2:")
print(f"psi: {euler_angles_set_2[0]} radian.({euler_angles_set_2[0] * 180 / np.pi} deg).")
print(f"theta: {euler_angles_set_2[1]} radian.({euler_angles_set_2[1] * 180 / np.pi} deg).")
print(f"phi: {euler_angles_set_2[2]} radian.({euler_angles_set_2[2] * 180 / np.pi} deg).")
# euler_angles_set_1:
# psi: 0.7853981633974483 radian.(45.0 deg).
# theta: 0.7853885733974476 radian.(44.99945053347443 deg).
# phi: 0.7853981633974483 radian.(45.0 deg).
# euler_angles_set_2:
# psi: -2.356194490192345 radian.(-135.0 deg).
# theta: 2.3562040801923456 radian.(135.00054946652557 deg).
# phi: -2.356194490192345 radian.(-135.0 deg).
- 먼저 첫번째 셋의 결과는 다음과 같습니다.
psi
= 0.7853981633974483 radian.(45.0 deg).theta
= 0.7853885733974476 radian.(44.99945053347443 deg).phi
= 0.7853981633974483 radian.(45.0 deg).
- 두번째 셋의 결과는 다음과 같습니다.
psi
= -2.356194490192345 radian.(-135.0 deg).theta
= 2.3562040801923456 radian.(135.00054946652557 deg).phi
= -2.356194490192345 radian.(-135.0 deg).
- 일반적으로 첫번째 셋의 결과를 사용하면 됩니다.
- 다음으로 많이 사용하는
roll
,pitch
,yaw
로 바꾸어서 표현하면 다음과 같습니다. 구현 방법 및 결과는 동일합니다.
import numpy as np
# Redefine the conversion function using roll, pitch, and yaw after the code state reset
def rotation_matrix_to_euler_angles(R):
assert(R.shape == (3, 3))
if R[2, 0] != 1 and R[2, 0] != -1:
pitch1 = -np.arcsin(R[2, 0])
pitch2 = np.pi - pitch1
roll1 = np.arctan2(R[2, 1] / np.cos(pitch1), R[2, 2] / np.cos(pitch1))
roll2 = np.arctan2(R[2, 1] / np.cos(pitch2), R[2, 2] / np.cos(pitch2))
yaw1 = np.arctan2(R[1, 0] / np.cos(pitch1), R[0, 0] / np.cos(pitch1))
yaw2 = np.arctan2(R[1, 0] / np.cos(pitch2), R[0, 0] / np.cos(pitch2))
return (roll1, pitch1, yaw1), (roll2, pitch2, yaw2)
else:
yaw = 0 # can set to anything, it's the gimbal lock case
if R[2, 0] == -1:
pitch = np.pi / 2
roll = yaw + np.arctan2(R[0, 1], R[0, 2])
else:
pitch = -np.pi / 2
roll = -yaw + np.arctan2(-R[0, 1], -R[0, 2])
return (roll, pitch, yaw), (None, None, None)
# Example rotation matrix
R_example = np.array([
[0.5, -0.1464, 0.8536],
[0.5, 0.8536, -0.1464],
[-0.7071, 0.5, 0.5]
])
# Get the Euler angles
euler_angles_set_1, euler_angles_set_2 = rotation_matrix_to_euler_angles(R_example)
print("euler_angles_set_1:")
print(f"Roll: {euler_angles_set_1[0]} radian.({euler_angles_set_1[0] * 180 / np.pi} deg).")
print(f"Pitch: {euler_angles_set_1[1]} radian.({euler_angles_set_1[1] * 180 / np.pi} deg).")
print(f"Yaw: {euler_angles_set_1[2]} radian.({euler_angles_set_1[2] * 180 / np.pi} deg).")
print("\neuler_angles_set_2:")
print(f"Roll: {euler_angles_set_2[0]} radian.({euler_angles_set_2[0] * 180 / np.pi} deg).")
print(f"Pitch: {euler_angles_set_2[1]} radian.({euler_angles_set_2[1] * 180 / np.pi} deg).")
print(f"Yaw: {euler_angles_set_2[2]} radian.({euler_angles_set_2[2] * 180 / np.pi} deg).")
# euler_angles_set_1:
# Roll: 0.7853981633974483 radian.(45.0 deg).
# Pitch: 0.7853885733974476 radian.(44.99945053347443 deg).
# Yaw: 0.7853981633974483 radian.(45.0 deg).
# euler_angles_set_2:
# Roll: -2.356194490192345 radian.(-135.0 deg).
# Pitch: 2.3562040801923456 radian.(135.00054946652557 deg).
# Yaw: -2.356194490192345 radian.(-135.0 deg).
- 위 코드의 결과를 수식을 통해 계산한 결과와 비교해 보도록 하겠습니다.
- R=[0.5−0.14640.85360.50.8536−0.1464−0.70710.50.5]
- θ1=−sin(−0.7071)=π4
- θ2=π−θ1=π−π4=3π4
- ψ1=atan2(0.5cos(π/4),0.5cos(π/4))=π4
- ψ2=atan2(0.5cos(3π/4),0.5cos(3π/4))=−3π4
- ϕ1=atan2(0.5cos(π/4),0.5cos(π/4))=π4
- ψ2=atan2(0.5cos(3π/4),0.5cos(3π/4))=−3π4
- (ψ1(Roll),θ1(Pitch),ϕ1(Yaw))=(π4,π4,π4)
- (ψ2(Roll),θ2(Pitch),ϕ2(Yaw))=(−3π4,3π4,−3π4)
- 만약
Gimbal Lock
과 같은 상황을 고려하지 않고 간단하게Roll
,Pitch
,Yaw
를 구하고 싶다면 다음과 같이 간단하게 사용할 수 있습니다.
def rotation_matrix_to_euler_angles(R):
assert(R.shape == (3, 3))
theta = -np.arcsin(R[2, 0])
psi = np.arctan2(R[2, 1] / np.cos(theta), R[2, 2] / np.cos(theta))
phi = np.arctan2(R[1, 0] / np.cos(theta), R[0, 0] / np.cos(theta))
return np.array([psi, theta, phi]) # Roll, Pitch, Yaw