IK(역기구학)를 구하는 방법

개요

이전 포스트에서 IK(역기구학)가 무엇인지 알아보았습니다. 이번에는 실제로 IK를 어떻게 구하는지, 즉 ROS 2 + MoveIt 환경에서 IK 솔버를 설정하고 사용하는 구체적인 방법을 다룹니다.

핵심: 원하는 XYZ 좌표를 관절 각도로 변환하는 과정이 IK이며, ROS에서는 KDL 솔버가 이 계산을 수행한다.


KDL 솔버란

**KDL (Kinematics and Dynamics Library)**은 Orocos 프로젝트에서 개발한 운동학/동역학 라이브러리입니다. MoveIt의 기본 IK 솔버로 탑재되어 있으며, 수치적(numerical) 방법으로 IK를 풀어냅니다.

구분해석적(Analytical) IK수치적(Numerical) IK
방법수학 공식으로 직접 해를 계산반복 계산으로 해에 수렴
속도매우 빠름상대적으로 느림
범용성특정 로봇 구조에만 적용임의의 로봇에 적용 가능
해 보장모든 해를 구할 수 있음초기값에 따라 다른 해 수렴
대표 솔버IKFastKDL, TRAC-IK

KDL은 범용성이 큰 장점입니다. URDF만 있으면 어떤 로봇이든 IK를 풀 수 있습니다.


Newton-Raphson 반복법

KDL 솔버의 핵심 알고리즘은 Newton-Raphson 반복법입니다. 현재 관절 상태에서 목표까지의 오차를 계산하고, 이를 점진적으로 줄여나가는 방식입니다.

현재 관절 각도 q\mathbf{q}에서 목표 위치 xd\mathbf{x}_d까지의 오차를 다음과 같이 정의합니다:

Δx=xdf(q)\Delta \mathbf{x} = \mathbf{x}_d - f(\mathbf{q})

여기서 f(q)f(\mathbf{q})는 정기구학(FK) 함수입니다. 관절 각도 업데이트는 **야코비안(Jacobian)**의 유사역행렬을 이용합니다:

qn+1=qn+J(qn)Δx\mathbf{q}_{n+1} = \mathbf{q}_n + J^{\dagger}(\mathbf{q}_n) \cdot \Delta \mathbf{x}

  • JJ^{\dagger}: 야코비안의 유사역행렬 (pseudoinverse)
  • 이 과정을 Δx<ϵ\|\Delta \mathbf{x}\| < \epsilon (수렴 조건)이 될 때까지 반복합니다.

flowchart TD
    A["초기 추정값 q₀ (시드 상태)"] --> B["FK 계산: x = f(q)"]
    B --> C{"오차 Δx = x_d - f(q)\n충분히 작은가?"}
    C -->|"‖Δx‖ < ε"| D["수렴 완료\n관절 각도 q 반환"]
    C -->|"‖Δx‖ ≥ ε"| E["야코비안 J(q) 계산"]
    E --> F["q = q + J†·Δx"]
    F --> G{"반복 횟수 초과?"}
    G -->|"아니오"| B
    G -->|"예"| H["실패: 해를 찾지 못함"]

Tip: Newton-Raphson은 **초기값(시드)**에 민감합니다. 좋은 시드를 주면 빠르게 수렴하지만, 나쁜 시드를 주면 수렴하지 못하거나 원치 않는 해에 도달할 수 있습니다.


IK 솔버 설정

MoveIt에서 KDL 솔버는 kinematics.yaml 파일에서 설정합니다:

# kinematics.yaml - MoveIt IK 솔버 설정
manipulator:
  kinematics_solver: kdl_kinematics_plugin/KDLKinematicsPlugin
  kinematics_solver_search_resolution: 0.005   # 탐색 해상도 (rad)
  kinematics_solver_timeout: 0.05              # 타임아웃 50ms
  kinematics_solver_attempts: 3                # 최대 3회 시도 (서로 다른 시드)
파라미터설명권장값
kinematics_solver사용할 IK 플러그인kdl_kinematics_plugin/KDLKinematicsPlugin
search_resolution관절 공간 탐색 해상도 (rad)0.005
timeout1회 시도 타임아웃 (초)0.005 ~ 0.1
attempts실패 시 재시도 횟수3 ~ 5

