> ## 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.

# 触觉标定

## 概述

本篇实践介绍如何采集引导动作并训练和安装触觉接触模型。应用场景包括建立手套触觉基线和为接触判定及残差信号处理准备模型。

对应的 Python 示例为 [7.tactile\_calibration.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/7.tactile_calibration.py)，C 示例为 [5\_tactile\_calibration.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/5_tactile_calibration.c)。 标定的适用场景、产物存储与版本要求见[标定与配置](/docs/wuji-glove/calibration)，本页说明集成流程和运行时行为。

<Warning>
  流程可以完成，但采集阶段使用的是**无人佩戴的静置手套**，生成的模型不可用，`dead_pct` 为 99.3%。正式标定时，请佩戴手套完成四组动作。
</Warning>

## 前置条件

### 硬件

| 项目 | 要求 |
| - | - |
| Wuji Glove | 1 只 |
| 连接方式 | 以太网 |
| 佩戴 | **必须有人戴着手套做动作**，原因见下文 |

标定只读传感器、只写 SDK 侧模型文件，不写固件。

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| Wuji SDK | v2026.8.31 或更高版本，需带 `model-export` 特性的构建 |
| Python | 3.10 及以上版本 |
| C 工具链 | CMake 3.16 及以上版本，以及解压后的 SDK 包 |

训练那一步需要 SDK 构建时带上 `model-export` 特性。缺了这个特性的构建会在训练阶段失败。

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

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

### 前序步骤

无硬性前置。触觉标定与手部模型标定互相独立，两条链路各标各的。

## 数据流

```mermaid theme={null}
stateDiagram-v2
    [*] --> Connect
    Connect --> Ready: connect(enable_bridge=False)
    Ready --> Collect: on_pose_prompt = pose_ready
    Collect --> Review: 采集 seconds_per_pose
    Review --> Ready: retry
    Review --> Next: proceed
    Next --> Ready: 还有动作
    Next --> Check: 四组动作完成
    Check --> Train: 检查采集数据
    Train --> Done: 训练完成
    Done --> Install: install=True
    Done --> Output: install=False
    Install --> Output: 模型安装到运行时目录
    Output --> [*]
    Review --> [*]: abort
```

## 四组引导动作

`step_total` 实测为 4，顺序固定：

| 步骤 | `step_name` | 操作 |
| - | - | - |
| 0 | `four_finger_L_thumb_in` | 四指弯成 L 形，拇指收进掌心，**变换按压力度** |
| 1 | `claw_curl` | 五指弯成爪形，反复屈伸 |
| 2 | `abduction` | 手指反复张开与合拢 |
| 3 | `finger_to_palm` | 依次用每个指尖按压掌心 |

四组动作合起来要覆盖整个触觉阵列——哪个区域没被按到，训练器就认为那片 taxel 是死的。

## prompt 协议实测

`on_pose_prompt` 是这套流程的交互枢纽，实测每组动作触发**两次**，一轮标定共 8 次。

### 两种 kind

| `kind` | 时机 | `payload` 字段 |
| - | - | - |
| `pose_ready` | 这组动作开始**前** | `step_index`、`step_total`、`step_name`、`seconds_per_pose` |
| `pose_review` | 这组动作采完**后** | `step_index`、`step_total`、`step_name`、`paired_frames`、`warnings` |

`pose_ready` 用于提示用户准备，`pose_review` 给出这一组的采集质量，让用户决定留还是重录。

### 返回值只能是这四个

```text theme={null}
RuntimeError: prompt callback must return 'proceed', 'retry', 'abort', or None
```

| 返回 | 行为 |
| - | - |
| `'proceed'` 或 `None` | 继续 |
| `'retry'` | 重录这一组 |
| `'abort'` | 中止整个标定 |

返回别的值直接抛 `RuntimeError`，不是警告。这是接入时第一个会撞上的坑。

## 状态机实测

一轮完整流程捕获到 11 次状态转移，`seconds_per_pose=3.0`、`epochs=5`：

