来源:https://zhuanlan.zhihu.com/p/1895609105902704289
前段时间一直在看具身智能的一些技术,包括openvla, rdt等模型,也想着在一个实际的设备上进行一些测试,当然预算有限,通过调研发现了一个较好的开源项目叫lerobot,可以在千元级满足测试需求。
一,材料购买
lerobot是hugging face的一个开源项目,它包含一些具身智能所需的低成本硬件,包括6/7自由度的机械臂,底盘小车等,这些硬件可以通过3D打印获得,所以具备较低的成本,我在闲鱼和京东上购买了相关配件,明细如下:
| 名称 | 明细 | 价格 | 来源 |
|---|---|---|---|
| so100机械臂 | 主从机械臂一套+100万像素相机一个 (主臂为虚拟舵机,只能人工控制,用于人工操作采集数据,从臂为有动力的舵机,模型训练完成后,由从臂进行自动化操作演示;按惯例在抓手上安装一个相机,所以就和机械臂一起购买,若没有在抓手上安装相机的需求,可以单独从电商购买也可以) | 1180+80(包含200元安装费用,若自行安装可省) | 闲鱼 |
| 相机 | 100万像素相机一个,一般模拟人眼的视角,放在高处 | 92 | 京东 |

lerobot项目中的机械臂也有7自由度的,还有移动底坐,我根据需要买的是6自由度的设备叫so-100。除了硬件,lerobot项目中也有丰富的代码,可以实现机械臂的标定,数据采集,可视化,模型训练,模型测试等,所以还是非常方便的。
lerobot项目相关资料:
lerobot机械臂中文资料docs.qq.com/sheet/DQnFNc0pVdUV2bEpZ?tab=BB08J2
软件:
二,硬件配置
机械臂的安装可能需要花费一些时间(若没有购买安装服务的话),上面的文档中有一些安装视频可参考。本节主要讲安装前后的一些配置工作。
舵机的ID设置
一个臂有6个自由度,就是6个舵机,所以需要根据顺序进行编号,主臂和从臂都是1-6,一般建议在装配之前进行配置,如果机械臂安装好再配置,会很麻烦,可能要部分折掉才能把数据线头取出来。下面这篇文章写得较好可参考(此文中高亮提示:“安装过程中不要去转动齿轮!”,可以忽略,因为安装过程中很难保证不转动舵机,可以通过后续的中位设置来调整)。
官方文档“10_use_so100.md”中也有详细的步骤,请大家结合自身的硬件型号/官方文档/网友文章多方对比,避免过于相信某一个来源,因为大家的硬件型号可能都不太一样,所以建议多对比一下各个文档。有些文章中还有拆舵机齿轮的操作,我买的这一款是不需要的。
有很多文章中,包括官方文档中在配置舵机id时,使用的都是命令行,因为我买的产品 ,主臂用的是虚拟舵机(表格中有讲),不是一个完备的舵机,虚拟舵机只能输出信号,不能输入信号控制它,所以价格会便宜一些。但问题是虚拟舵机通过脚本来配置会失败,需要通过官方的软件进行配置才行,链接如下:
软件-深圳飞特模型有限公司www.feetech.cn/software.html
下载1.9.8.3在windows下面运行,这个视频有详细讲解:
中位配置
主从机械臂安装后,需要进行中位配置,因为某个舵机的默认输出范围是1-4096,但在安装过程中,舵机可能被转动过,例如当整个机械臂底部的朝向物理上居中时,它正常应该输出2048,但大概率经过安装后,它不是输出的2048,这时候可以通过下面视频中的方法进行中位配置,配置成功的标志是1-6各关节在视频中所示的角度时,都稳定输出2048。注意,配置后可能不会立即生效,所以看起来好像没成功,需要重启一下。若还不行,请咨询商家。
整体上硬件成功安装+配置的标志应该是两个机械臂都能在软件(官方那个)中找到1-6舵机,并且在转动这2×6=12个关节时,在软件中可以看到每个关节的值在a->b之间连续变化,大部分关节都不能360度转,所以每个关节的a,b值不一样,大部分应该在1000->3000中间。
相机位置
我把相机的位置也放在了硬件章节中。我的机械臂共有2个相机,一个默认是全局视野,用于算法在全局视野中定位目标物体; 一个是局部视野,一般用于抓手的抓取过程中的定位,所以很多演示是直接安装在抓手上。我试过3组相机摆放位置,发现方案3模型训练出来效果最好,不过只是目前经验,与模型选型,训练数据量都有关系,所以仅供参考。
| 全局相机位置 | 局部相机位置 | 备注 |
|---|---|---|
| 桌面高处,机械臂左侧 | 抓手上 | 左侧导致机械臂在右边时,容易遮挡目标物体,导致相机看不见目标物体 |
| 桌面高处,机械臂正上方 | 抓手上 | 抓手在稍微偏离目标物体时,导致相机看不见目标物体 |
| 桌面高处,机械臂正上方 | 桌面低处,机械臂旁边固定 | 2个相机视野中都会有目标物体 |
图片(上面白色为高处相机,右侧支架为局部相机):

