Dela via


Utökad ögonspårning i inbyggd motor

Utökad ögonspårning är en ny funktion i HoloLens 2. Det är en supermängd av standard ögonspårning, som bara ger kombinerade ögonögadata. Utökad ögonspårning ger också individuella ögonögadata och gör det möjligt för program att ange olika framerates för blickdata, till exempel 30, 60 och 90fps. Andra funktioner som ögonöppning och ögonkantighet stöds inte av HoloLens 2 just nu.

Extended Eye Tracking SDK gör det möjligt för program att komma åt data och funktioner i utökad ögonspårning. Den kan användas tillsammans med WinRT-API:er eller OpenXR-API:er.

Den här artikeln beskriver hur du använder SDK för utökad ögonspårning i den interna motorn (C# eller C++/WinRT), tillsammans med WinRT-API:er.

Projektkonfiguration

  1. Skapa ett Holographic DirectX 11 App (Universal Windows) eller Holographic DirectX 11 App (Universal Windows) (C++/WinRT) ett projekt med Visual Studio 2019 eller senare, eller öppna ditt befintliga holografiska Visual Studio-projekt.
  2. Importera SDK:n för utökad ögonspårning till projektet.
    1. I Visual Studio Solution Explorer högerklickar du på projektet –> Hantera NuGet-paket...
    2. Kontrollera att paketkällan i det övre högra hörnet pekar på nuget.org: https://api.nuget.org/v3/index.json
    3. Klicka på fliken Webbläsare och sök Microsoft.MixedReality.EyeTrackingsedan .
    4. Klicka på knappen Installera för att installera den senaste versionen av SDK: et.
      Skärmbild av Nuget-paketet Eye Tracking SDK.
  3. Ange gaze-indatafunktion
    1. Dubbelklicka på filen Package.appxmanifest i Solution Explorer.
    2. Klicka på fliken Funktioner och kontrollera sedan gaze-indata.
  4. Inkludera huvudfil och använd namnområde.
    • För ett C#-projekt:
    using Microsoft.MixedReality.EyeTracking;
    
    • För ett C++/WinRT-projekt:
    #include <winrt/Microsoft.MixedReality.EyeTracking.h>
    using namespace winrt::Microsoft::MixedReality::EyeTracking;
    
  5. Använd SDK-API:erna för utökad ögonspårning och implementera din logik.
  6. Skapa och distribuera till HoloLens.

Stegöversikt för att hämta blickdata

Att hämta ögonögadata via SDK-API:erna för utökad ögonspårning kräver följande steg:

  1. Få åtkomst till funktionerna för ögonspårning från användaren.
  2. Titta efter eye gaze tracker anslutningar och frånkopplingar.
  3. Öppna ögonögaspåraren och fråga sedan dess funktioner.
  4. Läs blickdata upprepade gånger från ögonögaspåraren.
  5. Överför blickdata till andra SpatialCoordinateSystems.

Få åtkomst till funktionerna för ögonspårning

För att kunna använda ögonrelaterad information måste programmet först begära användarmedgivande.

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);

Identifiera ögonögaspårare

Identifiering av ögonögaspårare görs med hjälp av EyeGazeTrackerWatcher klassen. EyeGazeTrackerAdded och EyeGazeTrackerRemoved händelser höjs när en ögonögespårare identifieras eller kopplas bort.

Bevakaren måste uttryckligen startas med StartAsync() -metoden, som slutförs asynkront när spårare som redan är anslutna har signalerats via EyeGazeTrackerAdded händelsen.

När en ögonögonspårare identifieras skickas en EyeGazeTracker instans till programmet i EyeGazeTrackerAdded händelseparametrarna. När en spårare kopplas från skickas motsvarande EyeGazeTracker instans till händelsen 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)
{
    ...
}

Open eye gaze tracker

När du tar emot en EyeGazeTracker instans måste programmet först öppna den genom att anropa OpenAsync() metoden. Den kan sedan fråga efter spårningsfunktionerna om det behövs. Metoden OpenAsync() tar en boolesk parameter. Detta anger om programmet behöver komma åt funktioner som inte hör till standardögaspårning, till exempel enskilda ögonögavektorer eller ändring av spårarens framerate.

Kombinerad blick är en obligatorisk funktion som stöds av alla ögonögaspårare. Andra funktioner, till exempel åtkomst till individuell blick, är valfria och kan stödjas eller inte beroende på spåraren och dess drivrutin. För dessa valfria funktioner EyeGazeTracker exponerar klassen en egenskap som anger om funktionen stöds, AreLeftAndRightGazesSupported till exempel egenskapen, som anger om individuell blickinformation stöds av enheten.

All rumslig information som exponeras av ögonögonspåraren publiceras relaterad till själva spåraren, som identifieras av ett dynamiskt nod-ID. Om du använder nodeId för att hämta ett SpatialCoordinateSystem med WinRT-API:er kan koordinaterna för blickdata omvandlas till ett annat koordinatsystem.

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
   }
}