```text theme={null}
[   0.0s] state='collect'  step=0/4 four_finger_L_thumb_in
[   3.0s] state='pose_ok'  step=0/4 four_finger_L_thumb_in
[   3.0s] state='collect'  step=1/4 claw_curl
[   6.1s] state='pose_ok'  step=1/4 claw_curl
[   6.1s] state='collect'  step=2/4 abduction
[   9.1s] state='pose_ok'  step=2/4 abduction
[   9.1s] state='collect'  step=3/4 finger_to_palm
[  12.2s] state='pose_ok'  step=3/4 finger_to_palm
[  12.2s] state='check'
[  12.2s] state='train'
[  14.0s] state='done'
```

| `state` | 携带字段 | 含义 |
| - | - | - |
| `collect` | `collect_target`、`collect_elapsed`、`frames_collected` | 正在录这一组 |
| `pose_ok` | `frames_collected` | 这一组录完 |
| `check` | 无额外字段 | 采集完成后的整体校验 |
| `train` | `epoch`、`epoch_total`、`best_val` | 训练中 |
| `done` | 无额外字段 | 全部完成 |

`check` 与 `train` 不带 `step_index`，这两个阶段没有「第几步」的概念。写进度条时要处理这种情况，别无条件读 `step_index`。

采集耗时与参数严格对应：四组各 3.0 秒，实测每组 394 帧。131 Hz × 3 s = 393，完全吻合。

## 无人佩戴的后果：dead\_pct 99.3%

把静置在桌上、无人佩戴的手套跑完整轮标定，训练出的模型 summary 是：

```json theme={null}
{
  "alive_taxels": 5,
  "dead_pct": 99.32796,
  "best_val_loss": -1.5158646,
  "epochs": 5,
  "frames_per_pose": {
    "four_finger_L_thumb_in": 394,
    "claw_curl": 394,
    "abduction": 394,
    "finger_to_palm": 394
  },
  "installed": false,
  "poses_collected": 4,
  "verified_alive_taxels": null
}
```

**744 个 taxel 里只有 5 个被判为存活，99.3% 被判死。** 流程一路绿灯跑到 `done`，没有任何报错，产物也正常落盘——但这个模型完全不能用。

原因是：训练器靠「这个 taxel 在动作过程中是否出现过有意义的信号变化」来判断它是不是活的。手套静置，全部通道纹丝不动，自然全被判死。

这条实测给出的操作准则是：**`dead_pct` 是标定质量的第一判据。** 拿到 summary 先看它，数值高说明覆盖不够——要么没戴手套，要么动作幅度太小、按压力度不够、没覆盖到全部区域。

`alive_taxels` 的正常量级参考原始阵列：744 个通道里约 526 个有效，其余 218 个本来就是硬件屏蔽的无效通道。健康的标定结果里 `alive_taxels` 应该接近 526 这个量级。

## 产物布局实测

标定数据按用户与序列号分目录：

```text theme={null}
~/.wuji/sdk/users/<user_id>/tactile/<序列号>/
├── calibration-data/
│   └── run_<timestamp>/
│       ├── step1_four_finger_L_thumb_in.bag
│       ├── step2_claw_curl.bag
│       ├── step3_abduction.bag
│       ├── step4_finger_to_palm.bag
│       └── trained_model/
│           ├── contact.safetensors
│           ├── contact.npz
│           └── contact.json
└── model/                      ← install=True 时模型装到这里
```

默认 SDK 用户走的是共享根目录 `~/.wuji/sdk/tactile/<序列号>/`，具名用户各自存在自己的 `users/<user_id>/` 下。

`contact.json` 是清单文件，带契约与校验信息：

```json theme={null}
{
  "format_version": 1,
  "contract": { "tactile_pixels": 744, "state_dim": 40 },
  "entries": {
    "contact.npz": { "sha256": "...", "bytes": 8764 },
    "contact.safetensors": { "sha256": "...", "bytes": 1835448 }
  },
  "provenance": { "device_sn": "WG1KA00XXXXXXXXX", ... }
}
```

`tactile_pixels: 744` 与原始 `tactile` 流的通道数一致，模型是按完整阵列训练的。

**`install=False` 时模型只留在 run 目录，运行时目录不会被创建。** 实测 summary 的 `installed` 为 `false`，`model/` 目录不存在。想先评估再决定要不要用，就传这个。

一次标定的原始数据实测约 6.6 MB（四个 bag 加模型）。每跑一次就多一个 `run_<timestamp>` 目录，旧的不会自动清理。

