Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Támogatást nyújt a felhasználók tekintetének, figyelésének és jelenlétének nyomon követéséhez a szemük helye és mozgása alapján.
Megjegyzés:
Lásd a tekintetvezérlést Windows Mixed Reality alatt a következő oldalon: [Gaze]/windows/mixed-reality/mrtk-unity/features/input/gaze).
Important API-k: Windows. Devices.Input.Preview, GazeDevicePreview, GazePointPreview, GazeInputSourcePreview
Áttekintés
A tekintetvezérlés hatékony módja annak, hogy Windows alkalmazásokat használjon, különösen hasznos neuro-izom betegségekkel rendelkező és más csökkent izom- vagy idegfunkciókkal rendelkező fogyatékos felhasználók számára.
Emellett a tekintetes bemenet egyaránt lenyűgöző lehetőségeket kínál mind a játékhoz (beleértve a célszerzést és a nyomon követést), mind a hagyományos hatékonyságnövelő alkalmazásokhoz, a kioszkokhoz és más interaktív forgatókönyvekhez, ahol a hagyományos beviteli eszközök (billentyűzet, egér, érintés) nem érhetők el, vagy hasznos/hasznos lehet a felhasználó kezének felszabadítása más feladatokhoz (például bevásárlótáskák tárolásához).
Megjegyzés:
A szemkövető hardver támogatása a Windows 10 Fall Creators Update, valamint a beépített Eye control funkció keretében került bevezetésre, amely lehetővé teszi, hogy a szemével vezérelje a képernyőmutatót, gépeljen a képernyő-billentyűzettel, és kommunikáljon szövegfelolvasás használatával. Windows Runtime API-k készlete (Windows. Devices.Input.Preview) a szemkövető hardverrel kommunikáló alkalmazások készítéséhez a Windows 10 2018. áprilisi frissítéssel (1803-es verzió, 17134-es build) és újabb verzióval érhető el.
Adatvédelem
A szemkövető eszközök által gyűjtött bizalmas személyes adatok miatt deklarálnia kell a gazeInput képességet az alkalmazás alkalmazásjegyzékében (lásd a következő beállítási szakaszt). A deklaráláskor Windows automatikusan rákérdez a felhasználókra egy hozzájárulási párbeszédpanellel (az alkalmazás első futtatásakor), ahol a felhasználónak engedélyt kell adnia ahhoz, hogy az alkalmazás kommunikáljon a szemkövető eszközzel, és hozzáférhessen ezekhez az adatokhoz.
Emellett, ha az alkalmazás szemkövető adatokat gyűjt, tárol vagy továbbít, ezt az alkalmazás adatvédelmi nyilatkozatában kell leírnia, és be kell tartania az alkalmazásfejlesztői szerződés és a Microsoft Store szabályzatainakszemélyes adataira vonatkozó összes egyéb követelményt.
Beállítás
A tekintet bemeneti API-inak az Windows alkalmazásban való használatához a következőkre van szükség:
Adja meg a
gazeInputképességet az alkalmazásjegyzékben.Nyissa meg a Package.appxmanifest fájlt a Visual Studio jegyzéktervezővel, vagy adja hozzá manuálisan a képességet a View kód kiválasztásával, és szúrja be a következő
DeviceCapabilityaCapabilitiescsomópontba:<Capabilities> <DeviceCapability Name="gazeInput" /> </Capabilities>Egy Windows kompatibilis szemkövető eszköz, amely csatlakozik a rendszerhez (beépített vagy periféria) és be van kapcsolva.
A támogatott szemkövető eszközök listáját lásd a Ablakvezérlés szemmel a Windows 10-ben című oldalán.
Alapszintű szemkövetés
Ebben a példában azt mutatjuk be, hogyan követheti nyomon a felhasználó tekintetét egy Windows alkalmazásban, és hogyan használhat egy időzítési függvényt alapszintű találatteszteléssel annak jelzésére, hogy mennyire tudják fenntartani a tekintetüket egy adott elemre összpontosítva.
Egy kis ellipszisre van szükség annak megjelenítéséhez, hogy a nézéspont hol található az alkalmazás nézetablakán belül, míg a RadialProgressBar a Windows Community Toolkit véletlenszerűen kerül a vászonra. Amikor a folyamatjelző sávon a tekintetfókusz észlelhető, elindul egy időzítő, és a folyamatjelzőt véletlenszerűen áthelyezi a vászonra, amikor a folyamatjelző sáv eléri a 100%.
Tekintetkövetés időzítőmintával
Töltse le ezt a mintát a Gaze input mintából (alapszintű)
Először beállítjuk a felhasználói felületet (MainPage.xaml).
<Page x:Class="gazeinput.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:gazeinput" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid x:Name="containerGrid"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0"> <StackPanel.Transitions> <TransitionCollection> <AddDeleteThemeTransition/> </TransitionCollection> </StackPanel.Transitions> <TextBlock x:Name="Header" Text="Gaze tracking sample" Style="{ThemeResource HeaderTextBlockStyle}" Margin="10,0,0,0" /> <TextBlock x:Name="TrackerCounterLabel" VerticalAlignment="Center" Style="{ThemeResource BodyTextBlockStyle}" Text="Number of trackers: " Margin="50,0,0,0"/> <TextBlock x:Name="TrackerCounter" VerticalAlignment="Center" Style="{ThemeResource BodyTextBlockStyle}" Text="0" Margin="10,0,0,0"/> <TextBlock x:Name="TrackerStateLabel" VerticalAlignment="Center" Style="{ThemeResource BodyTextBlockStyle}" Text="State: " Margin="50,0,0,0"/> <TextBlock x:Name="TrackerState" VerticalAlignment="Center" Style="{ThemeResource BodyTextBlockStyle}" Text="n/a" Margin="10,0,0,0"/> </StackPanel> <Canvas x:Name="gazePositionCanvas" Grid.Row="1"> <controls:RadialProgressBar x:Name="GazeRadialProgressBar" Value="0" Foreground="Blue" Background="White" Thickness="4" Minimum="0" Maximum="100" Width="100" Height="100" Outline="Gray" Visibility="Collapsed"/> <Ellipse x:Name="eyeGazePositionEllipse" Width="20" Height="20" Fill="Blue" Opacity="0.5" Visibility="Collapsed"> </Ellipse> </Canvas> </Grid> </Grid> </Page>Ezután inicializáljuk az alkalmazást.
Ebben a kódrészletben deklaráljuk a globális objektumainkat, és felülbíráljuk az OnNavigatedTo oldaleseményt , hogy elindítsuk a tekintetes eszközfigyelőt és az OnNavigatedFrom oldaleseményt , hogy leállíthassuk a tekintetes eszközfigyelőt.
using System; using Windows.Devices.Input.Preview; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml; using Windows.Foundation; using System.Collections.Generic; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; namespace gazeinput { public sealed partial class MainPage : Page { /// <summary> /// Reference to the user's eyes and head as detected /// by the eye-tracking device. /// </summary> private GazeInputSourcePreview gazeInputSource; /// <summary> /// Dynamic store of eye-tracking devices. /// </summary> /// <remarks> /// Receives event notifications when a device is added, removed, /// or updated after the initial enumeration. /// </remarks> private GazeDeviceWatcherPreview gazeDeviceWatcher; /// <summary> /// Eye-tracking device counter. /// </summary> private int deviceCounter = 0; /// <summary> /// Timer for gaze focus on RadialProgressBar. /// </summary> DispatcherTimer timerGaze = new DispatcherTimer(); /// <summary> /// Tracker used to prevent gaze timer restarts. /// </summary> bool timerStarted = false; /// <summary> /// Initialize the app. /// </summary> public MainPage() { InitializeComponent(); } /// <summary> /// Override of OnNavigatedTo page event starts GazeDeviceWatcher. /// </summary> /// <param name="e">Event args for the NavigatedTo event</param> protected override void OnNavigatedTo(NavigationEventArgs e) { // Start listening for device events on navigation to eye-tracking page. StartGazeDeviceWatcher(); } /// <summary> /// Override of OnNavigatedFrom page event stops GazeDeviceWatcher. /// </summary> /// <param name="e">Event args for the NavigatedFrom event</param> protected override void OnNavigatedFrom(NavigationEventArgs e) { // Stop listening for device events on navigation from eye-tracking page. StopGazeDeviceWatcher(); } } }Ezután hozzáadjuk a szemkövető eszközfigyelő metódusokat.
Ebben
StartGazeDeviceWatchermeghívjuk a CreateWatchert , és deklaráljuk a szemkövető eszköz eseményfigyelőit (DeviceAdded, DeviceUpdated és DeviceRemoved).Ezen belül
DeviceAdded, ellenőrizzük a szemkövető eszköz állapotát. Ha egy működőképes eszköz, növeljük az eszközszámunkat, és engedélyezzük a tekintetek nyomon követését. További részletekért tekintse meg a következő lépést.Ebben
DeviceUpdatedaz esetben is engedélyezzük a tekintetek nyomon követését, mivel ez az esemény aktiválódik, ha egy eszköz újra van skálázva.Ebben
DeviceRemovedaz esetben megszüntetjük az eszközszámlálót, és eltávolítjuk az eszközesemény-kezelőket.StopGazeDeviceWatcherleállítjuk a tekintet eszközfigyelőt.
/// <summary>
/// Start gaze watcher and declare watcher event handlers.
/// </summary>
private void StartGazeDeviceWatcher()
{
if (gazeDeviceWatcher == null)
{
gazeDeviceWatcher = GazeInputSourcePreview.CreateWatcher();
gazeDeviceWatcher.Added += this.DeviceAdded;
gazeDeviceWatcher.Updated += this.DeviceUpdated;
gazeDeviceWatcher.Removed += this.DeviceRemoved;
gazeDeviceWatcher.Start();
}
}
/// <summary>
/// Shut down gaze watcher and stop listening for events.
/// </summary>
private void StopGazeDeviceWatcher()
{
if (gazeDeviceWatcher != null)
{
gazeDeviceWatcher.Stop();
gazeDeviceWatcher.Added -= this.DeviceAdded;
gazeDeviceWatcher.Updated -= this.DeviceUpdated;
gazeDeviceWatcher.Removed -= this.DeviceRemoved;
gazeDeviceWatcher = null;
}
}
/// <summary>
/// Eye-tracking device connected (added, or available when watcher is initialized).
/// </summary>
/// <param name="sender">Source of the device added event</param>
/// <param name="e">Event args for the device added event</param>
private void DeviceAdded(GazeDeviceWatcherPreview source,
GazeDeviceWatcherAddedPreviewEventArgs args)
{
if (IsSupportedDevice(args.Device))
{
deviceCounter++;
TrackerCounter.Text = deviceCounter.ToString();
}
// Set up gaze tracking.
TryEnableGazeTrackingAsync(args.Device);
}
/// <summary>
/// Initial device state might be uncalibrated,
/// but device was subsequently calibrated.
/// </summary>
/// <param name="sender">Source of the device updated event</param>
/// <param name="e">Event args for the device updated event</param>
private void DeviceUpdated(GazeDeviceWatcherPreview source,
GazeDeviceWatcherUpdatedPreviewEventArgs args)
{
// Set up gaze tracking.
TryEnableGazeTrackingAsync(args.Device);
}
/// <summary>
/// Handles disconnection of eye-tracking devices.
/// </summary>
/// <param name="sender">Source of the device removed event</param>
/// <param name="e">Event args for the device removed event</param>
private void DeviceRemoved(GazeDeviceWatcherPreview source,
GazeDeviceWatcherRemovedPreviewEventArgs args)
{
// Decrement gaze device counter and remove event handlers.
if (IsSupportedDevice(args.Device))
{
deviceCounter--;
TrackerCounter.Text = deviceCounter.ToString();
if (deviceCounter == 0)
{
gazeInputSource.GazeEntered -= this.GazeEntered;
gazeInputSource.GazeMoved -= this.GazeMoved;
gazeInputSource.GazeExited -= this.GazeExited;
}
}
}
Itt ellenőrizzük, hogy az eszköz működőképes-e
IsSupportedDevice, és ha igen, megpróbáljuk engedélyezni a tekintetkövetést.TryEnableGazeTrackingAsyncEbben
TryEnableGazeTrackingAsyncdeklaráljuk a tekintet eseménykezelőit, és meghívjuk a GazeInputSourcePreview.GetForCurrentView() parancsot a bemeneti forrásra való hivatkozás lekéréséhez (ezt a felhasználói felületen kell meghívni, lásd : A felhasználói felület szál válaszkészen tartása).Megjegyzés:
Csak akkor hívja meg a GazeInputSourcePreview.GetForCurrentView() parancsot, ha egy kompatibilis szemkövető eszköz csatlakozik, és az alkalmazás megköveteli. Ellenkező esetben a hozzájárulási párbeszédpanel szükségtelen.
/// <summary>
/// Initialize gaze tracking.
/// </summary>
/// <param name="gazeDevice"></param>
private async void TryEnableGazeTrackingAsync(GazeDevicePreview gazeDevice)
{
// If eye-tracking device is ready, declare event handlers and start tracking.
if (IsSupportedDevice(gazeDevice))
{
timerGaze.Interval = new TimeSpan(0, 0, 0, 0, 20);
timerGaze.Tick += TimerGaze_Tick;
SetGazeTargetLocation();
// This must be called from the UI thread.
gazeInputSource = GazeInputSourcePreview.GetForCurrentView();
gazeInputSource.GazeEntered += GazeEntered;
gazeInputSource.GazeMoved += GazeMoved;
gazeInputSource.GazeExited += GazeExited;
}
// Notify if device calibration required.
else if (gazeDevice.ConfigurationState ==
GazeDeviceConfigurationStatePreview.UserCalibrationNeeded ||
gazeDevice.ConfigurationState ==
GazeDeviceConfigurationStatePreview.ScreenSetupNeeded)
{
// Device isn't calibrated, so invoke the calibration handler.
System.Diagnostics.Debug.WriteLine(
"Your device needs to calibrate. Please wait for it to finish.");
await gazeDevice.RequestCalibrationAsync();
}
// Notify if device calibration underway.
else if (gazeDevice.ConfigurationState ==
GazeDeviceConfigurationStatePreview.Configuring)
{
// Device is currently undergoing calibration.
// A device update is sent when calibration complete.
System.Diagnostics.Debug.WriteLine(
"Your device is being configured. Please wait for it to finish");
}
// Device is not viable.
else if (gazeDevice.ConfigurationState == GazeDeviceConfigurationStatePreview.Unknown)
{
// Notify if device is in unknown state.
// Reconfigure/recalbirate the device.
System.Diagnostics.Debug.WriteLine(
"Your device is not ready. Please set up your device or reconfigure it.");
}
}
/// <summary>
/// Check if eye-tracking device is viable.
/// </summary>
/// <param name="gazeDevice">Reference to eye-tracking device.</param>
/// <returns>True, if device is viable; otherwise, false.</returns>
private bool IsSupportedDevice(GazeDevicePreview gazeDevice)
{
TrackerState.Text = gazeDevice.ConfigurationState.ToString();
return (gazeDevice.CanTrackEyes &&
gazeDevice.ConfigurationState ==
GazeDeviceConfigurationStatePreview.Ready);
}
Ezután beállítjuk a tekintet eseménykezelőket.
Megjelenítjük és elrejtjük a tekintetkövető ellipszist a
GazeEnteredésGazeExitedelemekben, illetve azokban.Ebben az eljárásban a
GazeMovedCurrentPoint által biztosított EyeGazePosition alapján mozgatjuk a tekintetkövetési ellipszist. Kezeljük a tekintet fókusz időzítőjét a RadialProgressBar-on is, ami a folyamatjelző sáv áthelyezését indítja el. További részletekért tekintse meg a következő lépést./// <summary> /// GazeEntered handler. /// </summary> /// <param name="sender">Source of the gaze entered event</param> /// <param name="e">Event args for the gaze entered event</param> private void GazeEntered( GazeInputSourcePreview sender, GazeEnteredPreviewEventArgs args) { // Show ellipse representing gaze point. eyeGazePositionEllipse.Visibility = Visibility.Visible; // Mark the event handled. args.Handled = true; } /// <summary> /// GazeExited handler. /// Call DisplayRequest.RequestRelease to conclude the /// RequestActive called in GazeEntered. /// </summary> /// <param name="sender">Source of the gaze exited event</param> /// <param name="e">Event args for the gaze exited event</param> private void GazeExited( GazeInputSourcePreview sender, GazeExitedPreviewEventArgs args) { // Hide gaze tracking ellipse. eyeGazePositionEllipse.Visibility = Visibility.Collapsed; // Mark the event handled. args.Handled = true; } /// <summary> /// GazeMoved handler translates the ellipse on the canvas to reflect gaze point. /// </summary> /// <param name="sender">Source of the gaze moved event</param> /// <param name="e">Event args for the gaze moved event</param> private void GazeMoved(GazeInputSourcePreview sender, GazeMovedPreviewEventArgs args) { // Update the position of the ellipse corresponding to gaze point. if (args.CurrentPoint.EyeGazePosition != null) { double gazePointX = args.CurrentPoint.EyeGazePosition.Value.X; double gazePointY = args.CurrentPoint.EyeGazePosition.Value.Y; double ellipseLeft = gazePointX - (eyeGazePositionEllipse.Width / 2.0f); double ellipseTop = gazePointY - (eyeGazePositionEllipse.Height / 2.0f) - (int)Header.ActualHeight; // Translate transform for moving gaze ellipse. TranslateTransform translateEllipse = new TranslateTransform { X = ellipseLeft, Y = ellipseTop }; eyeGazePositionEllipse.RenderTransform = translateEllipse; // The gaze point screen location. Point gazePoint = new Point(gazePointX, gazePointY); // Basic hit test to determine if gaze point is on progress bar. bool hitRadialProgressBar = DoesElementContainPoint( gazePoint, GazeRadialProgressBar.Name, GazeRadialProgressBar); // Use progress bar thickness for visual feedback. if (hitRadialProgressBar) { GazeRadialProgressBar.Thickness = 10; } else { GazeRadialProgressBar.Thickness = 4; } // Mark the event handled. args.Handled = true; } }Végül az alábbi módszereket használjuk az alkalmazás tekintetfókusz-időzítőjének kezelésére.
DoesElementContainPointellenőrzi, hogy a tekintetmutató a folyamatjelző sáv fölött van-e. Ha igen, elindítja a "gaze timer"-t, és növeli a folyamatjelző sávot minden egyes időzítő ticknél.SetGazeTargetLocationBeállítja az állapotsor kezdeti helyét, és ha az állapotsor befejeződik (a tekintet fókusz időzítőjének függvényében), a folyamatjelzőt egy véletlenszerű helyre helyezi át./// <summary> /// Return whether the gaze point is over the progress bar. /// </summary> /// <param name="gazePoint">The gaze point screen location</param> /// <param name="elementName">The progress bar name</param> /// <param name="uiElement">The progress bar UI element</param> /// <returns></returns> private bool DoesElementContainPoint( Point gazePoint, string elementName, UIElement uiElement) { // Use entire visual tree of progress bar. IEnumerable<UIElement> elementStack = VisualTreeHelper.FindElementsInHostCoordinates(gazePoint, uiElement, true); foreach (UIElement item in elementStack) { //Cast to FrameworkElement and get element name. if (item is FrameworkElement feItem) { if (feItem.Name.Equals(elementName)) { if (!timerStarted) { // Start gaze timer if gaze over element. timerGaze.Start(); timerStarted = true; } return true; } } } // Stop gaze timer and reset progress bar if gaze leaves element. timerGaze.Stop(); GazeRadialProgressBar.Value = 0; timerStarted = false; return false; } /// <summary> /// Tick handler for gaze focus timer. /// </summary> /// <param name="sender">Source of the gaze entered event</param> /// <param name="e">Event args for the gaze entered event</param> private void TimerGaze_Tick(object sender, object e) { // Increment progress bar. GazeRadialProgressBar.Value += 1; // If progress bar reaches maximum value, reset and relocate. if (GazeRadialProgressBar.Value == 100) { SetGazeTargetLocation(); } } /// <summary> /// Set/reset the screen location of the progress bar. /// </summary> private void SetGazeTargetLocation() { // Ensure the gaze timer restarts on new progress bar location. timerGaze.Stop(); timerStarted = false; // Get the bounding rectangle of the app window. Rect appBounds = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().VisibleBounds; // Translate transform for moving progress bar. TranslateTransform translateTarget = new TranslateTransform(); // Calculate random location within gaze canvas. Random random = new Random(); int randomX = random.Next( 0, (int)appBounds.Width - (int)GazeRadialProgressBar.Width); int randomY = random.Next( 0, (int)appBounds.Height - (int)GazeRadialProgressBar.Height - (int)Header.ActualHeight); translateTarget.X = randomX; translateTarget.Y = randomY; GazeRadialProgressBar.RenderTransform = translateTarget; // Show progress bar. GazeRadialProgressBar.Visibility = Visibility.Visible; GazeRadialProgressBar.Value = 0; }