Pelacakan mata yang diperluas di mesin asli
Pelacakan mata yang diperluas adalah kemampuan baru dalam HoloLens 2. Ini adalah superset dari pelacakan mata standar, yang hanya menyediakan data tatapan mata gabungan. Pelacakan mata yang diperluas juga menyediakan data tatapan mata individu dan memungkinkan aplikasi untuk mengatur framerate yang berbeda untuk data tatapan, seperti 30, 60, dan 90fps. Fitur lain seperti keterbukaan mata dan puncak mata tidak didukung oleh HoloLens 2 saat ini.
Extended Eye Tracking SDK memungkinkan aplikasi mengakses data dan fitur pelacakan mata yang diperluas. Ini dapat digunakan bersama dengan API WinRT atau Api OpenXR.
Artikel ini membahas cara menggunakan SDK pelacakan mata yang diperluas di mesin asli (C# atau C++/WinRT), bersama dengan API WinRT.
Penyusunan proyek
-
Buat
Holographic DirectX 11 App (Universal Windows)
proyek atauHolographic DirectX 11 App (Universal Windows) (C++/WinRT)
dengan Visual Studio 2019 atau yang lebih baru, atau buka proyek Visual Studio holografik yang sudah ada. - Impor SDK pelacakan mata yang diperluas ke dalam proyek.
- Di Penjelajah Solusi Visual Studio, klik kanan proyek Anda -> Kelola Paket NuGet...
- Pastikan sumber Paket di sudut kanan atas menunjuk ke nuget.org: https://api.nuget.org/v3/index.json
- Klik tab Browser, lalu cari
Microsoft.MixedReality.EyeTracking
. - Klik tombol Instal untuk menginstal SDK versi terbaru.
- Atur kemampuan Input Tatapan
- Klik dua kali file Package.appxmanifest di Penjelajah Solusi.
- Klik tab Kemampuan , lalu periksa Input Tatapan.
- Sertakan file kepala dan gunakan namespace layanan.
- Untuk proyek C#:
using Microsoft.MixedReality.EyeTracking;
- Untuk proyek C++/WinRT:
#include <winrt/Microsoft.MixedReality.EyeTracking.h> using namespace winrt::Microsoft::MixedReality::EyeTracking;
- Gunakan API SDK pelacakan mata yang diperluas dan terapkan logika Anda.
- Bangun dan sebarkan ke HoloLens.
Gambaran umum langkah-langkah untuk mendapatkan data tatapan
Mendapatkan data tatapan mata melalui EXTENDED Eye Tracking SDK API memerlukan langkah-langkah berikut:
- Dapatkan akses ke fitur Pelacakan Mata dari pengguna.
- Perhatikan koneksi pelacak tatapan mata dan pemutusan sambungan.
- Buka pelacak tatapan mata, lalu kueri kemampuannya.
- Berulang kali membaca data tatapan dari pelacak tatapan mata.
- Transfer data tatapan ke SpatialCoordinateSystems lainnya.
Mendapatkan akses ke fitur pelacakan mata
Untuk menggunakan informasi terkait mata apa pun, aplikasi harus terlebih dahulu meminta persetujuan pengguna.
var status = await Windows.Perception.People.EyesPose.RequestAccessAsync();
bool useGaze = (status == Windows.UI.Input.GazeInputAccessStatus.Allowed);
auto accessStatus = co_await winrt::Windows::Perception::People::EyesPose::RequestAccessAsync();
bool useGaze = (accessStatus.get() == winrt::Windows::UI::Input::GazeInputAccessStatus::Allowed);
Mendeteksi pelacak tatapan mata
Deteksi pelacak tatapan mata dilakukan melalui penggunaan EyeGazeTrackerWatcher
kelas .
EyeGazeTrackerAdded
dan EyeGazeTrackerRemoved
peristiwa masing-masing dinaikkan ketika pelacak tatapan mata terdeteksi atau terputus.
Pengamat harus secara eksplisit dimulai dengan StartAsync()
metode , yang selesai secara asinkron ketika pelacak yang sudah terhubung telah disinyalkan melalui EyeGazeTrackerAdded
peristiwa.
Ketika pelacak tatapan mata terdeteksi, EyeGazeTracker
instans diteruskan ke aplikasi dalam EyeGazeTrackerAdded
parameter peristiwa; secara timbal balik, ketika pelacak terputus, instans yang sesuai EyeGazeTracker
diteruskan ke peristiwa EyeGazeTrackerRemoved.
EyeGazeTrackerWatcher watcher = new EyeGazeTrackerWatcher();
watcher.EyeGazeTrackerAdded += _watcher_EyeGazeTrackerAdded;
watcher.EyeGazeTrackerRemoved += _watcher_EyeGazeTrackerRemoved;
await watcher.StartAsync();
...
private async void _watcher_EyeGazeTrackerAdded(object sender, EyeGazeTracker e)
{
// Implementation is in next section
}
private void _watcher_EyeGazeTrackerRemoved(object sender, EyeGazeTracker e)
{
...
}
EyeGazeTrackerWatcher watcher;
watcher.EyeGazeTrackerAdded(std::bind(&SampleEyeTrackingNugetClientAppMain::OnEyeGazeTrackerAdded, this, _1, _2));
watcher.EyeGazeTrackerRemoved(std::bind(&SampleEyeTrackingNugetClientAppMain::OnEyeGazeTrackerRemoved, this, _1, _2));
co_await watcher.StartAsync();
...
winrt::Windows::Foundation::IAsyncAction SampleAppMain::OnEyeGazeTrackerAdded(const EyeGazeTrackerWatcher& sender, const EyeGazeTracker& tracker)
{
// Implementation is in next section
}
void SampleAppMain::OnEyeGazeTrackerRemoved(const EyeGazeTrackerWatcher& sender, const EyeGazeTracker& tracker)
{
...
}
Buka pelacak tatapan mata
Saat menerima EyeGazeTracker
instans, aplikasi harus terlebih dahulu membukanya dengan memanggil OpenAsync()
metode . Kemudian dapat mengkueri kemampuan pelacak jika diperlukan. Metode ini OpenAsync()
mengambil parameter boolean; ini menunjukkan apakah aplikasi perlu mengakses fitur yang bukan milik pelacakan mata standar, seperti vektor tatapan mata individu atau mengubah framerate pelacak.
Tatapan gabungan adalah fitur wajib yang didukung oleh semua pelacak tatapan mata. Fitur lain, seperti akses ke tatapan individu, bersifat opsional dan mungkin didukung atau tidak tergantung pada pelacak dan drivernya. Untuk fitur opsional ini, EyeGazeTracker
kelas mengekspos properti yang menunjukkan apakah fitur didukung--misalnya, AreLeftAndRightGazesSupported
properti , yang menunjukkan apakah info tatapan mata individu didukung oleh perangkat.
Semua informasi spasial yang diekspos oleh pelacak tatapan mata diterbitkan terkait dengan pelacak itu sendiri, yang diidentifikasi oleh ID Node Dinamis. Menggunakan nodeId untuk mendapatkan SpatialCoordinateSystem
dengan API WinRT dapat mengubah koordinat data tatapan menjadi sistem koordinat lain.
private async void _watcher_EyeGazeTrackerAdded(object sender, EyeGazeTracker e)
{
try
{
// Try to open the tracker with access to restricted features
await e.OpenAsync(true);
// If it has succeeded, store it for future use
_tracker = e;
// Check support for individual eye gaze
bool supportsIndividualEyeGaze = _tracker.AreLeftAndRightGazesSupported;
// Get a spatial locator for the tracker, this will be used to transfer the gaze data to other coordinate systems later
var trackerNodeId = e.TrackerSpaceLocatorNodeId;
_trackerLocator = Windows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateLocatorForNode(trackerNodeId);
}
catch (Exception ex)
{
// Unable to open the tracker
}
}
winrt::Windows::Foundation::IAsyncAction SampleEyeTrackingNugetClientAppMain::OnEyeGazeTrackerAdded(const EyeGazeTrackerWatcher&, const EyeGazeTracker& tracker)
{
auto newTracker = tracker;
try
{
// Try to open the tracker with access to restricted features
co_await newTracker.OpenAsync(true);
// If it has succeeded, store it for future use
m_gazeTracker = newTracker;
// Check support for individual eye gaze
const bool supportsIndividualEyeGaze = m_gazeTracker.AreLeftAndRightGazesSupported();
// Get a spatial locator for the tracker. This will be used to transfer the gaze data to other coordinate systems later
const auto trackerNodeId = m_gazeTracker.TrackerSpaceLocatorNodeId();
m_trackerLocator = winrt::Windows::Perception::Spatial::Preview::SpatialGraphInteropPreview::CreateLocatorForNode(trackerNodeId);
}
catch (const winrt::hresult_error& e)
{
// Unable to open the tracker
}
}
Mengatur laju bingkai pelacak tatapan mata
Properti EyeGazeTracker.SupportedTargetFrameRates
mengembalikan daftar kecepatan bingkai target yang didukung oleh pelacak. HoloLens 2 mendukung 30, 60, dan 90fps.
EyeGazeTracker.SetTargetFrameRate()
Gunakan metode untuk mengatur kecepatan bingkai target.
// This returns a list of supported frame rate: 30, 60, 90 fps in order
var supportedFrameRates = _tracker.SupportedTargetFrameRates;
// Sets the tracker at the highest supported frame rate (90 fps)
var newFrameRate = supportedFrameRates[supportedFrameRates.Count - 1];
_tracker.SetTargetFrameRate(newFrameRate);
uint newFramesPerSecond = newFrameRate.FramesPerSecond;
// This returns a list of supported frame rate: 30, 60, 90 fps in order
const auto supportedFrameRates = m_gazeTracker.SupportedTargetFrameRates();
// Sets the tracker at the highest supported frame rate (90 fps)
const auto newFrameRate = supportedFrameRates.GetAt(supportedFrameRates.Size() - 1);
m_gazeTracker.SetTargetFrameRate(newFrameRate);
const uint32_t newFramesPerSecond = newFrameRate.FramesPerSecond();
Membaca data tatapan dari pelacak tatapan mata
Pelacak tatapan mata menerbitkan statusnya secara berkala dalam buffer melingkar. Ini memungkinkan aplikasi untuk membaca status pelacak pada satu waktu milik rentang waktu kecil. Ini memungkinkan, misalnya, pengambilan status pelacak terbaru, atau statusnya pada saat beberapa peristiwa seperti gerakan tangan dari pengguna.
Metode yang mengambil status pelacak sebagai EyeGazeTrackerReading
instans:
Metode
TryGetReadingAtTimestamp()
danTryGetReadingAtSystemRelativeTime()
mengembalikan yangEyeGazeTrackerReading
paling dekat dengan waktu yang dilewati oleh aplikasi. Pelacak mengontrol jadwal penerbitan, sehingga pembacaan yang dikembalikan mungkin sedikit lebih lama atau lebih baru dari waktu permintaan. PropertiEyeGazeTrackerReading.Timestamp
danEyeGazeTrackerReading.SystemRelativeTime
memungkinkan aplikasi untuk mengetahui waktu yang tepat dari status yang diterbitkan.Metode
TryGetReadingAfterTimestamp()
danTryGetReadingAfterSystemRelativeTime()
mengembalikan yang pertamaEyeGazeTrackerReading
dengan tanda waktu yang benar-benar lebih unggul daripada waktu yang diteruskan sebagai parameter. Ini memungkinkan aplikasi untuk membaca semua status yang diterbitkan oleh pelacak secara berurutan. Perhatikan bahwa semua metode ini mengkueri buffer yang ada dan segera kembali. Jika tidak ada status yang tersedia, mereka akan mengembalikan null (dengan kata lain, mereka tidak akan membuat aplikasi menunggu status diterbitkan).
Selain tanda waktunya, EyeGazeTrackerReading
instans memiliki IsCalibrationValid
properti , yang menunjukkan apakah kalibrasi pelacak mata valid atau tidak.
Terakhir, data tatapan dapat diambil melalui serangkaian metode seperti TryGetCombinedEyeGazeInTrackerSpace()
atau TryGetLeftEyeGazeInTrackerSpace()
. Semua metode ini mengembalikan boolean yang menunjukkan keberhasilan. Kegagalan untuk mendapatkan beberapa data mungkin berarti bahwa data tidak didukung (EyeGazeTracker
memiliki properti untuk mendeteksi kasus ini) atau bahwa pelacak tidak bisa mendapatkan data (misalnya, kalibrasi atau mata tersembunyi yang tidak valid).
Jika, misalnya, aplikasi ingin menampilkan kursor yang sesuai dengan tatapan gabungan, aplikasi dapat meminta pelacak menggunakan tanda waktu prediksi bingkai yang disiapkan sebagai berikut.
var holographicFrame = holographicSpace.CreateNextFrame();
var prediction = holographicFrame.CurrentPrediction;
var predictionTimestamp = prediction.Timestamp;
var reading = _tracker.TryGetReadingAtTimestamp(predictionTimestamp.TargetTime.DateTime);
if (reading != null)
{
// Vector3 needs the System.Numerics namespace
if (reading.TryGetCombinedEyeGazeInTrackerSpace(out Vector3 gazeOrigin, out Vector3 gazeDirection))
{
// Use gazeOrigin and gazeDirection to display the cursor
}
}
auto holographicFrame = m_holographicSpace.CreateNextFrame();
auto prediction = holographicFrame.CurrentPrediction();
auto predictionTimestamp = prediction.Timestamp();
const auto reading = m_gazeTracker.TryGetReadingAtTimestamp(predictionTimestamp.TargetTime());
if (reading)
{
float3 gazeOrigin;
float3 gazeDirection;
if (reading.TryGetCombinedEyeGazeInTrackerSpace(gazeOrigin, gazeDirection))
{
// Use gazeOrigin and gazeDirection to display the cursor
}
}
Mengubah data tatapan ke SpatialCoordinateSystem lainnya
API WinRT yang mengembalikan data spasial seperti posisi selalu memerlukan PerceptionTimestamp
dan SpatialCoordinateSystem
. Misalnya, untuk mengambil tatapan gabungan HoloLens 2 menggunakan API WinRT, API SpatialPointerPose.TryGetAtTimestamp() memerlukan dua parameter: a SpatialCoordinateSystem
dan PerceptionTimestamp
. Ketika tatapan gabungan kemudian diakses melalui SpatialPointerPose.Eyes.Gaze
, asal dan arahnya dinyatakan dalam diteruskan SpatialCoordinateSystem
.
Pelacakan tye YANG diperluas SDK API tidak perlu mengambil SpatialCoordinateSystem
dan data tatapan selalu diekspresikan dalam sistem koordinat pelacak. Tetapi Anda dapat mengubah data tatapan tersebut ke sistem koordinat lain dengan pose pelacak yang terkait dengan sistem koordinat lainnya.
Seperti yang disebutkan bagian di atas bernama "Pelacak tatapan mata terbuka", untuk mendapatkan
SpatialLocator
pelacak tatapan mata, panggilWindows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateLocatorForNode()
denganEyeGazeTracker.TrackerSpaceLocatorNodeId
properti .Asal tatapan dan petunjuk arah yang diambil
EyeGazeTrackerReading
terkait dengan pelacak tatapan mata.SpatialLocator.TryLocateAtTimestamp()
mengembalikan lokasi 6DoF penuh dari pelacak tatapan mata pada tertentuPerceptionTimeStamp
dan terkait denganSpatialCoordinateSystem
, yang dapat digunakan untuk membangun matriks transformasi Matrix4x4.Gunakan matriks transformasi Matrix4x4 yang dibangun untuk mentransfer asal tatapan dan arah ke SpatialCoordinateSystem lainnya.
Sampel kode berikut menunjukkan cara menghitung posisi kubus yang terletak di arah tatapan gabungan, dua meter di depan asal tatapan;
var predictionTimestamp = prediction.Timestamp;
var stationaryCS = stationaryReferenceFrame.CoordinateSystem;
var trackerLocation = _trackerLocator.TryLocateAtTimestamp(predictionTimestamp, stationaryCS);
if (trackerLocation != null)
{
var trackerToStationaryMatrix = Matrix4x4.CreateFromQuaternion(trackerLocation.Orientation) * Matrix4x4.CreateTranslation(trackerLocation.Position);
var reading = _tracker.TryGetReadingAtTimestamp(predictionTimestamp.TargetTime.DateTime);
if (reading != null)
{
if (reading.TryGetCombinedEyeGazeInTrackerSpace(out Vector3 gazeOriginInTrackerSpace, out Vector3 gazeDirectionInTrackerSpace))
{
var cubePositionInTrackerSpace = gazeOriginInTrackerSpace + 2.0f * gazeDirectionInTrackerSpace;
var cubePositionInStationaryCS = Vector3.Transform(cubePositionInTrackerSpace, trackerToStationaryMatrix);
}
}
}
auto predictionTimestamp = prediction.Timestamp();
auto stationaryCS = m_stationaryReferenceFrame.CoordinateSystem();
auto trackerLocation = m_trackerLocator.TryLocateAtTimestamp(predictionTimestamp, stationaryCS);
if (trackerLocation)
{
auto trackerOrientation = trackerLocation.Orientation();
auto trackerPosition = trackerLocation.Position();
auto trackerToStationaryMatrix = DirectX::XMMatrixRotationQuaternion(DirectX::XMLoadFloat4(reinterpret_cast<const DirectX::XMFLOAT4*>(&trackerOrientation))) * DirectX::XMMatrixTranslationFromVector(DirectX::XMLoadFloat3(&trackerPosition));
const auto reading = m_gazeTracker.TryGetReadingAtTimestamp(predictionTimestamp.TargetTime());
if (reading)
{
float3 gazeOriginInTrackerSpace;
float3 gazeDirectionInTrackerSpace;
if (reading.TryGetCombinedEyeGazeInTrackerSpace(gazeOriginInTrackerSpace, gazeDirectionInTrackerSpace))
{
auto cubePositionInTrackerSpace = gazeOriginInTrackerSpace + 2.0f * gazeDirectionInTrackerSpace;
float3 cubePositionInStationaryCS;
DirectX::XMStoreFloat3(&cubePositionInStationaryCS, DirectX::XMVector3TransformCoord(DirectX::XMLoadFloat3(&cubePositionInTrackerSpace), trackerToStationaryMatrix));
}
}
}
Referensi API dari SDK pelacakan mata yang diperluas
namespace Microsoft.MixedReality.EyeTracking
{
/// <summary>
/// Allow discovery of Eye Gaze Trackers connected to the system
/// This is the only class from Extended Eye Tracking SDK that the application will instantiate,
/// other classes' instances will be returned by method calls or properties.
/// </summary>
public class EyeGazeTrackerWatcher
{
/// <summary>
/// Constructs an instance of the watcher
/// </summary>
public EyeGazeTrackerWatcher();
/// <summary>
/// Starts trackers enumeration.
/// </summary>
/// <returns>Task representing async action; completes when the initial enumeration is completed</returns>
public System.Threading.Tasks.Task StartAsync();
/// <summary>
/// Stop listening to trackers additions and removal
/// </summary>
public void Stop();
/// <summary>
/// Raised when an Eye Gaze tracker is connected
/// </summary>
public event System.EventHandler<EyeGazeTracker> EyeGazeTrackerAdded;
/// <summary>
/// Raised when an Eye Gaze tracker is disconnected
/// </summary>
public event System.EventHandler<EyeGazeTracker> EyeGazeTrackerRemoved;
}
/// <summary>
/// Represents an Eye Tracker device
/// </summary>
public class EyeGazeTracker
{
/// <summary>
/// True if Restricted mode is supported, which means the driver supports to provide individual
/// eye gaze vector and framerate
/// </summary>
public bool IsRestrictedModeSupported;
/// <summary>
/// True if Vergence Distance is supported by tracker
/// </summary>
public bool IsVergenceDistanceSupported;
/// <summary>
/// True if Eye Openness is supported by the driver
/// </summary>
public bool IsEyeOpennessSupported;
/// <summary>
/// True if individual gazes are supported
/// </summary>
public bool AreLeftAndRightGazesSupported;
/// <summary>
/// Get the supported target frame rates of the tracker
/// </summary>
public System.Collections.Generic.IReadOnlyList<EyeGazeTrackerFrameRate> SupportedTargetFrameRates;
/// <summary>
/// NodeId of the tracker, used to retrieve a SpatialLocator or SpatialGraphNode to locate the tracker in the scene
/// for Perception API, use SpatialGraphInteropPreview.CreateLocatorForNode
/// for Mixed Reality OpenXR API, use SpatialGraphNode.FromDynamicNodeId
/// </summary>
public Guid TrackerSpaceLocatorNodeId;
/// <summary>
/// Opens the tracker
/// </summary>
/// <param name="restrictedMode">True if restricted mode active</param>
/// <returns>Task representing async action; completes when the initial enumeration is completed</returns>
public System.Threading.Tasks.Task OpenAsync(bool restrictedMode);
/// <summary>
/// Closes the tracker
/// </summary>
public void Close();
/// <summary>
/// Changes the target frame rate of the tracker
/// </summary>
/// <param name="newFrameRate">Target frame rate</param>
public void SetTargetFrameRate(EyeGazeTrackerFrameRate newFrameRate);
/// <summary>
/// Try to get tracker state at a given timestamp
/// </summary>
/// <param name="timestamp">timestamp</param>
/// <returns>State if available, null otherwise</returns>
public EyeGazeTrackerReading TryGetReadingAtTimestamp(DateTime timestamp);
/// <summary>
/// Try to get tracker state at a system relative time
/// </summary>
/// <param name="time">time</param>
/// <returns>State if available, null otherwise</returns>
public EyeGazeTrackerReading TryGetReadingAtSystemRelativeTime(TimeSpan time);
/// <summary>
/// Try to get first first tracker state after a given timestamp
/// </summary>
/// <param name="timestamp">timestamp</param>
/// <returns>State if available, null otherwise</returns>
public EyeGazeTrackerReading TryGetReadingAfterTimestamp(DateTime timestamp);
/// <summary>
/// Try to get the first tracker state after a system relative time
/// </summary>
/// <param name="time">time</param>
/// <returns>State if available, null otherwise</returns>
public EyeGazeTrackerReading TryGetReadingAfterSystemRelativeTime(TimeSpan time);
}
/// <summary>
/// Represents a Frame Rate supported by an Eye Tracker
/// </summary>
public class EyeGazeTrackerFrameRate
{
/// <summary>
/// Frames per second of the frame rate
/// </summary>
public UInt32 FramesPerSecond;
}
/// <summary>
/// Snapshot of Gaze Tracker state
/// </summary>
public class EyeGazeTrackerReading
{
/// <summary>
/// Timestamp of state
/// </summary>
public DateTime Timestamp;
/// <summary>
/// Timestamp of state as system relative time
/// Its SystemRelativeTime.Ticks could provide the QPC time to locate tracker pose
/// </summary>
public TimeSpan SystemRelativeTime;
/// <summary>
/// Indicates user calibration is valid
/// </summary>
public bool IsCalibrationValid;
/// <summary>
/// Tries to get a vector representing the combined gaze related to the tracker's node
/// </summary>
/// <param name="origin">Origin of the gaze vector</param>
/// <param name="direction">Direction of the gaze vector</param>
/// <returns></returns>
public bool TryGetCombinedEyeGazeInTrackerSpace(out System.Numerics.Vector3 origin, out System.Numerics.Vector3 direction);
/// <summary>
/// Tries to get a vector representing the left eye gaze related to the tracker's node
/// </summary>
/// <param name="origin">Origin of the gaze vector</param>
/// <param name="direction">Direction of the gaze vector</param>
/// <returns></returns>
public bool TryGetLeftEyeGazeInTrackerSpace(out System.Numerics.Vector3 origin, out System.Numerics.Vector3 direction);
/// <summary>
/// Tries to get a vector representing the right eye gaze related to the tracker's node position
/// </summary>
/// <param name="origin">Origin of the gaze vector</param>
/// <param name="direction">Direction of the gaze vector</param>
/// <returns></returns>
public bool TryGetRightEyeGazeInTrackerSpace(out System.Numerics.Vector3 origin, out System.Numerics.Vector3 direction);
/// <summary>
/// Tries to read vergence distance
/// </summary>
/// <param name="value">Vergence distance if available</param>
/// <returns>bool if value is valid</returns>
public bool TryGetVergenceDistance(out float value);
/// <summary>
/// Tries to get left Eye openness information
/// </summary>
/// <param name="value">Eye Openness if valid</param>
/// <returns>bool if value is valid</returns>
public bool TryGetLeftEyeOpenness(out float value);
/// <summary>
/// Tries to get right Eye openness information
/// </summary>
/// <param name="value">Eye Openness if valid</param>
/// <returns>bool if value is valid</returns>
public bool TryGetRightEyeOpenness(out float value);
}
}