주의: timeout을 너무 짧게 설정하면 솔버가 수렴하기 전에 포기합니다. 실시간 제어에서는 50ms 이내, 경로 계획에서는 넉넉하게 잡는 것이 좋습니다.


IK 사용 방식 두 가지

ROS 2에서 IK를 사용하는 방법은 크게 두 가지입니다.

flowchart LR
    subgraph "방법 A: 간접 호출"
        A1["목표 Pose 설정"] --> A2["MoveGroup"]
        A2 --> A3["IK 자동 계산"]
        A3 --> A4["경로 계획"]
        A4 --> A5["실행"]
    end
 
    subgraph "방법 B: 직접 호출"
        B1["목표 Pose 설정"] --> B2["/compute_ik 서비스"]
        B2 --> B3["관절각 반환"]
        B3 --> B4["컨트롤러에 직접 발행"]
    end

MoveGroup 인터페이스를 통해 목표 Pose만 지정하면, MoveIt이 내부적으로 IK 계산 + 경로 계획 + 충돌 검사를 모두 처리합니다.

# MoveIt을 통한 간접 IK 호출
# 목표 pose만 지정하면 IK + 경로 계획이 자동 수행됨
pose = make_pose(x, y, z, roll, pitch, yaw)
helper.plan_to_pose_goal(pose)  # IK → 경로 계획 → 실행까지 한 번에

장점: 충돌 회피, 경로 최적화가 자동으로 포함됩니다. 단점: 계산 시간이 길어 실시간 제어에는 부적합합니다.

/compute_ik 서비스를 직접 호출하여 관절 각도만 빠르게 계산합니다. 텔레오퍼레이션이나 서보 제어처럼 실시간으로 반복 호출이 필요한 경우에 사용합니다.

# /compute_ik 서비스를 직접 호출하여 IK만 계산
request = GetPositionIK.Request()
request.ik_request.group_name = 'manipulator'
 
# 원하는 목표 위치/자세 설정
request.ik_request.pose_stamped.pose = target_pose
 
# 시드(seed) 상태: 현재 관절 각도를 초기값으로 전달
request.ik_request.robot_state = current_robot_state
 
# 타임아웃: 50ms 이내에 해를 찾아야 함
request.ik_request.timeout.nanosec = int(0.05 * 1e9)
 
# 서비스 호출 → 관절 각도 반환
result = ik_client.call(request)
joint_angles = result.solution.joint_state
# → {joint1: θ1, joint2: θ2, ..., joint6: θ6}

장점: 빠른 응답 (수 ms), 실시간 제어에 적합합니다. 단점: 경로 계획/충돌 검사가 없으므로 별도 처리가 필요합니다.


Seed State의 중요성

IK는 일반적으로 **다중 해(multiple solutions)**가 존재합니다. 같은 목표 위치라도 로봇 팔이 도달할 수 있는 관절 조합이 여러 가지이기 때문입니다. **시드 상태(Seed State)**는 이 중에서 어떤 해를 선택할지를 결정하는 핵심 요소입니다.

stateDiagram-v2
    state "현재 관절 상태 [θ1...θ6]" as Current
    state "IK 솔버 (KDL)" as Solver
    state "가장 가까운 해 선택" as Select
    state "부드러운 연속 동작" as Smooth
 
    Current --> Solver : 시드로 전달
    Solver --> Select : 현재 상태 근처에서 탐색
    Select --> Smooth : 관절 점프 없이 이동
 
    state "시드 없음" as NoSeed
    state "IK 솔버" as Solver2
    state "임의의 해 반환" as Random
    state "관절 점프/불연속 동작" as Jump
 
    NoSeed --> Solver2 : 초기값 불명확
    Solver2 --> Random : 매번 다른 해 수렴
    Random --> Jump : 위험한 동작 발생 가능

  • 관절 점프(Joint Flip): 갑자기 관절이 180도 회전하는 현상
  • 불연속 동작: 매 프레임마다 완전히 다른 자세로 전환
  • 로봇 손상 위험: 급격한 관절 변화로 기구적 충격 발생
# 올바른 시드 상태 전달 예시
from sensor_msgs.msg import JointState
 
