Benutzerdefinierte Spracherkennung

Abgeschlossen

Zur Verbesserung der standardmäßigen Spracherkennung von Windows müssen wir App-spezifischen Code für ein Spracherkennungssystem schreiben, das ganze Sätze verarbeiten kann.

Da dieses Codieren ziemlich aufwendig ist, bietet es sich stattdessen an, die AutomationProperties.Name-Eigenschaft Ihrer App zu verbessern und dann mit der Spracherkennung von Windows zu testen. Das aktuelle System für diese Eingabemethode funktioniert leider noch nicht wie gewünscht. Daher wird dieses benutzerdefinierte System für eine problemlose Spracheingabe in einem speziellen Kontext entwickelt.

Referenzen

Die vollständige Liste der Befehle finden Sie unter Befehle der Windows-Spracherkennung.

Berechtigung für die Verwendung von Eingaben mit einem Mikrofon und Ausführen der Spracherkennung im Zusammenhang mit diesen Eingaben

Bevor die benutzerdefinierte Spracherkennung verwendet werden kann, müssen mehrere Berechtigungen und Funktionen festgelegt werden.

  1. Öffnen Sie, wenn das Rechner-Projekt geladen ist, in Visual Studio die Datei Package.appxmainifest, und klicken Sie dann auf Funktionen. Aktivieren Sie die Funktion Mikrofon.

Setting the microphone capability.

  1. Das Festlegen dieser Funktion bietet Zugriff auf den Audiofeed des Mikrofons. Speichern und schließen Sie die Manifestdatei.

  2. Im Gegensatz zur App sind weitere Schritte notwendig, damit die Spracherkennung funktioniert. Der Benutzer muss das Mikrofon und die Spracherkennung für die App aktivieren, da die Spracherkennung standardmäßig deaktiviert ist. Der Entwickler ist beim Testen auch der Benutzer. Geben Sie daher in der Windows-Suchleiste „Datenschutzeinstellungen“ ein.

Setting the privacy settings.

  1. Wählen Sie Sprache aus, und stellen Sie sicher, dass die Online speech recognition (Online-Spracherkennung) aktiviert ist. Wählen Sie Mikrofon aus, und stellen Sie sicher, dass Allow apps to access your microphone (Zulassen, dass Apps auf Ihr Mikrofon zugreifen) aktiviert ist. Schließen oder minimieren Sie das Fenster „Einstellungen“.

Später werden diese Einstellungen deaktiviert, um zu testen, ob diese Situationen in der App ordnungsgemäß behandelt werden.

Hinzufügen von Code zum Abgleichen von Wörtern und Sätzen mit UI-Elementen

Es muss ziemlich viel Code hinzugefügt werden, um die benutzerdefinierte Spracherkennung zu unterstützen. Wir beginnen zunächst mit den using-Anweisungen und globalen Variablen.

  1. Fügen Sie am Anfang des Codes die folgenden using-Anweisungen ein.
using Windows.Media.SpeechRecognition;
using Windows.Media.Capture;
  1. Fügen Sie die folgenden globalen Variablen und eine neue Enumeration ein.
        enum eElements
        {
            Button,
            ToggleSwitch,
            Unknown
        }

        bool isRecognitionAvailable;
        SpeechRecognizer speechRecognizer;
  1. Fügen Sie Ihrem Code die folgende Klasse hinzu, um die zuvor beschriebenen Berechtigungsprobleme zu beheben. Durch den Aufruf von RequestMicrophonePermission werden alle notwendigen Berechtigungen überprüft. Hierbei handelt es sich um generischen Code, der in jeder für Windows 10 entwickelten App zur Unterstützung von Berechtigungen für die Spracherkennung per Mikrofon verwendet werden kann, obwohl Cortana und die Diktatsprivatssphäre nicht unterstützt werden.
        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. Es ist empfehlenswert, eine Einstellung zu haben, durch die Spracherkennungsfunktionen ein- bzw. ausgeschaltet werden. Definieren Sie einen anderen Umschalter in der MainPage.xaml-Datei. Beachten Sie, dass die Zugriffstaste L (für „Listener“) festgelegt wird, um den Umschalter auszulösen. Fügen Sie diese vor dem Eintrag ListConstants hinzu.
        <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. Definieren Sie nun in der MainPage.xaml.cs-Datei noch mal das in der XAML genannte ToggleSpeechRecognition_Toggled-Ereignis und einige unterstützende Methoden.
        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. Der ParseSpokenCalculation-Methode wird ein per Spracherkennung aufgenommene Zeichenfolge als Eingabe übergeben. Es muss sehr viel App-spezifischer Code hinzugefügt werden, um diese Zeichenfolge zu verarbeiten.

