개요
로봇팔을 제어할 때, 원하는 위치로 엔드 이펙터를 이동시키려면 역기구학(Inverse Kinematics, IK) 솔버가 필요합니다. 이때 IK 솔버에게 "이 관절 각도 근처에서 답을 찾아라" 라고 알려주는 초기 추정값이 바로 **Seed State(시드 상태)**입니다.
핵심: Seed State = "지금 팔이 이 자세니까, 여기서 크게 안 벗어나는 답을 줘"
왜 Seed State가 필요한가?
6축 로봇팔에서 같은 엔드 이펙터 위치에 도달하는 관절 조합은 여러 개 존재합니다. 이를 다중 해(Multiple Solutions) 문제라고 합니다.
목표: (x=0.3, y=0.0, z=0.5)
해 1: [ 0°, 45°, -30°, 0°, 60°, 0°] ← 팔꿈치 위로 (Elbow-up)
해 2: [ 0°, -45°, 30°, 0°, 60°, 0°] ← 팔꿈치 아래로 (Elbow-down)
해 3: [180°, 45°, -30°, 0°, 60°, 0°] ← 뒤로 돌아서 (Shoulder-flip)
...시드 없이 풀면 어떤 해가 나올지 예측 불가하고, 관절이 갑자기 뒤집히는 문제가 발생합니다.
flowchart TD
TARGET["🎯 목표 위치<br/>(x=0.3, y=0.0, z=0.5)"]
TARGET --> SOL1["해 1: 팔꿈치 위로<br/>[0°, 45°, -30°, 0°, 60°, 0°]"]
TARGET --> SOL2["해 2: 팔꿈치 아래로<br/>[0°, -45°, 30°, 0°, 60°, 0°]"]
TARGET --> SOL3["해 3: 뒤로 돌아서<br/>[180°, 45°, -30°, 0°, 60°, 0°]"]
style TARGET fill:#ff6b6b,color:#fff
style SOL1 fill:#51cf66,color:#fff
style SOL2 fill:#339af0,color:#fff
style SOL3 fill:#fcc419,color:#333Seed State가 하는 일
Seed State를 전달하면, IK 솔버는 해당 값 근처에서 탐색을 시작하여 가장 가까운 해를 반환합니다.
sequenceDiagram
participant C as 컨트롤러
participant S as IK 솔버 (KDL)
participant R as 로봇
C->>C: 현재 관절 상태 읽기<br/>[10°, 45°, -30°, 5°, 60°, 0°]
C->>S: IK 요청 + Seed State 전달
S->>S: Seed 근처에서 탐색 시작
S-->>C: 결과: [11°, 46°, -29°, 5°, 61°, 0°]
C->>R: 부드러운 관절 이동 명령시드와 비슷한 해를 반환하기 때문에, 동작이 부드럽고 연속적입니다.
Seed 없이 동작하면?
Seed State 없이 IK를 풀면, 솔버가 매번 임의의 해를 반환할 수 있어 관절이 순간적으로 크게 뒤집히는 현상이 발생합니다.
| 시간 | 관절 상태 | 상태 |
|---|---|---|
| t=0 | [0°, 45°, -30°, ...] | 정상 |
| t=1 | [1°, 46°, -29°, ...] | 정상 |
| t=2 | [180°, -45°, 30°, ...] | 갑자기 뒤집힘! |
이런 경우 로봇이 순간적으로 크게 회전하게 되어 위험하고 비정상적인 동작을 하게 됩니다.
stateDiagram-v2
[*] --> 정상동작: Seed 사용
[*] --> 비정상동작: Seed 미사용
정상동작 --> 정상동작: 연속적인 해 선택
비정상동작 --> 관절뒤집힘: 임의의 해 선택
관절뒤집힘 --> 위험상황: 급격한 회전코드로 보는 Seed State 설정
ROS에서 IK 서비스를 호출할 때, RobotState에 현재 관절 상태를 담아 Seed로 전달합니다.
from moveit_msgs.msg import RobotState
from sensor_msgs.msg import JointState
# 현재 관절 상태를 시드로 설정
rs = RobotState()
js = JointState()
js.name = list(self._current_joints.keys()) # ['joint1', ..., 'joint6']
js.position = list(self._current_joints.values()) # 현재 각도들 (라디안)
rs.joint_state = js
# IK 요청에 시드 상태 포함
request.ik_request.robot_state = rs # ← 이게 시드!여기서 self._current_joints는 /joint_states 토픽을 구독하여 실시간으로 받아온 현재 관절 각도입니다.
import rospy
from sensor_msgs.msg import JointState
class RobotController:
def __init__(self):
self._current_joints = {}
# /joint_states 토픽 구독
rospy.Subscriber('/joint_states', JointState, self._joint_callback)
def _joint_callback(self, msg):
"""실시간 관절 상태 업데이트"""
for name, position in zip(msg.name, msg.position):
self._current_joints[name] = positionIK 솔버별 Seed 처리 방식
ROS/MoveIt에서 사용되는 주요 IK 솔버마다 Seed State를 처리하는 방식이 다릅니다.
| 솔버 | Seed 활용 방식 | 특징 |
|---|---|---|
| KDL | Newton-Raphson 반복법의 초기값으로 사용 | 기본 솔버, Seed 의존도 높음 |
| TRAC-IK | KDL + SQP 두 알고리즘 병렬 실행 | Seed에서 시작하되 더 넓게 탐색 |
| IKFast | 해석적 풀이 후 Seed에 가장 가까운 해 선택 | 가장 빠르고 안정적 |
| BioIK | 진화 알고리즘 초기 집단에 Seed 포함 | 복잡한 구속 조건에 강함 |
팁: 실제 로봇 프로젝트에서는 TRAC-IK나 IKFast를 권장합니다. KDL은 Seed에 따라 실패할 확률이 높습니다.
실전 팁과 주의사항
- 항상 최신 관절 상태를 Seed로 사용하세요. 오래된 값을 쓰면 솔버가 엉뚱한 해를 반환할 수 있습니다.
- Seed를 0으로 초기화하지 마세요. 모든 관절이 0인 상태는 특이점(Singularity)에 가까울 수 있어 솔버가 실패하기 쉽습니다.
- 연속 경로 생성 시, 이전 IK 결과를 다음 Seed로 체인처럼 연결하면 부드러운 궤적을 얻을 수 있습니다.
# 경로 생성 시 Seed 체이닝
seed = current_joint_state # 시작은 현재 상태
for waypoint in waypoints:
ik_result = solve_ik(waypoint, seed=seed)
trajectory.append(ik_result)
seed = ik_result # 이전 결과를 다음 Seed로 사용정리
| 항목 | 내용 |
|---|---|
| Seed State란? | IK 솔버에 전달하는 관절 각도 초기 추정값 |
| 왜 필요? | 다중 해 중 현재 자세와 가장 가까운 해를 선택하기 위해 |
| 없으면? | 관절 뒤집힘, 급격한 동작, 위험한 상황 발생 |
| 어떻게 설정? | /joint_states 토픽의 현재 관절 상태를 RobotState에 담아 전달 |
| 핵심 원칙 | 항상 최신 관절 상태를 Seed로 사용하여 연속적인 동작 보장 |