首页
/ LeRobot Isaac Teleop → SO-101 遥操作与数据采集实战指南

LeRobot Isaac Teleop → SO-101 遥操作与数据采集实战指南

2026-09-10 18:35:26作者:钟日瑜

本指南以 LeRobot 仓库中的 examples/isaac_teleop_to_so101/README.md 为规范参考,系统讲解如何用 NVIDIA Isaac Teleop 驱动 SO-101/SO-100 从臂(follower arm)完成遥操作与 LeRobot 数据集录制。读完本文,你将掌握 XR(VR)手柄与 SO-101 主臂(leader arm)两条完整链路的环境安装、命令行操作、启动流程,以及离合器(clutch)、笛卡尔逆运动学(IK)、复位/对齐摆位等底层机制的源码级原理,可直接复现到自己的真实硬件上采集数据。

概述:Isaac Teleop 为 LeRobot 提供了什么

Isaac Teleop 是 NVIDIA 的多模态遥操作框架,其核心是一个可由多种设备驱动的 TeleopSession:XR 手柄、手部追踪、Manus 数据手套等都能作为输入模态。LeRobot 仓库中的这个示例把 Isaac Teleop 与 LeRobot 的机器人控制、数据集流水线打通,目标是:遥操作一台 SO-101(或 SO-100)从臂,并同时录制可直接用于策略训练的 LeRobot 数据集

该示例内置了两种输入设备,通过 --teleop.type 选择:

设备 CLI 标识 控制方式 关键链路
XR(VR)手柄 xr_controller 手柄握把位姿经"按压-接合"离合器(squeeze-to-engage clutch)驱动末端执行器,走 LeRobot 的笛卡尔 IK 流水线;扳机(analog trigger)驱动夹爪开合 离合器 + 软姿态 IK
SO-101 主臂 so101_leader 可反向驱动(back-drivable)的主臂,通过 Isaac Teleop 原生 so101_leader 插件按 1:1 关节镜像映射到从臂 无离合器、无 IK

两种模式共享同一套底层控制循环基础设施,位于 common.py;命令行入口分别是 teleoperate.py(纯遥操作)与 record.py(遥操作 + 录数据集)。关于离合器工作原理、CloudXR 配置、头显配对、参数调优与排障的完整叙述性指南,见 docs/source/isaac_teleop.mdxdocs/source/isaac_teleop.mdx,该 README 是权威的安装与使用参考)。

从源码结构看,整个示例由三层组成:

前置条件(Requirements)

在开始之前,需要确认以下软硬件条件:

  1. Linux 工作站:需满足 NVIDIA 官方对 OS/GPU/头显组合的系统要求(isaacteleop 仅发布 Linux wheel,不支持其他平台)。
  2. SO-101(或 SO-100)从臂:需先用 lerobot-calibrate 完成标定(对应脚本见 scripts/lerobot_calibrate.py)。标定结果会按约定路径存放,供后续插件复用。
  3. XR 设备路径:需要一台支持 CloudXR 的头显(如 Quest 3、Pico 4、Apple Vision Pro),且与工作站处于同一网络。
  4. 主臂路径:需要第二台可反向驱动的 SO-101 主臂,以及从 Isaac Teleop 源码树编译出的 so101_leader 插件二进制(README 明确要求"Build from source")。

注意:本示例在 build_device() 入口处做了机型白名单校验(见 common.py):只支持 so101_followerso100_follower 两种从臂(两者共用 SO-101 URDF),传入其他 --robot.type 会直接抛出 ValueError

安装(Installation)

该示例位于 LeRobot 源码仓库内,不属于 lerobot pip 包的组成部分,因此必须从源码检出(source checkout)运行。在仓库根目录执行:

# LeRobot 及本示例所需 extras:
#   feetech    - SO-101 串行电机总线
#   kinematics - Placo IK 求解器(XR 手柄路径)
#   dataset    - 数据集录制(record.py)
# huggingface_hub >= 1.5 是自动拉取 URDF(Buckets API)所需
uv pip install -e ".[feetech,kinematics,dataset]" "huggingface_hub>=1.5"

# Isaac Teleop 从公共 PyPI 安装。cloudxr 提供 CloudXR 运行时绑定;
# retargeters-lite 是基于 scipy 的重定向器路径,在 x86_64 和 ARM 上都能解析
# (完整的 retargeters extra 在 aarch64 上无法解析)
uv pip install "isaacteleop[cloudxr,retargeters-lite]~=1.3.131" "scipy>=1.14"

# 可选,仅 x86_64:完整版重定向器栈
uv pip install "isaacteleop[retargeters]~=1.3.131"

