By gongqing 2021/12/23

VIVE_SRanipal SDK for Unity 自带眼动案例亲测

用SDK里的案例场景测试Unity开发环境是否搭好
点击查看【bilibili】

眼动数据获取

了解一下原始数据怎么获取对于开发有利无害。但网上搜并不能直接找到讲解,可能这玩意的开发者人数并不多,反而有一些人说这个SDK并不能获取眼动原始数据,挺离谱的,堂堂HTC总不能发布这么没用的SDK。
我最终还是用英文搜索在开发者论坛上挖到了一个代码段。所以我估计那些说不能获取眼动原始数据的人说的原始数据和我想要的不是一个东西,是我一开始查错了。我想要的所谓原始数据其实也是经过处理的眼动数据,当然对我来说了解到这层就够了。
后来发现其实手册上详细讲解了这个SDK提供的诸多方法和参数等,如果我下载了SDK之后第一时间就看手册,就不会有这么麻烦的过程了。这个事告诉我一个道理,有问题应该先看官方提供的手册,不要手册一眼都不看就先百度或者CSDN。

从开发者论坛上找到的工作人员的回答

看起来这个代码段可以获取瞳孔直径(float型)、瞳孔位置(二维坐标)和睁眼数据(也是float型,可能是睁眼大小吧)

  1. private static EyeData eyeData;
  2. private static VerboseData verboseData;
  3. private float puilDiameterLeft, pupilDiameterRight;
  4. private Vector2 pupilPositionLeft, pupilPositionRight;
  5. private float eyeOpenLeft, eyeOpenRight;
  6. void Update(){
  7. SRanipal_Eye.GetEyeData(ref eyeData);
  8. SRanipal_Eye.GetVerboseData(out verboseData);
  9. //pupil diameter
  10. pupilDiameterLeft = eyeData.verbose_data.left.pupil_diameter_mm;
  11. puilDiameterRight = eyeData.verbose_data.right.pupil_diameter_mm;
  12. //pupil positions
  13. pupilPositionLeft = eyeData.verbose_data.left.pupil_position_in_sensor_area;
  14. pupilPositionRight = eyeData.verbose_data.right.pupil_position_in_sensor_area;
  15. //eye open
  16. eyeOpenLeft = eyeData.verbose_data.left.eye_openness;
  17. eyeOpenRight = eyeData.verbose_data.right.eye_openness;
  18. }

我修改并测试过的代码

可惜所谓工作人员给的代码跑了一下就报错了,问题出在GetEyeData这个方法*上,所以我看了一下SDK附带的手册,修改了一下,跑通了。

*关于“方法”这个叫法:我看到手册上叫它们成员函数(Member Function),但之前学C#的教程里又管这些叫方法,也许可能大概在C#中直接叫成“方法”有些不严谨?我不太专业,不太能确定这么叫对不对,但出于方便,就先这么叫吧……

1640241684614_69E3197D-9117-401b-A464-4E3B547B4D73.png
从手册来看,除了这些参数,还可以获取注视起点gaze_origin_mm和注视方向gaze_direction_normalized。手册里还放了个原理图。因为SDK里有GetGazeRay的方法,所以我就不去试这两个参数了。
image.png

  1. using System.Runtime.InteropServices;
  2. using UnityEngine;
  3. using UnityEngine.Assertions;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System;
  7. namespace ViveSR
  8. {
  9. namespace anipal
  10. {
  11. namespace Eye
  12. {
  13. public class eyeDataget : MonoBehaviour
  14. {
  15. private static EyeData eyeData;
  16. private static VerboseData verboseData;
  17. private float pupilDiameterLeft, pupilDiameterRight;
  18. private Vector2 pupilPositionLeft, pupilPositionRight;
  19. private float eyeOpenLeft, eyeOpenRight;
  20. void Update(){
  21. //论坛上找到的代码写的是SRanipal_Eye,报错,查了手册发现应该改成SRanipal_Eye_API。GetEyeData是从眼动模块中获取数据的方法
  22. SRanipal_Eye_API.GetEyeData(ref eyeData);
  23. //获取详细数据的方法,这样使用不需要在Unity Inspector面板中给SRanipal_Eye_Framework打开eye data callback,如果GetVerboseData(out VerboseData data,EyeData eye_data)这样使用则要打开
  24. SRanipal_Eye.GetVerboseData(out verboseData);
  25. //pupil diameter 瞳孔直径
  26. pupilDiameterLeft = eyeData.verbose_data.left.pupil_diameter_mm;
  27. pupilDiameterRight = eyeData.verbose_data.right.pupil_diameter_mm;
  28. //pupil positions 瞳孔位置
  29. //pupil_position_in_sensor_area手册里写的是The normalized position of a pupil in [0,1],给坐标归一化了
  30. pupilPositionLeft = eyeData.verbose_data.left.pupil_position_in_sensor_area;
  31. pupilPositionRight = eyeData.verbose_data.right.pupil_position_in_sensor_area;
  32. //eye open 睁眼
  33. //eye_openness手册里写的是A value representing how open the eye is,也就是睁眼程度,从输出来看是在0-1之间,也归一化了
  34. eyeOpenLeft = eyeData.verbose_data.left.eye_openness;
  35. eyeOpenRight = eyeData.verbose_data.right.eye_openness;
  36. //控制台打印几个出来看一下
  37. Debug.Log("左眼瞳孔直径:"+pupilDiameterLeft+"左眼位置坐标:"+pupilPositionLeft+"左眼睁眼程度"+eyeOpenLeft);
  38. }
  39. }
  40. }
  41. }
  42. }

