[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注意力模块构建一个空间-通道协同机制,使空间注意力引导通道注意力增…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...