로봇공학기초

[로봇공학기초 / 2DOF] #2. Inverse Kinematics (역기구학)

jhpark0518 2025. 3. 28. 10:00

 

📐 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 upElbow 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)에 대해서 알아보겠습니다.