概述
本篇实践介绍如何使用离线管线将电磁位姿和原始 IMU 数据转换为手部追踪结果。应用场景包括重新处理录制数据、在无设备环境中调试和比较不同手部模型的求解结果。 对应的 Python 示例为 3.offline_pipeline.py。 接口与参数说明见原始数据转换,本页说明集成流程和运行时行为。C SDK 没有对应的离线管线接口,本页介绍 Python 用法。
前置条件
硬件
无。这是本系列唯一一篇不需要接设备的文章。软件
前序步骤
想重放真实数据先看数据录制。只跑示例里的合成帧则无需任何准备。数据流
管线提供的通道
实测offline_pipeline() 返回对象暴露这些方法:
另有三个只读属性:
sn、hand_side、urdf_source 与 urdf_source_path。
手部求解依赖 URDF,IMU 融合不依赖。 换手模只影响前三个方法的输出。
URDF 来源
构造时传的sn 决定 SDK 去哪里找手部模型。实测未标定的序列号:
urdf_source 有两种取值。builtin_default 表示用的是内置通用手模,path 为 None。标定过的设备会指向 ~/.wuji/sdk/models/<序列号>_hand.urdf。
想强制指定某份手模,传 urdf_path:
hand_side 只接受 "left" 和 "right",其他值抛 ValueError: hand_side must be 'left' or 'right'。
与在线结果的一致性实测
方法
- 连真实手套,用
TopicRecorder同时录emf_poses、hand_joint_angles、hand_skeleton三条流,4 秒 - 从 MCAP 里按
header.seq把三条流对齐,得到 525 个共同帧 - 把录到的
emf_poses重建成EmfPoseArray,喂给离线管线 - 拿离线算的结果与录到的在线结果逐元素相减
结果
对比前 200 帧:
关节角完全相同。骨骼位置的 7 纳米差异来自 MCAP 的 JSON 序列化把 float 往返了一遍,不是算法差异。
结论是离线重放能无损复现在线求解。拿录好的数据调算法、对比不同手模、复现线上问题,结果都可信。
重放代码
Quaternion 的构造顺序是 (x, y, z, w)。MCAP 里存的方向如果是对象形式,按字段名取,别按顺序解包。
IMU 融合实测
融合器有状态,逐帧推进。示例只喂了一帧,看不出行为,实测连续喂帧的演化。静止
持续喂angular_velocity=(0,0,0)、linear_acceleration=(0,0,9.8),共 399 帧:
姿态锁在单位四元数,没有漂移。重力方向与理想 Z 轴一致时融合器不产生修正量。
转动
改喂angular_velocity=(0, 0, 1.0) rad/s,每帧时间戳递增 5 ms,共 200 帧(1.0 秒):
1 rad/s 转 1 秒理论上是 1 rad = 57.30°,实测 57.0°。差的那 0.3° 是因为首帧没有前一帧可以算时间差,不参与积分,实际只积分了 199 帧:199/200 × 57.30° = 57.01°,与实测吻合。
这决定了两件事:时间戳必须真实递增,融合器完全靠它算积分步长。以及第一帧的输出永远是初始姿态,不要拿它做判断。
集成步骤
完整示例见 Python 示例。以下片段按接入顺序展示关键操作,参数解析、信号处理和完整错误分支见示例。1. 创建离线管线
序列号使用采集数据的手套序列号,手别与采集设备一致。整段回放复用同一个管线,无需连接设备。2. 准备电磁位姿帧
Python 示例 用make_emf_frame() 构造包含 5 指的合成帧。接入录制数据时,将同一时刻的位姿、置信度和帧头还原为 EmfPoseArray,保留采集时间顺序。
3. 逐帧计算所需输出
将一帧传入相应通道。关节角、指尖位姿和骨架可按应用需要选择,无需全部计算。4. 按需计算 IMU 融合结果
IMU 融合使用独立输入。Python 示例 的make_raw_imu() 构造原始 IMU 帧,真实回放按采集顺序持续传入。管线无需显式关闭,对象回收即可。
运行与输出
urdf_source 是 builtin_default。
三帧的 curl 参数从 0.00 递增到 0.70,模拟手指逐渐弯曲。拇指第 5 个角度随之从 0.629 增到 1.087,方向正确。thumb_conf 恒为 0.475,合成数据的置信度不随弯曲程度变化。
注意事项
扩展用法
对比两套手模
同一批数据跑两条管线,一条用默认手模、一条用标定手模,逐帧比较关节角,就能量化标定带来的差异:批量重放不要每帧新建管线
hand_joint_angles() 每次调用返回一个通道对象。循环里反复调没有问题,但 IMU 融合的状态挂在管线上,换管线等于重置融合器。整批数据用同一个 pipeline 实例跑完。