## 参数实测

完整签名：

```text theme={null}
calibrate_tactile_blocking(*, seconds_per_pose=30.0, epochs=60, install=True,
                           sensitivity=None, timeout_s=1800.0,
                           on_feedback=None, on_pose_prompt=None)
```

| 参数 | 默认 | 说明 |
| - | - | - |
| `seconds_per_pose` | 30.0 | 每组动作录多久。实测 3.0 秒得 394 帧 |
| `epochs` | 60 | 训练轮数。实测 5 轮约 1.8 秒 |
| `install` | True | 是否装到运行时目录 |
| `sensitivity` | None | 接触灵敏度，范围 \[0.5, 3.0]，大于 1 更灵敏 |
| `timeout_s` | 1800.0 | 整体超时 |
| `on_feedback` | None | 状态回调 |
| `on_pose_prompt` | None | 交互回调 |

默认参数一轮是 4 × 30 秒采集加 60 轮训练。调小只适合验证流程，正式标定用默认值——采集时长直接决定覆盖度。

## 标定前后的差别

实测未标定时三条流的状态，同一时刻采样：

| 流 | 长度 | 无效通道 | 非零正值 |
| - | - | - | - |
| `tactile` | 744 | 218 | 305 |
| `tactile_binary` | 744 | 218 | **0** |
| `tactile_residual` | 744 | 218 | **0** |

原始流照常有数据，两条派生流**精确恒为 0**，不是「接近 0」。所以判断一只手套是否完成标定，直接看这两条流是不是全零就行，不用去翻目录。

## 集成步骤

完整示例：[Python 触觉标定](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/7.tactile_calibration.py) 和 [C 触觉标定](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/5_tactile_calibration.c)。

以下为接入片段。Python 导入与 manager 初始化、C 初始化与连接错误分支、回调定义均沿用 [Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/7.tactile_calibration.py) 和 [C 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/5_tactile_calibration.c)。C 每次调用应检查返回状态，失败时按 [Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/7.tactile_calibration.py) 和 [C 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/5_tactile_calibration.c) 释放已有资源并退出。

### 1. 连接目标手套

使用目标手套序列号连接。[Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/7.tactile_calibration.py)显式关闭桥接，[C 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/5_tactile_calibration.c)使用默认连接选项。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    options = ConnectOptions(enable_bridge=False)
    glove = manager.connect(sn=serial_number, device_name="my_glove", options=options)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiConnectTarget target = { .kind = WUJI_CONNECT_TARGET_KIND_SN, .value = serial_number };
    struct WujiDevice *dev = NULL;
    wuji_connect(&target, "glove", NULL, &dev);
    ```
  </Tab>
</Tabs>

### 2. 运行引导标定

使用默认每组 30 秒和正式训练轮数。提示回调负责等待动作确认，反馈回调显示采集和训练状态。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    options = dict(seconds_per_pose=30.0, epochs=60, install=True,
                   on_feedback=on_feedback, on_pose_prompt=on_pose_prompt)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiTactileCalibrationOptions options = {
        .seconds_per_pose = 30.0, .epochs = 60, .install = true,
        .timeout_s = 1800.0,
    };
    WujiTactileCalibrationCallbacks callbacks = {
        .on_feedback = on_feedback, .on_pose_prompt = on_pose_prompt,
    };
    WujiTactileCalibrationSummary summary = {0};
    WujiStatus st = wuji_glove_calibrate_tactile_blocking(dev, &options, &callbacks, &summary);
    /* st 非 OK 时报告错误并清理设备，不读取 summary */
    ```
  </Tab>
</Tabs>

### 3. 检查质量并收尾

将上一步调用包在 `try/finally` 中，读取 `dead_pct` 和安装状态后断开连接。C 调用成功后释放 summary，再断开设备。提示回调须处理 `pose_ready` 和 `pose_review`，只返回 `proceed`、`retry`、`abort` 或 `None`，

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    try:
        result = glove.calibrate_tactile_blocking(**options)
        print(result["dead_pct"], result["installed"])
    finally:
        manager.disconnect_all()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    printf("dead_pct=%.2f installed=%s\n", summary.dead_pct,
           summary.installed ? "true" : "false");
    wuji_tactile_calibration_summary_free(&summary);
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    wuji_shutdown();
    ```
  </Tab>
</Tabs>

## 运行与输出

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_glove/7.tactile_calibration.py --sn <serial number>
    # 只训练不安装，先评估质量
    python examples/python/wuji_glove/7.tactile_calibration.py --sn <serial number> --no-install
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/glove/5_tactile_calibration
    ```
  </Tab>
