Touch Glove SDK
Touch Glove 数据采集 SDK。本项目包含由 Rust 实现的高性能、无锁(lock-free)底层驱动库,并对外提供标准的 C API 接口。
SDK 的核心目标是接收 5 通道(5 只手套)的实时 192x192 灰度图像流,支持手套硬件 RTC 时间同步、数据包丢包检测与吞吐量统计,支持 Python 和 ROS2 调用。 本版本集成了高性能 ONNX Runtime 推理引擎,支持在 Rust 底层直接推理 3D 位移场与 3D 力场,并对外提供高效的物理约束标定与预测接口。
跨平台 Release 输出
最终发布包固定输出 4 个系统目录,命名统一为 sdk_系统名:
release/
├── sdk_linux_x86/ # Linux x86_64;当前 sdk/ 就是这个版本应有的形态
├── sdk_linux_arm/ # Linux ARM64 / aarch64
├── sdk_windows/ # Windows x86_64
└── sdk_mac/ # macOS arm64 或 universal2
每个目录都是可独立复制使用的 SDK。sdk_linux_x86/ 会以当前 sdk/ 为模板,并补充 api/runtimes/ 目录;其他系统版本也保持同样的外形:
sdk_<system>/
├── api/ # Python API 与 PyO3 原生扩展
│ ├── __init__.py
│ ├── _rust.*
│ ├── _rust.pyi
│ └── runtimes/ # 当前系统所有可用推理包
│ ├── cpu/
│ ├── cuda/
│ ├── npu/
│ ├── coreml/
│ └── directml/
├── include/touch_glove_api.h # C/C++ API 头文件
├── lib/ # C ABI 原生库:.so / .dll / .dylib
├── model/ # ONNX / OpenVINO 模型
├── example.py
├── dense_dashboard.py
├── README.md
└── LICENSE
运行时选择通过环境变量控制:
# 自动选择当前系统可用 runtime,失败时回退 CPU
export TOUCH_GLOVE_RUNTIME=auto
# 指定 CUDA / NPU(OpenVINO) / CoreML / DirectML / CPU
export TOUCH_GLOVE_RUNTIME=cuda
export TOUCH_GLOVE_RUNTIME=npu
export TOUCH_GLOVE_RUNTIME=coreml
export TOUCH_GLOVE_RUNTIME=directml
export TOUCH_GLOVE_RUNTIME=cpu
# 如 runtime 库放在自定义目录,可覆盖搜索路径
export TOUCH_GLOVE_RUNTIME_DIR=/path/to/onnxruntime/libs
本机系统可以直接构建;非本系统的 release 文件夹统一通过 Docker 打包。Docker 打包入口见 scripts/package_with_docker.py,单系统构建入口见 scripts/build_release.py,ONNX Runtime 下载清单见 packaging/onnxruntime.toml。
1. 项目目录结构
整改后的 SDK 仅保留编译好的 Rust 库文件、C API 头文件和单个 Python 示例文件,删除了所有其他 .sh 和 .py 脚本:
.
├── lib/
│ ├── libtouch_glove.so # 编译好的 Rust 动态链接库
│ └── libonnxruntime.so # ONNX Runtime 动态链接库(底层推理依赖)
├── include/
│ └── touch_glove_api.h # C/C++ 接口头文件(包含推理 API)
├── example.py # 唯一的 Python 示例文件(包含 GUI 显示、标定、位移/力推理与标准 ROS2 图像发布)
├── README.md # SDK 使用及 API 说明文档
└── api/ # Rust 底层驱动源码项目 (可在此修改并编译库)
├── Cargo.toml
└── src/
2. 编译与构建
2.1 生成 sdk_系统名 release 目录
当前 Linux x86 主机上可直接生成 sdk_linux_x86:
python3 scripts/build_release.py \
--system linux_x86 \
--runtime cpu --runtime cuda --runtime npu \
--copy-existing-linux-x86-runtimes \
--zip
非本系统的包通过 Docker 打包。一次性生成四个 release 目录:
python3 scripts/package_with_docker.py \
--system linux_x86 \
--system linux_arm \
--system windows \
--system mac \
--zip
如果 Windows/macOS 原生二进制还没有挂载到 api/target/<target>/release/ 或通过参数传入,可先生成目录骨架:
python3 scripts/package_with_docker.py --system windows --system mac --package-only --allow-missing-artifacts --zip
单系统构建入口:
# Linux ARM64
python3 scripts/build_release.py --system linux_arm --runtime cpu --runtime npu --zip
# Windows x86_64
python scripts/build_release.py --system windows --runtime cpu --runtime cuda --runtime npu --runtime directml --zip
# macOS
python3 scripts/build_release.py --system mac --runtime cpu --runtime coreml --zip
构建完成后会生成:
release/sdk_linux_x86
release/sdk_linux_arm
release/sdk_windows
release/sdk_mac
并在指定 --zip 时生成对应的 sdk_linux_x86.zip、sdk_linux_arm.zip、sdk_windows.zip、sdk_mac.zip。
2.2 编译 Rust 底层库
在系统安装好 Rust (cargo) 之后,运行:
cd api
cargo build --release
编译生成的库位于 api/target/release/libtouch_glove.so。将其拷贝到 lib/ 目录下即可。
3. 示例程序 example.py 的使用
example.py 是本项目唯一的 Python 脚本,它整合了:
- Python 调用接口:通过
ctypes直接加载并调用编译好的 Rust 库libtouch_glove.so。 - GUI 图像显示:使用 OpenCV 将 5 个通道的 192x192 灰度图拼接为 3x2 网格,提供实时高帧率图像界面。
- Pcap/Tare 标定:当模型加载时,按下 OpenCV 窗口中的
[B]键可触发 1 秒图像基准值均值采集,并在 Rust 底层自动计算位移场和力场的零点偏移量。 - 实时物理推理:将当前 5 通道的原始图像流传给 Rust 底层进行推理,高频输出各通道的位移和力预测(带物理约束,如压应力约束、非接触零点噪声抑制等)。
- ROS2 调用接口:在当前终端 sourcing 了 ROS2 环境时,自动检测并初始化 ROS2 Node,除了发布原始图像外,若开启了模型推理,还会将 5 个通道的 3D 位移场与 3D 力场以标准的
32FC3(32位浮点型,3通道)图像格式发布至/touch_glove/ch{0..4}_disp和/touch_glove/ch{0..4}_force话题。
3.1 运行依赖
运行前请安装依赖(如需 GUI 请确保安装了 opencv-python,如需 ROS2 请加载 ROS2 Humble):
pip3 install numpy opencv-python
3.2 运行示例
可以直接在终端启动:
# 仅查看原始 2D 灰度图(不加载推理模型)
python3 example.py
# 加载多任务 ONNX 模型,并指定手套串口(如 /dev/ttyACM0)
python3 example.py /dev/ttyACM0 --model /path/to/best.onnx
启动后,程序会自动:
- 侦测并列出系统中所有的可用串口供您选择(如未指定串口参数)。
- 建立连接并与手套硬件执行握手(停止流、清空缓存、获取手套 UID、同步主机系统时间至硬件 RTC)。
- 如果指定了
--model,则会在 OpenCV 窗口左侧空栏提示“Press 'b' to Calibrate”。按下键盘b键静置 1 秒即完成标定,终端开始实时输出 Ch 1 的最大位移 (mm)、最大力 (N) 以及合力矢量 (N)。 - 若检测到 ROS2 环境,还会同时作为 ROS2 Publisher 广播对应的话题数据(包括
ch0-ch4的原始图像、位移场和力场)。
4. SDK C API 文档
底层库通过标准 C 链接(extern "C")导出了如下核心接口,用户可以使用任何支持 C 调用约定的语言自行加载和调用。
4.1 数据结构
图像帧结构体 (ts_frame_t)
typedef struct ts_frame_t {
uint8_t addr; // 设备地址 (默认 0x01)
uint8_t type; // 帧类型 (0x41)
uint8_t cmd; // 命令/通道号 (0x11-0x15 代表通道 0-4 的图像数据)
uint16_t param_len; // 参数长度 (图像为 36864 字节)
uint32_t seq_id; // 序列号 (用于检测丢包)
uint64_t timestamp_us; // 硬件接收时间戳 (Unix epoch, 微秒)
uint8_t sensor_id[8]; // 该通道传感器唯一的 8 字节 UID
uint8_t param[36864]; // 192x192 灰度图像的原始字节数据
} ts_frame_t;
4.2 核心函数
实例化与销毁
touch_glove_handle_t touch_glove_create(void)创建并初始化一个 SDK 实例上下文句柄。void touch_glove_destroy(touch_glove_handle_t h)断开串口连接并销毁实例释放内存。
串口控制与流控制
int touch_glove_open(touch_glove_handle_t h, const char* port_name)打开指定串口。成功返回0,失败返回-1。void touch_glove_close(touch_glove_handle_t h)关闭串口连接。int touch_glove_send_start(touch_glove_handle_t h)发送启动数据流指令。int touch_glove_send_stop(touch_glove_handle_t h)发送停止数据流指令。void touch_glove_poll_read(touch_glove_handle_t h)非阻塞轮询串口接收缓存,自动解析并将完整的数据帧分配到各通道的环形环缓冲区队列中。
物理推理引擎 API (模型加载、去皮标定与位移/力预测)
int touch_glove_init_model(touch_glove_handle_t h, const char* model_path)
加载指定的 dense 多任务 ONNX 模型并初始化底层的推理引擎。成功返回0,失败返回-1。void touch_glove_set_baseline(touch_glove_handle_t h, uint8_t channel, const uint8_t* image_data)
为指定通道设定 192x192 的背景基准图像(用于去皮和差分计算)。int touch_glove_calibrate(touch_glove_handle_t h)
使用当前的各通道基准图像进行一次推理,自动生成力与位移的零点 tare 偏置项。成功返回0,失败返回-1。int touch_glove_infer(touch_glove_handle_t h, const uint8_t* last_images, float* out_disp, float* out_force)
传入 5 个通道当前的原始图像字节(大小为5 * 192 * 192 = 184320字节),推理计算出其 3D 位移场与 3D 力场。out_disp和out_force为预先分配的 float32 数组,大小均为5 * 32 * 32 * 3 = 15360个元素。- 输出排布格式为
[Channel 0..4][Y 0..31][X 0..31][C 0..2 (dx, dy, dz 或 fx, fy, fz)]。 - 成功返回
0,失败返回-1。
5. 高级集成:如何调用 SDK
5.1 Python 推理调用示例
除了直接运行 example.py 之外,您可以在您自己的 Python 代码中通过 ctypes 极简地调用推理能力:
import ctypes
import numpy as np
import os
# 1. 设置 ONNX runtime 动态库寻找路径并加载底层驱动
os.environ["ORT_DYLIB_PATH"] = os.path.abspath("./lib/libonnxruntime.so")
lib = ctypes.CDLL("./lib/libtouch_glove.so")
# 2. 声明 API 签名
lib.touch_glove_create.restype = ctypes.c_void_p
lib.touch_glove_init_model.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
lib.touch_glove_set_baseline.argtypes = [ctypes.c_void_p, ctypes.c_uint8, ctypes.POINTER(ctypes.c_uint8)]
lib.touch_glove_calibrate.restype = ctypes.c_int
lib.touch_glove_infer.argtypes = [
ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint8),
ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float)
]
# 3. 创建句柄与加载模型
handle = lib.touch_glove_create()
lib.touch_glove_init_model(handle, b"./best.onnx")
# 4. 采集一帧作为背景基准并设定 (这里以 channel 0 为例)
baseline_img = np.zeros((192, 192), dtype=np.uint8) # 实际中从手套中 poll 得到
lib.touch_glove_set_baseline(handle, 0, baseline_img.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)))
# 5. 去皮校准
lib.touch_glove_calibrate(handle)
# 6. 推理预测
current_images = np.zeros((5, 192, 192), dtype=np.uint8) # 5通道当前原始图像
out_disp = (ctypes.c_float * 15360)()
out_force = (ctypes.c_float * 15360)()
# 调用推理
if lib.touch_glove_infer(handle, current_images.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), out_disp, out_force) == 0:
disp_array = np.ctypeslib.as_array(out_disp).reshape(5, 32, 32, 3)
force_array = np.ctypeslib.as_array(out_force).reshape(5, 32, 32, 3)
print("Channel 1 max force:", np.linalg.norm(force_array[1], axis=-1).max())
5.2 ROS2 C++ 调用示例
在 ROS2 节点中,可以通过 C++ 接口非常高频地推理并发布 PointCloud2 或 Image 消息。核心逻辑片段如下:
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>
#include "touch_glove_api.h"
class TouchGloveInferenceNode : public rclcpp::Node {
public:
TouchGloveInferenceNode() : Node("touch_glove_inference_node") {
disp_pub_ = this->create_publisher<sensor_msgs::msg::Image>("/touch_glove/ch1_disp", 10);
// 1. 初始化
h_ = touch_glove_create();
touch_glove_open(h_, "/dev/ttyACM0");
touch_glove_init_model(h_, "./best.onnx");
// 2. 假设已经通过 touch_glove_set_baseline 和 touch_glove_calibrate 完成校准
touch_glove_send_start(h_);
// 3. 定时推理并发布 (30Hz)
timer_ = this->create_wall_timer(
std::chrono::milliseconds(33),
std::bind(&TouchGloveInferenceNode::infer_and_publish, this)
);
}
private:
void infer_and_publish() {
touch_glove_poll_read(h_);
// 获取当前 5 通道的原始图像拼接缓冲区
uint8_t last_images[5 * 192 * 192];
// 填充 last_images(从 peek 队列头取最新的帧填入)
// 预分配预测输出数组 (5 * 32 * 32 * 3 = 15360)
float out_disp[15360];
float out_force[15360];
if (touch_glove_infer(h_, last_images, out_disp, out_force) == 0) {
auto msg = sensor_msgs::msg::Image();
msg.header.stamp = this->now();
msg.header.frame_id = "glove_ch1_disp";
msg.height = 32;
msg.width = 32;
msg.encoding = "32FC3"; // 3通道 float32 图像格式
msg.step = 32 * 3 * sizeof(float);
// 提取通道 1 的位移数据 (偏移量为 1 * 32 * 32 * 3 * 4 字节)
float* ch1_disp_start = out_disp + (1 * 32 * 32 * 3);
uint8_t* raw_data_ptr = reinterpret_cast<uint8_t*>(ch1_disp_start);
msg.data.assign(raw_data_ptr, raw_data_ptr + (32 * 32 * 3 * sizeof(float)));
disp_pub_->publish(msg);
}
}
touch_glove_handle_t h_;
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr disp_pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
6. 高级主题
6.1 macOS 平台加速与模型说明
在 macOS 平台上运行 example.py 开启推理模式时:
- SDK 会自动侦测系统架构并启用
mac推理模式。 - 内部使用
CoreMLInferenceEngine实现,优先利用 ONNX Runtime 的CoreMLExecutionProvider对 Apple Silicon 芯片(M1/M2/M3 等)进行硬件加速推理,如加速不可用,则自动降级回退至CPUExecutionProvider。 - 默认推荐使用 FP32 的 ONNX 模型。如果模型是加密格式(
.enc),引擎在加载时会自动在内存中执行 AES-GCM 解密,全程无需将解密模型写入磁盘,保护知识产权的同时保证加载效率。
6.2 一对手套(多路/10通道)接入方案
由于底层的 C/Rust 驱动采用**面向实例(Context Handle)**的设计模式,每个手套句柄独立封装了串口、数据队列、标定参数与推理资源,无全局静态变量冲突,原生支持同时连接多只手套。开发接入可选择以下三种方式:
A. 多进程部署(ROS2 架构下推荐)
在机器人多机或手部协同场景中,为每只手套启动一个独立的 Python 节点实例,配置不同的串口号,并通过 ROS2 的 namespace 或 topic 前缀进行区分:
# 启动左手节点,数据发布在 /left_glove/ch{0..4}_disp 话题
python3 example.py /dev/ttyACM0 --mode inference --model ./model/dense_3ch.onnx --ros-namespace left_glove
# 启动右手节点,数据发布在 /right_glove/ch{0..4}_disp 话题
python3 example.py /dev/ttyACM1 --mode inference --model ./model/dense_3ch.onnx --ros-namespace right_glove
此种方式下,单侧手套拔插或故障不影响另一侧,高并发和容错性能最佳。
B. 单进程多句柄调用
在普通 Python 代码中,可通过创建多个 Context 实例实现单进程聚合多只手套的数据:
# 初始化左/右手套句柄
handle_left = lib.touch_glove_create()
handle_right = lib.touch_glove_create()
# 分别打开对应的串口并开启流
lib.touch_glove_open(handle_left, b"/dev/ttyACM0")
lib.touch_glove_open(handle_right, b"/dev/ttyACM1")
lib.touch_glove_send_start(handle_left)
lib.touch_glove_send_start(handle_right)
# 轮询读取与数据获取
try:
while True:
lib.touch_glove_poll_read(handle_left)
lib.touch_glove_poll_read(handle_right)
# 分别拉取两个句柄的图像队列...
# left_imgs = get_images(handle_left)
# right_imgs = get_images(handle_right)
time.sleep(0.001)
finally:
lib.touch_glove_destroy(handle_left)
lib.touch_glove_destroy(handle_right)
C. 模型 Session 共享(节省内存)
当使用 Python 侧推理引擎时,为了避免为两个手套重复加载两套模型而占用多余显存/内存,可以使用同一个推理引擎实例去分别处理两只手套的图像输入:
# 初始化单个共享的推理引擎 Session
shared_engine = CoreMLInferenceEngine(model_path="./model/dense_3ch.onnx")
# 循环中将获取到的左右手图像分别送入推理
disp_left, force_left = shared_engine.infer(left_images)
disp_right, force_right = shared_engine.infer(right_images)
Metadata
Release files for touchglove 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| touchglove-0.1.0-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details for touchglove-0.1.0-py3-none-manylinux_2_17_x86_64.whl |
Release files / touchglove-0.1.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | touchglove-0.1.0-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 45.2 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e59c96bc8868137e2e79fbe490e7a6ab23fb05a053068a25d68c4bdc56c0f47c
|
|
BLAKE2b-256 checksum How to use checksums |
34678632e2a352ec68285235ca076a7655c8aee983fd4ee30ee581e35fa0f4e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|
Security for touchglove 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.