Niestandardowe rozpoznawanie głosu

Ukończone

Jeśli chcemy opracować lepsze rozwiązanie niż domyślna funkcja rozpoznawania mowy w systemie Windows, będziemy musieli zakodować system rozpoznawania mowy specyficzny dla aplikacji, który będzie obsługiwał dane wejściowe wypowiadane całymi zdaniami.

Wymaga to całkiem sporo pracy związanej z kodowaniem, dlatego być może warto poświęcić trochę czasu, aby poprawić właściwości AutomationProperties.Name aplikacji i ponownie ją przetestować względem aparatu rozpoznawania mowy systemu Windows. Prawdą jest, że mamy już system, który akceptuje tę metodę wprowadzania danych, ale jest on trochę niepraktyczny. Aby zagwarantować naprawdę płynne wprowadzanie danych za pomocą głosu w wyspecjalizowanym kontekście, musimy opracować system niestandardowy.

Informacje

Aby uzyskać pełną listę poleceń, zobacz Polecenia dotyczące rozpoznawania mowy w systemie Windows.

Uzyskiwanie uprawnienia do odbierania danych wejściowych z mikrofonu i uruchamiania rozpoznawania mowy na podstawie tych danych

Przed rozpoczęciem korzystania z niestandardowej funkcji rozpoznawania mowy należy ustawić kilka uprawnień i możliwości.

  1. W programie Visual Studio z załadowanym projektem kalkulatora otwórz plik Package.appxmainifest, a następnie wybierz pozycję Możliwości. Włącz możliwość mikrofon.

Setting the microphone capability.

  1. Ustawienie tej możliwości zapewnia dostęp do danych audio mikrofonu. Zapisz i zamknij plik manifestu.

  2. To już wszystko, co jest wymagane przez aplikację, ale nie do działania funkcji rozpoznawania mowy. Użytkownik musi włączyć dla aplikacji zarówno mikrofon, jak i funkcję rozpoznawania mowy, a ta druga opcja jest domyślnie wyłączona. Podczas przeprowadzania testów deweloper staje się również użytkownikiem, więc wpisz „ustawienia prywatności” na pasku wyszukiwania systemu Windows.

Setting the privacy settings.

  1. Wybierz pozycję Mowa i upewnij się, że opcja Rozpoznawanie mowy w trybie online jest włączona. Wybierz pozycję Mikrofon i upewnij się, że opcja Zezwalaj aplikacjom na dostęp do mikrofonu jest włączona. Zamknij lub zminimalizuj okno ustawień.

Później spróbujemy wyłączyć te ustawienia, aby przetestować, czy nasza aplikacja zachowuje się prawidłowo w tych sytuacjach.

Dodawanie kodu w celu dopasowania słów i fraz do elementów interfejsu użytkownika

Aby obsłużyć niestandardowy aparat rozpoznawania mowy, konieczne będzie dodanie dużej ilości kodu, ale zacznijmy od instrukcji using i zmiennych globalnych.

  1. Dodaj następujące instrukcje using na początku kodu.
