Quest3手追解析并控制灵巧手

配置

Unity XR Toolkit + XR Hands

基础配置参考:

https://youtu.be/mJ3fygb9Aw0?si=ubO0ELPzcc2Ohvcf

代码

我目前测试的的灵巧手是Curl(握紧/绷直)5个自由度(绳驱)+ Spread(侧向张开)5个自由度(电机驱动)。

Unity XR Hands自带5指curl和食指、中指、无名指的spread,拇指和小拇指需要自己写代码算

Unity的关节和其他参考:https://docs.unity3d.com/Packages/com.unity.xr.hands@1.5/manual/hand-data/xr-hand-data-model.html

直接上代码

using UnityEngine;
using UnityEngine.XR.Hands;
using UnityEngine.XR.Hands.Gestures;
using TMPro;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class XRHandsFlexDisplay : MonoBehaviour
{
    [Header("Settings")]
    [Range(0, 1)] public int handSide = 0;               // 0 = Left, 1 = Right
    public float sendInterval = 0.05f;                  // 20 Hz 默认
    public TextMeshProUGUI debugText;

    [Header("UDP Out")]
    public string remoteIP = "192.168.50.233";
    public int remotePort = 12345;

    UdpClient udp;
    float lastSendTime;

    XRHandSubsystem handSub;
    static readonly XRHandFingerID[] fids = {
        XRHandFingerID.Thumb, XRHandFingerID.Index, XRHandFingerID.Middle,
        XRHandFingerID.Ring,  XRHandFingerID.Little };

    void Start()
    {
        udp = new UdpClient();
        udp.Connect(IPAddress.Parse(remoteIP), remotePort);
    }

    void Update()
    {
        if (handSub == null)
        {
            handSub = UnityEngine.XR.Management.XRGeneralSettings
                      .Instance.Manager.activeLoader?
                      .GetLoadedSubsystem<XRHandSubsystem>();
            if (handSub == null) return;
        }

        XRHand hand = (handSide == 0) ? handSub.leftHand : handSub.rightHand;
        if (!hand.isTracked) { debugText.text = "Hand not tracked"; return; }

        // ---------- 1. 采集 Flex ----------
        float[] flex01 = new float[5];
        for (int i = 0; i < 5; ++i)
        {
            var s = XRFingerShapeMath.CalculateFingerShape(
                        hand, fids[i], XRFingerShapeTypes.FullCurl);
            s.TryGetFullCurl(out flex01[i]);
        }

        // ---------- 2. 采集 Spread ----------
        float[] spr01 = new float[5];
        spr01[0] = ThumbSpreadBy3Points(hand);           // Thumb

        for (int i = 1; i <= 3; ++i)                     // Index-Ring
        {
            var s = XRFingerShapeMath.CalculateFingerShape(
                        hand, fids[i], XRFingerShapeTypes.Spread);
            s.TryGetSpread(out spr01[i]);
        }
        spr01[4] = spr01[3];                             // Little = Ring
        float ringSend = spr01[3] * 0.5f;                // Ring 半值
        float middleSend = spr01[2] * 0.5f;                // Middle 半值

        // ---------- 3. UDP 发送(按节拍) ----------
        if (Time.time - lastSendTime >= sendInterval)
        {
            byte[] pkt = new byte[10];
            for (int i = 0; i < 5; ++i) pkt[i] = ToByte(flex01[i]);
            pkt[5] = ToByte(spr01[0]);
            pkt[6] = ToByte(spr01[1]);
            pkt[7] = ToByte(middleSend);
            pkt[8] = ToByte(ringSend);
            pkt[9] = ToByte(spr01[4]);

            udp.Send(pkt, pkt.Length);
            lastSendTime = Time.time;


            // ---------- 4. TMP 两行显示 ----------
            var sb = new StringBuilder();
            sb.Append("Flex:   ");
            foreach (var v in flex01) sb.Append($"{v:0.00} ");
            sb.AppendLine();
            sb.Append("Spread: ");
            foreach (var v in spr01) sb.Append($"{v:0.00} ");
            debugText.text = sb.ToString();
        }
    }

    static byte ToByte(float v) =>
        (byte)Mathf.Clamp(Mathf.RoundToInt(v * 255f), 0, 255);

    float ThumbSpreadBy3Points(XRHand hand)
    {
        XRHandJoint tip = hand.GetJoint(XRHandJointID.ThumbDistal);
        XRHandJoint root = hand.GetJoint(XRHandJointID.ThumbMetacarpal);
        XRHandJoint idxP = hand.GetJoint(XRHandJointID.IndexProximal);
        if (!tip.TryGetPose(out Pose a) ||
            !root.TryGetPose(out Pose b) ||
            !idxP.TryGetPose(out Pose c)) return 0f;

        float ang = Vector3.Angle(a.position - b.position, c.position - b.position);
        return Mathf.Clamp01(Mathf.InverseLerp(30f, 65f, ang));
    }

    void OnApplicationQuit() => udp?.Dispose();
}

接收端测试代码,python为例

#!/usr/bin/env python3
"""
UDP receiver for 10-byte hand-data packets (5 × flex, 5 × spread).

Usage
-----
$ python hand_udp_receiver.py           # 监听 0.0.0.0:12345
$ python hand_udp_receiver.py -p 5555   # 改用端口 5555
"""

import socket
import argparse

def main():
    ap = argparse.ArgumentParser(description="Receive 10-byte hand packets over UDP.")
    ap.add_argument("-p", "--port", type=int, default=12345,
                    help="listen port (default: 12345)")
    args = ap.parse_args()

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(("0.0.0.0", args.port))
    print(f"🟢 Listening on 0.0.0.0:{args.port} …")

    try:
        while True:
            data, addr = sock.recvfrom(1024)      # ≥10 bytes
            if len(data) < 10:
                continue                          # 跳过错误包
            frame = list(data[:10])               # 10 byte → int list
            # 一行打印:0-4 = curl, 5-9 = spread
            print(" ".join(f"{v:3d}" for v in frame))
    except KeyboardInterrupt:
        print("\n⏹  Stopped by user.")
    finally:
        sock.close()

if __name__ == "__main__":
    main()

定义

Byte IndexFinger / ChannelTypeValue 0Value 255
0Thumb CurlFlexThumb完全伸直Thumb握到掌心
1Index CurlFlex食指完全伸直食指握到掌心
2Middle CurlFlex中指完全伸直中指握到掌心
3Ring CurlFlex无名指完全伸直无名指握到掌心
4Little CurlFlex小指完全伸直小指握到掌心
5Thumb SpreadAbduction拇指贴近食指(对掌)拇指最大外展
6Index SpreadAbduction食指正前方(与中指平行)向拇指方向最大外展
7Middle Spread†Abduction中指正前方(基准)向拇指方向½ 幅度外展(原值×0.5)
8Ring Spread†Abduction无名指正前方小指方向½ 幅度外展(原值×0.5)
9Little SpreadAbduction小指正前方小指外侧(远离拇指)最大外展 = 无名指原始 spread

† Middle/Ring Spread 在发送前乘 0.5,用于减小中指/无名指舵机动作幅度。

小拇指因为没有直接可读取的值,直接取无名指原始数据(发送的数据的两倍)。

一些相关资料

解读&实测Oculus Quest 2手部追踪算法 – 知乎

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