[PICO VR眼镜]眼动追踪串流Unity开发与使用方法,眼动追踪打包报错问题解决(Eye Tracking/手势跟踪)

前言

最近在做一个工作需要用到PICO4 Enterprise VR头盔里的眼动追踪功能,但是遇到了如下问题:

  1. 在Unity里面没法串流调试眼动追踪功能,根本获取不到Device,只能将整个场景build成APK,安装到头盔里,才能在代码里调用眼动追踪功能。这使得每次修改代码都要打包一次apk安装到头盔里,十分不方便,难以调试。
  2. PICO VR 官方提供的Eye Tracking教程里,获取到的眼睛朝向和位置是相对于Head这个位置的,而不是相对于XR Origin下的Camera的位置,这使得API不能直接拿来使用。
  3. Unity在引入PICO VR眼动跟踪代码后,编译时点击"build and run"后,会报错“NullReferenceException: Object reference not set to an instance of an object

鉴于以上问题网络上均没有人进行解答,以及个人没能得到PICO官方的技术支持情况下(三周内发了2封技术工单邮件+2次催线上客服),遂打算自己捣鼓一下写篇教程。

(点名批评PICO官方的技术支持不回复邮件的问题,明明就几个特别简单的问题,但是官方承诺的3-5个工作日内回复并没有做到,等了三周一封邮件也没回,这个过程还问了客服,客服表示会催技术人员回复,但等了一周半也没看到,放弃了orz)

更新补充:据说在PICO企业版官网提交企业版工单能得到官方较快的回复。

1. 如何在代码里使用眼动跟踪,并转换成世界坐标 或 摄像机坐标系

首先,检查眼动追踪是否能正常工作:

void Start()
{CheckEyeTracking();}
private void CheckEyeTracking()
{//Start PICO Eye Tracking ServiceTrackingStateCode trackingState;trackingState = (TrackingStateCode)PXR_MotionTracking.WantEyeTrackingService();Debug.Log("告知PICO想要眼动跟踪服务 trackingState: " + trackingState.ToString());//Check Eye Tracking able or notEyeTrackingMode eyeTrackingMode = EyeTrackingMode.PXR_ETM_NONE;bool supported = false;int supportedModesCount = 0;trackingState = (TrackingStateCode)PXR_MotionTracking.GetEyeTrackingSupported(ref supported, ref supportedModesCount, ref eyeTrackingMode);Debug.Log("检查是否有眼动跟踪功能 trackingState: "+ trackingState.ToString()+"  code  "+ supported);// Get Eye Tracking Statebool tracking = true;EyeTrackingState eyeTrackingState = new EyeTrackingState();trackingState = (TrackingStateCode)PXR_MotionTracking.GetEyeTrackingState(ref tracking, ref eyeTrackingState);Debug.Log("获取当前眼动跟踪状态 trackingState: " + trackingState.ToString());// Start Eye Tracking//这里要严谨点的话,应该要加上if条件判断是否有眼动跟踪功能,再选择开启该功能EyeTrackingStartInfo info = new EyeTrackingStartInfo();info.needCalibration = 1;info.mode = eyeTrackingMode;trackingState = (TrackingStateCode)PXR_MotionTracking.StartEyeTracking(ref info);Debug.Log("开始眼动跟踪状态 trackingState: " + trackingState.ToString());//Get Eye Tracking Data//获取眼动跟踪数据EyeTrackingDataGetInfo eyeData = new EyeTrackingDataGetInfo();eyeData.displayTime = 0;eyeData.flags = EyeTrackingDataGetFlags.PXR_EYE_DEFAULT| EyeTrackingDataGetFlags.PXR_EYE_POSITION| EyeTrackingDataGetFlags.PXR_EYE_ORIENTATION;EyeTrackingData eyeTrackingData = new EyeTrackingData();trackingState = (TrackingStateCode)PXR_MotionTracking.GetEyeTrackingData(ref eyeData, ref eyeTrackingData);Debug.Log("eyeData:  "+ eyeData.ToString() + " TrackingData:  " + eyeTrackingData.ToString());}

接下来,每帧都获取眼动数据,并将眼睛位置和眼睛朝向数据转换成世界坐标

public Transform origin;//在Unity主界面把XR Origin拖进来即可
private Matrix4x4 headPoseMatrix,originPoseMatrix;
private Vector3 combineEyeGazeVector, combineEyeGazeOriginOffset, combineEyeGazeOrigin;
private Vector3 combineEyeGazeVectorInWorldSpace, combineEyeGazeOriginInWorldSpace;
void Update()
{GetEyeTrackingData();
}private void GetEyeTrackingData()
{//获取head的局部转世界矩阵,该矩阵点乘head局部坐标系下坐标,则能转换为世界坐标系下的坐标PXR_EyeTracking.GetHeadPosMatrix(out headPoseMatrix);	//获取双眼(取中间位置)位于Head坐标系下的朝向信息GazeVectorPXR_EyeTracking.GetCombineEyeGazeVector(out combineEyeGazeVector);//获取双眼(取中间位置)位于Head坐标系下的位置信息GazePointPXR_EyeTracking.GetCombineEyeGazePoint(out combineEyeGazeOrigin);//获取眼睛的世界坐标combineEyeGazeOriginInWorldSpace = originPoseMatrix.MultiplyPoint(headPoseMatrix.MultiplyPoint(combineEyeGazeOrigin));//获取眼睛的朝向信息combineEyeGazeVectorInWorldSpace = originPoseMatrix.MultiplyVector(headPoseMatrix.MultiplyVector(combineEyeGazeVector));//highlighArea是我添加的一个手电筒高亮区域,能让用户更直观地查看眼睛看向位置highlighArea.transform.position = combineEyeGazeOriginInWorldSpace ;//LookRotation默认是以z轴旋转,如果要以y轴旋转的话可以在后面加上 ,Vector3.uphighlighArea.transform.rotation = Quaternion.LookRotation(combineEyeGazeVectorInWorldSpace);
/*        RaycastHit hit;if (Physics.Raycast(highlighArea.transform.position, combineEyeGazeVectorInWorldSpace, out hit, 1000f)){highlighArea.transform.LookAt(hit.point);}*/
}

简易效果图(透明圈处是一个作为highlighArea的圆锥区域):
在这里插入图片描述

至此,便已经能获取到PICO VR里眼动追踪的位置和朝向信息。

2. "Build and Run"报错

引入EyeTracking代码后,发现Build and Run时会报以下错误:
在这里插入图片描述

在这里插入图片描述
这是因为run的话会导致Unity找不到可供眼动追踪的设备,导致编译报错。

解决方法,只点击Build,而不是Build and Run:
在这里插入图片描述
Build完后,打开Pico Developer Center,依次点击"应用管理"->“安装包”->“安装本地应用”:
在这里插入图片描述
选择build好的apk即可,后续便能直接在眼镜里运行:
在这里插入图片描述

3.眼镜内调试的Trick

对于这种要build到眼镜里才能调试的功能,可以利用Unity提高的UI->Text组件进行调试。

即,在Unity场景下创建一个UI->Text对象,然后把对象拖到下面脚本的GazeDebugText变量上,便能在眼镜里看到相应数据输出了

public TMP_Text GazeDebugText;
private void GetEyeTrackingData()
{GazeDebugText.text = combineEyeGazeOrigin.ToString("F3");//F3是指精确到小数点后3位
}

效果:
在这里插入图片描述

4.如何在Unity里串流调用眼动追踪功能

B站视频:使用PICO头显在VR串流环境下进行眼动追踪

我个人是在看了这个视频后才知道PICO VR也能在Unity下串流使用眼动跟踪功能,但我没有进一步深入探究,据该视频UP主表示,PICO neo3 pro eye是可以串流的,该方法理应也适用于其他头盔(带有企业串流功能的头盔)。

在此处也表达下对UP主的感谢,在后台私信耐心细致替我解答问题。

想要串流使用pico的眼动追踪功能,需要下载特定的串流软件和PICO系统版本。

4.1 串流工具准备

PC端我们要下载最新版本的PICO企业版串流工具(普通版的串流工具不知道可不可以):在这里插入图片描述
在PICO端上则是自动保持串流软件更新到最新版本即可。

在PC端下载好“企业串流”软件后,点击"设置"->“通用”:
在这里插入图片描述
把需要的功能都勾选上(默认是关闭状态):
在这里插入图片描述

business streaming安装后且打开眼动追踪功能后,sdk里会有一个用qt写的测试程序,可以试试看串流后眼动跟踪是否正常,测试程序不需要用steam,但是对头显的固件版本有要求:
在这里插入图片描述
点击get eye tracking(记住,一定要戴上头盔的情况下,盲点这个按钮,不如眼睛都不在头盔里面自然捕获不到数据),正常的话会显示如下数据:
在这里插入图片描述
如果这里显示ET Data 是 invalid的,那证明你没有戴上头盔后再点击get eye tracking,因为头盔没能捕捉到瞳孔数据,记住一定要先戴上头盔再盲点击按钮。

4.2 企业串流+OpenVR+SteamVR进行Unity开发

众所周知,任意VR头盔都能用SteamVR+OpenVR来开发VR应用,我们的PICO眼镜也可以,这里的“企业串流”只起到了传输EyeTrackingData的作用。我们如果要用到眼动追踪串流调试的话,就需要以OpenXR+SteamVR的方式进行开发,详细教程可以参考:知乎-Pico基于Unity XR Interaction Toolkit开发SteamVR串流应用 如果能正常串流到Unity界面,则我们进入下一步代码开发。

4.3 代码部分

接下来便是关键的部分,我们要在代码里引入企业版的PICO SDK并初始化(在你要用到Eye tracking代码最开始的地方引入即可,):

//引入 企业版串流SDK
private const string PXR_PLATFORM_DLL = "BStreamingSDK.dll";
//对 企业版串流SDK 进行初始化
[DllImport(PXR_PLATFORM_DLL, CallingConvention = CallingConvention.Cdecl)]
private static extern int BStreamingSDK_Init(IntPtr userData);
[DllImport(PXR_PLATFORM_DLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int BStreamingSDK_Deinit();

此处的SDK跟我们平常开发PICO用的PXR SDK不一样,是为串流功能专门要用的SDK,该SDK会随business streaming一起安装,不需要你特定指明路径。而且当我们将项目build成apk安装包之后,调用的SDK是正常使用的,不用担心冲突问题等。

接下来便是获取眼部追踪数据/手势追踪数据即可:

//眼动追踪
[DllImport(PXR_PLATFORM_DLL, CallingConvention = CallingConvention.Cdecl)]
private static extern int BStreamingSDK_GetEyeTrackingData(ref PxrEyeTrackingData etdata);//手势追踪
[DllImport(PXR_PLATFORM_DLL, CallingConvention = CallingConvention.Cdecl)]
private static extern int BStreamingSDK_GetHandTrackerAimState(HandType hand, ref HandAimState aimState);
[DllImport(PXR_PLATFORM_DLL, CallingConvention = CallingConvention.Cdecl)]
private static extern int BStreamingSDK_GetHandTrackerJointLocations(HandType hand, ref HandJointLocations jointLocations);

此外,注意这里的PxrEyeTrackingData并不是SDK自带的类,而是需要我们自己在代码里创建的类,然后将其传入官方提供的函数来获取数据(手势追踪的Hand AimState等也是如此要自己创建类,详细部分可以向官方发送企业工单获取技术人员帮助),该类的定义如下所示:

public struct PxrEyeTrackingData
{public int leftEyePoseStatus;public int rightEyePoseStatus;public int combinedEyePoseStatus;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public float[] leftEyeGazePoint;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public float[] rightEyeGazePoint;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] combinedEyeGazePoint;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] leftEyeGazeVector;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] rightEyeGazeVector;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] combinedEyeGazeVector;public float leftEyeOpenness;public float rightEyeOpenness;public float leftEyePupilDilation;public float rightEyePupilDilation;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] leftEyePositionGuide;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] rightEvePositionGuide;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]public float[] foveatedGazeDirection;public int foveatedGazeTrackingState;
}