首次运行前,还需要完成一次性的 CloudXR EULA 接受操作。原因在于:CloudXR 的自动启动会在 stdin 上弹出许可协议询问,如果是在无头(headless)机器上运行会直接挂起等待输入。预先接受即可避免:

python -m isaacteleop.cloudxr --accept-eula

从源码看,isaacteleop 是可选依赖:模块顶部通过 is_package_available("isaacteleop") 做可用性检查(见 base.py),未安装时模块仍可导入,但构造设备会立刻抛出带安装指引的 ImportError,实现"快速失败"。

使用方式(Usage)

所有命令都从仓库根目录以 python -m 方式运行,以确保 examples 包可以被解析到。两个入口脚本都支持 --help 查看全部参数。

方式一:XR(VR)手柄遥操作

python -m examples.isaac_teleop_to_so101.teleoperate \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.id=so101_follower_arm \
    --teleop.type=xr_controller

启动后的完整流程(对应 common.pysetup_xr() 的实现):

  1. 启动 CloudXR 运行时(约 30 秒)。connect() 会自动拉起 CloudXR 运行时(详见后文"CloudXR 生命周期"),除非通过配置或环境变量显式跳过。
  2. 打印工作站 IP:脚本会枚举本机各网卡的 IPv4 地址(主出口地址优先,跳过 dockerbr-vethvirbrl4tbr0 等虚拟/桥接接口),提示你在头显的 CloudXR Web 客户端中输入。
  3. 等待控制器接入:循环轮询 is_tracking,每 15 秒重打印一次连接提示(Ctrl-C 可中止等待)。
  4. 复位摆位:默认将机械臂各关节线性摆到复位位姿(--reset_to_origin=false 可跳过),摆位时长由 --reset_duration 控制,默认 5 秒。
  5. 进入遥操作循环按住握把(squeeze/grip)接合离合器,移动手柄驱动机械臂;扣动扳机(trigger)闭合夹爪;松开握把则机械臂冻结在当前位置。

SO-101 的 URDF 会在首次运行时自动从 lerobot/robot-urdfs Hugging Face bucket 同步到 LeRobot 缓存目录,之后直接复用本地缓存。

自定义复位位姿:手动把机械臂推到想要的位姿(建议先断电/释放扭矩),然后执行:

python -m examples.isaac_teleop_to_so101.override_reset_pose --port /dev/ttyACM0 --id so101_follower_arm

该脚本(override_reset_pose.py)会把当前各关节位置写入 HF_LEROBOT_HOME/reset_poses/<robot.name>/<robot.id>.json;之后任何使用相同 --robot.idteleoperate.py / record.py 运行都会自动优先加载该文件作为复位目标,而非内置默认值。

内置默认复位位姿(源码 common.py 中的 RESET_ORIGIN_DEG):shoulder_pan=-4.0°shoulder_lift=-103.0°elbow_flex=97.0°wrist_flex=78.0°wrist_roll=-65.0°gripper=0.0(夹爪单位 RANGE_0_100,100=张开)。这是一个肘部/腕部弯曲的经验舒适位姿,可避开完全伸展时的奇异位形;基于标准标定假设,可用 override_reset_pose.py 逐臂覆盖。

方式二:SO-101 主臂遥操作

python -m examples.isaac_teleop_to_so101.teleoperate \
    --robot.type=so101_follower --robot.port=/dev/ttyACM0 --robot.id=so101_follower_arm \
    --teleop.type=so101_leader --teleop.port=/dev/ttyACM1 --teleop.id=so101_leader_arm \
    --launch_plugin=/path/to/IsaacTeleop/install/plugins/so101_leader/so101_leader_plugin

关键参数说明:

  • --teleop.port:主臂所在串口(如 /dev/ttyACM1),会被转发给插件(插件直接读取舵机);留空则插件运行合成轨迹(synthetic trajectory)模式,用于无真实主臂时的联调。
  • --launch_pluginso101_leader 插件二进制路径。若不给该参数,则假定插件已在外部独立运行。插件在 CloudXR 起来之后才被拉起,这样它能继承 CloudXR 运行时环境变量(XR_RUNTIME_JSON 等)。
  • --align_duration:启动对齐摆位时长,默认 3 秒。启动时从臂会先被线性摆到主臂当前位姿(--align=false 跳过,从臂可能发生突跳),随后进入 1:1 关节镜像:直接反向驱动主臂,从臂完全复刻。

