Aracılığıyla paylaş


Windows uygulamalarında bakış etkileşimleri ve göz izleme

Göz izleme kahramanı

Bir kullanıcının bakışını, dikkatini ve iletişim durumunu, gözlerinin konumuna ve hareketine göre izlemek için destek sağlayın.

Uyarı

Windows Mixed Reality'de bakış girişi için bkz. [Bakış]/windows/mixed-reality/mrtk-unity/features/input/gaze).

Önemli API'ler: Windows.Devices.Input.Preview, GazeDevicePreview, GazePointPreview, GazeInputSourcePreview

Genel Bakış

Bakış girişi, sinir kas hastalıkları (ALS gibi) ve kas veya sinir fonksiyonları bozuk diğer engelleri olan kullanıcılar için yardımcı bir teknoloji olarak özellikle yararlı olan Windows uygulamalarıyla etkileşim kurmanın ve kullanmanın güçlü bir yoludur.

Buna ek olarak, bakış girişi hem oyun (hedef alma ve izleme dahil) hem de geleneksel üretkenlik uygulamaları, bilgi noktaları ve geleneksel giriş cihazlarının (klavye, fare, dokunma) kullanılamadığı veya diğer görevler (alışveriş çantalarını tutma gibi) için kullanıcının elini serbest bırakmanın yararlı/yararlı olabileceği diğer etkileşimli senaryolar için de eşit derecede cazip fırsatlar sunar.

Uyarı

Windows 10 Fall Creators Update'te göz izleme donanımı desteği, ekran işaretçisini denetlemek, ekran klavyesi ile yazmak ve metin okuma kullanarak kişilerle iletişim kurmak için gözlerinizi kullanmanızı sağlayan yerleşik bir özellik olan Göz denetimi ile birlikte sunulmuştur. Gözle izleme donanımıyla etkileşim kurabilen uygulamalar oluşturmaya yönelik bir dizi Windows Çalışma Zamanı API'si (Windows.Devices.Input.Preview), Windows 10 Nisan 2018 Güncelleştirmesi (Sürüm 1803, derleme 17134) ve daha yeni sürümlerle kullanılabilir.

Gizlilik

Gözle izleme cihazları tarafından toplanan hassas olabilecek kişisel veriler nedeniyle, uygulamanızın gazeInput uygulama bildiriminde özelliği bildirmeniz gerekir (aşağıdaki Kurulum bölümüne bakın). Bildirildiğinde, Windows otomatik olarak kullanıcılardan bir onay iletişim kutusu ister (uygulama ilk çalıştırıldığında), kullanıcının uygulamanın gözle izleme cihazıyla iletişim kurmasına ve bu verilere erişmesine izin vermesi gerekir.

Buna ek olarak, uygulamanız gözle izleme verilerini toplar, depolar veya aktarırsa, bunu uygulamanızın gizlilik bildiriminde açıklamanız ve Uygulama Geliştirici Sözleşmesi ile Microsoft Store İlkeleri'ndeKişisel Bilgiler için diğer tüm gereksinimleri izlemeniz gerekir.

Kurulum

Windows uygulamanızda bakış girişi API'lerini kullanmak için aşağıdakileri yapmanız gerekir:

  • gazeInput Uygulama bildiriminde özelliği belirtin.

    Package.appxmanifest dosyasını Visual Studio bildirim tasarımcısıyla açın veya Kodu görüntüle'yi seçip düğüme DeviceCapability aşağıdakileri Capabilities ekleyerek özelliği el ile ekleyin:

    <Capabilities>
       <DeviceCapability Name="gazeInput" />
    </Capabilities>
    
  • Sisteminize bağlı ve açık olan, yerleşik veya çevre birimi olarak kullanılabilen Windows uyumlu bir göz izleme cihazı bulunmalıdır.

    Desteklenen göz izleme cihazlarının listesi için bkz. Windows 10'da göz denetimini kullanmaya başlama .

Temel göz izleme

Bu örnekte, bir Windows uygulamasında kullanıcının bakışını izlemeyi ve belirli bir öğeye bakış odağını ne kadar iyi koruyabileceğini belirtmek için temel isabet testiyle bir zamanlama işlevi kullanmayı göstereceğiz.