后续要注意到的点是,串流SDK里 CombinedEyeGazeVector Z轴是要取反的,与PICO Integration SDK不同:

PxrEyeTrackingData etData = new PxrEyeTrackingData();
var result = BStreamingSDK_GetEyeTrackingData(ref etData);
headPoseMatrix = Matrix4x4.TRS(userView.transform.position, userView.transform.rotation, Vector3.one);
var eyePoseStatus = etData.combinedEyePoseStatus;
var gazePosition = new Vector3(etData.combinedEyeGazePoint[0], etData.combinedEyeGazePoint[1], etData.combinedEyeGazePoint[2]);
var gazeVector = new Vector3(etData.combinedEyeGazeVector[0], etData.combinedEyeGazeVector[1], -etData.combinedEyeGazeVector[2]);
combineEyeGazeOrigin = gazePosition;
combineEyeGazeVector = gazeVector;combineEyeGazeOriginInWorldSpace = headPoseMatrix.MultiplyPoint(combineEyeGazeOrigin);
combineEyeGazeVectorInWorldSpace = headPoseMatrix.MultiplyVector(combineEyeGazeVector);
highlightArea.position = combineEyeGazeOriginInWorldSpace;
highlightArea.rotation = Quaternion.LookRotation(combineEyeGazeVectorInWorldSpace);

