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

# 触觉残差网格视图

## 概述

本篇实践介绍如何将 `tactile_residual` 连续残差信号渲染为终端网格并调整显示阈值。应用场景包括观察信号强度变化、比较区域响应和选择应用侧接触阈值。

对应的 Python 示例为 [9.tactile\_residual\_view.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/9.tactile_residual_view.py)，C 示例为 [7\_tactile\_residual\_view.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/7_tactile_residual_view.c)。 触觉数据的字段定义见[触觉](/docs/wuji-glove/sdk-data-reference/tactile)，本页说明集成流程和运行时行为。

<Warning>
  本文的网格渲染、字符梯度规则、阈值行为、未标定时的表现均为实测结果。**有接触时的画面未实测**——手套需要有人戴着并真实触碰才会产生残差。
</Warning>

## 前置条件

### 硬件

| 项目 | 要求 |
| - | - |
| Wuji Glove | 1 只，**已完成触觉标定** |
| 连接方式 | 以太网 |
| 终端 | 至少 31 列宽、26 行高 |

### 软件

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

<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 7_tactile_residual_view
    ```
  </Tab>
</Tabs>

### 前序步骤

**先做触觉标定。** 残差是相对标定基线算的，没有基线就没有残差——实测未标定时这条流精确恒为 0。见[触觉标定](/docs/wuji-glove/sdk-practice/tactile-calibration)。

## 残差与二值的关系

```mermaid theme={null}
flowchart LR
    RAW["tactile 原始 z-score"] --> MODEL["已安装的接触模型"]
    MODEL --> RES["tactile_residual<br/>连续有符号残差"]
    RES --> BIN["tactile_binary<br/>SDK 按 sensitivity 判定"]
    RES --> APP["应用侧阈值与网格渲染"]
    BIN --> VIEW["二值视图"]
    APP --> VIEW2["残差视图"]
```

两条流出自同一个模型，`tactile_binary` 是 `tactile_residual` 过了一道阈值。需要现成判定就用前者，需要自己控制判据就用后者。

## 网格与二值视图完全一致

`tactile_residual` 长度同样是 744，无效通道位置同样是那 218 个，映射同样是行优先 `index = row * 31 + col`。24 × 31 = 744，网格就是传感器阵列的物理排布。

所以两个视图的手形轮廓一模一样，切换着看不会错位。通道数的来历见[数据订阅：回调](/docs/wuji-glove/sdk-practice/subscribe-callback)。

## 六级字符梯度

这是与二值视图最大的显示差异：

| 字符 | 判据 | 含义 |
| - | - | - |
| 空格 | `abs(v - (-1.0)) < 1e-6` | 无效或被屏蔽的 taxel |
| `#` | `threshold > 0` 且 `v >= threshold` | 达到或超过应用侧设定的阈值 |
| `.` | `v <= 0` 或 `threshold <= 0` | 无接触，或阈值为零时的兜底 |
| `. : - = + *` | `0 < v < threshold` | 阈值以下的六级强度，越靠后越强 |

梯度的映射是按比例切的：

```python theme={null}
RAMP = ".:-=+*"

idx = int(value / threshold * len(RAMP))
return RAMP[min(idx, len(RAMP) - 1)]
```

值落在阈值的 0 到 1/6 显示 `.`，1/6 到 2/6 显示 `:`，以此类推，到 `*` 就快够着阈值了。**梯度是相对阈值的**，调阈值会让整个梯度重新分布，不是绝对刻度。

无效通道的判据用的是浮点近似比较而不是 `== -1.0`，避开相等比较的坑：

```python theme={null}
MASKED_VALUE = -1.0
MASKED_EPS = 1e-6

if abs(value - MASKED_VALUE) < MASKED_EPS:
    return " "
```

## 阈值行为实测

| 传入 | 状态行显示 | 说明 |
| - | - | - |
| `0.5` | `0.5` | 默认值 |
| `0.0` | `0.0` | 合法，此时全部有效通道显示 `.` |
| `-1.0` | `0.0` | 钳到下限 |

钳制只有下限：

```python theme={null}
def clamp_threshold(value: float) -> float:
    return max(0.0, float(value))
```

**没有上限。** 这与二值视图的灵敏度不同——那个在示例层被钳在 `[0.5, 3.0]`。残差阈值由应用侧设定，示例不设定边界。

`threshold = 0` 是个特例：`cell_for` 里 `value <= 0.0 or threshold <= 0.0` 这一条会让所有有效通道都渲染成 `.`，`#` 永不出现。想「显示一切」不能靠把阈值设成 0，要设一个很小的正数。

交互式终端下 `+` / `-` 每次调 0.1（`THRESHOLD_STEP = 0.1`）。

## 状态行

```text theme={null}
above=0   threshold=0.5 peak=+0.000 row=0 col=14 seq=657713 len=744  (+/- threshold, q quit)
```

| 字段 | 含义 |
| - | - |
| `above` | 达到或超过阈值的 taxel 数 |
| `threshold` | 当前阈值 |
| `peak` | 本帧最大残差，**带符号** |
| `row` / `col` | 峰值所在的网格坐标 |
| `seq` | 帧序号，二值视图没有这个字段 |
| `len` | 帧长度，正常是 744 |

`peak` 带符号是关键——负峰值说明整片区域比标定基线还轻，可能是手套没戴紧或者标定时手的姿态与现在差太多。

