> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# 重定向会话：关键点到关节角

## 概述

本篇实践介绍如何将 MediaPipe 格式的人手关键点转换为灵巧手关节命令。应用场景包括接入摄像头或手套追踪数据、准备遥操作目标和离线验证重定向结果。

对应的 Python 示例为 [0.retarget\_session.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/retargeting/0.retarget_session.py)，C 示例为 [0\_retarget\_session.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/retargeting/0_retarget_session.c)。C 接口与 Python 一一对应。 接口清单、输入格式与平台限制见[重定向](/docs/wuji-sdk/retargeting)，本页说明集成流程和运行时行为。

## 前置条件

### 硬件

无。本示例用合成关键点，不扫描也不连接设备。

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| 架构 | 重定向仅在 Linux x86\_64 与 Linux aarch64 分发预编译 wheel |
| Wuji SDK | v2026.7.21 及更高版本。该版本起重定向接口移到包顶层，更早版本要从 `wuji_sdk.retargeting` 子模块导入，本示例的导入方式不适用 |
| Python | 3.10 及以上版本，另需 `numpy` 承载关键点与关节角数组 |
| C 工具链 | CMake 3.16 及以上版本，以及解压后的 SDK 包，需要其中的 `include/` 与 `lib/libwuji_sdk_c.so` |

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install wuji-sdk numpy
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    cmake -S examples/c/retargeting -B build/retargeting \
      -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
      -DWUJI_SDK_LIB="${SDK}/lib/libwuji_sdk_c.so"
    cmake --build build/retargeting --target 0_retarget_session
    ```
  </Tab>
</Tabs>

### 前序步骤

无。本示例是重定向链路的最小起点，跑通它再接真实设备。

## 数据流

单帧走完下面的链路。会话对象跨帧保留状态，创建一次后在循环里复用。

```mermaid theme={null}
stateDiagram-v2
    [*] --> Created: RetargetSession.for_hand()
    Created --> Tracking
    state Tracking {
        [*] --> Step
        Step --> Step: 每帧传入 21 个关键点
        Step --> Reset: 追踪中断
        Reset --> Step: session.reset()
    }
    Tracking --> Released: 追踪结束
    Released --> [*]: free() / 对象回收
```

关键点可来自摄像头、Wuji Glove、录制回放或 VR 追踪。输入按 MediaPipe landmark 顺序排列，Python 形状为 `(21, 3)`，C 使用长度为 63 的一维数组，按行主序排列 xyz。

会话按 `HandModel` 选择内置调参配置，跨帧保留 warm-start 与平滑状态。输出形状为 `(20,)`，按固件关节顺序排列，每根手指包含 4 个关节角。

关键点的单位是米，关节命令的单位是弧度。输出固定是 `float32`，形状固定，不随在线关节数变化。输入传 `float64` 也接受，内部会转换。

## 集成步骤

按创建会话、逐帧求解、结束追踪的顺序接入。

下文 `make_open_hand_keypoints` 是[Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/retargeting/0.retarget_session.py) 中的合成关键点函数，接入应用时替换为自己的关键点来源。C 片段需包含 `wuji_sdk.h`、`stdio.h` 和 `stdlib.h`。

### 1. 创建会话

选择目标手型和手性。同一段追踪复用一个会话，以保留跨帧状态。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from wuji_sdk import HandModel, Handedness, RetargetSession

    session = RetargetSession.for_hand(HandModel.WujiHand2, side=Handedness.Right)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    struct WujiRetargetSession *session = NULL;
    WujiStatus st = wuji_retarget_session_create(WUJI_HAND_MODEL_WUJI_HAND2, WUJI_HANDEDNESS_RIGHT, &session);
    if (st != WUJI_STATUS_OK) {
        fprintf(stderr, "%s\n", wuji_last_error());
        return EXIT_FAILURE;
    }
    ```
  </Tab>
</Tabs>

### 2. 逐帧求解

每帧传入 21 个 MediaPipe 关键点，坐标单位为米。Python 使用 `(21, 3)` 的 `float32` 数组，C 使用长度为 63 的 x、y、z 数组。返回 20 个关节角，单位为弧度，按固件关节顺序排列。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    keypoints = make_open_hand_keypoints()
    qpos = session.step(keypoints)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    float keypoints[63];
    make_open_hand_keypoints(keypoints);
    float qpos[20];
    st = wuji_retarget_session_step(session, keypoints, qpos);
    if (st != WUJI_STATUS_OK) {
        fprintf(stderr, "%s\n", wuji_last_error());
        wuji_retarget_session_free(session);
        return EXIT_FAILURE;
    }
    ```
  </Tab>
</Tabs>

将后续帧继续传给 `step`。此页面只计算关节角，连接设备和发送控制命令由应用另行处理。

### 3. 重置并释放

追踪中断后调用 `reset`，再开始下一段追踪。C 程序结束时释放会话。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    session.reset()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    wuji_retarget_session_reset(session);
    wuji_retarget_session_free(session);
    ```
  </Tab>