Dieser Code versucht, die Wörter und Sätze des gesprochenen Satzes mit den Schaltflächen, Umschaltern und Konstanten Ihrer App abzugleichen. Wörter, die nicht übereinstimmen, werden ignoriert. Der folgende Code entspricht einem Brute Force-Ansatz zur Lösung des Problems.

Fügen Sie in Ihrer App den folgenden Code ein.

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

Hinweis

Sagen Sie zunächst „constant“ (Konstante), um eine Konstante einzugeben. Nennen Sie anschließend den vollständigen Namen der Konstante. Wenn Sie „Say memory“ („Nenne Arbeitsspeicher“) sagen, erhalten Sie Informationen über den Inhalt des Arbeitsspeichers. „Say calculation“ („Nenne Berechnung“) gibt Auskunft über den Inhalt der aktuellen Gleichung.

  1. Erstellen Sie die App, führen Sie diese aus, und aktivieren Sie den Umschalter für die Spracherkennung.

  2. Sagen Sie „what is 1.23456 times 2.789“ (Was ist 1,23456 x 2,789?), wenn das Mikrofon bereit ist. Ihnen sollte nun im Dialogfeld Zuhören angezeigt werden, was Sie gesagt haben. Dieses wird schnell geschlossen, wenn Sie nichts mehr sagen. Die Gleichung sollte dann im Anzeigebereich angezeigt werden.

Speaking a natural addition.

Hinweis

Wenn im Dialogfeld „Zuhören“ die Meldung Sorry, didn't catch that (Es tut mir leid, ich konnte das nicht verstehen.) angezeigt wird, drücken Sie die LEERTASTE, um den Listener noch mal aufzurufen.

  1. Versuchen Sie, mithilfe Ihrer Stimme eine Reihe von einfachen Gleichungen einzugeben.

Hinweis

Drücken Sie nur die L-Taste, und sagen Sie „clear“ (Löschen), um eine fehlerhafte Gleichung zu löschen.

  1. Gleichungen können in Teilen erstellt werden, da der Listener schon nach einer kleinen Pause schließt und die Eingabe analysiert. Sagen Sie z. B. „what is sine 30 times“ (Was ist der Sinus von 30?). Klicken Sie dann auf die L-Taste, und sagen Sie „the cosine of 30“ (Der Cosinus von 30), wenn der Listener wieder angezeigt wird. Wählen Sie dann noch mal L aus, und sagen Sie „equals“ (Ist gleich). Daraufhin sollten Sie das Ergebnis erhalten.

  2. Beachten Sie, dass Füllwörter wie „what is“ (Was ist), „of“ (von) und „the“ (der/die/das) im Satz enthalten sein können, dann aber ordnungsgemäß ignoriert werden.

  3. Stellen Sie Gleichungen auf, in denen alle Schaltflächen und Umschalter getestet werden, auch wenn diese Gleichungen keinen mathematischen Sinn ergeben. Verwenden Sie auch die STRG- und die ENTF-TASTE, Speichertasten, Konstanten etc. Dadurch sollte sichergestellt werden, dass alle ordnungsgemäß vom Code verarbeitet werden.