using Windows.Media.SpeechRecognition;
using Windows.Media.Capture;
  1. Dodaj następujące zmienne globalne i jedno nowe wyliczenie.
        enum eElements
        {
            Button,
            ToggleSwitch,
            Unknown
        }

        bool isRecognitionAvailable;
        SpeechRecognizer speechRecognizer;
  1. Aby rozwiązać opisane wyżej problemy z uprawnieniami, dodaj do kodu następującą klasę. Jedno wywołanie funkcji RequestMicrophonePermission sprawdzi wszystkie niezbędne uprawnienia. Jest to kod generyczny, który może być używany w dowolnej aplikacji opracowanej dla systemu Windows 10 w celu obsługi uprawnień do rozpoznawania mowy za pośrednictwem mikrofonu, ale nie obsługuje on prywatności Cortany/Dyktowania.
        public class AudioCapturePermissions
        {
            // If no microphone is present, an exception is thrown with the following HResult value.
            private static readonly int NoCaptureDevicesHResult = -1072845856;

            /// <summary>
            ///  Note that this method only checks the Settings->Privacy->Microphone setting, it does not handle
            /// the Cortana/Dictation privacy check.
            /// </summary>
            /// <returns>True, if the microphone is available.</returns>
            public async static Task<bool> RequestMicrophonePermission()
            {
                try
                {
                    // Request access to the audio capture device.
                    var settings = new MediaCaptureInitializationSettings
                    {
                        StreamingCaptureMode = StreamingCaptureMode.Audio,
                        MediaCategory = MediaCategory.Speech,
                    };
                    var capture = new MediaCapture();

                    await capture.InitializeAsync(settings);
                }
                catch (TypeLoadException)
                {
                    // Thrown when a media player is not available.
                    var messageDialog = new Windows.UI.Popups.MessageDialog("Media player components are unavailable.");
                    await messageDialog.ShowAsync();
                    return false;
                }
                catch (UnauthorizedAccessException)
                {
                    // Thrown when permission to use the audio capture device is denied.
                    var messageDialog = new Windows.UI.Popups.MessageDialog("Permission to use the audio capture device is denied.");
                    await messageDialog.ShowAsync();
                    return false;
                }
                catch (Exception exception)
                {
                    // Thrown when an audio capture device is not present.
                    if (exception.HResult == NoCaptureDevicesHResult)
                    {
                        var messageDialog = new Windows.UI.Popups.MessageDialog("No Audio Capture devices are present on this system.");
                        await messageDialog.ShowAsync();
                        return false;
                    }
                    else
                    {
                        throw;
                    }
                }
                return true;
            }
        }
  1. Dobrym rozwiązaniem jest skonfigurowanie ustawienia, które włącza i wyłącza funkcje rozpoznawania mowy. Zdefiniuj jeszcze jeden przełącznik w pliku MainPage.xaml. Pamiętaj, że ustawiamy akcelerator klawiatury L (dla Odbiornika), który wyzwoli przełącznik. Dodaj to tuż przed wpisem ListConstants.
        <ToggleSwitch x:Name="ToggleSpeechRecognition"
            Margin="685,409,0,0"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Header="Speech recognition"
            IsOn="False"
            Toggled="ToggleSpeechRecognition_Toggled">
            <ToggleSwitch.KeyboardAccelerators>
                <KeyboardAccelerator Key="L" Modifiers="None" />
            </ToggleSwitch.KeyboardAccelerators>
        </ToggleSwitch>
  1. Teraz zdefiniuj zdarzenie ToggleSpeechRecognition_Toggled wymienione w kodzie XAML oraz kilka metod pomocniczych w pliku MainPage.xaml.cs.
        private async Task InitSpeechRecognition()
        {
            isRecognitionAvailable = await AudioCapturePermissions.RequestMicrophonePermission();

            if (isRecognitionAvailable)
            {
                // Create an instance of SpeechRecognizer.
                speechRecognizer = new SpeechRecognizer();

                // Compile the dictation grammar by default.
                await speechRecognizer.CompileConstraintsAsync();

                speechRecognizer.UIOptions.ShowConfirmation = true;
            }
            else
            {
                ToggleSpeechRecognition.IsOn = false;
                isRecognitionAvailable = false;
            }
        }

        private async void ToggleSpeechRecognition_Toggled(object sender, RoutedEventArgs e)
        {
            if (ToggleSpeechRecognition.IsOn)
            {
                await InitSpeechRecognition();
                await StartListening();
            }
            else
            {
                isRecognitionAvailable = false;
            }
        }

        private async Task StartListening()
        {
            if (isRecognitionAvailable)
            {
                try
                {
                    // Start recognition.
                    var speechRecognitionResult = await speechRecognizer.RecognizeWithUIAsync();
                    ParseSpokenCalculationAsync(speechRecognitionResult.Text);

                    // Turn off the Toggle each time.
                    ToggleSpeechRecognition.IsOn = false;
                }
                catch (Exception ex)
                {
                    var messageDialog = new Windows.UI.Popups.MessageDialog(ex.Message);
                    await messageDialog.ShowAsync();
                    ToggleSpeechRecognition.IsOn = false;
                    isRecognitionAvailable = false;
                }
            }
        }
  1. Metoda ParseSpokenCalculation przyjmuje jako dane wejściowe ciąg rozpoznany z mowy. Aby przetworzyć ten ciąg, musimy dodać duży fragment kodu specyficzny dla aplikacji.

Ten kod pobiera wymówione zdanie i próbuje dopasować frazy i słowa z tego zdania do przycisków, przełączników lub stałych w naszej aplikacji. Słowa, których nie można dopasować, są ignorowane. Poniższy kod prezentuje siłowe podejście do tego problemu.