</Tabs>

## 运行与输出

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/retargeting/0.retarget_session.py
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/retargeting/0_retarget_session
    ```
  </Tab>
</Tabs>

两侧输出同样的结构：先是关节命令的形状与数据类型，然后按手指分组打印 20 个关节角。

```text theme={null}
qpos shape : (20,), dtype float32
joint command (radians, finger-major thumb/index/middle/ring/pinky):
  thumb : ['+1.291', '+0.206', '+0.345', '+0.039']
  index : ['+0.219', '+0.654', '+0.202', '+0.087']
  middle: ['+0.290', '+0.477', '+0.004', '+0.001']
  ring  : ['+0.166', '+0.274', '-0.002', '-0.001']
  pinky : ['+0.061', '+0.130', '+0.029', '+0.039']
```

C 侧不打印形状行，直接从关节命令开始：

```text theme={null}
joint command (radians, finger-major thumb/index/middle/ring/pinky):
  thumb : [+1.291, +0.206, +0.345, +0.039]
  index : [+0.219, +0.654, +0.202, +0.087]
  middle: [+0.290, +0.477, +0.004, +0.001]
  ring  : [+0.166, +0.274, -0.002, -0.001]
  pinky : [+0.061, +0.130, +0.029, +0.039]
```

以上为实际运行结果，环境是 Wuji SDK v2026.8.31、Python 3.12、C SDK x86\_64-linux-gnu、Linux x86\_64，输入是示例内置的合成张开右手姿态。

同一组输入下，两个接口的 20 个关节角**逐位相同**，两侧共用同一套求解实现。换关键点或换手型，数值随之变化，形状与分组方式不变。

## 注意事项

| 分支点 | 取值 | 影响 |
| - | - | - |
| 手型 | `HandModel.WujiHand2` 或 `HandModel.WujiHand` | 决定内部使用的调参配置。调用方不需要指定配置文件路径 |
| 手性 | `Handedness.Right` 或 `Handedness.Left` | 示例默认右手。关键点与目标手必须同侧，混用会得到镜像错误的结果 |
| 追踪中断 | 调用 `reset()` 与否 | 中断后不 reset，上一段追踪的 warm-start 与平滑状态会带进新一段，前几帧出现跳变 |
| 关键点退化 | 全零或含 NaN、Inf | `step()` 抛 `WujiException: retargeting: invalid keypoints: contains NaN or Inf`。求解前先确认关键点来源有效，不要拿未初始化的数组占位 |
| 形状不符 | 元素数不等于 63 | Python 抛 `ValueError: cannot reshape array of size N into shape (63,)`。Python 内部同样按长度 63 处理，与 C 的一维布局一致 |
| C 侧错误 | create 或 step 返回非 `WUJI_STATUS_OK` | 用 `wuji_last_error()` 取原因。已创建的会话仍要 `free`，否则泄漏 |

会话是有状态的。同一段连续追踪里复用一个会话，跨段之间 reset，不要每帧新建。

## 扩展用法

### 换关键点来源

把示例里的 `make_open_hand_keypoints()` 换成真实来源。只要产出 `(21, 3)` 的米制关键点、landmark 顺序与 MediaPipe 一致，下游无需修改。Wuji Glove 的 `hand_skeleton()` 数据流就是这个格式，接法见[重定向](/docs/wuji-sdk/retargeting/)。

### 换手型与手性

目标是 Wuji Hand 时把 `HandModel.WujiHand2` 改为 `HandModel.WujiHand`。左手装配把 `Handedness.Right` 改为 `Handedness.Left`，同时确认关键点也来自左手。

### 接入实时回路

本示例只求解一帧。实时场景要在循环里按固定周期调用 `step()`，并处理取不到新帧的情况。完整回路见[重定向](/docs/wuji-sdk/retargeting/)。

### 输入有效性

求解前确认关键点不是退化值。全零数组、含 NaN 或 Inf 的数组都会让 `step()` 抛 `invalid keypoints` 异常，追踪丢失时用上一帧有效数据顶替，比直接送零值更安全。

### 追踪断续的处理

手离开视野、设备断连、回放切段，这几种情况恢复后都调用一次 `reset()`。不 reset 的表现是恢复后前几帧姿态从旧状态平滑过来，看起来像滞后。

## 相关内容

* [重定向](/docs/wuji-sdk/retargeting/)：接口说明与参数含义
* [通用数据类型](/docs/wuji-sdk/data-reference/)：关键点与关节命令涉及的数据结构


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.