0.写在开头#
LeRobot 仓库经常性变动,请务必参考官方教程和仓库代码。
本文记录LeRobot SO-101
机械臂实践,包括从硬件连接、环境搭建到基础的校准、采集数据、训练(基于 ACT
)、推理(基于 ACT
),具体的端口、路径等仅作参考,以实际环境为准。
硬件连接#
- 由于直接购买的成品
LeRobot SO-101
,所以无需自己组装零件和设置舵机。 - 机械臂连接电源和电脑
- 相机尽量直连电脑,机械臂两条信号线可以通过集线器连接电脑。
- 安装摄像头
环境搭建#
安装好miniforge
和配置conda
设置后,参考官方-安装文档安装lerobot
环境。
- 创建虚拟环境:
conda create -y -n lerobot python=3.10
- 激活虚拟环境:
conda activate lerobot
- 安装
ffmpeg
:conda install ffmpeg -c conda-forge
- 更新
pip
:python -m pip install -U pip setuptools wheel
- 拉取
lerobot
仓库并进入:git clone https://github.com/huggingface/lerobot.git && cd lerobot
- 安装
lerobot
:pip install 'lerobot[all]'
- 或者基础的:
pip install 'lerobot[feetech]'
- 或者基础的:
校准#
由于我们购买的成品LeRobot SO-101
,所以无需自己进行舵机 ID、波特率、磁编码器中位设置,直接进行校准。
- 参考视频:
- 找到
USB
端口: - 校准:
连接机械臂电源和电脑后,激活
lerobot
环境,进入lerobot
仓库。找到两个机械臂的端口:
- 通过运行
lerobot-find-port
,并断开 - 找到两个机械臂的端口:
- 主臂(
LeaderArm
):/dev/ttyACM0
- 从臂(
FollowerArm
):/dev/ttyACM1
- 主臂(
- 通过运行
设置串口设备权限:
sudo chmod 666 /dev/ttyACM*
- 如果不想每次连接设备都设置权限,可以把自己加入对应设备组:
- 查看设备组:
[xiadengma@archlinux ~]$ ls -l /dev/ttyACM* crw-rw---- 1 root uucp 166, 0 10月16日 13:15 /dev/ttyACM0 crw-rw---- 1 root uucp 166, 1 10月16日 13:15 /dev/ttyACM1
- 把自己加入对应设备组(根据
ls -l /dev/ttyACM*
输出修改),并验证是否加入成功:- 加入设备组 uucp 并刷新环境
sudo usermod -aG uucp "$USER" && exec su -l "$USER"
- 验证是否加入成功结果中组有对应设备组即可。
id
- 加入设备组 uucp 并刷新环境
- 查看设备组:
- 如果不想每次连接设备都设置权限,可以把自己加入对应设备组:
设置串口设备别名:
如果不想每次连接设备都需要运行
lerobot-find-port
来查找端口,可以设置别名:找到设备的唯一属性(
/dev/ttyACM0
请自行修改):udevadm info --query=property --name=/dev/ttyACM0 | sed -n 's/^ID_SERIAL_SHORT=\(.*\)$/ATTRS{serial}=="\1"/p'
输出示例:
ATTRS{serial}=="5A7C118736"
创建
udev
规则文件:sudo touch /etc/udev/rules.d/99-my-robot.rules
写入下面内容(
serial
部分内容请根据实际修改):# Rule for the Leader Arm SUBSYSTEM=="tty", ATTRS{serial}=="5A7C118736", SYMLINK+="leader_arm", MODE="0666" # Rule for the Follower Arm SUBSYSTEM=="tty", ATTRS{serial}=="5A7C118880", SYMLINK+="follower_arm", MODE="0666"
简单解释一下规则:
SUBSYSTEM=="tty"
: 表示规则应用于串行设备。ATTRS{serial}=="..."
: 匹配我们找到的唯一设备序列号。SYMLINK+="leader_arm"
: 创建一个名为/dev/leader_arm
的符号链接(别名)。MODE="0666"
: 这一步将设备文件的权限设置为所有人可读可写,可以避免很多 “Permission denied” 的错误。
重新加载
udev
规则并触发sudo udevadm control --reload-rules && sudo udevadm trigger
验证是否设置成功 拔插一下你的设备,然后运行
ls -l /dev/leader_arm /dev/follower_arm
。[xiadengma@archlinux ~]$ ls -l /dev/leader_arm /dev/follower_arm lrwxrwxrwx 1 root root 7 Oct 26 15:30 /dev/leader_arm -> ttyACM0 lrwxrwxrwx 1 root root 7 Oct 26 15:30 /dev/follower_arm -> ttyACM1
执行校准
- 注意:下面命令都指定了校准文件保存路径,默认路径在
~/.cache/huggingface/lerobot/calibration/
,如果不指定,可以去掉下面所有命令的--robot.calibration_dir
和--teleop.calibration_dir
。 - 校准过程:运行校准程序,然后将机器人移动到中间位,按下
Enter
后,再将每个关节在其完整的运动范围内移动。
- 校准主臂:
- 主臂名称(可自定义):
my_leader_arm
- 主臂校准文件保存路径(可自定义):
./data/calibration
lerobot-calibrate \ --robot.type=so101_leader \ --robot.port=/dev/leader_arm \ --robot.id=my_leader_arm \ --robot.calibration_dir=./data/calibration
- 主臂名称(可自定义):
- 校准从臂:
- 从臂名称(可自定义):
my_follower_arm
- 从臂校准文件保存路径(可自定义):
./data/calibration
lerobot-calibrate \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.id=my_follower_arm \ --robot.calibration_dir=./data/calibration
- 从臂名称(可自定义):
- 注意:下面命令都指定了校准文件保存路径,默认路径在
标定数据解读
{ "shoulder_pan": { #肩部旋转关节 "id": 1, # 电机的唯一标识符,用于在总线通信时精准定位和控制特定电机 "drive_mode": 0, # 电机的驱动模式,取值为 0 表示特定的驱动模式,不同驱动模式会影响电机的运动特性与控制方式 "homing_offset": 56, # 归位偏移量,指电机从物理零点位置到校准零点位置的偏移量。此参数能保证电机在每次启动时都能回到准确的零点位置,从而提升运动精度 "range_min": 829, #电机运动范围的最小值和最大值,以数值形式呈现。这两个参数限定了电机的运动边界,避免因超出范围而导致硬件损坏或者运动异常 "range_max": 2866 }, "shoulder_lift": { #肩部升降关节 "id": 2, "drive_mode": 0, "homing_offset": 463, "range_min": 836, "range_max": 3136 }, "elbow_flex": { #肘部弯曲关节 "id": 3, "drive_mode": 0, "homing_offset": -100, "range_min": 894, "range_max": 3100 }, "wrist_flex": { #腕部弯曲关节 "id": 4, "drive_mode": 0, "homing_offset": -582, "range_min": 928, "range_max": 3213 }, "wrist_roll": { #腕部旋转关节 "id": 5, "drive_mode": 0, "homing_offset": -650, "range_min": 140, "range_max": 3955 }, "gripper": { #夹爪关节 "id": 6, "drive_mode": 0, "homing_offset": -1229, "range_min": 2044, "range_max": 3461 } }
遥操作/示教#
无摄像头遥操作#
- 执行遥操作:
lerobot-teleoperate \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.id=my_follower_arm \ --robot.calibration_dir=./data/calibration \ --teleop.type=so101_leader \ --teleop.port=/dev/leader_arm \ --teleop.id=my_leader_arm \ --teleop.calibration_dir=./data/calibration
- 运行后摆动主臂,从臂会跟随运动,按
Ctrl+C
退出
安装摄像头#
如果在硬件连接中安装了摄像头,可以进行摄像头遥操作。
连接摄像头和电脑(最好有两个,一个摄像头拍操作区域,一个摄像头安装在机械臂腕部)
查看摄像头索引和输出
lerobot-find-cameras opencv --output-dir ./data/cameras_images
结果输出如下:
--- Detected Cameras --- # 检测到的相机列表 Camera #0: # 相机 0 Name: OpenCV Camera @ /dev/video0 # 相机名称及设备路径 Type: OpenCV # 相机类型:OpenCV Id: /dev/video0 # 相机 ID,即设备路径 Backend api: V4L2 # 使用的后端 API:V4L2 Default stream profile: # 默认流配置 Format: 0.0 # 格式:0.0(默认设置) Width: 640 # 图像宽度:640 像素 Height: 480 # 图像高度:480 像素 Fps: 30.0 # 每秒帧数:30 帧 -------------------- Camera #1: Name: OpenCV Camera @ /dev/video2 Type: OpenCV Id: /dev/video2 Backend api: V4L2 Default stream profile: Format: 0.0 Width: 640 Height: 480 Fps: 30.0 -------------------- Camera #2: Name: OpenCV Camera @ /dev/video6 Type: OpenCV Id: /dev/video6 Backend api: V4L2 Default stream profile: Format: 0.0 Width: 640 Height: 480 Fps: 30.0 -------------------- Camera #3: Name: OpenCV Camera @ /dev/video8 Type: OpenCV Id: /dev/video8 Backend api: V4L2 Default stream profile: Format: 0.0 Width: 640 Height: 480 Fps: 30.0 -------------------- Finalizing image saving... Image capture finished. Images saved to data/cameras_images
运行完成后会在
.data/cameras_images
目录下保存图片根据输出记录摄像头:
- 手腕左摄像头:
- 名称:
wrist_left
- 索引:
2
- 宽度:
640
- 高度:
480
- 帧率:
30
- 名称:
- 机械臂正前方摄像头:
- 名称:
front_rgb
- 索引:
8
- 宽度:
640
- 高度:
480
- 帧率:
30
- 名称:
- 手腕左摄像头:
使用摄像头进行遥操作#
- 执行遥操作:
lerobot-teleoperate \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.id=my_follower_arm \ --robot.cameras="{ wrist_left: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}, front_rgb: {type: opencv, index_or_path: 8, width: 640, height: 480, fps: 30}}" \ --teleop.type=so101_leader \ --teleop.port=/dev/leader_arm \ --teleop.id=my_leader_arm \ --display_data=true
- 运行后会打开
rerun
窗口,可以查看机械臂和摄像头画面 - 摆动主臂,从臂会跟随运动,按
Ctrl+C
退出
录制数据集#
每个回合录制流程:等待程序提示录制当前回合,通过主臂遥操作机械臂进行抓取,抓取结束后,将机械臂恢复到休息位再结束当前回合,等待程序记录数据的同时重置抓取环境,等待程序提示录制下一个回合。
键盘控制说明:
按键 何时使用 作用 右箭头 (→) 在当前回合采集期间,并且你已成功完成任务 成功并提前结束 当前 回合,保存数据,然后进入重置阶段。 左箭头 (←) 在当前回合采集期间,但你犯了个错误 作废并重新开始 当前 回合。这次的录制数据会被丢弃。 ESC 键 任何时候 完全终止 整个采集会话。程序会保存已完成的数据并退出。
录制测试数据集#
- 采集次数:5 次
- 采集任务(请自定义修改):
Put the red pepper toy in the cardboard box
- 采集数据保存路径:
./data/datasets/xiadengma/record-test-so101
lerobot-record \
--robot.type=so101_follower \
--robot.port=/dev/follower_arm \
--robot.id=my_follower_arm \
--robot.calibration_dir=./data/calibration \
--robot.cameras="{ wrist_left: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}, front_rgb: {type: opencv, index_or_path: 8, width: 640, height: 480, fps: 30}}" \
--teleop.type=so101_leader \
--teleop.port=/dev/leader_arm \
--teleop.id=my_leader_arm \
--teleop.calibration_dir=./data/calibration \
--display_data=true \
--dataset.repo_id=xiadengma/record-test-so101 \
--dataset.num_episodes=5 \
--dataset.single_task="Put the red pepper toy in the cardboard box" \
--dataset.push_to_hub=false \
--dataset.root=./data/datasets/xiadengma/record-test-so101
可视化数据集#
我们可以对录制的数据集进行可视化。
- 查看指定回合:
lerobot-dataset-viz \ --repo-id xiadengma/record-test-so101 \ --root ./data/datasets/xiadengma/record-test-so101 \ --episode-index 0
- 查看多个回合:
lerobot-dataset-viz \ --repo-id xiadengma/record-test-so101 \ --root ./data/datasets/xiadengma/record-test-so101 \ --episodes 0 1 2 3 4
回放数据集#
不稳定,可跳过,可尝试。
你也可以让机械臂根据数据集进行回放:
- 回放之前测试录制数据集的第一个回合:
lerobot-replay \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.id=my_follower_arm \ --dataset.repo_id=xiadengma/record-test-so101 \ --dataset.root=./data/datasets/xiadengma/record-test-so101 \ --dataset.episode=0
- 你应该可以看到机械臂按照数据集进行回放。
录制完整数据集#
现在,我们开始录制数据集,并将这个数据集训练后用于推理。
- 每个回合录制流程:等待程序提示录制当前回合,通过主臂遥操作机械臂进行抓取,抓取结束后,将机械臂恢复到休息位再结束当前回合,等待程序记录数据的同时重置抓取环境,等待程序提示录制下一个回合。
- 录制要求:
- 录制数量:至少 50 组数据,确保数据充分性
- 录制频次:每个位置重复录制 10 次,以提高数据的多样性和鲁棒性
- 录制位置:至少选择 5 个不同的位置,涵盖更多动作场景
- 键盘控制说明:
按键 何时使用 作用 右箭头 (→) 在当前回合采集期间,并且你已成功完成任务 成功并提前结束 当前 回合,保存数据,然后进入重置阶段。 左箭头 (←) 在当前回合采集期间,但你犯了个错误 作废并重新开始 当前 回合。这次的录制数据会被丢弃。 ESC 键 任何时候 完全终止 整个采集会话。程序会保存已完成的数据并退出。
录制数据集:
- 采集次数:50 次
- 采集任务(请自定义修改):
Put the red pepper toy in the cardboard box
- 采集数据保存路径(请自定义修改):
./data/datasets/xiadengma/so101-red-pepper
python -m lerobot.extra.lerobot_record_web \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.id=my_follower_arm \ --robot.calibration_dir=./data/calibration \ --robot.cameras="{ wrist_left: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}, front_rgb: {type: opencv, index_or_path: 8, width: 640, height: 480, fps: 30}}" \ --teleop.type=so101_leader \ --teleop.port=/dev/leader_arm \ --teleop.id=my_leader_arm \ --teleop.calibration_dir=./data/calibration \ --display_data=true \ --dataset.repo_id=xiadengma/so101-red-pepper \ --dataset.num_episodes=50 \ --dataset.single_task="Put the red pepper toy in the cardboard box" \ --web_port=9090 \ --dataset.push_to_hub=false \ --dataset.root=./data/datasets/xiadengma/so101-red-pepper
恢复录制(仅供参考):
Recording episode 15
时程序报错:Waiting for image writer to terminate...
和TimeoutError: Timed out waiting for frame from camera OpenCVCamera(8) after 200 ms. Read thread alive: True.
问题原因:在机械臂运动过程中,拉扯到机械臂腕部摄像头的连接处,导致摄像头连接不稳定。
解决方法:重新连接摄像头,并预留足够长度的线缆,确保摄像头连接稳定,接着运行下面命令恢复录制。
python -m lerobot.extra.lerobot_record_web \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.id=my_follower_arm \ --robot.calibration_dir=./data/calibration \ --robot.cameras="{ wrist_left: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}, front_rgb: {type: opencv, index_or_path: 8, width: 640, height: 480, fps: 30}}" \ --teleop.type=so101_leader \ --teleop.port=/dev/leader_arm \ --teleop.id=my_leader_arm \ --teleop.calibration_dir=./data/calibration \ --display_data=true \ --dataset.repo_id=xiadengma/so101-red-pepper \ --dataset.num_episodes=35 \ --dataset.single_task="Put the red pepper toy in the cardboard box" \ --web_port=9090 \ --dataset.push_to_hub=false \ --dataset.root=./data/datasets/xiadengma/so101-red-pepper \ --resume=true
- 在记录过程中会自动创建检查点。
- 如果记录过程中断,可以通过重新运行相同的命令并添加 –resume=true 来恢复记录。
- ⚠️ 重要提示:在恢复时,需将 –dataset.num_episodes 设置为要额外记录的剧集数量(而不是数据集中目标的总剧集数量)
录制结束后,数据集大小为
390M
训练#
- 安装 SwanLab:
pip install swanlab
- 在官网登录后获得
API Key
,运行swanlab login
登录 - 创建训练日志文件夹:
mkdir -p ./logs
- 开始训练(100000 步):
stdbuf -oL -eL nohup python -m lerobot.extra.lerobot_train_swanlab \ --dataset.repo_id=xiadengma/so101-red-pepper \ --dataset.root=./data/datasets/xiadengma/so101-red-pepper \ --policy.type=act \ --output_dir=./data/train/act_so101_red_pepper \ --job_name=act_so101_red_pepper_$(date +%Y%m%d_%H%M%S) \ --policy.device=cuda \ --wandb.enable=false \ --policy.push_to_hub=false \ --steps=100000 \ --tracker=swanlab \ --swanlab.project=so101-red-pepper \ --swanlab.mode=cloud \ > ./logs/train_$(date +"%Y-%m-%d-%H-%M-%S").log 2>&1 & echo $! > ./logs/train.pid
- 恢复训练:
stdbuf -oL -eL nohup lerobot-train \ --config_path=./data/train/act_so101_red_pepper/checkpoints/last/pretrained_model/train_config.json \ --resume=true \ > ./logs/resume_$(date +"%Y-%m-%d-%H-%M-%S").log 2>&1 & echo $! > ./logs/train.pid
- 查看最新日志:
tail -f $(ls -t ./logs/*.log | head -n 1)
- 中断训练:
kill -TERM $(cat ./logs/train.pid) || kill -KILL $(cat ./logs/train.pid)
运行推断并评估#
- 单回合完成一个任务:
python -m lerobot.extra.lerobot_record_web \ --robot.type=so101_follower \ --robot.port=/dev/follower_arm \ --robot.cameras="{ wrist_left: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}, front_rgb: {type: opencv, index_or_path: 8, width: 640, height: 480, fps: 30}}" \ --robot.id=my_follower_arm \ --robot.calibration_dir=./data/calibration \ --display_data=false \ --dataset.repo_id=xiadengma/eval_so101-red-pepper \ --dataset.single_task="Put the red pepper toy in the cardboard box" \ --policy.path=./data/train/act_so101_red_pepper/checkpoints/last/pretrained_model \ --policy.device=cuda \ --dataset.root=./data/datasets/xiadengma/eval_so101-red-pepper \ --web_port=9090 \ --dataset.episode_time_s=30 \ --dataset.episode=0
- 单回合完成长任务:
python -m lerobot.extra.lerobot_record_web \ --robot.type=so101_follower \ --robot.port=/dev/follow_arm \ --robot.cameras="{ wrist_left: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}, front_rgb: {type: opencv, index_or_path: 8, width: 640, height: 480, fps: 30}}" \ --robot.id=my_follower_arm \ --robot.calibration_dir=./data/calibration \ --display_data=false \ --dataset.repo_id=xiadengma/eval_so101-red-pepper \ --dataset.single_task="Put the red pepper toy in the cardboard box" \ --policy.path=./data/train/act_so101_red_pepper/checkpoints/last/pretrained_model \ --policy.device=cuda \ --dataset.root=./data/datasets/xiadengma/eval_so101-red-pepper \ --web_port=9090 \ --dataset.episode_time_s=240 \