Open XR 手势控制模块开发。Monado 自定义手势识别模型 基于UltraLeap python实现
本文基于 UltraLeap 原始关节数据训练训练自定义手势模型,并将其与 Monado 集成的完整流程,包括模型训练、格式转换、驱动集成和应用调用全链路实现。
- 在手势模型部分本文仅构建了一个简单的分类模型,在我另一篇文章中有效果更好的手势识别模型感兴趣可以去看下。
- 在手势数据提取部分需要特别注意,不要偷懒使用通用手势数据,不同的设备有不同的视野角度,识别偏差很大。自己采集也无需担心过于耗时耗力,可以参考我之前发的手势数据生成器那篇文章,采集过程会变得非常高效且高质。不过需要具备c++和js基础能力。
一、自定义手势模型训练
1. 环境准备
安装必要的依赖库,用于数据采集、模型训练和推理:
# UltraLeap Python SDK(获取关节数据)
pip install ultraleap
# 机器学习与数据处理库
pip install numpy scikit-learn pandas joblib
# 模型格式转换工具(后续部署用)
pip install skl2onnx onnxruntime
2. 数据采集
编写脚本从 UltraLeap 设备采集自定义手势的原始关节数据,提取特征并保存为训练样本。
数据采集脚本
import numpy as np
import pandas as pd
from ultraleap import TrackingService, Hand
import time
# 配置 UltraLeap 连接(默认本地服务)
service = TrackingService()
# 定义要采集的手势类别(可自定义)
GESTURE\_LABELS = {0: "fist", 1: "ok", 2: "open\_palm"}
SAMPLES\_PER\_GESTURE = 100 # 每个手势采集100个样本
# 存储数据的列表(特征 + 标签)
data = \[]
def extract\_features(hand: Hand) -> np.ndarray:"""从手部数据中提取特征(关节相对位置和角度)"""# 获取21个关节点的3D坐标(palm:0, 拇指1-4, 食指5-8, ..., 小指17-20)joints = np.array(\[\[j.x, j.y, j.z] for j in hand.fingers.joints]) # 形状 (21, 3)# 特征1:以掌心为原点的相对坐标(减少手部位置影响)palm\_center = joints\[0] # 掌心坐标(第0个关节)relative\_joints = joints - palm\_center # 所有关节相对掌心的位置# 特征2:手指长度(指尖到指根的距离)finger\_lengths = \[]for finger in \[1, 5, 9, 13, 17]: # 拇指、食指、中指、无名指、小指的指根索引fingertip = joints\[finger + 3] # 指尖索引(如拇指指尖是1+3=4)length = np.linalg.norm(fingertip - joints\[finger]) # 指根到指尖的欧氏距离finger\_lengths.append(length)# 特征3:关键关节角度(拇指与食指的角度)thumb\_tip = joints\[4]index\_tip = joints\[8]angle = np.arccos(np.dot(thumb\_tip - palm\_center, index\_tip - palm\_center) /(np.linalg.norm(thumb\_tip - palm\_center) \* np.linalg.norm(index\_tip - palm\_center) + 1e-8))# 合并所有特征(相对坐标展平 + 手指长度 + 角度)features = np.concatenate(\[relative\_joints.flatten(), # 21\*3=63维np.array(finger\_lengths), # 5维\[angle] # 1维])return features
def collect\_data():print("开始采集手势数据...")print(f"手势类别: {GESTURE\_LABELS}")for label, name in GESTURE\_LABELS.items():input(f"准备采集 {name}(标签 {label}),按回车开始...")count = 0while count < SAMPLES\_PER\_GESTURE:# 获取当前帧的手部数据frame = service.get\_frame()if frame.hands