Ange bildfrekvens för ögonögaspårare

Egenskapen EyeGazeTracker.SupportedTargetFrameRates returnerar listan över målramhastigheten som stöds av spåraren. HoloLens 2 stöder 30, 60 och 90fps.

EyeGazeTracker.SetTargetFrameRate() Använd metoden för att ange målramhastigheten.

// 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();

Läs blickdata från ögonögaspåraren

En ögonögaspårare publicerar regelbundet sina tillstånd i en cirkulär buffert. Detta gör det möjligt för programmet att läsa tillståndet för spåraren vid en tidpunkt som hör till ett litet tidsintervall. Det gör det till exempel möjligt att hämta det senaste tillståndet för spåraren eller dess tillstånd vid tidpunkten för en händelse, till exempel en handgest från användaren.

Metoder som hämtar spårningstillståndet som en EyeGazeTrackerReading instans:

  • Metoderna TryGetReadingAtTimestamp() och TryGetReadingAtSystemRelativeTime() returnerar närmast EyeGazeTrackerReading den tid som programmet har passerat. Spåraren styr publiceringsschemat, så den returnerade läsningen kan vara något äldre eller nyare än begärandetiden. Egenskaperna EyeGazeTrackerReading.Timestamp och EyeGazeTrackerReading.SystemRelativeTime gör det möjligt för programmet att känna till den exakta tiden för det publicerade tillståndet.

  • Metoderna TryGetReadingAfterTimestamp() och TryGetReadingAfterSystemRelativeTime() returnerar den första EyeGazeTrackerReading med en tidsstämpel som är strikt överlägsen tiden som skickades som en parameter. Detta gör det möjligt för ett program att sekventiellt läsa alla tillstånd som publicerats av spåraren. Observera att alla dessa metoder kör frågor mot den befintliga bufferten och att de returnerar omedelbart. Om inget tillstånd är tillgängligt returnerar de null (med andra ord gör de inte att programmet väntar på att ett tillstånd ska publiceras).

Förutom tidsstämpeln har en EyeGazeTrackerReading instans en IsCalibrationValid egenskap som anger om ögonspårarens kalibrering är giltig eller inte.

Slutligen kan blickdata hämtas via en uppsättning metoder som TryGetCombinedEyeGazeInTrackerSpace() eller TryGetLeftEyeGazeInTrackerSpace(). Alla dessa metoder returnerar ett booleskt värde som indikerar ett lyckat resultat. Om vissa data inte hämtas kan det antingen innebära att data inte stöds (EyeGazeTracker har egenskaper för att identifiera det här fallet) eller att spåraren inte kunde hämta data (till exempel ogiltig kalibrering eller dolda ögon).

Om programmet till exempel vill visa en markör som motsvarar den kombinerade blicken kan det fråga spåraren med hjälp av en tidsstämpel för förutsägelsen av den bildruta som förbereds på följande sätt.

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
    }
}

Transformera blickdata till andra SpatialCoordinateSystem

WinRT-API:er som returnerar rumsliga data, till exempel en position, kräver alltid både en PerceptionTimestamp och en SpatialCoordinateSystem. Om du till exempel vill hämta den kombinerade blicken för HoloLens 2 med winrt-API:et kräver API SpatialPointerPose.TryGetAtTimestamp() två parametrar: a SpatialCoordinateSystem och en PerceptionTimestamp. När den kombinerade blicken sedan nås via SpatialPointerPose.Eyes.Gazeuttrycks dess ursprung och riktning i den SpatialCoordinateSystem skickade in.

SDK-API:er för utökad tye-spårning behöver inte ta en SpatialCoordinateSystem och blickdata uttrycks alltid i spårarens koordinatsystem. Men du kan omvandla dessa blickdata till ett annat koordinatsystem med spårarens pose relaterad till det andra koordinatsystemet.

  • Som avsnittet ovan med namnet "Open eye gaze tracker" nämnde, för att få en SpatialLocator för ögonögaspåraren, ring Windows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateLocatorForNode() med fastigheten EyeGazeTracker.TrackerSpaceLocatorNodeId .

  • Blickens ursprung och riktningar som hämtas genom EyeGazeTrackerReading är relaterade till ögonögaspåraren.

  • SpatialLocator.TryLocateAtTimestamp() returnerar den fullständiga 6DoF-platsen för ögonögonspåraren vid en given PerceptionTimeStamp och relaterad till en viss SpatialCoordinateSystem, som kan användas för att konstruera en Matrix4x4-transformeringsmatris.

  • Använd matris4x4-transformeringsmatrisen som skapats för att överföra blickens ursprung och riktningar till andra SpatialCoordinateSystem.

Följande kodexempel visar hur du beräknar positionen för en kub som ligger i riktning mot den kombinerade blicken, två meter framför blickens ursprung;

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));
        }
    }
}

API-referens för utökad SDK för ögonspårning

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);
    }
}

Se även