双臂 Franka 的 PICO 数据采集与 DAgger#
使用 VR / PICO 进行双 Franka 遥操作数据采集。#
本指南介绍如何在双 Franka TCP-rot6d 环境中使用 PICO 进行示教数据采集,并以 PICO 人工接管运行在线 Human-Gated DAgger。双臂硬件、实时内核和相机检查请先参考 使用双臂 Franka;PICO / XRoboToolkit 数据发布链路请先参考 Franka 真机使用 VR 遥操作设备; HG-DAgger 的单臂流程可参考 在 Franka 上使用 HG-DAgger。
概览#
用 PICO 左右手柄分别控制双臂 Franka,先采集 tcp_rot6d 格式 LeRobot 数据, 再参考 使用双臂 Franka 准备 OpenPI π₀.₅ student checkpoint,最后在真机上启动在线 HG-DAgger。
OpenPI π₀.₅
SFT · HG-DAgger
Dual-arm manipulation
2× Franka · PICO · 3 cameras
任务#
任务 |
配置 / 入口 |
说明 |
|---|---|---|
PICO stream |
|
从 PICO / XRoboToolkit 发布左右手柄和按键数据。 |
Collection |
|
用 PICO 双手遥操作采集 tcp_rot6d LeRobot 数据。 |
HG-DAgger |
|
策略自主执行,PICO 按臂接管;归档完整成功轨迹,并仅用全接管 action chunk 训练。 |
观测与动作#
字段 |
说明 |
|---|---|
Observation |
左腕、右腕、全局相机,加双臂 TCP / 夹爪状态。 |
Action |
双臂 tcp_rot6d: |
Reward |
来自 USB 键盘或脚踏( |
Prompt |
|
这个标记不来自 PICO 正面按键。采集和 DAgger 在 keyboard_device 上的按键不同:
采集(
start_end):c记成功并写入本段数据。a开始或中止录制。b递增segment_id,不是失败。DAgger(
eval_control):a开始 policy,b记失败,c记成功。
PICO 的 A/B/X/Y 只开合夹爪。按键时序写在「执行采集」和「执行 DAgger」。
安装与节点布局#
软件环境#
机器人节点#
在每台直接与 Franka 通信的机器人节点上分别执行机器人节点安装。双臂 Franka 始终通过 Franky 控制机械臂,默认的 franka 环境安装的正是这一 backend。安装脚本会下载内置 libfranka 的 Franky 预编译 wheel,目前只提供 libfranka 0.15.0 和 0.19.0(默认)两个版本,且仅支持 x86_64。请按 Franka 官方 兼容性表 选择与固件对应的 LIBFRANKA_VERSION。如果固件需要其他版本,需要针对对应的 libfranka 自行构建 Franky wheel,并通过 FRANKY_WHEEL 传入其路径或 URL;旧版 ROS backend 支持其他 libfranka 版本,但仅适用于单臂 Franka。
git clone https://github.com/RLinf/RLinf.git
cd RLinf
export LIBFRANKA_VERSION=0.19.0 # 或 0.15.0,与固件匹配
bash requirements/install.sh embodied --env franka --use-mirror
source .venv/bin/activate
franka 环境会安装 Franky、相机和输入设备依赖,其中包含 PICO consumer 侧所需的 pyzmq。PICO 头显、XRoboToolkit PC Service 和 vr_data_publisher 的安装与验证流程见 Franka 真机使用 VR 遥操作设备。
推理节点#
运行在线 DAgger actor / rollout 的 GPU 推理节点使用 OpenPI 环境:
git clone https://github.com/RLinf/RLinf.git
cd RLinf
bash requirements/install.sh embodied --model openpi --env maniskill_libero
source .venv/bin/activate
Ray 节点布局#
采集配置使用一台主机。两台机械臂使用不同的 FCI IP,可以直连到这台主机(每台机械臂一根网线接到主机的一块网卡),也可以经过交换机(两台机械臂和主机接在同一台交换机上)。两个夹爪和三路相机都在 rank 0,left_controller_node_rank、right_controller_node_rank 和 node_rank 都是 0。采集前不要执行 ray start,启动脚本会自己拉起本地 Ray。两台机械臂分到两台机器时,把 num_nodes 设为 2,两台机器放在同一个 node group,node_rank 和 left_controller_node_rank 设为 env 所在机器(通常是 0),right_controller_node_rank 设为另一台(通常是 1)。这种拆分用下面「集群设置」里的 Ray 启动步骤,不要套用后面的三节点 DAgger 表。
Rank |
角色 |
注意事项 |
|---|---|---|
|
双臂控制、env worker、三路相机、PICO consumer |
需要键盘或脚踏,以及本机可达的 PICO ZeroMQ 地址。 |
在线 DAgger 配置使用三个节点:
Rank |
角色 |
注意事项 |
|---|---|---|
|
inference / rollout / actor |
通常为 GPU 节点,运行 OpenPI。 |
|
左臂控制、env worker、三路相机、PICO consumer |
需要键盘或脚踏。 |
|
右臂控制 |
只需要右臂 Franka / Robotiq 控制链路。 |
警告
Ray 会在 ray start 时捕获 Python 解释器和环境变量。请在启动 Ray 前完成 source .venv/bin/activate、PYTHONPATH、RLINF_NODE_RANK 以及 Franka 专用环境变量的配置。设置了 keyboard_device 时,路径直接传给监听器。该字段未设置时,监听器读取 RLINF_KEYBOARD_DEVICE,并且不会改写进程环境变量。DAgger 配置没有这一项,env 所在节点仍要在 ray start 前导出键盘或脚踏设备。
集群设置#
单机采集跳过这一节。启动脚本会自己拉起本地 Ray,不要执行 ray start。下面的步骤在 DAgger 或多机采集拆到多台机器上时使用。
在这种多机拆分之前,需要先正确地搭建 Ray 集群。
警告
这一步非常关键,请谨慎操作!任何细微的配置错误,都可能导致依赖缺失或无法正确控制机器人。
RLinf 使用 ray 来管理分布式环境,这意味着:
当你在某个节点上执行 ray start 时,ray 会记录当时的 Python 解释器路径和相关环境变量;
之后在该节点上由 ray 启动的所有进程都会继承同一套 Python 环境与环境变量。
我们提供了脚本 ray_utils/realworld/setup_before_ray.sh,
用于在每个节点启动 ray 之前帮助你统一设置环境。你可以根据自己的环境修改该脚本,并在每个节点上 source 它。
该脚本主要负责以下内容:
在使用自定义环境安装方式时,source 正确的虚拟环境;
在 Franka 控制节点上,加载 Franka / Robotiq / 相机所需的运行环境;
在所有节点上设置 RLinf 相关环境变量:
export PYTHONPATH=<path_to_your_RLinf_repo>:$PYTHONPATH
export RLINF_NODE_RANK=<node_rank_of_this_node>
export RLINF_COMM_NET_DEVICES=<network_device_for_communication> # 如果只有一个网卡可以省略
其中 RLINF_NODE_RANK 应在集群的 N 个节点之间设置为 0 ~ N-1,用来在配置文件中唯一标识每个节点。DAgger 的 env 节点没有 keyboard_device,需要在 ray start 前导出键盘或脚踏:
export RLINF_KEYBOARD_DEVICE=/dev/input/by-id/usb-KEYBOARD-event-kbd
采集配置 N=1,两台机械臂都在这一台主机上:rank 0 同时运行双臂控制器、env、三路相机和 PICO consumer。不要执行 ray start。如果 ray status 还能看到上次留下的集群,先执行 ray stop --force,避免启动脚本连上那个集群。DAgger 配置 N=3:rank 0 为 OpenPI inference / actor,rank 1 为左臂 / env / PICO consumer,rank 2 为右臂。这些都是集群 rank,不是 node group 内的下标。DAgger 硬件条目写 node_rank: 1、left_controller_node_rank: 1、right_controller_node_rank: 2。node_rank 是 env 节点,必须落在 franka 这个 node group 里。只有某条臂接到了别的机器上,才改对应的 controller rank。
多机运行时,按下面的方式在各节点启动 Ray。单机采集跳过这一步:
其中 <head_node_ip_address> 为 head 节点的 IP 地址,必须 能被集群中其他节点访问。
# 在 head 节点(节点 rank 0)上
ray start --head --port=6379 --node-ip-address=<head_node_ip_address>
# 在 worker 节点(节点 rank 1 ~ N-1)上
ray start --address='<head_node_ip_address>:6379'
可以通过执行 ray status 来检查集群是否已正确启动。
配置#
主要配置文件#
配置 |
用途 |
|---|---|
|
PICO 双臂 tcp_rot6d 数据采集。 |
|
PICO 人工接管的在线 HG-DAgger。 |
|
双臂 TCP-rot6d 真机环境默认配置。 |
硬件占位符#
在采集和 DAgger 配置中替换以下字段:
LEFT_ROBOT_IP/RIGHT_ROBOT_IP:左右臂 FCI IP。BASE_CAMERA_SERIAL、LEFT_CAMERA_SERIAL、RIGHT_CAMERA_SERIAL:RealSense 用 librealsense 的 ASIC serial,不是 V4L USB serial。Lumos 腕部改用该相机的/dev/v4l/by-id路径。base_camera_type、left_camera_type、right_camera_type:采集示例三路都是realsense。DAgger 示例的底座是realsense,左右腕是lumos。图像落到错误的手臂上时,对调对应 serial。left_gripper_type/right_gripper_type:左右夹爪类型。LEFT_GRIPPER_CONNECTION/RIGHT_GRIPPER_CONNECTION:左右夹爪转接器的稳定/dev/serial/by-id路径。keyboard_device:采集配置里的 evdev 节点;留空则使用RLINF_KEYBOARD_DEVICE。按键来自这台 USB 键盘或脚踏,不来自采集终端。left_controller_node_rank/right_controller_node_rank/node_rank:采集示例三者都是0。DAgger 示例分别是集群 rank1、2、1。node_rank是 env 节点,必须落在 franka node group 内;两侧 controller rank 是接该臂的那台机器的集群 rank。TASK_DESCRIPTION:采集和 DAgger 使用的任务文本,应与 checkpoint 训练时一致。joint_reset_qpos:采集配置不写这一项,reset 使用环境默认关节位。DAgger 配置里的全零占位要换成数据集首帧关节均值或安全 home。它是每臂 7 个关节角,不是target_ee_pose。target_ee_pose:每臂[x, y, z, roll, pitch, yaw]。xyz 必须落在共享环境realworld_dual_franka_tcp_rot6d的限位内:x ∈ [0.3, 0.9],y ∈ [-0.4, 0.4],z ∈ [0.02, 0.7]。示例[0.5, ±0.2, 0.5, -3.14, 0, 0]在这个盒子里。
PICO 配置#
采集配置中 PICO consumer 地址位于 env.eval.pico.zmq_addr;DAgger 配置中位于 env.train.pico.zmq_addr。该地址必须与 publisher 绑定地址匹配。采集和 DAgger 示例都默认 ipc:///tmp/vr_data.ipc,即 publisher 和 env worker 在同一台机器。
teleop 是按臂绑定的列表,不是单个 pico。pico.hand: dual 让左右手柄分别对应左右臂。
env:
eval:
teleop:
- {pico: {drives: left}}
- {pico: {drives: right}}
keyboard_device: null # 或 /dev/input/by-id/usb-...-event-kbd
keyboard_reward_wrapper: start_end
pico:
zmq_addr: "ipc:///tmp/vr_data.ipc"
hand: "dual"
hold_current_when_inactive: True
control_trigger: "grip"
calibration:
button: "trigger"
DAgger 把同一段 teleop 列表和 pico 放在 env.train,并设置 hold_current_when_inactive: False、keyboard_reward_wrapper: eval_control。跨机器时,publisher 绑定 tcp://0.0.0.0:<port>,consumer 填写 tcp://<vr_publisher_ip>:<port>,不要把 0.0.0.0 写到 consumer 配置里。
默认手柄语义:
左手 grip -> 接管左臂
右手 grip -> 接管右臂
左手 X/Y -> 关 / 开左夹爪
右手 A/B -> 关 / 开右夹爪
trigger -> 以当前头显朝向重新标定 operator base
采集和 DAgger 的 hold_current_when_inactive 语义不同:
采集配置为
True:未按grip的手臂保持当前 TCP,适合纯遥操作数据采集。DAgger 配置为
False:未按grip的手臂保留 rollout action;按下任意一侧grip时,只用对应侧的 PICO action 覆盖 policy 动作,另一侧仍保留 rollout action。
双臂 DAgger 的接管与记录按臂合成。例如只接管左臂时,实际执行并写入 intervene_action 的 20D 动作为:
[左臂 PICO 10D action, 右臂 rollout 10D action]
只接管右臂时则相反。任意一臂发生替换都会设置 intervene_flag=True;只有两臂都未接管时才不会产生 intervention 记录。完整成功 episode 仍会由 online LeRobot collector 保存。启用 only_save_expert: True 后,sampler 使用 intervene_flag,只暴露所有非 padding 帧均为人工纠正的 action chunk。
机械臂柔顺性参数#
采集配置在 DualFranka 硬件条目里写了两条臂共用的 compliance:平移刚度 1000 N/m,旋转刚度 40 Nm/rad,平移限幅 0.02 m,旋转限幅 0.3 rad,每步平移上限 0.02 m,每步旋转上限 0.30 rad,max_delta_tau 0.2。旋转刚度、旋转限幅和每步转角与 Franky 脚本一致。max_step 0.03 m(10 Hz)在带 Robotiq 负载时容易触发 joint_velocity_violation,所以平移步长保持 0.02 m。DAgger 配置没有这段 mapping,因此使用 Franky 默认值;要和采集手感一致,把同一段抄到 DAgger 的硬件条目。默认值和 reset 请求的影响见 配置机械臂运动。
单侧 mapping 会替换该机械臂使用的共享 mapping,其中未填写的字段使用 Franky 默认值。例如:
compliance:
translational_stiffness: 900.0
left_compliance:
max_step: 0.02
这里右臂使用 900 N/m 的刚度和默认的 3 cm 目标变化上限;左臂使用默认的 1000 N/m 刚度和 2 cm 上限。不填写 left_compliance 时,左臂也使用共享 mapping;填写 left_compliance: {} 则让左臂全部使用 backend 默认值。right_compliance 遵循相同规则。这些参数也作用于 DAgger 中的 policy 目标。GELLO 关节遥操作使用关节控制,不受这些 Cartesian 参数影响。
启动 PICO 数据流#
先在 PICO publisher 机器启动 XRoboToolkit PC Service,再启动 VR 数据发布进程。示例命令如下,具体安装路径见 Franka 真机使用 VR 遥操作设备:
cd /opt/apps/roboticsservice
bash runService.sh
cd /path/to/pico_software/XRoboToolkit-Teleop-Sample-Python
source .venv/bin/activate
cd /path/to/pico_software
python -m vr_data_publisher --config configs/vr_bridge.yaml
在运行 env worker 的节点上验证 PICO 数据可达:
cd /path/to/RLinf
source .venv/bin/activate
export PYTHONPATH=$PWD:${PYTHONPATH:-}
python toolkits/realworld_check/test_pico_data.py \
--zmq-addr tcp://<vr_publisher_ip>:<port>
输出持续刷新,并且按下 grip、trigger、A/B、X/Y 时数值变化,才继续启动真机采集或 DAgger。
采集 PICO 示教数据#
执行采集#
确认左右 Franka、Robotiq、相机、keyboard_device、PICO 数据流都正常后,在这台机器上执行。采集脚本调用的是当前环境里的 python,需要先激活 Python 3.11 虚拟环境;系统 /usr/bin/python 不能运行这份脚本。
bash examples/embodiment/collect_data.sh realworld_dual_franka_collect_data_pico
按键来自 keyboard_device,不是终端,也不是 PICO A/B/X/Y:
a:开始录制。开始后 1.5 秒内再按a会被忽略;之后再按则中止并丢弃 buffer。b:录制中递增segment_id。1 秒内的重复按下会被忽略,未在录制时按下也会被忽略。c:标记成功并写入 LeRobot shard。未在录制时按下会被忽略。
PICO 操作:
戴好头显并面向工作台正前方。
扣下
trigger完成 PICO base 标定。按住左 / 右手
grip分别接管左 / 右臂。用
X/Y控制左夹爪,用A/B控制右夹爪。松开某只手的
grip后,对应手臂保持当前 TCP。
采集脚本会在 logs/<timestamp>/ 下写出:
replay-buffer 轨迹:
demo_buffer/LeRobot 数据:
lerobot_dataset/rank_0/id_0/,后续 shard 为id_1、id_2
PICO 双臂采集已经使用 realworld_dual_franka_tcp_rot6d 环境,数据动作就是 tcp_rot6d;因此不需要执行 GELLO 流程中的 backfill_tcp_rot6d.py。无图形界面的 OpenCV 打不开预览,采集示例因此把 enable_camera_player 设为 False。
备注
配置中 data_collection.resume: True 只会在相同 save_dir 下续写。
collect_data.sh 默认每次创建新的 logs/<timestamp>,若要多次追加到同一数据集,
请把 data_collection.save_dir 改为固定路径。
准备 checkpoint#
在线 DAgger 需要一个可部署的 OpenPI checkpoint。数据整理、norm stats、SFT 和 checkpoint 目录准备请直接参考 使用双臂 Franka 中的 SFT 与部署 checkpoint 流程。
使用本页 PICO 采集的数据时,数据已经来自 realworld_dual_franka_tcp_rot6d 环境,
不需要再执行 GELLO 关节数据流程中的 backfill_tcp_rot6d.py。
完成 checkpoint 准备后,在
examples/embodiment/config/realworld_dual_franka_dagger_openpi.yaml 中设置:
rollout:
model:
model_path: /path/to/deploy/global_step_<N>
actor:
model:
openpi_data:
repo_id: <repo_id>/tcp_rot6d_v1
运行在线 HG-DAgger#
检查 DAgger 关键配置#
启动前确认以下字段:
algorithm:
dagger:
only_save_expert: True
online_lerobot:
enabled: True
only_success: True
robot_type: "dual_FR3"
fps: 10
finalize_interval: 1
data_path: ${runner.logger.log_path}/online_lerobot
rolling_lerobot_window_size: 50000
min_frames: 1
lerobot_num_workers: 0
env:
train:
smooth_intervene: True
teleop:
- {pico: {drives: left}}
- {pico: {drives: right}}
keyboard_reward_wrapper: eval_control
pico:
zmq_addr: "ipc:///tmp/vr_data.ipc"
hand: "dual"
hold_current_when_inactive: False
eval:
teleop: none
online_lerobot.enabled: True 表示启用在线 LeRobot 数据链路。env worker 按 episode 收集 rollout,并将满足过滤条件的 episode 发送给 actor;actor 将其加入 RollingLeRobotDataset 进行训练,因此在线训练不再使用 trajectory replay buffer。
smooth_intervene: True 用于消除 PICO 接管时 action chunk 边界的停顿。如果一个 chunk 的最后一帧仍由人工接管,env worker 会跳过下一次策略推理,改用形状兼容的 dummy chunk 继续执行;接管侧仍使用 PICO 动作,暂时未接管的帧保持机械臂当前 TCP 位姿。最后一帧不再接管或 episode 结束后恢复模型推理。该模式只接受 PICO:env.train.teleop 的每一项都必须是 pico,且当前要求每个 env worker pipeline stage 只运行一个环境。
only_success: True 表示失败 rollout 会被丢弃,只保存成功 episode;
only_save_expert: True 仍会归档完整的成功 episode,但训练只采样 action chunk
内所有非 padding 帧均满足 intervene_flag=True 的起点。双臂任意一臂被替换时,
该帧就会标记为接管,因此这样的 chunk 可能由一侧 PICO action 与另一侧 rollout
action 共同组成。每个成功 episode 会立即归档到
${runner.logger.log_path}/online_lerobot/rank_0/id_<N>/。
env.eval.teleop: none 表示评测阶段只看策略本身,不混入人工接管。
真机 DAgger 配置不包含 beta 相关字段,因为没有配置 rollout.expert_model。Beta 只用于模型 expert 和 student 之间的动作混合;这里的人工接管由 PICO intervention wrapper 决定。
执行 DAgger#
确认 DAgger Ray 集群已经启动后,在 head 节点启动在线训练:
bash examples/embodiment/run_realworld_async.sh realworld_dual_franka_dagger_openpi
运行中的操作方式:
a:从 idle 状态启动一次策略 rollout。左 / 右手
grip:接管对应机械臂;未接管的手臂继续执行策略动作。b:标记失败并结束当前 rollout。c:标记成功并结束当前 rollout。
每次 episode 结束后,env 会 reset 并再次等待 a。策略执行中只在需要纠正时按住
grip,松开后让策略继续运行。按住任意一侧 grip 时,该侧 PICO action 与
另一侧 rollout action 会合成为完整 20D info["intervene_action"]。成功结束后,
整条 episode 经内存发送给 actor,并写入 online LeRobot shard;失败 episode 被丢弃。
actor 保留完整物理归档,但只向训练暴露全程带接管标记的 action chunk。
监控#
启动 TensorBoard:
tensorboard --logdir ./logs
推荐关注:
train/dagger/actor_loss:基于 expert-only action chunk 的监督损失。train/lerobot_dataset/total_episodes:actor 已接收的成功 episode 数量。train/lerobot_dataset/physical_frames:已接收的 LeRobot 物理帧数量。train/lerobot_dataset/logical_samples:rolling window 内符合专家条件、可训练的 chunk 起点数。train/lerobot_dataset/num_sub_datasets:当前加载的 LeRobot shard 数量。train/actor/lr和train/actor/grad_norm:训练稳定性。
采集和在线 DAgger 阶段都可以查看 logs/<timestamp>/run_embodiment.log,确认成功
episode 计数和 LeRobot 写出路径。在线 DAgger shard 位于
logs/<timestamp>-realworld_dual_franka_dagger_openpi/online_lerobot/rank_0/。
故障排查#
- DAgger 等待时间过长未启动
这是
keyboard_reward_wrapper: eval_control的预期行为。若 DAgger 运行后长时间 等待未开始,在 USB 键盘或脚踏上按a启动 rollout。