主臂链路复用了串行主臂的标定文件:HF_LEROBOT_CALIBRATION/teleoperators/so_leader/<teleop.id>.json(见 common.py_leader_calibration_path(),若该文件不存在会给出警告并让插件回退到内置默认)。这是因为插件输出的是弧度制的关节角,需要同一套零点约定才能正确映射。

录制 LeRobot 数据集

record.py 接受与 teleoperate.py 相同的 --robot.* / --teleop.* / 循环控制参数(--reset_to_origin--reset_duration--align--align_duration),并叠加 lerobot-record 风格的 --dataset.* 参数:

python -m examples.isaac_teleop_to_so101.record \
    --robot.type=so101_follower --robot.port=/dev/ttyACM0 --robot.id=so101_follower_arm \
    --teleop.type=xr_controller \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --dataset.repo_id=<hf_user>/<dataset_name><|begin▁of▁sentence|>      # 数据集标识
    --dataset.single_task="Pick up the cube"            # 任务描述
    --dataset.num_episodes=3                            # 采集片段数
    --dataset.episode_time_s=20                         # 每段时长(秒)
    --dataset.reset_time_s=5                            # 片段间复位窗口(秒)

同样的设备选择逻辑同样适用于主臂模式:只需把 --teleop.type 换成 so101_leader 并补上 --teleop.port / --teleop.id / --launch_plugin。录制时所有帧都会写入数据集(包括离合器松开时的保持帧)

键盘快捷键(终端优先设计,因此通过 SSH 也能使用):

按键 功能
Right / n 提前结束当前片段并保存
Left / r 丢弃当前片段并重新录制
Esc / q 完成当前片段后停止

从源码看(common.pyinit_keyboard_listener()),当 stdin 是 TTY 时优先使用 stdlib 的 TerminalKeyListener 而非上游的 pynput 全局监听,避免 SSH 会话下按键被工作站控制台截获;n/r/q 是方向键/Esc 在延迟链路上的替代键,大小写不敏感。录制循环本身位于 record.py:每段 episode_time_s 内持续采样,段间进入 reset_time_s 的复位窗口(该窗口仍控制机械臂但不写帧,方便操作者调整场景),最后统一由 VideoEncodingManager 完成视频编码,并支持 --dataset.push_to_hub 一键上传。

目录结构(Layout)

examples/isaac_teleop_to_so101/
├── isaac_teleop/                设备库:会话生命周期(base.py)、XRController、
│                                SO101LeaderArm、Clutch、配置类,以及 XR→IK 处理器步骤
├── common.py                    共享循环基础设施:设备 Bundle、离合器/IK 流水线接线、
│                                复位/对齐摆位、URDF 拉取、键盘监听
├── teleoperate.py               遥操作 CLI(通过 --teleop.type 选择设备)
├── record.py                    数据集录制 CLI(相同设备选择 + --dataset.*)
├── override_reset_pose.py       把当前关节保存为逐臂复位位姿
├── default.env                  CloudXR 设备配置覆盖,传给启动器
└── README.md                    本指南对应的权威文档

源码级原理:控制循环与设备抽象

teleoperate.pyrecord.py 共享同一套循环骨架,理解它有助于调试和二次开发。

Device Bundle 抽象common.py):每个输入设备被封装为 Device 冻结数据类,内含三个闭包:compute(obs) -> RobotAction | NoneNone 表示空闲、保持当前位姿)、startup(预热)与 cleanup(回收/断开)。build_device() 负责:先把默认的 CloudXR 配置文件指向本示例的 default.env → 创建并连接从臂(先连从臂,这样启动摆位和离合器 home 播种可以读取实时关节)→ 按 --teleop.type 分发到 setup_xrsetup_leader → 执行 device.startup();任何失败都会兜底断开从臂,防止串口连接泄漏。

主循环teleoperate.py):固定 30 Hz(FPS = 30),每帧执行 get_observation() → compute(obs) → send_action() → precise_sleep(1/FPS - 耗时)KeyboardInterrupt 或异常时按"先设备清理、再从臂断开"的顺序释放硬件。

HoldLatch 与重力蠕变(ratchet)问题:空闲时若每帧都重新发送刚测到的关节位置,机械臂会在重力作用下不断下坠——P 控制伺服存在稳态误差,每次"用测量值重新命令"都会让目标再低一截。因此 HoldLatch 在"激活→空闲"的边界上锁定一次目标位姿,空闲期间持续重发这个锁定位姿,而不是实时测量值。

XR 链路:离合器(Clutch)与软姿态 IK

