Rozšířené sledování očí v nativním modulu
Rozšířené sledování očí je nová funkce v HoloLens 2. Je to nadmnožina standardního sledování očí, která poskytuje pouze kombinovaná data o pohledu. Rozšířené sledování očí také poskytuje individuální data o pohledu a umožňuje aplikacím nastavit různé snímkové frekvence pro data pohledu, například 30, 60 a 90 snímků za sekundu. Jiné funkce, jako je otevření očí a vzruch očí, HoloLens 2 v tuto chvíli nepodporují.
Sada Extended Eye Tracking SDK umožňuje aplikacím přístup k datům a funkcím rozšířeného sledování očí. Dá se použít společně s rozhraními WinRT API nebo rozhraními OpenXR API.
Tento článek popisuje způsoby použití rozšířené sady SDK pro sledování očí v nativním modulu (C# nebo C++/WinRT) společně s rozhraními WinRT API.
Nastavení projektu
-
Vytvořte
Holographic DirectX 11 App (Universal Windows)
projekt neboHolographic DirectX 11 App (Universal Windows) (C++/WinRT)
pomocí sady Visual Studio 2019 nebo novější nebo otevřete existující holografický projekt sady Visual Studio. - Importujte do projektu rozšířenou sadu SDK pro sledování očí.
- V Průzkumník řešení sady Visual Studio klikněte pravým tlačítkem na projekt –> Správa balíčků NuGet...
- Ujistěte se, že zdroj balíčku v pravém horním rohu ukazuje na nuget.org: https://api.nuget.org/v3/index.json
- Klikněte na kartu Prohlížeč a vyhledejte
Microsoft.MixedReality.EyeTracking
. - Kliknutím na tlačítko Instalovat nainstalujte nejnovější verzi sady SDK.
- Nastavení možnosti vstupu pohledu
- V Průzkumník řešení poklikejte na soubor Package.appxmanifest.
- Klikněte na kartu Capabilities (Schopnosti ) a zaškrtněte políčko Gaze Input (Vstup pohledu).
- Zahrňte hlavní soubor a použijte obor názvů.
- Pro projekt jazyka C#:
using Microsoft.MixedReality.EyeTracking;
- Projekt C++/WinRT:
#include <winrt/Microsoft.MixedReality.EyeTracking.h> using namespace winrt::Microsoft::MixedReality::EyeTracking;
- Využívejte rozšířená rozhraní API sady Eye Tracking SDK a implementujte logiku.
- Sestavení a nasazení do HoloLensu
Přehled kroků pro získání dat pohledu
Získání dat z pohledu do očí prostřednictvím rozhraní API rozšířené sady Eye Tracking SDK vyžaduje následující kroky:
- Získejte přístup k funkcím sledování očí od uživatele.
- Sledujte spojení a odpojení sledování zraku.
- Otevřete sledování pohledu a zadejte dotaz na jeho možnosti.
- Opakované čtení dat pohledu ze sledování očí.
- Přenos dat pohledu do jiných systémů SpatialCoordinateSystems
Získání přístupu k funkcím sledování očí
Aby bylo možné použít jakékoli informace související s očima, musí aplikace nejprve požádat o souhlas uživatele.
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);
Detekce sledování očí
Detekce sledování očí se provádí pomocí EyeGazeTrackerWatcher
třídy .
EyeGazeTrackerAdded
a EyeGazeTrackerRemoved
události jsou vyvolány, když je detekován nebo odpojen sledovací nástroj pro sledování očí.
Sledovací proces musí být explicitně spuštěn pomocí StartAsync()
metody , která se dokončí asynchronně, když sledovací moduly, které jsou již připojeny, jsou signalizovány prostřednictvím EyeGazeTrackerAdded
události.
Při detekci sledování očí je instance předána EyeGazeTracker
do aplikace v EyeGazeTrackerAdded
parametrech události; reciproční, když je sledovací modul odpojen, je odpovídající EyeGazeTracker
instance předána události 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)
{
...
}
Otevřít sledování očí
Při přijetí EyeGazeTracker
instance musí aplikace nejprve otevřít voláním OpenAsync()
metody . V případě potřeby se pak může dotazovat na možnosti sledování. Metoda OpenAsync()
přebírá logický parametr, který označuje, jestli aplikace potřebuje přístup k funkcím, které nepatří do standardního sledování očí, jako jsou například jednotlivé vektory pohledů očí nebo změna snímkové frekvence sledování.
Kombinovaný pohled je povinná funkce podporovaná všemi sledovacími nástroji pro sledování očí. Další funkce, jako je přístup k individuálnímu pohledu, jsou volitelné a v závislosti na sledování a jeho ovladači můžou být podporované nebo ne. U těchto volitelných funkcí třída zveřejňuje vlastnost označující EyeGazeTracker
, jestli je tato funkce podporovaná – například vlastnost, která určuje, AreLeftAndRightGazesSupported
jestli zařízení podporuje informace o individuálním pohledu.
Všechny prostorové informace vystavené sledováním pohledu se publikují v souvislosti se samotným sledovacím zařízením, který je identifikovaný pomocí ID dynamického uzlu. Použití nodeId k získání SpatialCoordinateSystem
rozhraní API WinRT by mohlo transformovat souřadnice dat pohledu do jiného souřadnicového systému.
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
}
}
Nastavit snímkovou frekvenci sledování pohledu na oči
Vlastnost EyeGazeTracker.SupportedTargetFrameRates
vrátí seznam cílové snímkové frekvence podporované sledováním. HoloLens 2 podporuje 30, 60 a 90 snímků za sekundu.
EyeGazeTracker.SetTargetFrameRate()
Pomocí metody nastavte cílovou snímkovou frekvenci.
// 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();
Čtení dat pohledu ze sledování očí
Sledování očí pravidelně publikuje své stavy v kruhové vyrovnávací paměti. To aplikaci umožňuje číst stav sledování v čase, který patří do malého časového rozsahu. Umožňuje například načtení posledního stavu sledování nebo jeho stavu v době nějaké události, jako je gesto ruky od uživatele.
Metody, které načítají stav sledování jako EyeGazeTrackerReading
instanci:
Metody
TryGetReadingAtTimestamp()
aTryGetReadingAtSystemRelativeTime()
vrátíEyeGazeTrackerReading
čas, který je nejblíže času, který aplikace uplynula. Sledování řídí plán publikování, takže vrácené čtení může být o něco starší nebo novější než čas požadavku. VlastnostiEyeGazeTrackerReading.Timestamp
aEyeGazeTrackerReading.SystemRelativeTime
umožňují aplikaci zjistit přesný čas publikovaného stavu.Metody
TryGetReadingAfterTimestamp()
aTryGetReadingAfterSystemRelativeTime()
vrátí prvníEyeGazeTrackerReading
s časovým razítkem, které je naprosto vyšší než čas předaný jako parametr. To umožňuje aplikaci postupně číst všechny stavy publikované sledováním. Všimněte si, že všechny tyto metody dotazují existující vyrovnávací paměť a že se okamžitě vrátí. Pokud není k dispozici žádný stav, vrátí hodnotu null (jinými slovy, aplikace nebude čekat na publikování stavu).
Kromě časového razítka EyeGazeTrackerReading
má IsCalibrationValid
instance vlastnost, která označuje, jestli je kalibrace sledování očí platná nebo ne.
Data pohledu je možné načíst pomocí sady metod, jako TryGetCombinedEyeGazeInTrackerSpace()
je nebo TryGetLeftEyeGazeInTrackerSpace()
. Všechny tyto metody vrátí logickou hodnotu označující úspěch. Selhání získání některých dat může znamenat, že data nejsou podporovaná (EyeGazeTracker
mají vlastnosti pro zjištění tohoto případu) nebo že sledovací modul nemohl získat data (například neplatná kalibrace nebo skryté oko).
Pokud například aplikace chce zobrazit kurzor odpovídající kombinovanému pohledu, může se na sledovací modul dotazovat pomocí časového razítka předpovědi rámce, který se připravuje, následujícím způsobem.
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
}
}
Transformace dat pohledu na jiný SpatialCoordinateSystem
Rozhraní WINRT API, která vracejí prostorová data, jako je pozice, vždy vyžadují a PerceptionTimestamp
SpatialCoordinateSystem
. Například k načtení kombinovaného pohledu HoloLens 2 pomocí rozhraní WinRT API, rozhraní API SpatialPointerPose.TryGetAtTimestamp() vyžaduje dva parametry: SpatialCoordinateSystem
a a PerceptionTimestamp
. Když se ke kombinovanému pohledu přistupuje prostřednictvím SpatialPointerPose.Eyes.Gaze
objektu , je jeho původ a směr vyjádřeny v předaném objektu SpatialCoordinateSystem
.
Rozšířená rozhraní API sady SDK pro sledování tye nemusí přijímat SpatialCoordinateSystem
a data pohledu jsou vždy vyjádřena v souřadnicovém systému sledovacího modulu. Tato data pohledu ale můžete transformovat do jiného souřadnicového systému pomocí pozice sledování, která souvisí s jiným souřadnicovým systémem.
Jak je uvedeno v části s názvem "Open Eye Gaze Tracker" (Otevřít sledování očí), pro sledování očí získáte
SpatialLocator
volánímWindows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateLocatorForNode()
sEyeGazeTracker.TrackerSpaceLocatorNodeId
vlastností .Počátky pohledu a směry získané přes
EyeGazeTrackerReading
se vztahují ke sledování očí.SpatialLocator.TryLocateAtTimestamp()
vrátí úplnou polohu 6DoF sledování pohledu na danéPerceptionTimeStamp
a související s danouSpatialCoordinateSystem
, která by mohla být použita k vytvoření transformační matice Matrix4x4.Použijte transformační matici Matrix4x4 vytvořenou k přenosu počátků a směrů pohledu do jiného SpatialCoordinateSystem.
Následující ukázky kódu ukazují, jak vypočítat pozici datové krychle umístěné ve směru kombinovaného pohledu, dva metry před počátek pohledu;
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));
}
}
}
Referenční informace k rozhraní API rozšířené sady SDK pro sledování očí
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);
}
}