Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Обеспечивает поддержку отслеживания взгляда, внимания и присутствия пользователя на основе расположения и перемещения глаз.
Замечание
Сведения о вводе с помощью взгляда в Windows Mixed Reality см. в разделе [Gaze]/windows/mixed-reality/mrtk-unity/features/input/gaze).
Важные API: Windows.Devices.Input.Preview, GazeDevicePreview, GazePointPreview, GazeInputSourcePreview
Обзор
Входные данные взгляда — это мощный способ взаимодействия и использования приложений Windows, которые особенно полезны в качестве вспомогательной технологии для пользователей с нейро-мышечной болезнью (например, ALS) и другими ограниченными возможностями, связанными с нарушениями мышц или нервных функций.
Кроме того, управление взглядом предлагает одинаково убедительные возможности как для игр (включая наведение на цель и отслеживание), так и для традиционных рабочих приложений, киосков и других интерактивных сценариев, где традиционные устройства ввода (клавиатура, мышь, сенсорный ввод) недоступны или где может быть полезно освободить руки пользователя для других задач (например, держать сумки для покупок).
Замечание
Поддержка оборудования отслеживания глаз была добавлена в Windows 10 Fall Creators Update вместе с элементом управления взглядом, встроенной функцией, которая позволяет использовать глаза для управления указателем на экране, набирать текст с экранной клавиатуры и общаться с людьми, используя преобразование текста в речь. Набор API среды выполнения Windows (Windows.Devices.Input.Preview) для создания приложений, которые могут взаимодействовать с оборудованием отслеживания глаз, доступны с обновлением Windows 10 апреля 2018 г. (версия 1803, сборка 17134) и более поздние версии.
Конфиденциальность
Из-за потенциально конфиденциальных персональных данных, собранных устройствами отслеживания глаз, необходимо указать gazeInput возможность в манифесте вашего приложения (см. следующий раздел Настройка). При объявлении Windows автоматически запрашивает пользователям диалоговое окно согласия (при первом запуске приложения), где пользователь должен предоставить приложению разрешение на взаимодействие с устройством отслеживания глаз и получить доступ к этим данным.
Кроме того, если ваше приложение собирает, хранит или передает данные отслеживания глаз, необходимо описать это в заявлении о конфиденциальности приложения и следовать всем другим требованиям к персональным данным в соглашении разработчика приложений и политиках Microsoft Store.
Настройка
Чтобы использовать API ввода с помощью взгляда в приложении Windows, вам потребуется:
gazeInputУкажите возможность в манифесте приложения.Откройте файл Package.appxmanifest с помощью конструктора манифестов Visual Studio или добавьте его вручную, выбрав код представления и вставив следующий
DeviceCapabilityкод вCapabilitiesузел:<Capabilities> <DeviceCapability Name="gazeInput" /> </Capabilities>Устройство отслеживания глаз, совместимое с Windows, подключенное к системе (встроенное или периферийное устройство) и включено.
Ознакомьтесь со статьей "Начало работы с управлением глазами в Windows 10" для списка поддерживаемых устройств для отслеживания взгляда.
Базовое отслеживание глаз
В этом примере мы покажем, как отслеживать взгляд пользователя в приложении Windows и использовать функцию таймера с базовым тестированием попаданий в цель, чтобы указать, насколько хорошо пользователь может удерживать взгляд на определенном элементе.
Маленький эллипс используется для отображения того, где точка взгляда находится в поле зрения приложения, в то время как RadialProgressBar из Windows Community Toolkit размещается случайным образом на холсте. При обнаружении фокуса взгляда на индикаторе прогресса запускается таймер, и когда индикатор прогресса достигает 100%, он случайным образом перемещается на холсте.
Отслеживание взгляда с примером таймера
Скачайте этот пример из примера входных данных Gaze (базовый)
Сначала мы настраиваем пользовательский интерфейс (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>Затем мы инициализируем наше приложение.
В этом фрагменте кода мы объявляем наши глобальные переменные и переопределяем событие OnNavigatedTo для запуска наблюдателя устройств отслеживания взгляда, а событие OnNavigatedFrom — для его остановки.
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(); } } }Затем мы добавим методы наблюдателя за устройствами слежения за глазами.
В
StartGazeDeviceWatcherэтом случае мы вызываем CreateWatcher и объявляем прослушиватели событий устройства отслеживания движения глаз (DeviceAdded, DeviceUpdated и DeviceRemoved).В
DeviceAddedмы проверяем состояние устройства отслеживания глаз. Если устройство является жизнеспособным, мы увеличиваем число устройств и включаем отслеживание взгляда. Дополнительные сведения см. на следующем шаге.В
DeviceUpdatedэтом событии мы также включаем отслеживание взгляда, так как это событие активируется, если устройство перекалибровано.В
DeviceRemovedмы уменьшаем счетчик устройств и удаляем обработчики событий устройства.В
StopGazeDeviceWatcherмы выключаем гаджет отслеживания взгляда.
/// <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;
}
}
}
Здесь мы проверяем, является ли устройство жизнеспособным
IsSupportedDeviceи, если да, попытаемся включить отслеживание взгляда вTryEnableGazeTrackingAsync.В
TryEnableGazeTrackingAsync, мы объявляем обработчики событий взгляда и вызываем GazeInputSourcePreview.GetForCurrentView(), чтобы получить ссылку на источник входных данных (этот вызов должен быть выполнен в потоке UI, см. Обеспечение отзывчивости потока UI).Замечание
Необходимо вызывать GazeInputSourcePreview.GetForCurrentView() только в том случае, если совместимое устройство отслеживания глаз подключено и требуется для приложения. В противном случае диалоговое окно согласия не требуется.
/// <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);
}
Затем мы настроим обработчики событий взгляда.
Мы отображаем и скрываем эллипс отслеживания взгляда в
GazeEnteredиGazeExited, соответственно.В
GazeMoved, мы перемещаем эллипс отслеживания взгляда на основе EyeGazePosition, предоставляемой CurrentPoint объекта GazeEnteredPreviewEventArgs. Мы также управляем таймером фокуса взгляда на RadialProgressBar, который активирует изменение положения индикатора выполнения. Дополнительные сведения см. на следующем шаге./// <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; } }Наконец, вот методы, используемые для управления таймером фокусировки взгляда для этого приложения.
DoesElementContainPointпроверяет, находится ли указатель взгляда на индикатор выполнения. Если это так, он запускает таймер взгляда и увеличивает индикатор хода выполнения с каждым тиком таймера.SetGazeTargetLocationзадает начальное расположение индикатора выполнения и, если индикатор выполнения завершается (в зависимости от таймера фокусировки взгляда), перемещает его в случайное расположение./// <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; }
См. также
Ресурсы
Примеры тем
Windows developer