XR 手柄设备本身是"故意做薄"的纯读取器(teleop_xr_controller.py):从 ControllersSource 读出手柄原始握把位姿(经 base_T_anchor 静态重定基到机器人基座坐标系,默认把 OpenXR 的 X=右、Y=上、Z=后 映射为机器人的 X=前、Y=左、Z=上),外加 squeeze 与 trigger,不包含重定向器与离合器——这两者都在下游的主循环中。

离合器(clutch.py 把原始手柄位姿转换为基座系下的绝对末端执行器目标,实现"按压接合、相对增量":

  • **接合瞬间(engage)**锁定两组原点:home(机械臂当前位姿,来自对实时关节做正运动学 forward_kinematics)与 origin(手柄当前位姿);
  • 每帧按公式计算目标:pos = home_pos + (grip_pos - origin_pos)(1:1 平移);rot = (R_ctrl · R_origin⁻¹) · R_home(基座系左复合的旋转增量),因此接合瞬间输出恰好等于当前位姿,无跳变
  • 位置 home 取自实测位姿而非最后指令:若机械臂在离合器松开期间被外力移动(重力下垂、外部接触),重新接合时不会以全速伺服速度弹回旧指令位置;而方向 home 始终取最后指令方向,因为 5 自由度 SO-101 对姿态只是"软跟踪",实测腕部方向与指令存在持续偏差,若锁实测会把偏差注入每次指令。

接合后的末端执行器目标还需经过一条纯处理器流水线common.pysetup_xr() 构建的 RobotProcessorPipeline)才能变成关节指令:

  1. MapXRControllerActionToRobotActionxr_controller_processor.py):把 ee_pose(7 维 [x,y,z,qx,qy,qz,qw])拆成 ee.x/y/z 位置与 ee.wx/wy/wz 旋转矢量(rotvec),并把 closedness ∈ [0,1] 反转为夹爪目标 (1 - closedness) × 100(SO-101 标定 RANGE_0_100,100=张开)。
  2. EEBoundsAndSafety:末端执行器安全边界与速率限制。end_effector_bounds 限制目标在 x,y ∈ [-1,1] mz ∈ [0,1] m(z 下限 0.0 防止目标钻到桌面以下);max_ee_step_m=0.1 在 30 Hz 下把末端速度限制在约 3 m/s;raise_on_jump=False 让超限帧被钳制而非抛异常——若中途崩溃机械臂将失去控制,所以采用"钳制+告警"策略。
  3. InverseKinematicsEEToJoints:Placo IK 求解,initial_guess_current_joints=False 表示用上一帧的 IK 解做热启动,保证关节轨迹帧间连续;orientation_weight=0.01 是"软姿态"权重——小但非零,让手腕跟随手部的同时位置占主导(5 自由度 SO-101 无法实现任意姿态)。

IK 求解器由 RobotKinematicssrc/lerobot/model/kinematics.py)基于 URDF 构建,目标坐标系为 gripper_frame_link。URDF 通过 Hugging Face 的 sync_bucketlerobot/robot-urdfs bucket 同步,并用 .sync_complete 标记文件保证"完整同步"(仅存在 URDF 文件不足以证明网格文件齐全,中断的首次同步可能留下残缺缓存;重新同步是幂等的)。

主臂链路:1:1 关节镜像

主臂设备(teleop_so101_leader_arm.py)读取 JointStateSource 推流的六个关节角(弧度),转换为从臂可消费的 {joint}.pos

  • 臂关节:rad2deg 直接换算(前提是主/从臂的标定零点与物理零点对齐,即标准的同硬件假设);
  • 夹爪:从 [gripper_open_rad, gripper_close_rad] 归一化到 RANGE_0_100(越界裁剪),默认端点取自插件 README 的示例标定(home_ticks=2048,范围 2000..3000),可用插件自带的 calibrate 子命令或 --teleop.gripper_open_rad/--teleop.gripper_close_rad 覆盖。

主臂链路没有 IK、没有离合器、没有重定向器(与从臂运动学相同),是真正的直接关节驱动。当主臂流中断(is_tracking=False)时,compute() 返回 None,从臂保持锁定位姿而不是执行可能过期的目标。插件拉起前会等待主臂流出第一帧实时数据(默认 20 秒超时,超时给出明确报错,可检查 --teleop.collection_id 是否与插件一致、CloudXR 是否已就绪)。

CloudXR 生命周期与设备配置

