[로봇공학기초 / 2DOF] #2. Inverse Kinematics (역기구학)
📐 Inverse Kinematics란?
지난 글에서는 Forward Kinematics(정기구학)에 대해 살펴보았습니다.
로봇 관절의 각도를 알 때, 로봇팔의 끝단(End-effector)이 어디에 위치하는지를 계산하는 방법이었죠.
이번에는 반대로,
"로봇팔의 끝단(End-effector) 이 특정 위치에 있어야 한다면, 각 관절은 어떤 각도를 가져야 할까?"
에 대한 문제를 다룹니다.
사실 실제 환경에서는 사람이 로봇과 상호작용할 때
“관절 각도를 몇 도로 꺾을까”라고 생각하기보다는,
“로봇팔의 끝을 이 위치로 이동시키고 싶다” 라는 방식으로 접근하는 경우가 많습니다.
따라서, 목표 위치에 따른 관절의 값을 계산하는 과정이 필요하며,
그걸 해결하는 것이 바로 Inverse Kinematics(역기구학)입니다
🎯 목표 위치 → 관절각 계산
아래와 같이 정의하겠습니다:
- 말단의 목표 위치: $(x_{2}, y_{2})$
- 링크의 길이: $L_{1}, L_{2}$
- 구하고자 하는 관절각: $\theta_1,\ \theta_2$
이때 역기구학 문제는 다음과 같은 기하학적 문제로 바뀝니다:
“ 두 길이의 막대를 이용해, 지정된 점에 도달할 수 있는 각도를 찾는 것 ”
✍ 수학적 해석 (삼각법 기반)
두 번째 관절 각도 $\theta_2$는 다음 수식을 통해 계산할 수 있습니다:
\begin{align*}
\theta_2 &= \cos^{-1}\left( \frac{{x_2}^2 + {y_2}^2 - L_1^2 - L_2^2}{2 L_1 L_2} \right) \\[1.5em]
\because\quad &\cos(\pi - \theta_2) = \cos\theta_2 = \frac{{x_2}^2 + {y_2}^2 - L_1^2 - L_2^2}{2 L_1 L_2}
\end{align*}
첫 번째 관절 각도 $\theta_1$는 삼각형의 내각 관계를 이용하여 다음과 같이 계산됩니다:
\begin{align*}
\theta_1 &= \phi - \alpha \\
&= \mathrm{atan2}(y_{2}, x_{2}) - \mathrm{atan2}\left(L_2 \sin\theta_2,\ L_1 + L_2 \cos\theta_2\right)
\end{align*}
⚠️ 해가 2개? (Elbow-up / Elbow-down)
위 수식은 보통 두 개의 해를 갖습니다.
- 하나는 팔꿈치가 위로 향하는 자세 (Elbow-up)
- 다른 하나는 팔꿈치가 아래로 향하는 자세 (Elbow-down)
즉, 하나의 목표 위치에 대해 두 가지 가능한 자세가 존재할 수 있습니다.
아래 그림을 보면 두 자세의 차이가 훨씬 직관적으로 이해됩니다.
(2 DOF 로봇에서는 가능한 자세가 두 가지뿐이지만,자유도가 늘어날수록 같은 말단 위치와 방향에 대해 여러 가지 자세가 가능해집니다.이와 관련된 개념은 이후 6 DOF 시리즈에서 함께 다뤄보겠습니다.)
📈 그래프로 그려보기
아래는 Python을 활용해 2자유도 로봇팔의 역기구학 계산 및 시각화를 구현한 코드입니다.
정말로 Elbow up과 Elbow down의 두 가지 해가 있죠?
그럼 실제 로봇은 이 두 해 중 어떤 자세를 선택해야 할까요? 이런 질문이 떠오른다면, 여러분은 이미 훌륭한 로봇공학자의 감각을 갖고 있는 셈입니다.
이 부분은 이후 다룰 동작 계획(Motion Planning) 주제에서 자세히 살펴보겠습니다.
좀 많이 뒤에 나옵니다.
💻 아래는 그래프를 그릴 때 사용한 Python 코드입니다.
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False
# 링크 길이 설정
l1, l2 = 1, 0.5
def inverse_kinematics_solutions(x, y, l1, l2):
"""
주어진 (x, y) 목표점에 대해 2자유도 로봇팔의 두 가지 역기구학 해를 구합니다.
반환값은 ((theta1_1, theta2_1), (theta1_2, theta2_2)) 입니다.
"""
r = np.sqrt(x**2 + y**2)
# 작업공간 내에 있는지 확인
if r > l1 + l2 or r < abs(l1 - l2):
return None
# cos(theta2) 계산
cos_theta2 = (x**2 + y**2 - l1**2 - l2**2) / (2 * l1 * l2)
# 두 해: theta2는 arccos 또는 -arccos
theta2_1 = np.arccos(cos_theta2)
theta2_2 = -np.arccos(cos_theta2)
# 각각에 대해 theta1 계산
theta1_1 = np.arctan2(y, x) - np.arctan2(l2 * np.sin(theta2_1), l1 + l2 * np.cos(theta2_1))
theta1_2 = np.arctan2(y, x) - np.arctan2(l2 * np.sin(theta2_2), l1 + l2 * np.cos(theta2_2))
return (theta1_1, theta2_1), (theta1_2, theta2_2)
def forward_kinematics(theta1, theta2, l1, l2):
"""
주어진 theta1, theta2에 대해 로봇팔의 관절 좌표를 계산합니다.
반환값은 (base, joint, end_effector) 튜플입니다.
"""
base = (0, 0)
joint = (l1 * np.cos(theta1), l1 * np.sin(theta1))
end_effector = (joint[0] + l2 * np.cos(theta1 + theta2),
joint[1] + l2 * np.sin(theta1 + theta2))
return base, joint, end_effector
# 목표 위치 (x, y) 설정
x_target, y_target = -1.0, 1.5
# 역기구학 해 계산
solutions = inverse_kinematics_solutions(x_target, y_target, l1, l2)
if solutions is None:
print("주어진 목표점에 도달할 수 없습니다.")
else:
(theta1_1, theta2_1), (theta1_2, theta2_2) = solutions
points1 = forward_kinematics(theta1_1, theta2_1, l1, l2)
points2 = forward_kinematics(theta1_2, theta2_2, l1, l2)
plt.figure(figsize=(6, 6))
ax = plt.gca()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_aspect('equal')
ax.set_title("2자유도 로봇팔 역기구학 해 (목표: ({}, {}))".format(x_target, y_target))
# 솔루션 1 (파란색)
xs1, ys1 = zip(*points1)
plt.plot(xs1, ys1, 'bo-', label="솔루션 1")
# 솔루션 2 (초록색)
xs2, ys2 = zip(*points2)
plt.plot(xs2, ys2, 'go-', label="솔루션 2")
# 목표점 표시 (빨간색)
plt.plot(x_target, y_target, 'ro', label="목표점")
# 각도 값을 도(degree) 단위로 변환하여 오른쪽 하단에 텍스트로 표시 (transAxes 좌표 사용)
theta1_1_deg = np.rad2deg(theta1_1)
theta2_1_deg = np.rad2deg(theta2_1)
theta1_2_deg = np.rad2deg(theta1_2)
theta2_2_deg = np.rad2deg(theta2_2)
# ax.transAxes 좌표계: (0,0) ~ (1,1) 범위, 오른쪽 하단에 배치
ax.text(0.5, 0.08, f"솔루션 1: θ₁ = {theta1_1_deg:.1f}°, θ₂ = {theta2_1_deg:.1f}°",
transform=ax.transAxes, fontsize=10, color='blue',
bbox=dict(facecolor='white', alpha=0.8, edgecolor='blue'))
ax.text(0.5, 0.03, f"솔루션 2: θ₁ = {theta1_2_deg:.1f}°, θ₂ = {theta2_2_deg:.1f}°",
transform=ax.transAxes, fontsize=10, color='green',
bbox=dict(facecolor='white', alpha=0.8, edgecolor='green'))
plt.xlabel("X")
plt.ylabel("Y")
plt.legend()
plt.grid(True)
plt.show()
🔧 실행 환경
- Python 3.8 이상
- numpy
- matplotlib
- Jupyter Notebook (추천)
📘 다음 편 예고
이번 글에서는 말단의 목표 위치가 주어졌을 때 각 관절이 어떤 각도로 움직여야 하는지를 계산하는 역기구학(Inverse Kinematics)에 대해 알아보았습니다.
다음 글에서는 로봇이 활동 할 수 있는 범위인 작업 영역(Work Space)에 대해서 알아보겠습니다.