dataset 변환
Convert 는 원시 에피소드를 LeRobot dataset 으로 바꿉니다. 원시 에피소드는 시연과 policy 의 실행 기록 두 종류이고, 둘을 한 dataset 에 함께 넣을 수 있습니다. studio 에서 Convert 를 누르는 방법은 dataset 정리에 있습니다.
입력과 출력
섹션 제목: “입력과 출력”studio 의 Convert 는 Collect 페이지 목록에 남은 완료된 에피소드 전체를 녹화한 순서대로 변환합니다. 학습에 쓰지 않을 에피소드는 변환 전에 목록에서 지웁니다. 결과는 ~/pinkrab/datasets/<이름>/ 에 저장되고, 같은 이름의 dataset 이 이미 있으면 변환을 시작하지 않습니다. 변환이 중간에 실패하면 반쯤 만든 dataset 은 지웁니다.
dataset 의 meta/pinkrab_convert.json 에는 변환의 기록이 남습니다. 원시 에피소드마다 종류와 잘라 낸 구간, 건너뛴 이유가 있고, 지연·평활·튀는 값의 기준처럼 변환에 쓴 상수도 함께 적힙니다.
같은 변환을 명령으로도 할 수 있습니다.
pinkrab-umi convert --fps 50 --out pick_place --task "pick and place the block" <episode> <episode> ...| 인자 | 뜻 |
|---|---|
<episode> |
~/pinkrab/episodes/ 아래의 원시 에피소드 이름. 여러 개 |
--out |
dataset 이름 |
--task |
작업 설명. 에피소드가 자기 작업 설명을 가지면 그것을 씁니다 |
--fps |
dataset 의 fps. 기본 50 |
--gop |
dataset 영상의 keyframe 간격. 기본 30. 2 로 하면 학습이 몇 배 빨라지는 대신 dataset 이 몇 배 커집니다 |
--from |
원시 에피소드가 있는 곳. 기본 ~/pinkrab/episodes/ |
시연의 변환 단계
섹션 제목: “시연의 변환 단계”시연 에피소드는 다음 단계를 거칩니다.
-
카메라의 자리: 탑 카메라는 시연자 머리에 달려 고개와 함께 움직이므로, 프레임마다 그 프레임에서 푼 테이블 마커 pose 로 카메라의 자리를 정합니다. 테이블 마커가 보이지 않은 프레임은 Hermit pose 를 테이블 마커 기준으로 옮길 수 없어 빈 프레임으로 다룹니다. 테이블 마커 pose 에도 프레임마다 작은 흔들림이 있는데, 이 흔들림은 Hermit pose 에 더해진 채로 5단계의 평활에서 함께 줄어듭니다.
-
시각 맞추기: 탑 카메라의 노출 시각이 각 마커 pose 의 시각입니다. 그리퍼 영상과 jaw 열림은 장치마다 잰 지연(그리퍼 카메라와 jaw 모두 12 ms)을 빼고 이 시각 위에 놓습니다.
-
자르기: 어느 손이든 마커를 풀지 못했거나, 테이블 마커가 보이지 않았거나, 그 시각 전 1.5 프레임 안에 찍힌 그리퍼 영상이 없는 프레임은 빈 프레임입니다. 빈 자리가 1/6 초 이하(30 fps 에서 5 프레임)면 이어 붙입니다. pose 는 양쪽 프레임 사이를 보간하고, 영상은 마지막으로 찍힌 것을 씁니다. 더 긴 빈 자리에서는 구간을 끊고, 0.95 초(30 fps 에서 약 30 프레임)보다 짧은 구간은 버립니다.
짧은 빈 자리를 이어 붙이는 이유는 이렇습니다. 손 두 개와 카메라 세 대를 쓰면 이런 프레임이 1–2 초마다 하나씩 생기고, 거기서 끊으면 시연 하나가 짧은 조각 여러 개로 나뉘며 그중 일부는 작업 중간에서 시작합니다. LeRobot 에는 무효 프레임이라는 개념이 없어 이어 붙인 값도 진짜 라벨로 학습되지만, dataset 의 프레임은 어차피 카메라 프레임 사이를 spline 으로 보간해 만든 것이라 1/6 초 이하의 빈 자리는 그 보간과 다르지 않습니다. 기준값은 config 의
convert_max_hole_s이고, 시연 판정이 허용하는 마커 놓침과 같습니다. -
TCP 궤적: 탑 카메라가 재는 것은 Hermit 에 달린 마커 다면체의 pose 이지만, 팔이 따라가야 하는 것은 두 jaw 사이의 tool 중심(TCP)입니다. 그래서 다면체 frame 에서 본 TCP 의 자리
solid_T_tcp를 곱해 다면체 궤적을 TCP 궤적으로 바꿉니다.solid_T_tcp는 Hermit 의 조립 치수로 정해지고 녹화마다 함께 기록됩니다. 다면체는 TCP 에서 떨어져 있어서, 다면체 궤적을 그대로 쓰면 TCP 를 중심으로 손목만 돌려도 다면체가 크게 움직인 것으로 잡혀 동작의 위치 변화가 실제 tool 의 움직임과 달라집니다. -
평활: TCP 의 위치와 회전에 Savitzky-Golay 필터(창 약 0.23 초, 약 50 fps 탑 카메라에서 11~13 프레임, 2차)를 겁니다. 마커 추적에는 프레임마다 작은 잡음이 있고 동작은 이웃 프레임 사이의 차이라서 이 잡음이 그대로 동작에 실리는데, 그 크기가 한 step 동작의 절반쯤 됩니다. Savitzky-Golay 필터는 창 안의 점들에 2차 곡선을 맞춰 가운데 값을 읽으므로, 이동 평균처럼 가속·감속 구간을 깎지 않고 잡음만 줄입니다. 녹화가 끝난 뒤 앞뒤 프레임을 함께 보고 계산해서 실시간 필터처럼 궤적이 늦어지지도 않습니다. 회전은 quaternion 의 네 값에 같은 필터를 걸고,
q와−q가 같은 회전이므로 그 전에 이웃한 quaternion 의 부호를 한쪽으로 맞춥니다. -
정지 구간 제거: 어느 손이든 처음 움직이기 전과 마지막으로 움직인 뒤의 프레임을 뺍니다. 처음 자리에서 4 mm 넘게 움직이거나 jaw 열림이 0.03 넘게 바뀌면 움직인 것으로 봅니다. 페달을 밟는 동안 손이 멈춰 있던 시작을 보여 주면, policy 는 시작할 때 멈춰 있는 법을 배워 실행에서 움직이지 않습니다.
-
dataset 의 시각: 구간의 첫 노출부터
1/fps초마다 프레임을 하나씩 둡니다. tool 의 위치는 평활한 궤적을 지나는 cubic spline 에서, 회전은 rotation spline 에서 그 시각의 값을 읽고, jaw 열림은 보간하며, 그리퍼 영상은 그 시각까지 찍힌 마지막 프레임을 씁니다. 자세한 내용은 아래 dataset 의 fps 에 있습니다. -
상대 동작: 손마다 프레임
t에서t+1로의 변화를 그 순간 tool 의 frame 에서 계산합니다. 위치는R_tᵀ (p_{t+1} − p_t), 회전은R_tᵀ R_{t+1}의 앞 두 열(6D)입니다. -
튀는 값 제거: 초당 2400 mm 나 150° 보다 빠른 변화(30 fps 에서 한 프레임에 80 mm 나 5°)는 추적 오류로 보고 0 으로 둡니다.
-
jaw 동작: 다음 프레임의 열림(0–1)을 그대로 씁니다. 닫힘 끝까지 몰아 붙이도록 바꾸지 않습니다. 사람이 살살 쥔 물체를 팔이 끝까지 쥐면 찌그러지기 때문입니다.
-
state: 두 jaw 의 열림과 왼손 tool frame 에서 본 오른손 tool 의 pose 를 넣습니다. 두 손의 상대 pose 는 한 손이 다른 손의 위치를 알고 움직이게 해 줍니다(UMI 의 inter-gripper proprioception).
-
절대 pose: 테이블 마커 frame 의 TCP pose 를
pose.tcp_<hand>로 함께 저장합니다. 이름을observation.으로 시작하지 않아 어떤 policy 의 입력도 되지 않고, 실행 시작 자세를 계산하는 데만 씁니다.
실행 기록의 변환
섹션 제목: “실행 기록의 변환”실행 기록은 시연과 같은 단계를 거치지만 영상과 동작을 Hermit 이 아니라 팔에서 계산하고, 교정 구간만 남깁니다. 녹화 방식은 교정 데이터에 있습니다.
| 항목 | 시연 | 교정 |
|---|---|---|
| 영상 | Hermit 의 그리퍼 카메라 | 팔의 그리퍼 카메라 |
| tool pose | 탑 카메라가 잰 마커 다면체의 pose × solid_T_tcp |
팔의 관절각의 forward kinematics |
| jaw 열림 | Hermit 의 jaw 읽기값 | 팔의 jaw 관절을 열림 폭으로 옮긴 값 |
| dataset 의 시계 | 탑 카메라의 노출 시각 | 첫 팔 카메라의 노출 시각 |
| 남기는 구간 | 두 손의 마커를 모두 푼 구간 | controller 가 human 인 구간 |
pose.tcp_<hand> |
테이블 마커 frame | 로봇의 base frame |
영상이 팔의 카메라인 것은 교정 중 작업 장면을 찍는 것이 팔의 카메라이기 때문입니다. 교정은 Hermit 이 움직인 만큼만 팔을 움직이는 상대 방식이라 사람은 Hermit 을 책상 어디에서 들고 있어도 되고, 그동안 Hermit 의 카메라는 작업과 상관없는 곳을 봅니다. 실행 중인 policy 가 보는 것도 팔의 카메라입니다.
tool pose 를 관절각에서 계산하는 것은 팔에는 마커가 없지만 관절각이 늘 읽히기 때문입니다. forward kinematics 로 구한 팔의 tool frame 은 Hermit 의 tool frame 과 축의 방향이 다를 수 있어서(왼팔은 z 축을 중심으로 반 바퀴), Hermit 의 규약으로 돌려 둡니다. 그래야 교정에서 계산한 상대 동작이 시연의 상대 동작과 같은 뜻을 가지고, 실행 때 policy 의 동작을 팔에 적용하며 다시 돌리는 것과도 맞습니다.
state 의 두 tool 사이 상대 pose 도 forward kinematics 에서 나옵니다. 실행 때 policy 에게 주는 state 가 같은 계산이라, 교정 에피소드의 state 는 policy 가 실행에서 보는 state 와 같은 값입니다.
계산 단계
섹션 제목: “계산 단계”Convert 는 원시 에피소드의 kind 가 rollout 이면 아래와 같이 계산합니다.
- 남길 읽기값:
controller가human이고,correction번호에redo가 없는 읽기값입니다. - 시각 맞추기: 첫 팔 카메라의 노출 시각마다(카메라 지연 12 ms 를 뺀 시각), 그 앞뒤 두 관절 읽기값(관절 읽기 지연 17 ms 를 뺀 시각) 사이를 선형 보간해 관절각을 정합니다. 두 읽기값이 0.1 초 넘게 떨어져 있거나 둘 가운데 하나라도 남길 읽기값이 아니면 그 프레임은 비웁니다.
- 자르기: 빈 프레임, 다른 팔 카메라에 1.5 프레임 안에 찍힌 영상이 없는 프레임, 영상이 끊긴 자리에서 구간을 끊고, 0.95 초보다 짧은 구간은 버립니다.
hold와policy구간이 남길 읽기값이 아니므로, 페달 1 로 세운 자리와 policy 에게 돌려준 자리에서 구간이 나뉘고 구간마다 dataset 의 에피소드 하나가 됩니다. - tool 궤적: 프레임마다 관절각으로 forward kinematics 를 풀고 Hermit 의 tool frame 규약으로 돌린 뒤, 시연과 같은 Savitzky-Golay 필터로 평활합니다. jaw 열림은 jaw 관절각을 그 팔의 열림 범위로 옮겨 구합니다.
- 나머지 단계: 정지 구간 제거, dataset 의 시각, 상대 동작, 튀는 값 제거, jaw 동작, state 는 시연과 같습니다.
다음 경우에는 그 실행 기록을 건너뜁니다.
- 실행한 팔이 dataset 의 손과 다를 때
- 녹화할 때의 팔 description 지문이 지금 팔과 다를 때. 관절각이 같아도 tool pose 가 달라지기 때문입니다
- 팔 카메라의 렌즈 중심을 모를 때
dataset 구성
섹션 제목: “dataset 구성”| feature | 크기 | 내용 |
|---|---|---|
observation.images.gripper_left, gripper_right |
영상 | 그리퍼 카메라 영상에서 렌즈 축을 중심으로 1560×1140 창을 잘라 폭 780 으로 줄인 것. 어안 렌즈의 상이 센서보다 좁아 그대로 쓰면 화면의 4분의 1 이 검은 테두리입니다 |
observation.state |
11 | 두 jaw 의 열림(2), 왼손 tool 에서 본 오른손 tool 의 위치(3)와 회전 6D(6) |
action |
20 | 손마다 위치 변화(3), 회전 변화 6D(6), 다음 열림(1) |
pose.tcp_left, pose.tcp_right |
7 | 테이블 마커 frame 의 TCP 위치와 quaternion. policy 입력이 아닙니다 |
회전을 행렬의 앞 두 열(6D)로 쓰는 것은 연속이기 때문입니다. axis-angle 은 180° 근처에서 값이 끊기고, quaternion 은 q 와 −q 가 같은 회전이라 같은 동작이 두 가지 값을 가집니다.
state 에 직전 step 의 동작을 넣지 않습니다. 넣으면 policy 가 영상을 보지 않고 직전 동작을 되풀이하는 것만으로 학습 오차를 줄일 수 있어서, 실행에서는 처음 움직인 방향으로 계속 가 버립니다(copycat).
dataset 의 fps
섹션 제목: “dataset 의 fps”dataset 의 fps 는 Convert 할 때 정합니다. 기본은 ALOHA 와 같은 50 이고, 1 부터 60 까지 카메라의 fps 와 상관없이 고를 수 있습니다. policy 의 step 하나가 dataset 의 프레임 하나이므로, 이 값이 곧 학습한 policy 를 실행할 때 묻는 주기입니다.
| 값 | 카메라보다 fps 가 높을 때 | 카메라보다 fps 가 낮을 때 |
|---|---|---|
| tool pose | spline 이 카메라 프레임 사이의 자리를 채웁니다. step 마다 pose 가 달라 움직임이 0 인 step 이 생기지 않습니다 | 프레임 사이의 자리를 spline 에서 읽습니다 |
| jaw 열림 | 보간합니다 | 보간합니다 |
| 그리퍼 영상 | 그 시각까지 찍힌 마지막 프레임이라, 같은 영상이 여러 프레임에 쓰입니다 | 그 시각까지 찍힌 마지막 프레임입니다 |
영상을 그 시각까지 찍힌 마지막 프레임으로 고르는 것은 실행 때와 같게 하기 위해서입니다. 실행 중인 policy 도 그 순간까지 찍힌 마지막 영상을 봅니다.
fps 가 바뀌면 step 의 길이가 바뀌므로, 시간으로 정해야 하는 값은 초 단위로 두고 step 수를 fps 에 맞춰 셉니다.
- chunk 길이: 학습 recipe 가 초 단위(ACT 는 논문과 같은 2 초)로 정하고, step 수는 dataset 의 fps 를 곱해 셉니다. 기본 50 fps 에서 100 step 입니다.
- 한 번에 실행하는 step: 기본값이 0.1 초 분량입니다. 기본 50 fps 에서 5 step 입니다.
- 나쁜 답의 기준: tool 이 초당 900 mm 나 300° 보다 빠르게 움직이라는 step 은 실행하지 않습니다(추론 설정의
max_tool_speed_mm_s,max_tool_turn_dps). - IK 의 속도 예산: 한 step 에 관절이 돌 수 있는 각도를 step 의 길이로 계산합니다.
서보에 명령을 쓰는 safety gate 는 dataset 의 fps 와 상관없이 초당 60번 명령을 다듬어 씁니다. fps 가 60 을 넘으면 gate 의 한 tick 사이에 낀 step 이 서보에 가지 않으므로, fps 의 상한이 60 입니다.
상대 동작을 쓰는 이유
섹션 제목: “상대 동작을 쓰는 이유”- 시연 자리와 무관: tool frame 에서 잰 변화는 물체가 책상 어느 쪽에 있었든, 시연자가 어디 서 있었든 같은 동작이면 같은 값입니다. 테이블 마커 frame 은 8 단계에서 상쇄됩니다.
- 로봇 좌표를 모름: 시연할 때 로봇은 그 자리에 없어 Hermit 의 절대 위치를 로봇 기준으로 잴 수 없고, 테이블 마커로 정한 로봇 좌표는 근사입니다. 상대 동작은 이 근사의 오차를 학습에 들이지 않습니다.
- UMI 의 결과: UMI 논문은 같은 데이터에서 절대 동작이 상대 동작보다 크게 떨어진다고 보고합니다.
시연과 교정이 섞인 dataset
섹션 제목: “시연과 교정이 섞인 dataset”Convert 는 목록에 남은 시연과 실행 기록을 함께 변환해 한 dataset 을 만들고, 교정 에피소드도 시연과 같은 작업 설명을 받습니다. 교정을 더한 dataset 은 새 이름으로 만듭니다. 같은 이름으로는 Convert 가 시작하지 않고, 기존 dataset 을 지우고 다시 만들면 그것으로 학습한 checkpoint 는 dataset 이 바뀌었다며 실행을 거부합니다.
dataset 은 어느 에피소드가 교정인지를 meta/pinkrab_convert.json 에 함께 적습니다. 실행의 시작 자세는 시연들의 첫 tool pose 평균인데, 교정 에피소드는 작업 중간에서 시작하므로 이 평균에서 뺍니다.