会话生命周期统一由 base.pyIsaacTeleopTeleoperator 管理:

  • connect():自动拉起 CloudXR 运行时(首次约 30 秒,可能弹 EULA 询问)→ 构建设备的重定向流水线 → 创建 TeleopSession。可以通过 --teleop.auto_launch_cloudxr=false 或环境变量 LEROBOT_CLOUDXR_SKIP_AUTOLAUNCH=1(环境变量优先)跳过自动拉起,用于 CloudXR 已在外部运行的场景。
  • disconnect():先结束会话(即使会话退出失败也先置空句柄,防止设备被"卡死"在已连接状态),再回收 CloudXR 运行时;不会误停外部托管的运行时。
  • 每步 _step() 带健康守卫:重定向 worker 抛异常则上抛;帧超期(frame_deadline_miss)则告警,便于发现系统负载过高导致的延迟。

设备配置(config_isaac_teleop.py)采用 draccus 的独立 _choice_registry,让 --teleop.type 只解析 Isaac 设备(xr_controller / so101_leader),避免与全局遥操作器注册表冲突。常用配置项汇总:

配置项 默认值 适用范围 说明
--teleop.app_name LeTeleop 通用 OpenXR / Isaac Teleop 会话的应用名
--teleop.auto_launch_cloudxr true 通用 是否自动拉起 CloudXR 运行时
--teleop.cloudxr_env_file 本示例 default.env 通用 CloudXR 设备配置 .env 文件
--teleop.hand_side right XR 使用哪只手的手柄(left/right
--teleop.clutch_threshold 0.5 XR 握把力度超过该值即接合离合器(按住启用)
--teleop.base_T_anchor 见源码 XR 手柄锚定坐标系→机器人基座坐标系的静态 4×4 变换
--teleop.port 主臂 主臂串口,转发给插件(空=合成轨迹)
--teleop.collection_id so101_leader 主臂 插件推送的张量集合 ID,需与插件第二位置参数一致
--teleop.gripper_open_rad / --teleop.gripper_close_rad 见源码 主臂 夹爪全开/全闭弧度端点
--reset_to_origin / --reset_duration true / 5.0s XR 启动复位摆位开关与时长
--align / --align_duration true / 3.0s 主臂 启动对齐摆位开关与时长
--launch_plugin None 主臂 要拉起的插件二进制路径(None=外部运行)

default.env 中的 CloudXR 配置default.env):

# 运行时对外宣告的传输 profile(CloudXR 默认 auto-webrtc)。
# "Quest3" 同样覆盖 Pico 4。其他取值:auto-native、AppleVisionPro。
NV_DEVICE_PROFILE=Quest3

# 输入设备发现通道(两者默认都为 true,此处显式固定)。
NV_CXR_ENABLE_PUSH_DEVICES=true
NV_CXR_ENABLE_TENSOR_DATA=true

# 运行时日志写入 ~/.cloudxr/logs——有助于排查连接问题
# (例如 "Failed to get OpenXR system: -35")。
NV_CXR_FILE_LOGGING=true

其中 XR_RUNTIME_JSONXRT_NO_STDINNV_CXR_RUNTIME_DIRNV_CXR_OUTPUT_DIR 等运行时解析键为保留项,在文件中设置会被忽略。

常见问题与排障提示

以下排查要点均能从源码行为直接推导:

  • 无头机器启动挂起:多为 CloudXR 首次启动的 EULA 询问阻塞了 stdin,先执行 python -m isaacteleop.cloudxr --accept-eula
  • 头显连不上工作站:确认头显与工作站同网段,选择脚本打印的对应网卡 IP;桥接/虚拟网卡(docker0br-*veth*virbr*l4tbr0)已被过滤不会打印。连接前需在浏览器接受自签证书(端口 48322)。
  • 主臂无数据--teleop.collection_id 必须与插件的第二位置参数一致;若 --launch_plugin 未给,需确认插件已在外部运行。20 秒内无实时帧会直接 SystemExit 报错。
  • 主臂标定警告:插件会寻找 HF_LEROBOT_CALIBRATION/teleoperators/so_leader/<teleop.id>.json,缺失时回退插件内置默认并打印警告;可用串行主臂先跑 lerobot-calibrate --teleop.type=so101_leader --teleop.id=<id> 生成。
  • 从臂异常移动/抖动:检查 --robot.use_degrees(默认 true,角度制流水线依赖此设置);XR 路径可留意日志中的"帧超期"告警(frame_deadline_miss),说明主机负载过高或 CloudXR 传输延迟偏大。
  • 机械臂在空闲时缓慢下坠:这是每帧重发测量值导致的重力蠕变,本示例已通过 HoldLatch 在空闲边缘锁定目标位姿规避;若仍出现,优先检查伺服扭矩与机械装配。

延伸阅读

热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.03 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.47 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.05 K
529