Pick and Place 구현
Pick and Place는 하나의 동작이 아니다
로봇팔이 물체를 집어 다른 곳에 놓는 장면은 겉으로 보면 단순해 보인다. 팔이 다가가고, 집고, 옮기고, 놓는다. 하지만 실제 구현에서는 이 짧은 동작 안에 로봇팔 제어의 거의 모든 핵심이 들어 있다. 좌표 변환, 목표 자세 생성, 경로 계획, 충돌 회피, 그리퍼 제어, 실패 확인, 재시도 전략이 한 줄로 이어져야 한다.
Pick and Place를 제대로 구현하면 이후에 등장할 모방학습, 강화학습, LLM 기반 로봇 에이전트의 기초가 단단해진다. LLM이 "빨간 컵을 오른쪽에 놓아줘"라는 명령을 이해하더라도, 결국 실제 로봇이 실행하는 것은 pick과 place 같은 구체적인 스킬이다. 이 장에서 만드는 Pick and Place는 바로 그 스킬의 원형이다.
기본 흐름은 다음과 같다.
물체의 위치와 잡기 자세를 정한다.
그리퍼를 열고 물체 근처의 안전한 접근 자세로 이동한다.
물체를 잡을 수 있는 자세로 천천히 접근한다.
그리퍼를 닫고 물체가 잡혔는지 확인한다.
물체를 들어 올린 뒤 목표 위치 위로 이동한다.
목표 위치에 내려놓고 그리퍼를 연다.
로봇팔을 물체에서 멀리 빼낸다.
중요한 점은 "최종 목표로 곧장 이동하지 않는다"는 것이다. 로봇팔은 항상 중간 자세를 거쳐야 한다. 물체 위 또는 앞쪽에서 한 번 멈추고, 잡기 직전 자세로 조심스럽게 들어가며, 집은 뒤에는 곧바로 옆으로 움직이지 않고 먼저 위로 들어 올린다. 이러한 중간 단계가 로봇을 안정적으로 만든다.

