Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Fornecer suporte para rastrear o olhar, a atenção e a presença de um usuário com base na localização e no movimento de seus olhos.
Observação
Para entrada de olhar em de Realidade Mista do Windows, consulte [Gaze]/windows/mixed-reality/mrtk-unity/features/input/gaze).
APIs importantes: Windows.Devices.Input.Preview, GazeDevicePreview, GazePointPreview, GazeInputSourcePreview
Visão geral
A entrada por olhar é uma maneira poderosa de interagir e usar aplicações do Windows, sendo especialmente útil como tecnologia assistiva para utilizadores com doenças neuromusculares (como ELA) e outras deficiências envolvendo funções musculares ou nervosas comprometidas.
Além disso, a entrada através do olhar oferece oportunidades igualmente atraentes para jogos eletrônicos (incluindo aquisição e rastreamento de alvos) e aplicativos tradicionais de produtividade, quiosques e outros cenários interativos onde os dispositivos de entrada tradicionais (teclado, rato, toque) não estão disponíveis, ou onde pode ser útil liberar as mãos do utilizador para outras tarefas, como segurar sacos de compras.
Observação
O suporte para hardware de rastreamento ocular foi introduzido no Windows 10 Fall Creators Update junto com o controle de olhos, um recurso interno que permite que você use seus olhos para controlar o ponteiro na tela, digite com o teclado na tela e se comunique com as pessoas usando conversão de texto em fala. Um conjunto de APIs do Tempo de Execução do Windows (Windows.Devices.Input.Preview) para criar aplicativos que podem interagir com hardware de rastreamento ocular está disponível com Windows 10 April 2018 Update (Versão 1803, compilação 17134) e mais recente.
Privacidade
Devido aos dados pessoais potencialmente sensíveis recolhidos por dispositivos de rastreamento ocular, é necessário declarar o recurso gazeInput
no manifesto da sua aplicação (consulte a secção Configuração a seguir). Quando declarado, o Windows solicita automaticamente aos usuários uma caixa de diálogo de consentimento (quando o aplicativo é executado pela primeira vez), onde o usuário deve conceder permissão para que o aplicativo se comunique com o dispositivo de rastreamento ocular e acesse esses dados.
Além disso, se a sua aplicação recolher, armazenar ou transferir dados de rastreio ocular, deve incluí-lo na declaração de privacidade da sua aplicação e seguir todos os outros requisitos para Informações Pessoais no Contrato para Programador de Aplicações e nas Políticas da Microsoft Store.
Configuração
Para usar as APIs de entrada de olhar na sua aplicação do Windows, você precisará:
Especifique o
gazeInput
recurso no manifesto do aplicativo.Abra o arquivo de Package.appxmanifest do
com o designer de manifesto do Visual Studio ou adicione o recurso manualmente selecionando código de exibição e inserindo o seguinteno nó : <Capabilities> <DeviceCapability Name="gazeInput" /> </Capabilities>
Um dispositivo de rastreamento ocular compatível com o Windows conectado ao seu sistema (integrado ou periférico) e ligado.
Consulte Introdução ao controlo ocular no Windows 10 para obter uma lista de dispositivos de rastreio ocular suportados.
Rastreamento ocular básico
Neste exemplo, demonstramos como rastrear o olhar do usuário em um aplicativo do Windows e usar uma função de temporização com teste de acerto básico para indicar o quão bem ele pode manter o foco do olhar em um elemento específico.
Uma pequena elipse é usada para mostrar onde o ponto de observação está dentro da área de exibição do aplicativo, ao passo que um RadialProgressBar do Windows Community Toolkit é colocado aleatoriamente na tela. Quando o foco do olhar é detetado na barra de progresso, um temporizador é iniciado e a barra de progresso é realocada aleatoriamente na tela quando a barra de progresso atinge 100%.
Rastreamento do olhar com amostra de temporizador
Baixe este exemplo do Exemplo de entrada do Gaze (básico)
Primeiro, configuramos a interface do usuário (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>
Em seguida, inicializamos nosso aplicativo.
Neste trecho, declaramos nossos objetos globais e substituímos o evento
OnNavigatedTo page para iniciar nosso de observação de dispositivos de olhare o evento de página OnNavigatedFrom para parar nosso observador de dispositivos de olhar. 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(); } } }
Em seguida, adicionamos os nossos métodos de monitorização de dispositivos oculares.
No
StartGazeDeviceWatcher
, chamamos CreateWatcher e declaramos os gestores de eventos do dispositivo de rastreamento ocular (DeviceAdded, DeviceUpdatede DeviceRemoved).Na
DeviceAdded
, verificamos o estado do dispositivo de rastreamento ocular. Se for um dispositivo viável, aumentamos nossa contagem de dispositivos e habilitamos o rastreamento do olhar. Consulte a próxima etapa para obter detalhes.No
DeviceUpdated
, também habilitamos o rastreamento de olhar, pois esse evento é acionado se um dispositivo for recalibrado.No
DeviceRemoved
, diminuímos nosso contador de dispositivos e removemos os manipuladores de eventos de dispositivo.Em
StopGazeDeviceWatcher
, desativámos o dispositivo de monitorização ocular.
/// <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;
}
}
}
Aqui, verificamos se o dispositivo é viável em
IsSupportedDevice
e, se for, tentamos ativar o rastreamento do olhar emTryEnableGazeTrackingAsync
.No
TryEnableGazeTrackingAsync
, declaramos os manipuladores de eventos gaze e chamamos GazeInputSourcePreview.GetForCurrentView() para obter uma referência à fonte de entrada (isso deve ser chamado no thread da interface do usuário, consulte Manter o thread da interface do usuário responsivo).Observação
Você deve chamar GazeInputSourcePreview.GetForCurrentView() somente quando um dispositivo de rastreamento ocular compatível estiver conectado e exigido pelo seu aplicativo. Caso contrário, a caixa de diálogo de consentimento será desnecessária.
/// <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);
}
Em seguida, configuramos nossos manipuladores de eventos de olhar.
Exibimos e escondemos a elipse de rastreamento do olhar em
GazeEntered
eGazeExited
, respectivamente.Em
GazeMoved
, movemos a elipse de rastreamento de olhar com base na EyeGazePosition fornecida pelo CurrentPoint do GazeEnteredPreviewEventArgs. Também gerimos o temporizador de foco visual no RadialProgressBar, que provoca o reposicionamento da barra de progresso. Consulte a próxima etapa para obter detalhes./// <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; } }
Finalmente, aqui estão os métodos usados para gerenciar o temporizador de foco do olhar para este aplicativo.
DoesElementContainPoint
verifica se o ponteiro do olhar está sobre a barra de progresso. Em caso afirmativo, ele inicia o temporizador de olhar e incrementa a barra de progresso a cada batida do temporizador de olhar.SetGazeTargetLocation
Define a localização inicial da barra de progresso e, se a barra de progresso for concluída (dependendo do temporizador de foco do olhar), move a barra de progresso para uma localização aleatória./// <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; }
Ver também
Recursos
Exemplos de tópicos
Windows developer