[PICO VR眼镜]眼动追踪串流Unity开发与使用方法,眼动追踪打包报错问题解决(Eye Tracking/手势跟踪)
前言
最近在做一个工作需要用到PICO4 Enterprise VR头盔里的眼动追踪功能,但是遇到了如下问题:
- 在Unity里面没法串流调试眼动追踪功能,根本获取不到Device,只能将整个场景build成APK,安装到头盔里,才能在代码里调用眼动追踪功能。这使得每次修改代码都要打包一次apk安装到头盔里,十分不方便,难以调试。
- PICO VR 官方提供的Eye Tracking教程里,获取到的眼睛朝向和位置是相对于Head这个位置的,而不是相对于XR Origin下的Camera的位置,这使得API不能直接拿来使用。
- 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进行应用调试与开发。
相关文章:
[PICO VR眼镜]眼动追踪串流Unity开发与使用方法,眼动追踪打包报错问题解决(Eye Tracking/手势跟踪)
前言 最近在做一个工作需要用到PICO4 Enterprise VR头盔里的眼动追踪功能,但是遇到了如下问题: 在Unity里面没法串流调试眼动追踪功能,根本获取不到Device,只能将整个场景build成APK,安装到头盔里,才能在…...
一周热门|比GPT-4强100倍,OpenAI有望年底发布GPT-Next;1个GPU,1分钟,16K图像
大模型周报将从【企业动态】【技术前瞻】【政策法规】【专家观点】四部分,带你快速跟进大模型行业热门动态。 01 企业动态 Ilya 新公司 SSI 官宣融资 10 亿美元 据路透社报道,由 OpenAI 联合创始人、前首席科学家 Ilya Sutskever 在 2 个多月前共同创…...
软考流水线计算
某计算机系统输入/输出采用双缓冲工作方式,其工作过程如下图所示,假设磁盘块与缓冲区大小相同,每个盘块读入缓冲区的时间T为10μs,由缓冲区送至用户区的时间M为6μs,系统对每个磁盘块数据的处理时间C为2μs。若用户需要…...
1份可以派上用场丢失数据恢复的应用程序列表
无论如何,丢失您的宝贵数据是可怕的。您的 Android 或 iOS 设备可能由于事故、硬件损坏、存储卡问题等而丢失了数据。这就是为什么我们编制了一份可以派上用场以恢复丢失数据的应用程序列表。 如果您四处走动,您大多会随身携带手机或其他移动设备。这些…...
MySQL Workbench 超详细安装教程(一步一图解,保姆级安装)
前言: MySQL Workbench 是一款强大的数据库设计和管理工具,它提供了图形化界面,使得数据库的设计、管理、查询等操作变得更加直观和便捷。本文将详细介绍如何在 Windows 系统上安装 MySQL Workbench。相信读者看这篇文章前一定安装了MySQL数…...
深度学习常见面试题及答案(16~20)
算法学习、4对1辅导、论文辅导或核心期刊以及其他学习资源可以通过公众号滴滴我 文章目录 16. 简述深度学习中的批量归一化(Batch Normalization)的目的和工作原理。一、批量归一化的目的1. 加速训练收敛:2. 提高模型泛化能力:3. …...
Packet Tracer - IPv4 ACL 的实施挑战(完美解析)
目标 在路由器上配置命名的标准ACL。 在路由器上配置命名的扩展ACL。 在路由器上配置扩展ACL来满足特定的 通信需求。 配置ACL来控制对网络设备终端线路的 访问。 在适当的路由器接口上,在适当的方向上 配置ACL。…...
Langchain-chatchat源码部署及测试实验
一年多前接触到Langchain-chatchat的0.2版本,对0.2版本进行了本地部署和大量更新,但0.2版本对最新的大模型支持不够好,部署框架支持也不好且不太稳定,特别是多模态大模型,因此本次主要介绍0.3版本的源码部署,希望对大家有所帮助。Langchain-chatchat从0.3版本开始,支持更…...
【Linux】线程(第十六篇)
目录 线程 1.线程基本概述: 2.线程类型: 3.线程间的共享资源与非共享资源 4.线程原语 1.线程创建函数 2.获取当前线程id的函数 3.回收线程资源 4.将线程设置为分离态 5.结束线程 6.退出线程 线程 1.线程基本概述: 是操作系统能够…...
2024华为杯研赛E题保姆级教程思路分析
E题题目:高速公路应急车道紧急启用模型 今年的E题设计到图像/视频处理,实际上,E题的难度相对来说较低,大家不用畏惧视频的处理,被这个吓到。实际上,这个不难,解决了视频的处理问题,…...
国内可以使用的ChatGPT服务【9月持续更新】
首先基础知识还是要介绍得~ 一、模型知识: GPT-4o:最新的版本模型,支持视觉等多模态,OpenAI 文档中已经更新了 GPT-4o 的介绍:128k 上下文,训练截止 2023 年 10 月(作为对比,GPT-4…...
Linux环境Docker安装Mongodb
Linux环境Docker安装Mongodb 环境要求拉取指定版本镜像创建映射目录(相当于数据存放于容器外,容器被删除不会影响数据)启动容器 进入mongo命令行为指定db创建新用户查看mongodb的容器id进入命令行查看所有db切换db为指定db创建新用户使用新账…...
PyTorch 池化层详解
在深度学习中,池化层(Pooling Layer)是卷积神经网络(CNN)中的关键组成部分。池化层的主要功能是对特征图进行降维和减少计算量,同时增强模型的鲁棒性。本文将详细介绍池化层的作用、种类、实现方法…...
Intel架构的基本知识
1.字节序 CPU的字节序分为LittleEndian和BigEndian。 所谓Endian,就是多字节数据在内存中的排列方式。 例如,假设有一个整数0x11223344: LittleEndian的排列方式是,从内存的低地址开始,依次存放 0x44 0x33 0x22 0x11; BigEndian的排列方式是,从内存的低地址开始,依…...
Element Plus 中Input输入框
通过鼠标或键盘输入字符 input为受控组件,他总会显示Vue绑定值,正常情况下,input的输入事件会正常被响应,他的处理程序应该更新组件的绑定值(或使用v-model)。否则,输入框的值将不会改变 不支…...
大模型中常见 loss 函数
loss 函数 首先,Loss 是允许不降到 0 的,模型计算的 loss 最终结果可以接近 0。 可以成为 loss 函数的条件## 常用 loss 以下函数调用基于 Pytorch,头文件导入: import torch.nn as nn 均方差(MSE) nn.…...
(十六)Ubuntu 20.04 下搭建PX4+MATLAB 仿真环境(HITL)
在文章(十五)Ubuntu 20.04 下搭建PX4MATLAB 仿真环境我们学习了如何配置仿真环境,在本节,主要进行HITL的仿真环境搭建。 根据(十五)Ubuntu 20.04 下搭建PX4MATLAB 仿真环境完成配置到如下界面:…...
Matlab simulink建模与仿真 第十七章(补充离散库和补充数学库)
参考视频:simulink1.1simulink简介_哔哩哔哩_bilibili 一、补充离散库和补充数学库中的模块概览 1、补充离散库 注:每个版本的补充离散库不一定相同,也不是每个版本的库都有如上所有模块。 2、补充数学库 二、离散直接传递函数Ⅱ模块 1、…...
Android Glide:让图片加载从未如此简单
在 Android 开发中,图片加载一直是一个关键环节。无论是从网络还是本地加载图片,都需要考虑到性能、内存管理和用户体验等多个方面。而在这方面,Glide 成为了众多开发者的首选库之一。本文将带你深入了解 Glide 的强大之处,并介绍如何在项目中快速集成和使用 Glide。 为什…...
YOLOv9改进策略【注意力机制篇】| 2024 SCSA-CBAM 空间和通道的协同注意模块
一、本文介绍 本文记录的是基于SCSA-CBAM注意力模块的YOLOv9目标检测改进方法研究。现有注意力方法在空间-通道协同方面未充分挖掘其潜力,缺乏对多语义信息的充分利用来引导特征和缓解语义差异。SCSA-CBAM注意力模块构建一个空间-通道协同机制,使空间注意力引导通道注意力增…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