Wklej poniższy kod do swojej aplikacji.

        private bool FindConstantFromSpeech(string spokenText, ref string value)
        {
            bool isLocated = false;
            int n = 0;
            string[] nameValue;

            // Remove the word "constant" from the start of the spoken text.
            spokenText = spokenText.Remove(0, spokenText.IndexOf(' ')).Trim();

            while (n < ListConstants.Items.Count && !isLocated)
            {
                nameValue = ListConstants.Items[n].ToString().Split('=');

                if (spokenText == nameValue[0].Trim().ToLower())
                {
                    value = nameValue[1].Trim();
                    isLocated = true;
                }
                else
                {
                    ++n;
                }
            }
            return isLocated;
        }

        private async void SayCurrentCalculationAsync()
        {
            if (TextDisplay.Text.Length == 0)
            {
                await SayAsync("The current calculation is empty.");
            }
            else
            {
                await SayAsync($"The current calculation is: {TextDisplay.Text}.");
            }
        }

        private async void ParseSpokenCalculationAsync(string spokenText)
        {
            spokenText = spokenText.ToLower().Trim();
            if (spokenText.Length == 0)
            {
                return;
            }

            // First check for specific control phrases.
            if (spokenText == "say memory")
            {
                await SayAsync($"The current memory is: {TextMemory.Text}.");
            }
            else
                if (spokenText == "say calculation")
            {
                SayCurrentCalculationAsync();
            }
            else
                 if (spokenText.StartsWith("const"))
            {
                string value = "";
                if (FindConstantFromSpeech(spokenText, ref value))
                {
                    MathEntry(value, "Number");
                    SayCurrentCalculationAsync();
                }
                else
                {
                    await SayAsync("Sorry, I did not recognize that constant.");
                }
            }
            else
            {
                // Ensure + is a word in its own right.
                // Sometimes the speech recognizer will enter "+N" and we need "+ N".
                spokenText = spokenText.Replace("+", "+ ");
                spokenText = spokenText.Replace("  ", " ");

                double d;
                string[] words = spokenText.Split(' ');
                int w = 0;
                ToggleSwitch ts;
                object obj;
                var eType = eElements.Unknown;

                while (w < words.Length)
                {
                    try
                    {
                        // Is the word a number?
                        d = double.Parse(words[w]);
                        MathEntry(d.ToString(), "Number");
                    }
                    catch
                    {
                        try
                        {
                            // Is the word a ratio?
                            string[] ratio = words[w].Split('/');
                            d = double.Parse(ratio[0]) / double.Parse(ratio[1]);
                            MathEntry(d.ToString(), "Number");
                        }
                        catch
                        {
                            // Check if a word or phrase refers to a button, test phrases up to 4 words long.
                            // There are only buttons in gridButtons, so no need to test for anything else.
                            obj = FindElementFromString(GridButtons.Children, words, w, 4, ref w, ref eType);
                            if (obj != null)
                            {
                                Button_Click(obj, null);
                            }
                            else
                            {
                                // Controls can be up to three words in our app.
                                obj = FindElementFromString(GridCalculator.Children, words, w, 3, ref w, ref eType);
                                if (obj != null)
                                {
                                    switch (eType)
                                    {
                                        case eElements.Button:
                                            Button_Click(obj, null);
                                            break;

                                        case eElements.ToggleSwitch:
                                            ts = (ToggleSwitch)obj;
                                            ts.IsOn = !ts.IsOn;
                                            break;

                                        default:
                                            break;
                                    }
                                }
                            }
                        }
                    }
                    ++w;
                }
                if (mode != Emode.CalculateDone)
                {
                    SayCurrentCalculationAsync();
                }
            }
        }

        private bool IsMatchingElementText(eElements elementType, object obj, string textToMatch)
        {
            string name = "";
            string accessibleName = "";

            switch (elementType)
            {
                case eElements.Button:
                    var b = (Button)obj;
                    name = b.Content.ToString().ToLower();
                    accessibleName = b.GetValue(AutomationProperties.NameProperty).ToString().ToLower();
                    break;

                case eElements.ToggleSwitch:
                    var ts = (ToggleSwitch)obj;
                    name = ts.Header.ToString().ToLower();
                    accessibleName = ts.GetValue(AutomationProperties.NameProperty).ToString().ToLower();
                    break;
            }

            // Return true if the name or accessibleName matches the spoken text.
            if ((textToMatch == name && name.Length > 0) || (textToMatch == accessibleName && accessibleName.Length > 0))
            {
                return true;
            }

            return false;
        }

        private object FindElementFromString(UIElementCollection elements, string[] words, int startIndex, int maxConcatenatedWords, ref int updatedIndex, ref eElements elementType)
        {
            // Return true if the spoken text matches the text for a button.
            int n;
            Button b;
            ToggleSwitch ts;

            // Longer phrazes take precendence over shorter ones, so start with the longest allowed and work down.
            for (int c = maxConcatenatedWords; c > 0; c--)
            {
                if (startIndex + c - 1 < words.Length)
                {
                    // Build the phraze from the following words.
                    string txt = words[startIndex];
                    for (n = 1; n < c; n++)
                    {
                        txt += " " + words[startIndex + n];
                    }

                    // Test the word or phrase against the content/tag/name of each button.
                    for (int i = 0; i < elements.Count; i++)
                    {
                        // Is the UI element a button?
                        try
                        {
                            b = (Button)elements[i];
                            if (IsMatchingElementText(eElements.Button, (object)b, txt))
                            {
                                updatedIndex = startIndex + c - 1;
                                elementType = eElements.Button;
                                return (object)b;
                            }
                        }
                        catch
                        {
                            // UI element is not a button, is it a ToggleSwitch?
                            try
                            {
                                ts = (ToggleSwitch)elements[i];
                                if (IsMatchingElementText(eElements.ToggleSwitch, (object)ts, txt))
                                {
                                    updatedIndex = startIndex + c - 1;
                                    elementType = eElements.ToggleSwitch;
                                    return (object)ts;
                                }
                            }
                            catch
                            {
                                // Ignore the UI element.
                            }
                        }
                    }
                }
            }
            updatedIndex = startIndex;
            return null;
        }