Küçük bir üç nokta, bakış noktasının uygulama görünüm penceresi içinde nerede olduğunu göstermek için kullanılırken, Windows Topluluk Araç Seti'nden bir RadialProgressBar tuvale rastgele yerleştirilir. İlerleme çubuğunda bakış odağı algılandığında bir zamanlayıcı başlatılır ve ilerleme çubuğu 100%ulaştığında ilerleme çubuğu tuvalde rastgele yeniden konumlandırılır.

Zamanlayıcılı bakış izleme örneği

Zamanlayıcı örneği ile bakış izleme

Gaze input sample (basic) örneğini indirin

  1. İlk olarak kullanıcı arabirimini (MainPage.xaml) ayarlayacağız.

    <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>
    
  2. Ardından uygulamamızı başlatacağız.

    Bu kod parçacığında genel nesnelerimizi bildirir ve OnNavigatedTo sayfa olayını bakış cihazı izleyicimizi başlatmak için, OnNavigatedFrom sayfa olayını ise bakış cihazı izleyicimizi durdurmak için geçersiz kılarız.

    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();
            }
        }
    }
    
  3. Sonra, bakış izleme cihazı yöntemlerimizi ekleyeceğiz.

    StartGazeDeviceWatcher içinde CreateWatcher'ı çağırır ve göz izleme cihazı olay dinleyicilerini tanımlarız (DeviceAdded, DeviceUpdated ve DeviceRemoved).

    DeviceAdded sırasında, gözle izleme cihazının durumunu denetleriz. Uygun bir cihazsa cihaz sayımızı artırır ve bakış izlemeyi etkinleştiririz. Ayrıntılar için sonraki adıma bakın.

    DeviceUpdated'de, bir cihaz yeniden kalibre edilirse bu olay tetiklendiğinden bakış takibini de etkinleştiriyoruz.

    DeviceRemoved içinde, cihaz sayacımızı azaltır ve cihaz olay işleyicilerini kaldırırız.

    StopGazeDeviceWatcher içinde bakış cihazı izleyicisini kapatıyoruz.

    /// <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;
            }
        }
    }
  1. Burada cihazın IsSupportedDevice uygun olup olmadığını denetleyeceğiz ve uygunsa TryEnableGazeTrackingAsync içinde bakış izlemeyi etkinleştirmeyi deneyeceğiz.

    içinde TryEnableGazeTrackingAsync, bakış olay işleyicilerini bildiririz ve giriş kaynağına referans almak için GazeInputSourcePreview.GetForCurrentView() çağrısını yaparız (bu işlem kullanıcı arabirimi iş parçacığında gerçekleştirilmelidir, bkz. UI iş parçacığının yanıt vermesini sağlama).

    Uyarı

    GazeInputSourcePreview.GetForCurrentView() öğesini yalnızca uyumlu bir göz izleme cihazı bağlı olduğunda ve uygulamanız için gerekli olduğunda çağırmalısınız. Aksi takdirde, onay iletişim kutusu gereksizdir.

    /// <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);
    }
  1. Ardından, bakış olay işleyicilerimizi yapılandıracağız.

    Bakış izleme elipsini GazeEntered görüntüler ve GazeExited'de gizleriz.

    içinde GazeMoved, GazeEnteredPreviewEventArgs'ın CurrentPoint'u tarafından sağlanan EyeGazePosition'a göre bakış izleme elipsimizi hareket ettiriyoruz. RadialProgressBar üzerindeki bakış odak zamanlayıcısını da yönetiriz ve bu da ilerleme çubuğunun yeniden konumlandırılma işlemini tetikler. Ayrıntılar için sonraki adıma bakın.

    /// <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;
        }
    }
    
  2. Son olarak, bu uygulama için bakış odak zamanlayıcısını yönetmek için kullanılan yöntemler aşağıdadır.

    DoesElementContainPoint bakış işaretçisinin ilerleme çubuğunun üzerinde olup olmadığını denetler. Bu durumda, bakış zamanlayıcısını başlatır ve bakış zamanlayıcısının her bir hareketinde ilerleme çubuğunu artırır.

    SetGazeTargetLocation ilerleme çubuğunun ilk konumunu ayarlar ve ilerleme çubuğu tamamlanırsa (bakış odağı zamanlayıcısına bağlı olarak), ilerleme çubuğunu rastgele bir konuma taşır.

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

Ayrıca bakınız

Kaynaklar

Konu örnekleri