三,软件安装与校准
软件安装与端口配置
软件环境安装与端口配置,上面列的官方文档和网友文章写得都差不多,可自行参考。不再赘述。不过我这边在安装过程中,遇到av安装失败的问题,后面排查后发现默认安装的ffmpeg是7.1.1,后来换成低版本的6.1.1就可以。另外,官方文档10_use_so100.md和README.md的环境安装过程稍微有一些不一样,建议先README.md再10_use_so100.md,相关环境安装命令全部执行。
conda list|grep ffmpeg
#ffmpeg 6.1.1
主从校准
主从校准的目标是可以由主机械臂控制从机械臂,它们之间没有明显的diff,校准的目标是方便数据采集。例如主机械臂向左旋转15度,从机械臂应该也是向左旋转15度,人工控制过程中,没有发现明显的偏差就行。校准成功的标志是:1,从臂跟随主臂没有明显偏差。2,各关节的活动范围也没有明显偏差,例如不能出现主臂可以向左旋转90度,但从臂只能向左旋转45度。官方文档和网友文章写得已经很详细了,请参考。
校准后,在项目目录中的.cache/calibration/so100/文件夹下面会生成2个校准文件,2个文件格式是一样的,如下:
{"homing_offset": [-1984, 3075, -1052, -1975, 2025, -2455],
"drive_mode": [0, 1, 0, 0, 1, 0],
"start_pos": [2060, 3061, 1039, 1898, 2023, 2087],
"end_pos": [3008, -2051, 2076, 2999, -1001, 3479],
"calib_mode": ["DEGREE", "DEGREE", "DEGREE", "DEGREE", "DEGREE", "LINEAR"],
"motor_names": ["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll", "gripper"]}
从原理上讲(若不关心原理,可以忽略此节),校准文件的核心就是对每个关节定义了一个零位(校准过程中的零位就是软件层面定义的零位)和正方向,上面讲的输出2048只是硬件层面的输出,在软件层面有软件层面的定义与量纲。例如某个关节在定义的零位时,硬件输出值是1984,那么homing_offset对应的校准值就是-1984,目标就是把硬件值映射到0。正方向就是舵机往哪个方向旋转是正向,正向0,负向为1,上面的drive_mode就是这个值。通过上面的标定文件,可以将舵机物理输出值(一般是0-4096)转换成软件内部用的值(一般是-180~180度)。
举例来讲,若一号舵机硬件值是2048,转换过程:(2048-1984)/2048 * 180度 = 5.625度。若二号舵机的硬件值是868,转换过程:(系数 * 868 + 3075 )/2048 * 180度 = 193度,drive_mode==0时,系数是1, drive_mode==1时,系数是-1。
start_pos和end_pos是校准过程中,关节的活动范围,校准完成后一般就不用了(6号关节除外)。calib_mode的DEGREE就代表着上面的角度转换过程。LINEAR代表的是另一种转换过程,对6号关节,若物理值是2048,转换过程:(2048-2087)/(3479-2087) * 100。
整体的转换过程参考:FeetechMotorsBus:apply_calibration函数。
四,数据记录与模型训练/测试
此部分参考官方文档即可,非常详细了。
数据采集后格式如下:
.
├── data
│ └── chunk-000
│ ├── episode_000000.parquet
│ ├── episode_000001.parquet
│ ├── episode_000002.parquet
│ ...
├── meta
│ ├── episodes.jsonl
│ ├── episodes_stats.jsonl
│ ├── info.json
│ └── tasks.jsonl
└── videos
└── chunk-000
├── observation.images.laptop
│ ├── episode_000000.mp4
│ ├── episode_000001.mp4
│ ├── episode_000002.mp4
...
└── observation.images.phone
├── episode_000000.mp4
├── episode_000001.mp4
...
可以看到图片是用mp4存储的,非常节省空间。上面的parquet文件就是state与action信息,就是各关节的角度。模型训练在默认配置(ACT模型)下,需要5G左右的显存,本地的3090显卡训练100K个steps需要3个小时左右。同时 ,我也使用了autodl平台上的算力,4090D相对于3090会快个40%左右,一小时2元。关于模型后面有单独的文章会讲其原理和实际效果。在玩具块在数据采集时刻那个固定的位置上,成功率是非常高的,但换个位置的话,成功率就很低,这与训练数据有很大的关联,所以为啥叫模仿学习^v^,在文章最后贴一个成功抓取的视频吧:

00:31
高处相机

00:31
侧面相机
上面的内容撰写于2025.4月
————————–分割线———————
下面的内容撰写于2025.10月
最近发现lerobot的架构更新了很多,包括以前的一些命令写法,机械臂的标定方法,记录数据的方法等有很多更新,所以在这里补充一下最新版本的lerobot的一些命令使用方法:
文档
文档在新的框架下面也汇总在了一起。看起来更集中,更统一了一些。大家按照github根目录中的README.md环境安装完成后,再按照./docs目录中README.md把文档生成一下。然后就可以在本地用网页统一查看了,如下:

标定
标定方法与标定文件的格式与以前相比都发生重大变化,所以需要重新标定,过程如下:
标定主臂
lerobot-calibrate --teleop.type=so100_leader --teleop.port=/dev/ttyACM0 --teleop.id=my_awesome_leader_arm
标定从臂:
lerobot-calibrate --robot.type=so100_follower --robot.port=/dev/ttyACM1 --robot.id=my_awesome_follower_arm
标定后在下面的文件夹内生成标定参数:
(主)/home/ubuntu/.cache/huggingface/lerobot/calibration/teleoperators/so100_leader/my_awesome_leader_arm.json
(从)/home/ubuntu/.cache/huggingface/lerobot/calibration/robots/so100_follower/my_awesome_follower_arm.json
其中主臂的标定参数结果如下,可以发现与以前相比格式和内容都发生较大变化:
{
"shoulder_pan": {
"id": 1,
"drive_mode": 0,
"homing_offset": -1077,
"range_min": 942,
"range_max": 3497
},
"shoulder_lift": {
"id": 2,
"drive_mode": 0,
"homing_offset": -61,
"range_min": 886,
"range_max": 3306
},
"elbow_flex": {
"id": 3,
"drive_mode": 0,
"homing_offset": -968,
"range_min": 824,
"range_max": 3063
},
"wrist_flex": {
"id": 4,
"drive_mode": 0,
"homing_offset": 2027,
"range_min": 779,
"range_max": 3259
},
"wrist_roll": {
"id": 5,
"drive_mode": 0,
"homing_offset": -2047,
"range_min": 0,
"range_max": 4095
},
"gripper": {
"id": 6,
"drive_mode": 0,
"homing_offset": -586,
"range_min": 2025,
"range_max": 3194
}
}
teleoperate(遥操)
不启用相机:
lerobot-teleoperate --robot.type=so100_follower --robot.port=/dev/ttyACM1 --robot.id=my_awesome_follower_arm --teleop.type=so100_leader --teleop.port=/dev/ttyACM0 --teleop.id=my_awesome_leader_arm
启用相机:
lerobot-teleoperate \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM1 \
--robot.id=my_awesome_follower_arm \
--robot.cameras='{
left: {"type": "opencv", "index_or_path": 2, "width": 640, "height": 480, "fps": 30},
top: {"type": "opencv", "index_or_path": 0, "width": 640, "height": 480, "fps": 30}
}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyACM0 \
--teleop.id=my_awesome_leader_arm \
--display_data=true
同时会打开一个统一的可视化界面,这个是以前没有的:

record数据
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM1 \
--robot.cameras='{
left: {"type": "opencv", "index_or_path": 2, "width": 640, "height": 480, "fps": 30},
top: {"type": "opencv", "index_or_path": 0, "width": 640, "height": 480, "fps": 30}
}' \
--robot.id=my_awesome_follower_arm \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyACM0 \
--teleop.id=my_awesome_leader_arm \
--dataset.repo_id=hxdoso/new_frame \
--dataset.num_episodes=2 \
--dataset.single_task="Grab the cube" \
--display_data=true \
--dataset.reset_time_s=10
# --dataset.reset_time_s=10代表Reset the environment打印出来后,10秒后开始下一个episode(若记录多个record时)
文件树:
可以发现与以前相比也有变化,其中的dataset版本号从以前的2.1升级为3.0,所以以前的数据在新版式中是用不了的,需要转换一下。
.
├── data
│ └── chunk-000
│ └── file-000.parquet
├── meta
│ ├── episodes
│ │ └── chunk-000
│ │ └── file-000.parquet
│ ├── info.json
│ ├── stats.json
│ └── tasks.parquet
└── videos
├── observation.images.left
│ └── chunk-000
│ └── file-000.mp4
└── observation.images.top
└── chunk-000
└── file-000.mp4
replay
可以通过replay回放数据,查看机械臂的复现动作方面的一致性如何。
lerobot-replay --robot.type=so100_follower --robot.port=/dev/ttyACM1 --robot.id=my_awesome_follower_arm --dataset.repo_id=hxdoso/new_frame --dataset.episode=1
train
训练一个模型(用act模型):
lerobot-train --dataset.repo_id=hxdoso/new_frame --policy.type=act --output_dir=outputs/train/act_so100_test --job_name=act_so100_test --policy.device=cuda --policy.push_to_hub=false
用pi0.5模型:
python src/lerobot/scripts/lerobot_train.py --dataset.repo_id=hxdoso/new_frame --policy.type=pi05 --output_dir=./outputs/pi05_training --job_name=pi05_training --policy.repo_id=lerobot/pi05_base --policy.pretrained_path=lerobot/pi05_base --policy.compile_model=true --policy.gradient_checkpointing=true --policy.dtype=bfloat16 --steps=3000 --policy.device=cuda --batch_size=1
infer&eval
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM1 \
--robot.cameras='{
left: {"type": "opencv", "index_or_path": 2, "width": 640, "height": 480, "fps": 30},
top: {"type": "opencv", "index_or_path": 0, "width": 640, "height": 480, "fps": 30}
}' \
--robot.id=my_awesome_follower_arm \
--dataset.repo_id=hxdoso/eval_new_frame \
--dataset.num_episodes=1 \
--dataset.single_task="Grab the cube" \
--dataset.reset_time_s=10 \
--policy.type=pi05 \
--policy.pretrained_path=lerobot/pi05_base

评论0