Uwaga

Aby wprowadzić stałą, powiedz „constant” (stała), a następnie powiedz pełną nazwę stałej. Fraza „Say memory” (Powiedz pamięć) spowoduje przedstawienie zawartości pamięci. Fraza „Say calculation” (Powiedz obliczenie) spowoduje przedstawienie zawartości bieżącego obliczenia.

  1. Skompiluj i uruchom aplikację, a następnie włącz przełącznik funkcji rozpoznawania mowy.

  2. Gdy mikrofon będzie gotowy, powiedz „what is 1.23456 times 2.789” (ile jest 1,23456 razy 2,789). Wypowiedziany tekst powinien pojawić się w oknie dialogowym Nasłuchiwanie, które zostanie szybko zamknięte po zakończeniu mówienia. Obliczenie powinno następnie zostać wyświetlone na ekranie.

Speaking a natural addition.

Uwaga

Jeśli w oknie dialogowym Nasłuchiwanie pojawi się komunikat Sorry, didn't catch that (Niezrozumiała fraza), naciśnij spację, aby ponownie wyświetlić Odbiornik.

  1. Spróbuj wprowadzić kilka prostych obliczeń przy użyciu głosu.

Uwaga

Po prostu naciśnij klawisz L i powiedz „clear” (wyczyść), aby wyczyścić błędne obliczenia.

  1. Obliczenia można budować w częściach, ponieważ krótka pauza wystarczy Odbiornikowi do zamknięcia danych wejściowych i rozpoczęcia analizowania. Powiedz na przykład „what is sine 30 times” (ile wynosi sinus z 30 razy). Następnie wybierz klawisz L i gdy Odbiornik pojawi się ponownie, powiedz „the cosine of 30” (cosinus z 30). Następnie wybierz ponownie klawisz L i powiedz „equals” (równa się). Powinien zostać wyświetlony wynik.

  2. Zwróć uwagę, że zbędne słowa, takie jak „what is” (ile wynosi), „of” (z) i „the” mogą być zawarte w zdaniu, ale są prawidłowo ignorowane.

  3. Spróbuj utworzyć jakieś równania (nie muszą mieć matematycznego sensu), dzięki którym przetestujesz wszystkie przyciski i przełączniki, w tym przyciski Clr i Del, przyciski magazynu pamięci, stałe i wszystkie inne elementy. Powinno to pomóc w upewnieniu się, że wszystko jest prawidłowo obsługiwane przez kod.