如果经过测试后,eye tracking data什么的没有问题,后续在Unity串流过程中使用Eye Tracking Dat并基于OpenXR进行应用调试与开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python语言初识(五)】

一、文件和异常 在Python中实现文件的读写操作其实非常简单,通过Python内置的open函数,我们可以指定文件名、操作模式、编码信息等来获得操作文件的对象,接下来就可以对文件进行读写操作了。这里所说的操作模式是指要打开什么样的文件&#…

泛型(Java)

1.泛型&#xff1a; 将数据类型作为参数进行传递。(传递的数据类型必须是引用数据类型) 本质是参数化类型。 泛型集合&#xff1a;可以约束集合内的元素类型 典型泛型集合ArrayList<E>、HashMap<K,V> <E>、<K,V>表示该泛型集合中的元素类型泛型集合中的…

计算机视觉学习路线

计算机视觉&#xff08;Computer Vision&#xff09;是计算机科学的一个重要分支&#xff0c;旨在使计算机能够理解和解释视觉数据。以下是一个详细的计算机视觉学习路线&#xff0c;帮你系统地掌握这个领域所需的知识和技能。 1. 基础数学和编程 在深入学习计算机视觉之前&…

数据结构之——单循环链表和双向循环链表

一、单循环链表的奥秘 单循环链表是一种特殊的链表结构&#xff0c;它在数据结构领域中具有重要的地位。其独特的循环特性使得它在某些特定的应用场景中表现出强大的优势。 &#xff08;一&#xff09;结构与初始化 单循环链表的结构由节点组成&#xff0c;每个节点包含数据域…

343.整数拆分

刷题刷题找工作&#xff01; 题目链接 DP入门之整数拆分&#xff01; 题目&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 自己的思路 。。…

引入Scrum激发研发体系活力

引言 在当今快速变化的技术环境中&#xff0c;IT企业面临着持续的市场压力和竞争&#xff0c;传统的瀑布式开发模式已经难以满足现代企业的需要。瀑布模型过于僵化&#xff0c;缺乏灵活性&#xff0c;导致项目经常延期&#xff0c;成本增加&#xff0c;最终可能无法达到预期效果…

bert系列模型区别(bert-base-cased/bert-base-uncased/bert-base-chinese)

文章目录 BERT模型介绍bert-base-casedbert-base-uncasedbert-base-chineseBERT-BILSTM-CRF模型介绍模型下载地址BERT模型介绍 BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的语言模型,由Google开发并于2018年发布。BERT的目标是通过将大量…

【Qualcomm】高通SNPE框架的使用 | 原始模型转换为量化的DLC文件 | 在Android的DSP端运行模型

目录 ① 激活snpe环境 ② 设置环境变量 ③ 模型转换 ④ run 首先&#xff0c;默认SNPE工具已经下载并且Setup相关工作均已完成。同时&#xff0c;拥有原始模型文件&#xff0c;本文使用的模型文件为SNPE 框架示例的inception_v3_2016_08_28_frozen.pb文件。image_file_list…

数据集-目标检测系列-口罩检测数据集 mask>> DataBall

数据集-目标检测系列-口罩检测数据集 mask>> DataBall 数据集-目标检测系列-口罩检测数据集 mask 数据量&#xff1a;1W DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;享有百种数据集&#xff0c;持续增加中。 数据项目地址&#xff1a; gitcode: https…