</Tabs>

示例开场会打印本轮参数：

```text theme={null}
========================================================================
Wuji Glove tactile calibration
========================================================================
  serial number:     <serial number>
  seconds per pose:  10.0
  epochs:            60
  install model:     True

You will be asked to confirm before each motion and then keep, redo, or abort after it.
```

实测一轮完整流程（`seconds_per_pose=3.0`、`epochs=5`、`install=False`、无人佩戴）的结果：

```json theme={null}
{
  "alive_taxels": 5,
  "dead_pct": 99.32796,
  "best_val_loss": -1.5158646,
  "epochs": 5,
  "frames_per_pose": {
    "abduction": 394,
    "claw_curl": 394,
    "finger_to_palm": 394,
    "four_finger_L_thumb_in": 394
  },
  "installed": false,
  "poses_collected": 4,
  "verified_alive_taxels": null
}
```

以上为实机运行结果，设备是右手 Wuji Glove，当前为具名 SDK 用户。手套静置无人佩戴，所以 `dead_pct` 冲到 99.3%。本次验证的产物已全部删除，未安装到运行时目录。

## 注意事项

| 分支点 | 表现 | 处理 |
| - | - | - |
| prompt 回调返回非法值 | `RuntimeError: prompt callback must return 'proceed', 'retry', 'abort', or None` | 只返回这四个之一 |
| 无人佩戴或动作不到位 | 流程正常完成，但 `dead_pct` 接近 100% | 看 `dead_pct` 判断质量，重标 |
| 某组动作录得不好 | `pose_review` 的 `warnings` 给出提示 | 回调返回 `'retry'` 重录这一组 |
| SDK 构建不带 `model-export` | 训练阶段失败 | 换带该特性的构建 |
| 超过 `timeout_s` | 超时中止 | 默认 1800 秒，四组 30 秒够用 |
| `install=False` | 模型留在 run 目录，`installed: false` | 评估后手动决定 |
| 标定前 | `tactile_binary` 与 `tactile_residual` 精确恒为 0 | 见下一节 |

## 扩展用法

### 先检查 dead\_pct

标定跑完不报错不代表成功。先看 `dead_pct`，再看 `alive_taxels` 是不是接近 526 这个量级。数值不对就重标，确认质量后再安装。

### 用 pose\_review 的 warnings 决定重录

每组动作采完都会给一次 `pose_review`，里面的 `warnings` 是针对这一组的。发现问题当场返回 `'retry'` 重录，比整轮跑完再返工省事。

### 别为了快而调小 seconds\_per\_pose

采集时长直接决定阵列覆盖度。默认 30 秒是有道理的，调到 3 秒只适合验证代码能不能跑通。

### 定期清理 run 目录

每次标定新建一个 `run_<timestamp>` 目录，约 6.6 MB 起，不会自动清理。多标几次要手动收拾。

### 标定完就去看网格

装好模型后跑接触网格视图，用手碰一碰确认响应正常，比看数字直观。见[触觉接触网格视图](/docs/wuji-glove/sdk-practice/tactile-contact-view)。

## 相关内容

* [触觉接触网格视图](/docs/wuji-glove/sdk-practice/tactile-contact-view)：标定后用它验证效果
* [触觉残差网格视图](/docs/wuji-glove/sdk-practice/tactile-residual-view)：看接触判定背后的连续信号
* [数据订阅：回调](/docs/wuji-glove/sdk-practice/subscribe-callback)：744 / 526 / 218 这几个通道数的来历
* [手部模型标定](/docs/wuji-glove/sdk-practice/hand-model-calibration)：另一条标定链路，产出手部模型
* [SDK 本地用户管理](/docs/wuji-sdk/sdk-practice/user-management/)：标定产物按 SDK 用户进行管理


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