세 가지 핵심 자세 다시 보기
이전 장에서 물체를 집기 위한 인식 결과를 바탕으로 pre_grasp_pose, grasp_pose, post_grasp_pose를 만들었다. Pick and Place 구현은 이 세 자세를 실제 로봇팔 동작으로 연결하는 과정이다.
grasp_pose는 그리퍼가 실제로 물체를 잡는 자세다. 물체 중심, 물체의 높이, 그리퍼의 접근 방향, 손가락 간격을 고려해 정한다. 컵을 위에서 집는다면 그리퍼는 컵 위쪽으로 내려오고, 상자를 옆에서 집는다면 그리퍼는 상자 옆면을 향해 수평으로 접근한다.
pre_grasp_pose는 잡기 직전의 대기 자세다. 물체와 충분히 떨어져 있지만, 방향은 이미 잡기 자세와 거의 같아야 한다. 위에서 집는 경우에는 물체 위 10cm 정도의 위치가 될 수 있고, 옆에서 집는 경우에는 물체 앞 10cm 정도의 위치가 될 수 있다. 이 자세가 필요한 이유는 간단하다. 로봇팔이 멀리서부터 물체로 바로 돌진하면 주변 물체와 충돌하기 쉽고, 계획 경로도 복잡해진다.
post_grasp_pose는 물체를 잡은 뒤 빠져나오는 자세다. 보통은 물체를 위로 들어 올린 위치다. 테이블 위 물체를 집었다면 먼저 수직으로 들어 올리는 것이 안전하다. 물체를 잡자마자 옆으로 움직이면 바닥이나 주변 물체에 걸릴 수 있고, 그리퍼 안에서 물체가 미끄러질 수도 있다.
이 세 자세를 그림 없이 머릿속으로 그려 보면 다음과 같다. 물체가 테이블 위에 있다. 로봇팔은 물체 바로 위의 높은 지점으로 이동한다. 그다음 천천히 아래로 내려와 물체를 잡는다. 잡은 뒤에는 다시 위로 올라간다. 이후 목표 위치 위로 이동하고, 다시 내려놓는다. 이 단순한 "위에서 내려가고, 잡고, 위로 올라오는" 패턴이 가장 안정적인 Pick and Place의 출발점이다.
접근은 빠르게가 아니라 안전하게
초보자가 Pick and Place를 구현할 때 가장 자주 하는 실수는 물체 위치를 알자마자 그 좌표를 로봇팔 목표로 보내는 것이다. 예를 들어 컵의 중심 좌표가 (0.45, 0.10, 0.05)라고 해서 엔드 이펙터를 그 위치로 바로 보내면 안 된다. 그 좌표는 물체의 중심일 뿐이고, 그리퍼가 도달해야 할 실제 자세는 물체보다 위쪽이거나 옆쪽이다. 또한 그리퍼의 방향도 중요하다.
접근 단계에서는 먼저 pre_grasp_pose로 이동한다. 이때는 MoveIt2 같은 경로 계획기가 관절 공간에서 안전한 경로를 찾도록 맡기는 것이 일반적이다. 관절 공간 경로란 엔드 이펙터가 반드시 직선으로 움직이지 않더라도, 각 관절이 충돌 없이 목표 자세에 도달하도록 계획하는 방식이다.
반대로 pre_grasp_pose에서 grasp_pose로 들어가는 짧은 구간은 가능한 한 직선에 가깝게 움직이는 것이 좋다. 이 구간을 접근 경로라고 부른다. 물체를 잡는 마지막 몇 센티미터에서는 로봇팔이 이상한 곡선을 그리며 돌아 들어오면 안 된다. 그리퍼가 물체를 향한 방향을 유지한 채 천천히 다가가야 한다.
아래는 개념적인 접근 흐름이다. 실제 MoveIt2 API는 사용하는 래퍼나 언어에 따라 달라질 수 있지만, 구조는 거의 같다.
def approach_object(robot, pre_grasp_pose, grasp_pose):
robot.set_speed_scale(0.3)
plan = robot.plan_to_pose(pre_grasp_pose)
if not plan.success:
return False, 'pre_grasp_plan_failed'
if not robot.execute(plan):
return False, 'pre_grasp_execute_failed'
robot.set_speed_scale(0.1)
path = robot.plan_cartesian_path(
target_pose=grasp_pose,
step_size=0.01
)
if not path.success:
return False, 'grasp_approach_plan_failed'
if not robot.execute(path):
return False, 'grasp_approach_execute_failed'
return True, 'ok'여기서 속도를 두 단계로 나눈 점에 주목해야 한다. 물체 근처로 이동할 때는 너무 빠르지 않게, 마지막 접근은 더 천천히 움직인다. 실제 로봇에서는 마지막 5~15cm가 가장 위험하다. 카메라 좌표 오차, 캘리브레이션 오차, 물체 크기 추정 오차가 모두 이 구간에서 충돌로 나타난다.
pre-grasp 자세를 만드는 방법
pre_grasp_pose는 grasp_pose에서 접근 방향의 반대쪽으로 일정 거리만큼 물러난 자세라고 생각하면 된다. 위에서 집는 경우 접근 방향은 아래쪽이다. 따라서 pre-grasp는 grasp보다 위에 있다. 옆에서 집는 경우 접근 방향이 앞쪽에서 뒤쪽이라면, pre-grasp는 물체 앞쪽에 있다.
위에서 집는 가장 단순한 예를 보자. 물체의 중심 좌표가 로봇 베이스 기준으로 (x, y, z)이고, 그리퍼가 아래를 향한다고 하자. 그러면 잡기 자세와 접근 자세는 다음과 같이 만들 수 있다.
def make_top_grasp_poses(object_pose, object_height):
x = object_pose.x
y = object_pose.y
table_z = object_pose.z
grasp_z = table_z + object_height + 0.02
pre_grasp_z = grasp_z + 0.10
post_grasp_z = grasp_z + 0.15
down_orientation = quaternion_from_euler(3.14, 0.0, 0.0)
grasp_pose = Pose(x, y, grasp_z, down_orientation)
pre_grasp_pose = Pose(x, y, pre_grasp_z, down_orientation)
post_grasp_pose = Pose(x, y, post_grasp_z, down_orientation)
return pre_grasp_pose, grasp_pose, post_grasp_pose숫자 0.10m나 0.15m는 절대적인 정답이 아니다. 작은 데스크톱 로봇팔에서는 5cm가 충분할 수 있고, 큰 산업용 로봇팔에서는 20cm 이상이 필요할 수도 있다. 중요한 것은 로봇의 크기, 그리퍼 길이, 카메라 오차, 물체 높이를 함께 고려해 여유 공간을 두는 것이다.
또한 grasp_z를 물체 중심 높이로 잡으면 그리퍼가 너무 깊이 내려갈 수 있다. 컵이나 상자처럼 높이가 있는 물체는 잡는 위치가 물체 윗면보다 약간 아래일 수도 있고, 납작한 물체는 표면보다 조금 위에서 손가락이 닫혀야 할 수도 있다. 따라서 실제 구현에서는 물체 종류마다 잡기 높이 오프셋을 따로 두는 편이 좋다.
예를 들어 컵, 블록, 얇은 카드의 잡기 오프셋은 서로 다르다. 컵은 몸통을 잡아야 하고, 블록은 중심을 잡아도 되며, 카드는 위에서 집기보다 흡착 그리퍼가 더 적합할 수 있다. Pick and Place 구현은 결국 물체의 성질을 로봇 동작의 숫자로 바꾸는 일이다.
그리퍼를 닫기 전에 해야 할 일
물체에 접근했다고 해서 바로 그리퍼를 닫으면 안 된다. 닫기 전에는 몇 가지 조건을 확인해야 한다. 엔드 이펙터가 목표 자세에 충분히 가까운지, 로봇팔이 아직 움직이고 있지 않은지, 그리퍼가 물체를 감쌀 만큼 열려 있는지 확인해야 한다. 특히 위치 오차가 큰 상태에서 그리퍼를 닫으면 물체를 밀어내거나 넘어뜨릴 수 있다.
그리퍼는 접근 전에 미리 열려 있어야 한다. 손가락 간격은 물체 폭보다 조금 넓게 잡는다. 너무 좁으면 접근 중에 물체와 부딪히고, 너무 넓으면 주변 물체에 걸릴 수 있다. 물체 폭이 5cm라면 그리퍼를 6~7cm 정도 열어 두는 식으로 여유를 둔다.
def prepare_gripper(gripper, object_width):
margin = 0.015
open_width = object_width + margin
if open_width > gripper.max_width:
return False, 'object_too_wide'
gripper.open(width=open_width)
if not gripper.wait_until_stopped(timeout=2.0):
return False, 'gripper_open_timeout'
return True, 'ok'간단해 보이지만, 이 단계는 매우 중요하다. 그리퍼의 최대 폭보다 큰 물체는 애초에 집을 수 없다. 로봇이 할 수 없는 일을 억지로 시도하게 만들면 이후 단계에서 더 위험한 실패가 발생한다. 좋은 로봇 스킬은 실행 전에 가능한 작업인지 먼저 판단한다.
그리퍼 닫기와 잡힘 확인
잡기 단계에서는 그리퍼를 닫는다. 하지만 "닫기 명령을 보냈다"와 "물체를 실제로 잡았다"는 전혀 다른 이야기다. 로봇은 명령을 수행했지만 물체가 손가락 사이에서 빠져나갔을 수도 있고, 애초에 물체 위치가 조금 어긋나 빈 공간을 집었을 수도 있다.
잡힘 여부를 확인하는 방법은 로봇과 그리퍼의 센서 구성에 따라 달라진다. 가장 단순한 방법은 그리퍼의 최종 폭을 보는 것이다. 그리퍼가 완전히 닫혀 버렸다면 손가락 사이에 아무것도 없을 가능성이 크다. 반대로 일정 폭에서 멈췄다면 물체가 끼어 있을 가능성이 있다.
더 좋은 방법은 그리퍼의 힘이나 모터 전류를 함께 보는 것이다. 물체를 누르면 손가락이 저항을 느끼고, 모터 전류가 증가하거나 힘 센서 값이 올라간다. 고급 로봇에서는 손가락 끝의 촉각 센서나 6축 힘/토크 센서를 사용하기도 한다. 하지만 교육용 로봇이나 실습 환경에서는 그리퍼 폭, 명령 결과, 짧은 들어 올리기 테스트만으로도 꽤 쓸 만한 확인을 할 수 있다.
def close_and_check_grasp(gripper, expected_width):
gripper.close(force=20.0)
if not gripper.wait_until_stopped(timeout=2.0):
return False, 'gripper_close_timeout'
width = gripper.get_width()
too_closed = width < expected_width * 0.3
too_open = width > expected_width * 1.5
if too_closed:
return False, 'empty_grasp_suspected'
if too_open:
return False, 'object_not_between_fingers'
return True, 'ok'이 코드는 매우 단순한 휴리스틱이다. 휴리스틱이란 완벽한 이론식은 아니지만 실전에서 쓸 만한 경험적 규칙을 말한다. 실제 시스템에서는 물체 종류마다 기준값을 조정해야 한다. 부드러운 스펀지와 단단한 블록은 같은 폭 기준으로 판단하기 어렵다. 투명한 컵이나 얇은 물체는 카메라 인식도 흔들릴 수 있으므로, 가능하면 여러 신호를 조합하는 것이 좋다.
잡힘 확인을 더 확실히 하고 싶다면 물체를 아주 조금 들어 올린 뒤 다시 확인한다. 예를 들어 2~3cm만 위로 올리고 카메라로 물체 위치가 함께 움직였는지 보거나, 그리퍼 힘이 유지되는지 확인한다. 이 작은 테스트는 이후의 큰 이동에서 물체를 떨어뜨리는 위험을 줄여 준다.
들어올리기는 수직으로, 천천히
물체를 잡은 직후에는 로봇팔이 가장 조심해야 한다. 그리퍼는 물체를 잡고 있지만, 아직 안정적인 상태인지 확실하지 않다. 손가락의 마찰이 부족하면 물체가 미끄러질 수 있고, 잡은 위치가 한쪽으로 치우쳤다면 들어 올리는 순간 기울어질 수 있다.
그래서 들어올리기 동작은 가능한 한 단순해야 한다. 테이블 위 물체라면 먼저 수직 방향으로 들어 올린다. 이때 엔드 이펙터의 방향은 바꾸지 않는 것이 좋다. 물체를 잡자마자 손목을 회전하거나 옆으로 크게 이동하면 관성 때문에 물체가 흔들린다.
def lift_object(robot, post_grasp_pose):
robot.set_speed_scale(0.1)
path = robot.plan_cartesian_path(
target_pose=post_grasp_pose,
step_size=0.01
)
if not path.success:
return False, 'lift_plan_failed'
if not robot.execute(path):
return False, 'lift_execute_failed'
return True, 'ok'들어올리기 높이는 주변 환경에 따라 달라진다. 테이블 위에 다른 물체가 많다면 더 높이 들어야 한다. 하지만 너무 높게 들면 로봇팔의 관절 한계에 가까워질 수 있고, 이동 시간이 길어진다. 보통은 주변 물체보다 충분히 높은 최소 높이를 선택하는 것이 좋다.
여기서 중요한 개념이 하나 있다. 물체를 잡은 뒤에는 그 물체도 로봇의 일부처럼 다루어야 한다. MoveIt2에서는 잡은 물체를 planning scene에 attached object로 추가할 수 있다. attached object는 "로봇에 붙어 있는 물체"라는 뜻이다. 이렇게 하면 경로 계획기가 로봇팔뿐 아니라 들고 있는 물체까지 고려해 충돌을 피할 수 있다.
예를 들어 로봇이 긴 막대를 집고 이동한다고 해 보자. 엔드 이펙터는 장애물과 부딪히지 않더라도, 막대 끝이 주변 물체를 칠 수 있다. 잡은 물체를 planning scene에 등록하지 않으면 계획기는 이 위험을 모른다. 따라서 실전에서는 잡기 성공 후 물체의 대략적인 크기를 충돌 객체로 추가하고, 그 객체를 그리퍼에 붙이는 방식이 안전하다.
목표 위치로 이동하기
물체를 들어 올렸다면 이제 목표 위치로 이동할 수 있다. 이 단계에서는 place_pose로 바로 가지 않고, 먼저 pre_place_pose로 이동한다. pre_place_pose는 목표 위치 위쪽의 안전한 자세다. Pick에서 pre_grasp_pose가 필요했던 것처럼, Place에서도 중간 대기 자세가 필요하다.
목표 위치로 이동할 때는 보통 관절 공간 경로 계획을 사용한다. 이미 물체를 충분히 들어 올렸기 때문에 엔드 이펙터가 반드시 직선으로 갈 필요는 없다. 오히려 로봇의 관절 한계, 자기 충돌, 주변 장애물을 고려해 안전한 경로를 찾는 것이 더 중요하다.
def move_to_place_area(robot, pre_place_pose):
robot.set_speed_scale(0.2)
plan = robot.plan_to_pose(pre_place_pose)
if not plan.success:
return False, 'pre_place_plan_failed'
if not robot.execute(plan):
return False, 'pre_place_execute_failed'
return True, 'ok'물체를 들고 이동할 때는 자세 제약을 고려해야 한다. 예를 들어 컵 안에 내용물이 있다고 가정하면, 로봇팔은 컵을 기울이지 않도록 손목 자세를 유지해야 한다. 상자를 옮길 때도 물체가 그리퍼에서 미끄러지지 않도록 급격한 회전과 가속을 피해야 한다.
실제 구현에서는 속도와 가속도를 제한하는 것이 좋다. 빠른 이동은 데모 영상에서는 멋져 보일 수 있지만, 로봇이 물체를 떨어뜨릴 가능성을 높인다. 처음에는 느리고 안정적인 동작을 만든 뒤, 성공률을 확인하면서 조금씩 속도를 올리는 방식이 안전하다.
내려놓기와 그리퍼 열기
Place 동작도 Pick 동작과 마찬가지로 두 단계로 나누는 것이 좋다. 먼저 목표 위치 위의 pre_place_pose로 이동하고, 그다음 실제 놓을 위치인 place_pose로 천천히 내려간다. 내려가는 경로는 직선에 가까운 카테시안 경로가 적합하다. 카테시안 경로란 엔드 이펙터가 작업 공간에서 지정된 방향으로 움직이도록 계획하는 방식이다.
def place_object(robot, gripper, place_pose, retreat_pose):
robot.set_speed_scale(0.1)
descend_path = robot.plan_cartesian_path(
target_pose=place_pose,
step_size=0.01
)
if not descend_path.success:
return False, 'place_descend_plan_failed'
if not robot.execute(descend_path):
return False, 'place_descend_execute_failed'
gripper.open()
if not gripper.wait_until_stopped(timeout=2.0):
return False, 'gripper_open_failed'
robot.detach_object_from_gripper()
retreat_path = robot.plan_cartesian_path(
target_pose=retreat_pose,
step_size=0.01
)
if not retreat_path.success:
return False, 'retreat_plan_failed'
if not robot.execute(retreat_path):
return False, 'retreat_execute_failed'
return True, 'ok'물체를 놓을 때도 너무 높은 곳에서 그리퍼를 열면 안 된다. 물체가 떨어지면서 튀거나 넘어질 수 있다. 특히 컵, 병, 블록처럼 세워야 하는 물체는 바닥에 거의 닿은 상태에서 그리퍼를 열어야 한다. 반대로 너무 낮게 내려가면 그리퍼가 물체를 바닥에 눌러 버릴 수 있다. 따라서 place 높이에는 작은 여유가 필요하다.
그리퍼를 연 뒤에는 잠깐 기다리는 것도 좋다. 물체가 완전히 손가락에서 빠져나가고 안정될 시간을 주는 것이다. 그런 다음 로봇팔을 위로 빼낸다. 이 후퇴 동작을 생략하면, 로봇팔이 다음 계획을 세우는 동안 손가락이 물체와 닿아 물체를 밀어낼 수 있다.
전체 Pick and Place 시퀀스
이제 각 단계를 하나의 함수로 묶어 보자. 실전 로봇 소프트웨어에서는 이런 구조를 스킬이라고 부른다. 스킬은 상위 계획기가 호출할 수 있는 실행 단위다. 나중에 LLM 에이전트가 pick('red_cup') 또는 place('right_tray') 같은 명령을 내리게 하려면, 내부적으로는 아래와 같은 안정적인 시퀀스가 준비되어 있어야 한다.
def pick_and_place(robot, gripper, target, place_target):
pre_grasp_pose = target.pre_grasp_pose
grasp_pose = target.grasp_pose
post_grasp_pose = target.post_grasp_pose
pre_place_pose = place_target.pre_place_pose
place_pose = place_target.place_pose
retreat_pose = place_target.retreat_pose
ok, reason = prepare_gripper(gripper, target.width)
if not ok:
return {'success': False, 'stage': 'prepare_gripper', 'reason': reason}
ok, reason = approach_object(robot, pre_grasp_pose, grasp_pose)
if not ok:
return {'success': False, 'stage': 'approach', 'reason': reason}
ok, reason = close_and_check_grasp(gripper, target.width)
if not ok:
return {'success': False, 'stage': 'grasp', 'reason': reason}
robot.attach_object_to_gripper(
object_id=target.id,
size=target.size
)
ok, reason = lift_object(robot, post_grasp_pose)
if not ok:
robot.detach_object_from_gripper()
return {'success': False, 'stage': 'lift', 'reason': reason}
ok, reason = move_to_place_area(robot, pre_place_pose)
if not ok:
return {'success': False, 'stage': 'move_to_place', 'reason': reason}
ok, reason = place_object(robot, gripper, place_pose, retreat_pose)
if not ok:
return {'success': False, 'stage': 'place', 'reason': reason}
return {'success': True, 'stage': 'done', 'reason': 'ok'}반환값에 stage와 reason을 넣은 이유는 실패를 다루기 위해서다. "실패했다"라는 정보만으로는 로봇이 무엇을 해야 할지 알 수 없다. 접근 계획이 실패한 것인지, 그리퍼가 물체를 잡지 못한 것인지, 내려놓기 경로가 막힌 것인지 구분해야 한다. 다음 장에서 다룰 실패 감지와 재시도 전략은 바로 이런 반환값에서 출발한다.
좌표, 충돌, 속도: 성공률을 좌우하는 세 가지
Pick and Place가 잘 되지 않을 때 많은 사람이 알고리즘을 먼저 의심한다. 하지만 실제 원인은 더 기본적인 곳에 있는 경우가 많다. 좌표가 조금 틀렸거나, 충돌 객체가 planning scene에 제대로 들어가지 않았거나, 속도가 너무 빠른 식이다.
첫째, 좌표가 정확해야 한다. 카메라에서 본 물체 위치가 로봇 베이스 좌표로 잘 변환되어야 하고, 테이블 높이도 맞아야 한다. z축 오차가 2cm만 나도 그리퍼가 물체 위에서 헛돌거나 테이블을 누를 수 있다. 따라서 처음에는 물체 중심점과 grasp pose를 RViz 같은 시각화 도구에 표시해 보는 것이 좋다.
둘째, 충돌 정보를 업데이트해야 한다. 테이블, 벽, 주변 물체, 로봇이 들고 있는 물체가 planning scene에 들어 있어야 한다. 계획기가 모르는 장애물은 피할 수 없다. 특히 Pick 직후에는 물체가 세상에 놓인 독립 객체가 아니라 로봇에 붙은 객체로 바뀐다. Place 이후에는 다시 로봇에서 떼어 내고, 목표 위치에 있는 객체로 갱신해야 한다.
셋째, 속도를 낮춰야 한다. 느린 로봇은 답답해 보이지만, 개발 초기에는 느린 로봇이 훨씬 좋다. 느리면 충돌 직전에 멈추기 쉽고, 물체가 미끄러지는 문제도 줄어든다. 안정적인 성공률을 확인한 뒤에 속도와 가속도를 조금씩 올려야 한다.
이 세 가지는 화려한 딥러닝 모델보다 더 직접적으로 성공률에 영향을 준다. 로봇팔 작업에서는 "좋은 인식 결과"와 "안전한 실행"이 함께 있어야 한다. 인식만 좋아도 안 되고, 경로 계획만 좋아도 안 된다.
예제 시나리오: 빨간 컵을 오른쪽 트레이에 놓기
간단한 예제를 통해 전체 흐름을 다시 연결해 보자. 카메라가 테이블 위의 빨간 컵을 찾았고, 깊이 정보와 좌표 변환을 통해 컵 중심이 로봇 베이스 기준 좌표로 계산되었다고 하자. 오른쪽 트레이의 위치도 미리 알고 있다.
로봇은 먼저 컵의 중심점 위쪽에 pre_grasp_pose를 만든다. 그리퍼는 컵의 폭보다 조금 넓게 열린다. 로봇팔은 충돌을 피하며 컵 위로 이동하고, 마지막 10cm는 천천히 수직으로 내려간다. 그리퍼가 컵을 감싸면 손가락을 닫는다. 닫힌 뒤 그리퍼 폭이 적절한지 확인하고, 컵을 3cm 정도 살짝 들어 올려 잡힘을 한 번 더 확인한다.
잡힘이 괜찮다면 컵을 15cm 정도 위로 들어 올린다. 이때 컵을 planning scene에서 로봇에 붙은 객체로 등록한다. 그런 다음 오른쪽 트레이 위의 pre_place_pose로 이동한다. 컵이 트레이 위에 도착하면 천천히 내려가고, 바닥에 거의 닿은 높이에서 그리퍼를 연다. 컵이 놓이면 로봇팔은 다시 위로 빠져나온다.
사람에게는 한 문장으로 끝나는 작업이다. "빨간 컵을 오른쪽 트레이에 놓아줘." 하지만 로봇에게는 인식, 좌표 변환, 자세 생성, 경로 계획, 그리퍼 제어, 상태 확인이 이어지는 긴 절차다. AI 로봇 에이전트의 핵심은 이 긴 절차를 안정적인 스킬로 감싸고, 사용자는 자연어로 쉽게 호출할 수 있게 만드는 데 있다.
실전에서 자주 쓰는 안정화 팁
Pick and Place를 처음 성공시키는 것과 반복적으로 성공시키는 것은 다르다. 한 번 성공한 로봇이 열 번 연속 성공하지 못한다면, 아직 스킬로 부르기 어렵다. 반복 성공률을 높이려면 작은 안정화 장치들이 필요하다.
먼저 접근 전에 한 번 더 인식하는 방법이 있다. 로봇팔이 움직이는 동안 물체가 흔들렸거나, 사람이 물체를 건드렸을 수 있다. pre_grasp_pose에 도착한 뒤 카메라로 물체 위치를 다시 확인하면 마지막 오차를 줄일 수 있다. 이를 시각적 재확인이라고 부를 수 있다.
다음으로 grasp pose에 작은 후보를 여러 개 둘 수 있다. 하나의 중심점만 믿지 않고, 중심에서 약간 왼쪽, 오른쪽, 위쪽으로 이동한 후보를 만들어 충돌이 적고 관절 상태가 좋은 자세를 선택하는 것이다. 특히 물체가 완벽한 직육면체가 아니거나 세그멘테이션 마스크가 불안정할 때 도움이 된다.
마지막으로 모든 실행 결과를 로그로 남겨야 한다. 성공한 grasp pose, 실패한 grasp pose, 그리퍼 폭, 물체 종류, 카메라 좌표 오차를 기록하면 나중에 어떤 조건에서 실패가 많았는지 알 수 있다. 이 로그는 4부에서 다룰 모방학습 데이터로도 이어질 수 있다. 사람이 직접 조정한 좋은 동작과 로봇이 실패한 동작 모두 학습 데이터가 될 수 있기 때문이다.
스킬로 만들기
Pick and Place는 단순한 예제가 아니라 로봇 에이전트의 기본 스킬이다. 이 스킬을 잘 설계하려면 내부 구현과 외부 인터페이스를 분리해야 한다. 상위 계획기는 복잡한 경로 계획 과정을 몰라도 된다. 대신 다음과 같은 요청과 응답만 주고받으면 충분하다.
request = {
'object_id': 'red_cup',
'target_location': 'right_tray'
}
response = {
'success': True,
'final_state': 'object_placed',
'message': 'red_cup placed on right_tray'
}내부에서는 객체 검출, 좌표 변환, pre-grasp 생성, MoveIt2 계획, 그리퍼 제어가 모두 실행된다. 하지만 외부에서는 "무엇을 어디로 옮길 것인가"만 지정한다. 이 구조가 중요하다. 나중에 LLM은 자연어 명령을 해석해 object_id와 target_location을 채우고, 로봇 스킬 서버는 실제 동작을 수행한다.
이때 스킬은 반드시 실패 이유를 명확히 돌려줘야 한다. 물체를 찾지 못했는지, 경로가 막혔는지, 그리퍼가 물체를 놓쳤는지 알아야 상위 에이전트가 다시 계획할 수 있다. 예를 들어 물체를 찾지 못했다면 카메라 위치를 바꾸거나 사용자에게 "빨간 컵이 보이지 않습니다"라고 말할 수 있다. 경로가 막혔다면 다른 접근 방향을 시도할 수 있다.
Pick and Place가 열어 주는 다음 단계
로봇팔 조작에서 Pick and Place는 가장 대표적인 작업이다. 하지만 동시에 가장 좋은 훈련장이다. 이 작업 안에는 로봇 조작의 거의 모든 질문이 들어 있다. 물체를 어떻게 볼 것인가, 어디를 잡을 것인가, 어떤 경로로 움직일 것인가, 실패를 어떻게 알아차릴 것인가, 다시 시도할 것인가.
규칙 기반으로 Pick and Place를 구현하면 로봇 시스템의 뼈대가 생긴다. 이후 모방학습은 사람이 보여준 Pick and Place 동작을 데이터로 배운다. 강화학습은 더 안정적인 잡기나 더 부드러운 움직임을 보상으로 학습한다. LLM 에이전트는 자연어 명령을 여러 개의 Pick and Place 스킬 호출로 바꾼다.
따라서 이 장의 목표는 단순히 컵 하나를 옮기는 것이 아니다. 로봇이 물리 세계에서 의미 있는 작업을 수행하기 위한 실행 단위를 만드는 것이다. 물체에 접근하고, 잡고, 들어 올리고, 이동하고, 내려놓는 이 흐름이 안정적으로 동작할 때, 비로소 로봇은 "생각한 것을 행동으로 옮기는" 에이전트가 된다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.