# 현재 관절 상태를 구독하여 시드로 사용
current_joints = get_current_joint_state()  # 현재 관절 각도 구독
request.ik_request.robot_state.joint_state = current_joints
# → 솔버가 현재 상태에서 가장 가까운 해를 반환

실전 팁: 실시간 서보 제어에서는 반드시 매 프레임마다 현재 관절 상태를 시드로 전달해야 합니다. 이전 IK 결과를 시드로 재사용하면 실제 로봇 상태와 괴리가 생길 수 있습니다.


IK 결과를 로봇에 적용하기

IK로 얻은 관절 각도를 실제 로봇에 적용하는 전체 파이프라인입니다.

sequenceDiagram
    participant User as 사용자 입력
    participant Node as ROS 노드
    participant IK as /compute_ik
    participant Ctrl as /arm_controller
 
    loop 30Hz 실시간 루프
        User->>Node: 목표 좌표 (x, y, z)
        Node->>Node: 현재 관절 상태 조회 (시드)
        Node->>IK: IK 요청 (목표 + 시드)
        IK-->>Node: 관절 각도 [θ1...θ6]
        Node->>Ctrl: JointTrajectory 발행
        Ctrl-->>Node: 로봇 동작 실행
    end
from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
from builtin_interfaces.msg import Duration
 
# IK로 얻은 관절각을 trajectory 메시지로 변환
msg = JointTrajectory()
msg.joint_names = ['joint1', 'joint2', 'joint3',
                   'joint4', 'joint5', 'joint6']
 
point = JointTrajectoryPoint()
point.positions = [θ1, θ2, θ3, θ4, θ5, θ6]  # IK 결과
point.time_from_start = Duration(sec=0, nanosec=int(0.05 * 1e9))  # 50ms
 
msg.points = [point]
 
# 컨트롤러 토픽에 발행 → 로봇 동작
publisher.publish(msg)  # → /arm_controller/joint_trajectory

주의: time_from_start는 "이 지점에 도달할 목표 시간"입니다. 너무 짧으면 컨트롤러가 따라가지 못하고, 너무 길면 반응이 느려집니다. 실시간 서보 제어에서는 제어 주기(예: 33ms)에 맞추는 것이 좋습니다.


방식 비교 요약

항목간접 호출 (MoveGroup)직접 호출 (/compute_ik)
방식MoveGroup이 IK + 경로 계획 수행/compute_ik 서비스 직접 호출
호출 주기1회성 (계획 후 실행)실시간 30Hz 반복 호출
용도점-대-점(PTP) 이동텔레오퍼레이션, 서보 제어
시드 관리플래너가 자동 관리현재 관절 상태를 명시적으로 전달
충돌 검사자동 포함별도 처리 필요
응답 속도수백 ms ~ 수 초수 ms

IK 솔버 대안: TRAC-IK

KDL이 기본 솔버이지만, 성능이 부족할 때 TRAC-IK를 대안으로 사용할 수 있습니다. TRAC-IK는 KDL의 Newton-Raphson과 SQP(Sequential Quadratic Programming)를 병렬로 실행하여 성공률을 크게 높인 솔버입니다.

# TRAC-IK로 솔버 변경
manipulator:
  kinematics_solver: trac_ik_kinematics_plugin/TRAC_IKKinematicsPlugin
  kinematics_solver_timeout: 0.05
비교KDLTRAC-IK
성공률~60-70%~95%+
알고리즘Newton-Raphson 단일NR + SQP 병렬
설치MoveIt 기본 포함별도 패키지 설치 필요

정리

  1. KDL 솔버는 Newton-Raphson 반복법으로 IK를 수치적으로 풀며, URDF만 있으면 어떤 로봇이든 적용 가능합니다.
  2. IK 사용 방식은 간접 호출(MoveGroup)과 직접 호출(/compute_ik)로 나뉘며, 용도에 따라 선택합니다.
  3. 시드 상태를 올바르게 전달하는 것이 안정적인 IK 해를 얻는 핵심입니다.
  4. 전체 파이프라인: 목표 좌표 → /compute_ik → 관절 각도 → JointTrajectory 발행 → 로봇 동작
  5. 성공률이 중요하다면 TRAC-IK 솔버로 교체를 고려하세요.