`seq` 可以用来确认流没有停：数字不动就是没收到新帧。

## 渲染限流

与二值视图一样限到 10 Hz：

```python theme={null}
RENDER_INTERVAL_S = 0.1
```

流是 130 Hz，回调在 SDK 工作线程上按帧触发。不限流会把这条线程耗在终端输出上。

## 集成步骤

完整示例：[Python tactile\_residual 视图](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/9.tactile_residual_view.py) 和 [C tactile\_residual 视图](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/7_tactile_residual_view.c)。

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

### 1. 连接目标手套

按序列号连接目标手套。多设备环境不要依赖扫描结果顺序。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    glove = manager.connect(sn=serial_number, device_name="my_glove")
    ```
  </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_0", NULL, &dev);
    ```
  </Tab>
</Tabs>

### 2. 订阅残差流

残差流不需要下发设备参数。回调中读取有符号触觉单元数组，自行应用阈值并限流渲染。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sub = glove.tactile_residual().subscribe_with_callback(on_residual)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    struct WujiSub *sub = NULL;
    wuji_glove_subscribe_tactile_residual(dev, on_residual, NULL, &sub);
    ```
  </Tab>
</Tabs>

### 3. 限时运行并收尾

视图退出时先关闭订阅，再断开设备。回调线程不直接执行阻塞渲染。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    try:
        time.sleep(seconds)
    finally:
        sub.close()
        manager.disconnect_all()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    sleep(seconds);
    wuji_sub_close(sub);
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    wuji_shutdown();
    ```
  </Tab>
</Tabs>

## 运行与输出

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_glove/9.tactile_residual_view.py --sn <serial number>
    python examples/python/wuji_glove/9.tactile_residual_view.py --sn <serial number> \
      --seconds 60 --threshold 0.8
    ```
  </Tab>

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

实测输出（未标定、无人佩戴）：

```text theme={null}
above=0   threshold=0.5 peak=+0.000 row=0 col=14 seq=557357 len=744  (+/- threshold, q quit)
              ....
      ....   ......   ...
     ......  ......  .....
     ......  ......  .....
     ......  ......  .....
 ... ......  ......  .....
...........  ......  .....
...........  ......  .....
...........  ......  .....
...........  ......  ..... ...
...................  ..........
...............................
...............................
.... .................... .....
     .................... .....
.    .................... .....
.    ..........................
.    ..................... ...
.    .....................
.    .....................
.    .....................
.    ...................
.    ..................
.    ..................
```

以上为实机运行结果，设备是右手 Wuji Glove，手套静置无人佩戴且未做触觉标定。

网格轮廓与二值视图完全相同，因为无效通道位置一致。`peak=+0.000` 与 `above=0` 是未标定的典型表现。`seq` 在递增，说明流本身是通的——**流有数据不等于流有内容**，这正是 `seq` 字段的用处。

标定并戴上手套之后，接触区域会按强度显示 `. : - = + *` 的梯度，达到阈值的位置变成 `#`，`peak` 给出当前最强残差。

## 注意事项

| 分支点 | 表现 | 处理 |
| - | - | - |
| 未做触觉标定 | 全网格 `.`，`peak=+0.000` 恒定 | 先跑触觉标定 |
| 手套未佩戴 | 同上 | 戴上手套触碰 |
| `threshold` 传负数 | 钳到 0.0 | 用正数 |
| `threshold` 设成 0 | `#` 永不出现，全是 `.` | 想显示一切就用很小的正数 |
| `peak` 持续为负 | 整体比基线轻 | 检查佩戴松紧，或重新标定 |
| 非交互终端 | 键盘控制不可用 | 用 `--threshold` 预设 |
| `--seconds 0` | 跑到按 `q` 或 `Ctrl-C` | 默认就是 0 |

## 扩展用法

### 用 peak 找阈值

这是本示例最实用的用法：保持应用场景中的最小接触，观察 `peak` 的稳定值，并将阈值设为略低于该值。比拍脑袋猜或者反复调灵敏度靠谱得多。

### 梯度用来看接触边缘

`#` 只标记超过阈值的位置，梯度用于显示接触向边缘衰减的过程。抓握姿态分析、接触面积估计等任务，梯度信息比二值有用。

### 负残差是个信号

`peak` 长期为负说明整只手套都比标定基线轻。常见原因是手套戴得比标定时松。发现这种情况先重新佩戴手套后重试，不要靠调低阈值硬凑。

### 需要判定就用二值流

自定义阈值逻辑需要处理：要处理噪声、要做时间滤波、要定滞回。仅需判断是否接触时，可使用 `tactile_binary`，相关处理由 SDK 完成。残差适用于自定义判据与默认判据不一致的场景。

## 相关内容

* [触觉标定](/docs/wuji-glove/sdk-practice/tactile-calibration)：本篇的前置条件，残差相对标定基线计算
* [触觉接触网格视图](/docs/wuji-glove/sdk-practice/tactile-contact-view)：同一网格，看 SDK 判定后的结果
* [数据订阅：回调](/docs/wuji-glove/sdk-practice/subscribe-callback)：744 / 526 / 218 三个通道数的来历
* [通用数据类型](/docs/wuji-sdk/data-reference/)：`TactileResidual` 结构


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