3. LeRobot Manipulation
last updated Dec/26/2025
3-1. Teleoperation
์นด๋ฉ๋ผ index, ์คํ, fps ํ์ธ
2๊ฐ ์ด์์ ์นด๋ฉ๋ผ๋ฅผ ์ฐ๊ฒฐํด ๋ฐ์ดํฐ์
๊ตฌ์ถ/๋ชจ๋ธ ์ถ๋ก ์ ํ์ฉํ ์ ์์ต๋๋ค.
PC์ ์ฐ๊ฒฐ๋ ์นด๋ฉ๋ผ์ index, ํด์๋, fps ๋ฑ ์ ๋ณด๋ฅผ ํ์ธํ๋ ค๋ฉด ์๋ ๋ช
๋ น์ด๋ฅผ ์คํํ์ธ์.
lerobot-find-cameras
# Example print
--- Detected Cameras ---
Camera #0:
Name: OpenCV Camera @ /dev/video0
Type: OpenCV
Id: /dev/video0
Backend api: V4L2
Default stream profile:
Format: 0.0
Fourcc: YUYV
Width: 640
Height: 480
Fps: 30.0
--------------------
Camera #1:
Name: OpenCV Camera @ /dev/video2
Type: OpenCV
Id: /dev/video2
Backend api: V4L2
Default stream profile:
Format: 0.0
Fourcc: YUYV
Width: 640
Height: 480
Fps: 30.0
--------------------
Finalizing image saving...
Image capture finished. Images saved to outputs/captured_images
TIP: ์นด๋ฉ๋ผ ์ฐ๊ฒฐ ์์์ ๋ฐ๋ผ index๊ฐ ๋ฌ๋ผ์ง ์ ์์ผ๋, ์คํ ์ ํ๋ก ํญ์
lerobot-find-cameras๋ค์์.../lerobot/outputs/captured_images์์ ์บก์ฒ๋ ์ฌ์ง์ ํตํด ๊ฐ camera์ ํฌํธ๋ฅผ ํ์ธํ์ธ์.
Teleoperation command
lerobot-teleoperate \
--robot.type=so101_follower \
--robot.port=/dev/so101_follower \
--robot.id=follower_new_gripper \
--robot.cameras="{
top: {type: opencv, index_or_path: "/dev/video0", width: 640, height: 480, fps: 30},
wrist: {type: opencv, index_or_path: "/dev/video2", width: 640, height: 480, fps: 30}
}" \
--teleop.type=so101_leader \
--teleop.port=/dev/so101_leader \
--teleop.id=leader \
--display_data=true
์ต์ ์ค๋ช
| ์ต์ ๋ช | ์ค๋ช | ์์ ๊ฐ |
|---|---|---|
--robot.type |
follower ๋ก๋ด ํ์ | so101_follower |
--robot.port |
follower USB ํฌํธ | /dev/so101_follower |
--robot.id |
follower ๋ก๋ด ๊ณ ์ ID (calibration ๋ ์ค์ ํ ID) | |
--robot.cameras |
์นด๋ฉ๋ผ ์ ๋ณด | lerobot-find-cameras๋ก ํ์ธํ ๊ฐ |
--teleop.type |
leader ๋ก๋ด ํ์ | so101_leader |
--teleop.port |
leader USB ํฌํธ | /dev/so101_leader |
--teleop.id |
leader ์ด๋ฆ | leader |
3-2. Record
HuggingFace ๊ณ์ ์ค์
# HuggingFace CLI ํ ํฐ ๋ก๊ทธ์ธ
hf auth login --token ${YOUR_TOKEN_HERE} --add-to-git-credential
# ๋ก๊ทธ์ธ ํ์ธ
HF_USER=$(hf auth whoami | head -n 1)
export HF_USER=$(python -c "from huggingface_hub import whoami; print(whoami().get(\"name\", \"\"))")
# (Optional) ์๊ตฌ ํ๊ฒฝ ๋ณ์ ์ค์
echo 'export HF_USER=$(python -c "from huggingface_hub import whoami; print(whoami().get(\"name\", \"\"))")' >> ~/.bashrc
source ~/.bashrc
echo ${HF_USER}
# Task Name ํ๊ฒฝ ๋ณ์ ์ค์
echo export ${TASK_NAME}=TASK_NAME >> ~/.bashrc # Task ๋ฐ๋ ๋๋ง๋ค ์๋ก ์ค์
#-------------------------------------------------------------------
# Huggingface ๋ก๊ทธ์ธ ํ์ง ์๊ณ ๋ก์ปฌ์์ ์งํํ๋ ๊ฒฝ์ฐ
echo export ${HF_USER}=YOUR_NAME >> ~/.bashrc
echo export ${TASK_NAME}=TASK_NAME >> ~/.bashrc
source ~/.bashrc
echo ${HF_USER} && echo ${TASK_NAME}
- HuggingFace ํ ํฐ ์ค์ ํ์ด์ง์์ ์ ํ ํฐ ์์ฑ
- ๊ถํ:
Write์ ํ (๋์ค์ pretrained model์ ํ์ต์ํค๋ ๊ฒฝ์ฐRead๊ถํ๋ ํ์ํ๋ฏ๋กFine-grained์ ํ ์ถ์ฒ) - ์์ฑ๋ ํ ํฐ์ ์ ๋ช
๋ น์ด์
YOUR_TOKEN_HERE์ ์ ๋ ฅ
Record command
lerobot-record \
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \
--robot.id=follower_new_gripper \
--robot.cameras="{
top: {type: opencv, index_or_path: "/dev/video0", width: 640, height: 480, fps: 30},
wrist: {type: opencv, index_or_path: "/dev/video2", width: 640, height: 480, fps: 30}
}" \
--teleop.type=so101_leader \
--teleop.port=/dev/ttyACM1 \
--teleop.id=leader \
--dataset.single_task="make a cup of coffee." \
--dataset.repo_id=${HF_USER}/${TASK_NAME} \
--dataset.num_episodes=10 \
--dataset.episode_time_s=1200 \
--dataset.reset_time_s=3 \
--display_data=true \
--dataset.push_to_hub=true \
--resume=true # ์ด๋ฏธ ์์ฑ๋ ๋ฐ์ดํฐ์
repo์ ์ถ๊ฐ๋ก ๋ฐ์ดํฐ ์์งํ๋ ๊ฒฝ์ฐ
์ฃผ์ Record ์ต์
| ์ต์ | ์ค๋ช |
|---|---|
--robot.type |
follower ๋ก๋ด ํ์ |
--robot.port |
follower USB ํฌํธ |
--robot.id |
follower ๋ก๋ด ๊ณ ์ ID (calibration ๋ ์ค์ ํ ID) |
--robot.cameras |
์นด๋ฉ๋ผ ์ ๋ณด |
--teleop.type |
leader ๋ก๋ด ํ์ |
--robot.port |
leader USB ํฌํธ |
--teleop.id |
leader ๋ก๋ด ๊ณ ์ ID (calibration ๋ ์ค์ ํ ID) |
--dataset.single_task |
language instruction |
--dataset.repo_id |
๋ฐ์ดํฐ์ repo ์ด๋ฆ |
--dataset.num_episodes |
์์งํ ์ํผ์๋ ์ |
--dataset.episode_time_s |
๊ฐ ์ํผ์๋ ๋ นํ ์๊ฐ |
--dataset.reset_time_s |
์ํผ์๋ ๊ฐ ๋ฆฌ์ ์๊ฐ |
--display_data |
์ค์๊ฐ ๋ฐ์ดํฐ ์๊ฐํ ์ฌ๋ถ (Rerun) |
--dataset.push_to_hub |
Huggingface ์๋ ์ ๋ก๋ ์ฌ๋ถ |
Record ์ ํค๋ณด๋ ๋จ์ถํค
| ํค | ๊ธฐ๋ฅ |
|---|---|
| โ | ์ํผ์๋ ์กฐ๊ธฐ ์ข ๋ฃ |
| โ | ์ํผ์๋ ์ฌ๊ธฐ๋ก |
| ESC | Command ์ข ๋ฃ ๋ฐ ์ ๋ก๋ |
FileExistsError
๋ฐ์ดํฐ์
repo๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ฉด ๊ธฐ์กด ๋๋ ํ ๋ฆฌ๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ณ FileExistsError๊ฐ ๋ฐ์ํ๋ฏ๋ก ๊ธฐ์กด repo๋ฅผ ์ญ์ ํ๊ฑฐ๋ --resume=true ์ต์
์ผ๋ก ๊ธฐ์กด repo์ ์ด์ด์ ์์งํด์ผ ํฉ๋๋ค.
FileExistsError: [Errno 17] File exists: '/home/.../so101_pickplace'
ํด๊ฒฐ ๋ฐฉ๋ฒ:
rm -rf ~/.cache/huggingface/lerobot/${HF_USER}/${TASK_NAME}
3-3. Replay
lerobot-replay \
--robot.type=so101_follower \
--robot.port=/dev/so101_follower \
--robot.id=follower \
--dataset.repo_id=${HF_USER}/${TASK_NAME} \
--dataset.episode=0
์ฃผ์ ์ต์
| ์ต์ | ์ค๋ช |
|---|---|
--dataset.repo_id |
์ฌ์ํ ๋ฐ์ดํฐ์ repo ์ด๋ฆ |
--dataset.episode |
์ฌ์ํ ์ํผ์๋ ๋ฒํธ |
--dataset.fps |
์ฌ์ ์๋ |
3-4. Visualization (๋ฐ์ดํฐ์ ํ์ธ)
๋ฐฉ๋ฒ 1: HuggingFace ์น์์ ํ์ธ
LeRobot Dataset Visualizer ํ์ด์ง์์ ๋ฐ์ดํฐ์ repo๋ฅผ ์ ๋ ฅ ํ ํ์ธ
๋ฐฉ๋ฒ 2: ๋ก์ปฌ ์๊ฐํ
lerobot-dataset-viz \
--repo-id=${HF_USER}/${TASK_NAME} \
--episode-index=0 # visualizeํ ์ํผ์๋ ๋ฒํธ
- Rerun ๋ทฐ์ด ์๋ ์คํ
- ์นด๋ฉ๋ผ ์์, ๋ก๋ด ์ก์ , ์ํ ์ ๋ณด ํ์ธ ๊ฐ๋ฅ