【Python报错已解决】TypeError: list indices must be integers or slices, not str

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

Linux中通过cgroups限制进程的资源

文章目录 1. 安装和配置cgroups 2. 创建cgroup 3. 设置资源限制 1&#xff09;CPU限制 2&#xff09;内存限制 4. 将Java进程加入到cgroup 5. 监控cgroup 在Linux系统中&#xff0c;cgroups&#xff08;Control Groups&#xff09;是一种可以对一组进程进行细粒度资源控…

打造同城O2O平台:外卖跑腿APP的架构与功能设计详解

今天&#xff0c;小编将于大家共同讨论外卖跑腿APP的架构设计及其核心功能&#xff0c;旨在为开发者提供一份详尽的参考。 一、外卖跑腿APP的架构设计 1.整体架构概述 通常包括前端、后端和数据库。 2.前端设计 用户端提供直观的界面&#xff0c;方便用户下单、查询订单状态…

初学51单片机之I2C总线与E2PROM

首先先推荐B站的I2C相关的视频I2C入门第一节-I2C的基本工作原理_哔哩哔哩_bilibili 看完视频估计就大概知道怎么操作I2C了&#xff0c;他的LCD1602讲的也很不错&#xff0c;把数据建立tsp和数据保持thd&#xff0c;比喻成拍照时候的摆pose和按快门两个过程&#xff0c;感觉还是…

C语言实现归并排序(Merge Sort)

目录 一、递归实现归并排序 1. 归并排序的基本步骤 2.动图演示 3.基本思路 4.代码 二、非递归实现 1.部分代码 2.代码分析 修正后代码&#xff1a; 归并过程打印 性能分析 复杂度分析 归并排序是一种高效的排序算法&#xff0c;采用分治法&#xff08;Divide and Con…

华为仓颉语言入门(6):if条件表达式

解锁Python编程的无限可能&#xff1a;《奇妙的Python》带你漫游代码世界 仓颉语言中的 if 表达式用于根据条件的值来决定是否执行相关代码逻辑。if 表达式有三种形式&#xff1a;单分支的 if 表达式、双分支的 if 表达式和嵌套的 if 表达式。 单分支的 if 表达式 单分支的 …

javase复习day35反射

反射 获取class对象的方法 public class Demo1 {public static void main(String[] args) throws ClassNotFoundException {//获取反射的三种方式//第一种 Class.forName(全类名)//用法&#xff1a;最为常用Class<?> clazz1 Class.forName("Reflection.Student&q…

程序员如何以最快的方式提升自己?分享4个有效方法!

作家周国平说&#xff1a;人与人之间最重要的区别&#xff0c;不在物质的贫富和社会方面的境遇&#xff0c;是内在的素质和层次&#xff0c;把人分出了伟大与渺小、优秀与平庸。有的人醉心于三五成群的消遣&#xff0c;有的人专注于一步一脚印的努力&#xff0c;人和人之间的差…

docker修改默认存储路径和网段

在安装完成 Docker 后&#xff0c;可以修改 Docker 的数据目录和默认网段(172.17.0.0/16)&#xff0c;以防止与其他系统或应用的网络配置冲突。以下是详细步骤&#xff1a; 停止 Docker 服务&#xff1a; sudo systemctl stop docker 修改 Docker 配置文件&#xff1a; 编辑…

Shiro-550—漏洞分析(CVE-2016-4437)

文章目录 漏洞原理源码分析加密过程解密过程 漏洞复现 漏洞原理 Shiro-550(CVE-2016-4437)反序列化漏洞 在调试cookie加密过程的时候发现开发者将AES用来加密的密钥硬编码了&#xff0c;并且所以导致我们拿到密钥后可以精心构造恶意payload替换cookie&#xff0c;然后让后台最…

利用Puppeteer-Har记录与分析网页抓取中的性能数据

引言 在现代网页抓取中&#xff0c;性能数据的记录与分析是优化抓取效率和质量的重要环节。本文将介绍如何利用Puppeteer-Har工具记录与分析网页抓取中的性能数据&#xff0c;并通过实例展示如何实现这一过程。 Puppeteer-Har简介 Puppeteer是一个Node.js库&#xff0c;提供…