注视点渲染:用于VR人机交互实验

方案一:LineRenderer

暂时没找到Unity的插件,但原理估计是按眼动位置发出射线,碰到物体,且注视时长达到阈值,即判定为凝视,渲染射线所触及的该区域。
所以利用射线碰撞检测的原理,我尝试写了一个,能实现眼动轨迹绘制,如果注视到物体(开碰撞盒)的话轨迹会绘制在物体的表面(即绘制射线和物体相交点的运动轨迹),不会穿过物体。为了防止轨迹穿模,我给轨迹往视线向量的反方向移动了一点点,但在弧形物体的表面效果还是不太好,仿佛是采样频率太低了之类的问题。
点击查看【bilibili】
所以改了一下LineRenderer的代码,让线渲染快一点,然后把轨迹绘制方法设为当射线检测到碰撞才调用。现在大概是这个效果。当然这是没加注视时间的效果,翻了一遍手册,没找到有关注视时长的方法,有可能得自己写。(可能直接用时间戳?)
image.png
目前视线的可视化和轨迹的可视化都用的是LineRenderer,所以这版视觉效果比较粗糙,主要先走通原理。就是不知道为什么圆柱体的转角处路径绘制不上,其他几何体都没啥问题。之后得研究一下LineRenderer,要是实现不了我的想法就弃了,换个方案。

方案二:RaycastHit.textureCoord

直接在物体的纹理上绘制。用Unity官方的一个API——RaycastHit.textureCoord: The uv texture coordinate at the collision location。原理是检测射线碰撞到物体碰撞盒的uv纹理坐标,然后用SetPixel方法重绘此处的纹理。
代码基本直接用了官方API说明里的案例,所以不注释了。

  1. Renderer rend = hit.transform.GetComponent<Renderer>();
  2. MeshCollider meshCollider = hit.collider as MeshCollider;
  3. if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null){
  4. return;
  5. Texture2D tex = rend.material.mainTexture as Texture2D;
  6. Vector2 pixelUV = hit.textureCoord;
  7. pixelUV.x *= tex.width;
  8. pixelUV.y *= tex.height;
  9. Color colorcustom = new Color(100f/255f,200f/255f,100f/255f,1f/255f);
  10. tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, colorcustom);
  11. tex.SetPixel((int)pixelUV.x-1, (int)pixelUV.y-1, colorcustom);
  12. tex.SetPixel((int)pixelUV.x+1, (int)pixelUV.y+1, colorcustom);
  13. tex.SetPixel((int)pixelUV.x+1, (int)pixelUV.y-1, colorcustom);
  14. tex.SetPixel((int)pixelUV.x-1, (int)pixelUV.y+1, colorcustom);
  15. tex.Apply();

这个方案遇到了很多坑,包括(1)碰撞盒必须要是MeshCollider类型的,其他都不行,一个对象也不能同时开着多个类型的碰撞盒(2)对象必须要有mainTexture(2)texture要允许读写(3)SetPixel仅适用于RGBA32,ARGB32,RGB24和Alpha8纹理格式-> 官网说法
通过同时在不同位置绘制多个像素点,可以实现改变绘制笔刷的形态,颜色也可以自己定义。然而这个方法似乎并不能改变笔刷的透明度,可能因为这个方法不是在纹理上绘制,而是改变纹理的像素点,所以不能直接改变纹理的透明度。
这个方案能确保轨迹只在物体表面绘制,不会绘制在空间中的其他地方,而且也能保存下绘制了轨迹的纹理图片,效果也相对理想。
点击查看【bilibili】
image.pngimage.png

方案三:用Tobii pro sdk

虽然官网写着支持带眼动模块的HTC头显,但我试了一下这个SDK,在连接了HTC VIVE pro eye 的时候sdk并不能获取眼动设备,所以估计是不支持这个设备的。

待解决问题

  1. 注视时长怎么测?
  2. 可视化怎么做?
  3. 实时注视点渲染有什么用?
  4. 人机实验不一定需要实时的注视点渲染,所以怎么导出实验数据,并快速完成后期渲染?

    VIVE Facial Tracker 官方案例亲测

    点击查看【bilibili】