Stimmerkennung

Spracherkennung in .NET-Desktopanwendungen

James McCaffrey

Laden Sie die Codebeispiele herunter

Seit der Einführung von Windows Phone Cortana (und der ähnlichen Ich-brauche-den-Namen-nicht-zu-erwähnen von der Obstfirma) haben die sprachaktivierten Anwendungen auf Personal Assistants einen zunehmend wichtigen Platz in der Softwareentwicklung eingenommen. In diesem Artikel zeige ich Ihnen, wie Sie den Einstieg in die Spracherkennung und Sprachsynthese in Windows-Konsolenanwendungen und WPF-Anwendungen (Windows Presentation Foundation) finden.

Beachten Sie, dass Sie die Sprachfähigkeit auch zu Windows Phone-Apps, ASP.NET-Web Apps, Windows Store-Apps, Windows RT-Apps und Xbox Kinect hinzufügen können, die Techniken dafür unterscheiden sich jedoch von den in diesem Artikel vorgestellten.

Sie können sich dem, was in diesem Artikel erläutert wird, gut annähern, wenn Sie einen Blick auf die beiden verschiedenen Demoprogramme in Abbildung 1 und Abbildung 2 werfen. Nach dem Start hat die Konsolenanwendung in Abbildung 1 sofort den Satz "Ich bin wach" gesprochen. Natürlich können Sie die Demo beim Lesen dieses Artikels nicht hören, daher zeigt das Demoprogramm den Text an, den der Computer spricht. Als nächstes hat der Benutzer den Befehl "Sprache ein" gesprochen. Die Demo hat den erkannten Text wiederholt und dann hinter den Kulissen die Anwendung zum Hören und Beantworten von Anfragen zum Addieren von Zahlen aktiviert.

Spracherkennung und -synthese in einer Konsolenanwendung
Abbildung 1 Spracherkennung und -synthese in einer Konsolenanwendung

Spracherkennung in einer Windows Forms-Anwendung
Abbildung 2 Spracherkennung in einer Windows Forms-Anwendung

Der Benutzer hat die Anwendung zuerst gebeten, eins und zwei und dann zwei und drei zu addieren. Die Anwendung hat diese gesprochenen Befehle erkannt und die Antworten laut zurückgegeben. Ich beschreibe später sinnvollere Anwendungen der Spracherkennung.

Der Benutzer hat dann den Befehl "Sprache aus" ausgegeben, mit dem er das Lauschen nach Befehlen zum Addierren von Zahlen deaktiviert hat, nicht aber die gesamte Spracherkennung. Mit deaktivierter Spracherkennung wurde der nächste gesprochene Befehl zum Addieren von eins und zwei ignoriert. Schließlich hat der Benutzer die Sprache wieder eingeschaltet und den unsinnigen Befehl "Klatu barada nikto" gesprochen, den die Anwendung als Befehl zum vollständigen Deaktivieren der Spracherkennung und Schließen der Anwendung erkannt hat.

Abbildung 2 zeigt eine fiktive sprachaktivierte Windows Forms-Anwendung. Die Anwendung erkennt gesprochene Befehle, antwortet aber nicht mit Sprachausgabe. Beim ersten Starten der Anwendung war das Kontrollkästchen "Sprache ein" nicht aktiviert und zeigte so an, dass die Spracherkennung nicht aktiv war. Der Benutzer hat das Steuerelement "Sprache ein" aktiviert und sagte dann "Hello". Die Anwendung hat den erkannten gesprochenen Text im ListBox-Steuerelement unten in der Anwendung wiederholt.

Der Benutzer sagte dann "Set text box 1 to red" ("Mache Textfeld 1 rot"). Die Anwendung erkannte "Set text box 1 red", was fast – aber nicht ganz – genau das ist, was der Benutzer gesprochen hat. Es ist zwar in Abbildung 2 nicht zu sehen, doch der Text iim TextBox-Steuerelement oben in der Anwendung wurde tatsächlich "red" gemacht.

Als nächstes sagte der Benutzer dann "Please set text box 1 to white" ("Bitte mache Textfeld 1 weiß"). Die Anwendung hat "set text box 1 white" und den Befehl umgesetzt. Der Benutzer sagte zum Abschluss "Good-bye", und die Anwendung hat den Befehl wiederholt, das Windows Forms-Formular aber nicht geändert, was sie aber hätte tun können, z. B. durch Deaktivieren des Kontrollkästchens "Sprache ein".

In den folgenden Abschnitten führe ich Sie schrittweise durch das Erstellen beider Demoprogramme, einschließlich der Installation der erforderlichen .NET-Sprachbibliotheken. Dieser Artikel geht davon aus, dass Sie über mindestens fortgeschrittene Programmierkenntnisse verfügen, aber er nimmt nicht an, dass Sie irgendetwas über Spracherkennung oder Sprachsynthese wissen.

Hinzufügen von Sprache zu einer Konsolenanwendung

Zum Erstellen der in Abbildung 1 dargestellten Demo habe ich Visual Studio gestartet und eine neue C#-Konsolenanwendung mit dem Namen "ConsoleSpeech" erstellt. Ich habe Sprache erfolgreich mit Visual Studio 2010 und 2012 verwendet, jedoch sollte jede neuere Version funktionieren. Nach dem Laden des Vorlagencodes im Editor habe ich im Fenster des Projektmappen-Explorers die Datei "Program.cs" umbenannt und ihr den anschaulicheren Namen "ConsoleSpeechProgram.cs" gegeben. Visual Studio hat daraufhin automatisch die Programmklasse umbenannt.

Als nächsten Schritt habe ich dann einen Verweis auf die Datei "Microsoft.Speech.dll" hinzugefügt, die sich unter "C:\ProgramFiles (x86)\Microsoft SDKs\Speech\v11.0\Assembly" befand. Diese DLL befand sich nicht auf meinem Hostcomputer und musste heruntergeladen werden. Das Installieren der erforderlichen Dateien, um einer Anwendung Spracherkennung und -synthese hinzuzufügen, ist nicht ganz trivial. Ich erläutere den Installationsvorgang detailliert im nächsten Abschnitt dieses Artikels, aber für den Moment gehen wir davon aus, dass "Microsoft.Speech.dll" auf Ihrem Computer vorhanden ist.

Nachdem ich den Verweis auf die Sprach-DLL hinzugefügt habe, habe ich am Anfang des Quellcodes alle "using"-Anweisungen entfernt, mit Ausnahme der, die auf den Namespace der obersten Ebene, "System", verweist. Anschließend habe ich "using"-Anweisungen für die Namespaces "Microsoft.Speech.Recognition", "Microsoft.Speech.Synthesis" und "System.Globalization" hinzugefügt. Die beiden ersten Namespaces sind der Sprach-DLL zugeordnet. Anmerkung: Verwirrender Weise gibt es außerdem die Namespaces "System.Speech.Recognition" und "System.Speech.Synthesis". Ich werde den Unterschied gleich erläutern. Der Namespace "Globalization" war schon standardmäßig verfügbar und machte keinen neuen Verweis im Projekt erforderlich.

Der gesamte Quellcode für die Demo der Konsolenanwendung ist in Abbildung 3 dargestellt und steht auch im Codedownload zu diesem Artikel zur Verfügung. Ich habe die üblichen Fehlerprüfungen entfernt, damit die wichtigsten Konzepte so deutlich wie möglich sind.

Abbildung 3 Quellcode für die Demo der Konsolenanwendung

using System;
using Microsoft.Speech.Recognition;
using Microsoft.Speech.Synthesis;
using System.Globalization;
namespace ConsoleSpeech
{
  class ConsoleSpeechProgram
  {
    static SpeechSynthesizer ss = new SpeechSynthesizer();
    static SpeechRecognitionEngine sre;
    static bool done = false;
    static bool speechOn = true;
    static void Main(string[] args)
    {
      try
      {
        ss.SetOutputToDefaultAudioDevice();
        Console.WriteLine("\n(Speaking: I am awake)");
        ss.Speak("I am awake");
        CultureInfo ci = new CultureInfo("en-us");
        sre = new SpeechRecognitionEngine(ci);
        sre.SetInputToDefaultAudioDevice();
        sre.SpeechRecognized += sre_SpeechRecognized;
        Choices ch_StartStopCommands = new Choices();
        ch_StartStopCommands.Add("speech on");
        ch_StartStopCommands.Add("speech off");
        ch_StartStopCommands.Add("klatu barada nikto");
        GrammarBuilder gb_StartStop = new GrammarBuilder();
        gb_StartStop.Append(ch_StartStopCommands);
        Grammar g_StartStop = new Grammar(gb_StartStop);
        Choices ch_Numbers = new Choices();
        ch_Numbers.Add("1");
        ch_Numbers.Add("2");
        ch_Numbers.Add("3");
        ch_Numbers.Add("4");
        GrammarBuilder gb_WhatIsXplusY = new GrammarBuilder();
        gb_WhatIsXplusY.Append("What is");
        gb_WhatIsXplusY.Append(ch_Numbers);
        gb_WhatIsXplusY.Append("plus");
        gb_WhatIsXplusY.Append(ch_Numbers);
        Grammar g_WhatIsXplusY = new Grammar(gb_WhatIsXplusY);
        sre.LoadGrammarAsync(g_StartStop);
        sre.LoadGrammarAsync(g_WhatIsXplusY);
        sre.RecognizeAsync(RecognizeMode.Multiple);
        while (done == false) { ; }
        Console.WriteLine("\nHit <enter> to close shell\n");
        Console.ReadLine();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
      }
    } // Main
    static void sre_SpeechRecognized(object sender,
      SpeechRecognizedEventArgs e)
    {
      string txt = e.Result.Text;
      float confidence = e.Result.Confidence;
      Console.WriteLine("\nRecognized: " + txt);
      if (confidence < 0.60) return;
      if (txt.IndexOf("speech on") >= 0)
      {
        Console.WriteLine("Speech is now ON");
        speechOn = true;
      }
      if (txt.IndexOf("speech off") >= 0)
      {
        Console.WriteLine("Speech is now OFF");
        speechOn = false;
      }
      if (speechOn == false) return;
      if (txt.IndexOf("klatu") >= 0 && txt.IndexOf("barada") >= 0)
      {
        ((SpeechRecognitionEngine)sender).RecognizeAsyncCancel();
        done = true;
        Console.WriteLine("(Speaking: Farewell)");
        ss.Speak("Farewell");
      }
      if (txt.IndexOf("What") >= 0 && txt.IndexOf("plus") >= 0)
      {
        string[] words = txt.Split(' ');
        int num1 = int.Parse(words[2]);
        int num2 = int.Parse(words[4]);
        int sum = num1 + num2;
        Console.WriteLine("(Speaking: " + words[2] + " plus " +
          words[4] + " equals " + sum + ")");
        ss.SpeakAsync(words[2] + " plus " + words[4] +
          " equals " + sum);
      }
    } // sre_SpeechRecognized
  } // Program
} // ns

Nach den "using"-Anweisungen beginnt der Code der Demo wie folgt:

namespace ConsoleSpeech
{
  class ConsoleSpeechProgram
  {
    static SpeechSynthesizer ss = new SpeechSynthesizer();
    static SpeechRecognitionEngine sre;
    static bool done = false;
    static bool speechOn = true;
    static void Main(string[] args)
    {
...

Das klassenweit gültige Objekt "SpeechSynthesizer" verleiht der Anwendung die Fähigkeit zu sprechen. Das Objekt "SpeechRecognitionEngine" ermöglicht der Anwendung, auf gesprochene Wörter zu lauschen und gesprochene Wörter oder Formulierungen zu erkennen. Die Boolesche Variable "done" legt fest, wann die gesamte Anwendung abgeschlossen ist. Die Boolesche Variable "speechOn" steuert, ob die Anwendung auf andere Befehle als einen Befehl zum Schließen des Programms lauscht.

Die Idee dabei ist, dass die Konsolenanwendung keine Tastatureingaben akzeptiert, sodass die Anwendung ständig nach Befehlen lauscht. Wenn "speechOn" jedoch den Wert "false" trägt, wird nur der Befehl zum Beenden des Programms erkannt und verarbeitet, andere Befehle werden erkannt aber ignoriert.

Die Methode "Main" beginnt mit:

try
{
  ss.SetOutputToDefaultAudioDevice();
  Console.WriteLine("\n(Speaking: I am awake)");
  ss.Speak("I am awake");

Das Objekt "SpeechSynthesizer" wurde bei der Deklaration instanziiert. Die Verwendung eines Synthetisiererobjekts ist ziemlich einfach. Die Methode "SetOutput­ToDefaultAudioDevice" sendet Ausgaben an die Lautsprecher Ihres Computers (Ausgaben können auch an eine Datei gesendet werden). Die Methode "Speak" akzeptiert eine Zeichenfolge und, nun ja, spricht. So einfach ist das.

Spracherkennung ist sehr viel schwieriger als Sprachsynthese. Die Methode "Main" fährt mit dem Erstellen des Erkennerobjekts fort:

CultureInfo ci = new CultureInfo("en-us");
sre = new SpeechRecognitionEngine(ci);
sre.SetInputToDefaultAudioDevice();
sre.SpeechRecognized += sre_SpeechRecognized;

Zuerst wird die zu erkennende Sprache, in diesem Fall amerikanisches Englisch, in einem Objekt "CultureInfo" angegeben. Das Objekt "CultureInfo" befindet sich im Namespace "Globalization", auf den mithilfe einer Anweisung verwiesen wurde. Als nächstes wird, nach dem Aufrufen des "SpeechRecognitionEngine"-Konstruktors, die Sprachausgabe auf das Standardaudiogerät festgelegt, in den meisten Fällen ein Mikrofon. Beachten Sie, dass die meisten Laptops über ein eingebautes Mikrofon verfügen, die meisten Desktopcomputer benötigen jedoch ein externes Mikrofon (heutzutage häufig in einem Headset kombiniert).

Die Schlüsselmethode für das Erkennerobjekt ist der "SpeechRecognized"-Ereignishandler. Wenn Sie Visual Studio verwenden, "sre.Speech­Recognized +=" eingeben und nur einen Sekundenbruchteil warten, ergänzt die AutoVervollständigung Ihre Anweisung mit "sre_SpeechRecognized", dem Namen des Ereignishandlers. Ich empfehle, die Tabulatortaste zu drücken, um diesen Standardnamen zu akzeptieren und verwenden.

Als nächstes richtet die Demo die Möglichkeit zum Erkennen von Befehlen zum Addieren von zwei Zahlen ein:

Choices ch_Numbers = new Choices();
ch_Numbers.Add("1");
ch_Numbers.Add("2");
ch_Numbers.Add("3");
ch_Numbers.Add("4"); // Technically Add(new string[] { "4" });
GrammarBuilder gb_WhatIsXplusY = new GrammarBuilder();
gb_WhatIsXplusY.Append("What is");
gb_WhatIsXplusY.Append(ch_Numbers);
gb_WhatIsXplusY.Append("plus");
gb_WhatIsXplusY.Append(ch_Numbers);
Grammar g_WhatIsXplusY = new Grammar(gb_WhatIsXplusY);

Die drei Schlüsselobjekte hierzu sind eine Sammlung "Choices", eine Vorlage "GrammarBuilder" und das steuernde "Grammar". Wenn ich ein Objekt "Grammar" zur Erkennung entwerfe, beginne ich, indem ich einige spezifische Beispiele dessen aufliste, was ich erkennen möchte. Also z. B. "Was ist eins plus zwei?" und "Was ist drei plus vier?"

Anschließend bestimme ich die entsprechende allgemeine Vorlage, z. B. "Was ist <x> plus <y>?". Die Vorlage ist ein "GrammarBuilder", und die spezifischen Werte, die in diese Vorlage eingehen, sind die "Choices". Das Objekt "Grammar" verkapselt die Vorlage und die Wahlmöglichkeiten (choices).

In der Demo habe ich die zu addierenden Zahlen auf 1 bis 4 beschränkt und sie der Sammlung "Choices" als Zeichenfolgen hinzugefügt. Ein besserer Ansatz ist:

string[] numbers = new string[] { "1", "2", "3", "4" };
Choices ch_Numbers = new Choices(numbers);

Ich stelle den schwächeren Ansatz zum Erstellen einer "Choices"-Sammlung aus zwei Gründen vor. Erstens war das Hinzufügen jeweils immer nur einer Zeichenfolge der einzige Ansatz, den ich in anderen Sprachbeispielen gefunden habe. Zweitens könne man sich denken, dass das Hinzufügen von jeweils nur einer Zeichenfolge gar nicht einmal funktionieren sollte; das in Echtzeit funktionierende IntelliSense von Visual Studio zeigt, dass eine der Add-Überladungen einen Parameter vom Typ "params string[] phrases" akzeptiert. Wenn Ihnen das Schlüsselwort "params" entgangen ist, könnten Sie denken, die Methode "Add" akzeptiere nur ein Array von Zeichenfolgen anstelle entweder eines Arrays vom Typ Zeichenfolge oder eine einzelne Zeichenfolge. Ich empfehle die Übergabe eines Arrays.

Das Erstellen einer Sammlung "Choices" aus aufeinander folgenden Zahlen ist ein ziemlicher Sonderfall und lässt einen programmatischen Ansatz wie diesen zu:

string[] numbers = new string[100];
for (int i = 0; i < 100; ++i)
  numbers[i] = i.ToString();
Choices ch_Numbers = new Choices(numbers);

Nach dem Erstellen der Wahlmöglichkeiten (Choices), mit denen die "Einschübe" von "GrammarBuilder" gefüllt werden, erstellt die Demo den "GrammarBuilder" und dann das steuernde Objekt "Grammar" in dieser Weise:

GrammarBuilder gb_WhatIsXplusY = new GrammarBuilder();
gb_WhatIsXplusY.Append("What is");
gb_WhatIsXplusY.Append(ch_Numbers);
gb_WhatIsXplusY.Append("plus");
gb_WhatIsXplusY.Append(ch_Numbers);
Grammar g_WhatIsXplusY = new Grammar(gb_WhatIsXplusY);

Die Demo verwendet ein ähnliches Muster, um ein Objekt "Grammar" für start- und stoppbezogene Befehle zu erstellen:

Choices ch_StartStopCommands = new Choices();
ch_StartStopCommands.Add("speech on");
ch_StartStopCommands.Add("speech off");
ch_StartStopCommands.Add("klatu barada nikto");
GrammarBuilder gb_StartStop = new GrammarBuilder();
gb_StartStop.Append(ch_StartStopCommands);
Grammar g_StartStop = new Grammar(gb_StartStop);

Beim Definieren von Grammatiken (also "Grammar"-Objekten) haben Sie viel Flexibilität. Hier werden die Befehle "speech on", "speech off" und "klatu barada nikto" alle in der gleichen Grammatik platziert, da sie logisch verwandt sind. Die drei Befehle hätten in drei verschiedenen Grammatiken definiert werden können; alternativ können Sie auch die Befehle "speech on" und "speech off" in einer Grammatik und den Befehl "klatu barada nikto" in einer zweiten Grammatik definieren.

Nachdem alle "Grammar"-Objekte erstellt wurden, werden sie an die Spracherkennung übergeben, und die Spracherkennung wird aktiviert:

sre.LoadGrammarAsync(g_StartStop);
sre.LoadGrammarAsync(g_WhatIsXplusY);
sre.RecognizeAsync(RecognizeMode.Multiple);

Das Argument "RecognizeMode.Multiple" ist erforderlich, wenn mehr als eine Grammatik vorhanden ist, was in allen außer den allereinfachsten Programmen der Fall ist. Die Methode "Main" endet in dieser Weise:

...
    while (done == false) { ; }
    Console.WriteLine("\nHit <enter> to close shell\n");
    Console.ReadLine();
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message);
    Console.ReadLine();
  }
} // Main

Die seltsam aussehende leere While-Schleife ermöglicht der Shell der Konsolenanwendung, aktiv zu bleiben. Die Schleife wird beendet, wenn die klassenweit gültige Boolesche Variable "done" vom Ereignishandler der Spracherkennung auf "true" festgelegt wird.

Verarbeiten von erkannter Sprache

Der Code für den Ereignishandler für erkannte Sprache beginnt wie folgt:

static void sre_SpeechRecognized(object sender,
  SpeechRecognizedEventArgs e)
{
  string txt = e.Result.Text;
  float confidence = e.Result.Confidence;
  Console.WriteLine("\nRecognized: " + txt);
  if (confidence < 0.60) return;
...

Der eigentliche Text, der erkannt wurde, wird in der Eigenschaft "SpeechRecognized­EventArgs Result.Text" gespeichert. Alternativ können Sie die Auflistung "Result.Words" verwenden. Die Eigenschaft "Result.Confidence" enthält einen Wert zwischen 0,0 und 1,0, der ein ungefähres Maß dafür angibt, wie genau der gesprochene Text mit einer der Grammatiken übereinstimmt, die dem Erkennungsmodul zugeordnet sind. Die Demo weist den Ereignishandler an, jeden mit nur geringer Vertrauenswürdigkeit erkannten Text zu ignorieren.

Konfidenzwerte können stark schwanken, abhängig von der Komplexität Ihrer Grammatiken, der Qualität Ihres Mikrofons usw. Wenn das Demoprogramm beispielsweise nur 1 bis 4 erkennen muss, sind die Konfidenzwerte auf meinem Computer normalerweise um 0,75. Wenn die Grammatik jedoch Werte von 1 bis 100 erkennen muss, fallen die Konfidenzwerte auf ungefähr 0,25. Kurz gesagt, normalerweise müssen Sie mit den Konfidenzwerten experimentieren, um gute Ergebnisse bei der Spracherkennung zu erhalten.

Als nächstes schaltet der Ereignishandler der Spracherkennung die Erkennung ein und aus:

if (txt.IndexOf("speech on") >= 0)
{
  Console.WriteLine("Speech is now ON");
  speechOn = true;
}
if (txt.IndexOf("speech off") >= 0)
{
  Console.WriteLine("Speech is now OFF");
  speechOn = false;
}
if (speechOn == false) return;

Die Logik ist vielleicht nicht auf den ersten Blick einleuchtend, wenn Sie sie aber einen Moment betrachten, sollte sie Ihnen klar werden. Als Nächstes wird der geheime Befehl zum Beenden verarbeitet:

if (txt.IndexOf("klatu") >= 0 && txt.IndexOf("barada") >= 0)
{
  ((SpeechRecognitionEngine)sender).RecognizeAsyncCancel();
  done = true;
  Console.WriteLine("(Speaking: Farewell)");
  ss.Speak("Farewell");
}

Beachten Sie, dass das Spracherkennungsmodul tatsächlich unsinnige Wörter erkennen kann. Wenn ein "Grammar"-Objekt Wörter enthält, die sich nicht im integrierten Wörterbuch des Objekts befinden, versucht die Grammatik, diese Wörter mithilfe von semantischer Heuristik so gut wie möglich zu identifizieren und ist dabei meistens ziemlich erfolgreich. Das ist der Grund, warum ich "klatu" anstelle des korrekten "klaatu" (aus einem alten Science Fiction-Film) verwendet habe.

Beachten Sie ferner, dass Sie nicht den gesamten erkannten Grammatiktext ("klatu barada nikto") zu verarbeiten brauchen sondern nur genügend Informationen benötigen, um eine Formulierung der Grammatik eindeutig zu identifizieren ("klatu" und "barada").

Als nächstes werden die Befehle zum Addieren von zwei Zahlen verarbeitet und der Ereignishandler, die Programmklasse und der Namespace dann beendet:

...
      if (txt.IndexOf("What") >= 0 && txt.IndexOf("plus") >= 0)
      {
        string[] words = txt.Split(' ');
        int num1 = int.Parse(words[2]);
        int num2 = int.Parse(words[4]);
        int sum = num1 + num2;
        Console.WriteLine("(Speaking: " + words[2] +
          " plus " + words[4] + " equals " + sum + ")");
        ss.SpeakAsync(words[2] + " plus " + words[4] +
          " equals " + sum);
      }
    } // sre_SpeechRecognized
  } // Program
} // ns

Beachten Sie, dass beim Text in "Results.Text" Groß- und Kleinschreibung unterschieden werden ("What" i. Ggs. zu "what"). Sobald Sie eine Formulierung erkannt haben, können Sie bestimmte Wörter herauslösen. In diesem Fall hat der erkannte Text die Form "What is x plus y" (Was ist x plus y), so das sich das “What” in "words[0]" und die beiden zu addierenden Zahlen (als Zeichenfolgen) in "words[2]" und "words[4]" befinden.

Installieren der Bibliotheken

Bei der Erläuterung des Demoprogramms sind wir davon ausgegangen, dass auf Ihrem Computer alle erforderlichen Sprachbibliotheken installiert sind. Zum Erstellen und Ausführen der Demoprogramme müssen Sie vier Pakete installieren: ein SDK, um in der Lage zu sein, die Demos in Visual Studio zu erstellen, eine Runtime, um die Demos nach dem Erstellen ausführen zu können, eine Erkennungssprache und eine Synthesesprache (für die Sprachausgabe).

Um das SDK zu installieren, führen Sie eine Internetsuche nach "Speech Platform 11 SDK" aus. Dadurch gelangen Sie zur richtigen Seite im Microsoft Download Center, wie in Abbildung 4 dargestellt. Nach dem Klicken auf die Schaltfläche "Herunterladen" sehen Sie die in Abbildung 5 dargestellten Optionen. Das SDK ist in 32- und 64-Bit-Versionen erhältlich. Ich empfehle dringend, die 32-Bit-Version zu verwenden, unabhängig von der Konfiguration Ihres Hostcomputers. Die 64-Bit-Version arbeitet mit einigen Anwendungen nicht zusammen.

Die Hauptseite der SDK-Installation im Microsoft Download Center
Abbildung 4 Die Hauptseite der SDK-Installation im Microsoft Download Center

Installieren des Sprach-SDKs
Abbildung 5 Installieren des Sprach-SDKs

Sie brauchen nichts außer der einzelnen x86 (32-Bit) MSI-Datei. Nach dem Auswählen dieser Datei und Klicken auf die Schaltfläche "Weiter" können Sie das Installationsprogramm direkt ausführen. Eis Sprachbibliotheken geben nicht viel Feedback, wenn die Installation abgeschlossen ist, daher sollten Sie nicht auf eine Erfolgsnachricht warten.

Als nächstes sollten Sie die Runtime für die Sprache installieren. Nach dem Aufsuchen der Hauptseite und Klicken auf die Schaltfläche "Weiter" sehen Sie die in Abbildung 6 dargestellten Optionen.

Installing the Speech Runtime
Abbildung 6 Installieren der Sprach-Runtime

Es ist wichtig, die gleiche Plattformversion (11 in der Demo) und Bitversion (32 [x86] oder 64 [x64]) wie für das SDK auszuwählen. Noch einmal, ich empfehle dringend die 32-Bit-Version, selbst wenn Sie auf einem 64-Bit-Computer arbeiten.

Als nächstes können Sie die Erkennungssprache installieren. Die Downloadseite ist in Abbildung 7 dargestellt. Für die Demo wurde "MSSpeech_SR_en-us_TELE.msi" (Englisch USA) verwendet. Das SR steht für "Speech Recognition" (Spracherkennung), und das TELE für Telefonie, was bedeutet, dass die Erkennungssprache für die Zusammenarbeit mit Audioquellen geringer Qualität, wie dem Signal von einem Telefon oder Tischmikrofon ausgelegt ist.

Installieren der Erkennungssprache
Abbildung 7 Installieren der Erkennungssprache

Schließlich können Sie die Synthesesprache und -stimme installieren. Die Downloadseite ist in Abbildung 8 dargestellt. Die Demo verwende die Datei "MSSpeech_TTS_en-us_Helen.msi". Das TTS steht für Text-to-Speech (Sprache-zu-Text), was im Grunde ein synonymer Ausdruck für Sprachsynthese ist. Beachten Sie, dass für amerikanisches Englisch zwei Stimmen verfügbar sind. Es gibt auch andere als amerikanische Stimmen für Englisch. Das Erstellen von Synthesedateien ist ziemlich schwierig. Es ist möglich, eine Reihe anderer Stimmen von einer Handvoll Unternehmen zu kaufen und dann zu installieren.

Installieren der Synthesesprache und -stimm
Abbildung 8 Installieren der Synthesesprache und -stimme

Obwohl eine Sprache für eine Spracherkennung und eine Stimme/Sprache für Sprachsynthese wirklich zwei völlig verschiedene Dinge sind, stellen doch beide Downloads Optionen auf der gleichen Downloadseite dar. Die Benutzeroberfläche des Download Centers erlaubt Ihnen zwar, sowohl eine Erkennungssprache als auch eine Synthesesprache auszuwählen, der Versuch, beide gleichzeitig zu installieren, hat sich für mein System jedoch als desaströs erweisen, daher empfehle ich, sie nacheinander zu installieren.

Microsoft.Speech im Gegensatz zu System.Speech

Wenn Sie bei Spracherkennung und -synthese für Windows-Anwendungen neu sind, können Sie durch die Dokumentation leicht verwirrt werden, weil es mehrere Sprachplattformen gibt. Insbesondere gibt es über die "Microsoft.Speech.dll"-Bibliothek, die von den Demos in diesem Artikel verwendet wird, hinaus noch eine "System.Speech.dll"-Bibliothek, die Teil des Windows-Betriebssystems ist. Die beiden Bibliotheken sind insofern ähnlich, dass die APIs fast, aber nicht ganz, gleich sind. Wenn Sie also online nach Sprachbeispielen suchen und einen Codeausschnitt anstelle eines vollständigen Programms finden, ist nicht immer zu ersehen, ob sich das Beispiel auf "System.Speech" oder auf "Microsoft.Speech" bezieht.

Unterm Strich lässt sich sagen, wenn Sie in Spracherkennung erst einsteigen, sollten Sie zum Hinzufügen von Sprache zu einer .NET-Anwendung die Microsoft.Speech-Bibliothek und nicht die System.Speech-Bibliothek verwenden.

Obwohl die beiden Bibliotheken teilweise auf einer gemeinsamen Codebasis beruhen und über ähnliche APIs verfügen, sind sie doch eindeutig verschieden. Einige der wichtigsten Unterschiede sind in der Tabelle in Abbildung 9 zusammengefasst.

Abbildung 9 "Microsoft.Speech" i. Ggs. zu "System.Speech"

Microsoft.Speech.dll System.Speech.dll
Muss separat installiert werden Teil des BS (Windows Vista+)
Kann mit Apps paketiert werden Kann nicht weiterverteilt werden
Muss Grammatiken erstellen Verwender Grammatiken oder freies Diktat
Kein Benutzertraining Training für bestimmten Benutzer
Verwendet verwalteten Code für API (C#) Verwendet systemeigenen Code für -API (C++)

Die "System.Speech"-DLL ist ein Teil des Betriebssystems, daher ist sie auf jedem Windows-Computer installiert. Die "Microsoft.Speech"-DLL (und die zugehörige(n) Runtime und Sprachen) müssen auf einen Computer heruntergeladen und dann installiert werden. "System.Speech"-Erkennung erfordert normalerweise Training durch den Benutzer, bei dem der Benutzer etwas Text liest und das System lernt, die Aussprache dieses bestimmten Benutzers zu verstehen. "Microsoft.Speech"-Erkennung funktioniert sofort für jeden Benutzer. "System.Speech" kann praktisch alle Wörter erkennen (als freies Diktat bezeichnet). "Microsoft.Speech" erkennt nur Wörter und Formulierungen, die in einer im Programm definierten Grammatik vorkommen.

Hinzufügen von Spracherkennung zu einer Windows Forms-Anwendung

Der Vorgang, einer Windows Forms- oder WPF-Anwendung Spracherkennung und -synthese hinzuzufügen ist dem beim Hinzufügen von Sprache zu einer Konsolenanwendung ähnlich. Zum Erstellen des fiktiven Demoprogramms aus Abbildung 2 habe ich Visual Studio gestartet und eine neue Windows Forms-Anwendung in C# erstellt, die ich dann WinFormSpeech benannt habe.

Nachdem der Vorlagencode im Visual Studio-Editor geladen wurde, habe ich im Fenster des Projektmappen-Explorers einen Verweis auf die Datei "Microsoft.Speech.dll" hinzugefügt, genau so, wie ich das auch bei der Konsolenanwendung getan habe. Am Anfang des Quellcodes habe ich unnötige "using"-Anweisungen gelöscht und nur die Verweise auf die Namespaces "System", "Data", "Drawing" und "Forms" stehen gelassen. Ich habe zwei "using"-Anweisungen hinzugefügt, um die Namespaces "Microsoft.Speech.Recognition" und "System.Globalization" in den Bereich der Anwendung zu bringen.

Das Windows Forms-Demo verwendet keine Sprachsynthese, daher benutze ich keinen Verweis auf die "Microsoft.Speech.Synthesis"-Bibliothek. Das Hinzufügen von Sprachsynthese zu einer Windows Forms-App verläuft genau gleich wie das Hinzufügen von Sprachsynthese zu einer Konsolen-App.

In der Visual Studio-Entwurfsansicht habe ich ein TextBox-Steuerelement, ein CheckBox-Steuerelement und ein ListBox-Steuerelement auf das Formular gezogen. Ich habe auf das CheckBox-Steuerelement doppelgeklickt, daraufhin hat Visual Studio automatisch ein Gerüst der "CheckChanged"-Ereignishandlermethode erstellt.

Erinnern Sie sich, dass die Demo der Konsolen-App sofort begonnen hat, nach gesprochenen Befehlen zu lauschen und das bis zum Beenden der App fortgesetzt hat. Dieser Ansatz kann für eine Windows Forms-App verwendet werden, ich habe mich jedoch entschieden, dem Benutzer stattdessen das Ein- und Ausschalten der Spracherkennung mithilfe des CheckBox-Steuerelements zu ermöglichen.

Der Quellcode für die "Form1.cs"-Datei, die eine Teilklasse definiert, ist in Abbildung 10 zu sehen. Ein Objekt eines Spracherkennungsmoduls wird deklariert und als Formularelement instanziiert. Innerhalb des Formularkonstruktors richte ich den SpeechRecognized-Ereignishandler ein und erstelle und lade zwei Grammatiken:

public Form1()
{
  InitializeComponent();
  sre.SetInputToDefaultAudioDevice();
  sre.SpeechRecognized += sre_SpeechRecognized;
  Grammar g_HelloGoodbye = GetHelloGoodbyeGrammar();
  Grammar g_SetTextBox = GetTextBox1TextGrammar();
  sre.LoadGrammarAsync(g_HelloGoodbye);
  sre.LoadGrammarAsync(g_SetTextBox);
  // sre.RecognizeAsync() is in CheckBox event
}

Abbildung 10 Hinzufügen von Spracherkennung zu einem Windows Forms-Formular

using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Speech.Recognition;
using System.Globalization;
namespace WinFormSpeech
{
  public partial class Form1 : Form
  {
    static CultureInfo ci = new CultureInfo("en-us");
    static SpeechRecognitionEngine sre = 
      new SpeechRecognitionEngine(ci);
    public Form1()
    {
      InitializeComponent();
      sre.SetInputToDefaultAudioDevice();
      sre.SpeechRecognized += sre_SpeechRecognized;
      Grammar g_HelloGoodbye = GetHelloGoodbyeGrammar();
      Grammar g_SetTextBox = GetTextBox1TextGrammar();
      sre.LoadGrammarAsync(g_HelloGoodbye);
      sre.LoadGrammarAsync(g_SetTextBox);
      // sre.RecognizeAsync() is in CheckBox event
    }
    static Grammar GetHelloGoodbyeGrammar()
    {
      Choices ch_HelloGoodbye = new Choices();
      ch_HelloGoodbye.Add("hello");
      ch_HelloGoodbye.Add("goodbye");
      GrammarBuilder gb_result = 
        new GrammarBuilder(ch_HelloGoodbye);
      Grammar g_result = new Grammar(gb_result);
      return g_result;
    }
    static Grammar GetTextBox1TextGrammar()
    {
      Choices ch_Colors = new Choices();
      ch_Colors.Add(new string[] { "red", "white", "blue" });
      GrammarBuilder gb_result = new GrammarBuilder();
      gb_result.Append("set text box 1");
      gb_result.Append(ch_Colors);
      Grammar g_result = new Grammar(gb_result);
      return g_result;
    }
    private void checkBox1_CheckedChanged(object sender, 
      EventArgs e)
    {
      if (checkBox1.Checked == true)
        sre.RecognizeAsync(RecognizeMode.Multiple);
      else if (checkBox1.Checked == false) // Turn off
        sre.RecognizeAsyncCancel();
    }
    void sre_SpeechRecognized(object sender, 
      SpeechRecognizedEventArgs e)
    {
      string txt = e.Result.Text;
      float conf = e.Result.Confidence;
      if (conf < 0.65) return;
      this.Invoke(new MethodInvoker(() =>
      { listBox1.Items.Add("I heard you say: " 
      + txt); })); // WinForm specific
      if (txt.IndexOf("text") >= 0 && txt.IndexOf("box") >=
        0 && txt.IndexOf("1")>= 0)
      {
        string[] words = txt.Split(' ');
        this.Invoke(new MethodInvoker(() =>
        { textBox1.Text = words[4]; })); // WinForm specific
      }
    }
  } // Form
} // ns

Ich hätte die zwei "Grammar"-Objekte direkt erstellen können, wie ich das in der Demo der Konsolenanwendung getan habe, aber stattdessen habe ich zu diesem Zweck zwei Hilfsmethoden "GetHelloGoodbyeGrammar" und "GetTextBox1TextGrammar" definiert, um alles ein bisschen sauberer zu halten.

Beachten Sie, dass der Formularkonstruktor nicht die Methode "RecognizeAsync" aufruft, was bedeutet, dass die Spracherkennung nicht sofort beim Starten der Anwendung aktiv ist.

Die Hilfsmethode "GetHelloGoodbyeGrammar" folgt dem gleichen Muster, das zuvor in diesem Artikel beschrieben wurde:

static Grammar GetHelloGoodbyeGrammar()
{
  Choices ch_HelloGoodbye = new Choices();
  ch_HelloGoodbye.Add("hello"); // Should be an array!
  ch_HelloGoodbye.Add("goodbye");
  GrammarBuilder gb_result =
    new GrammarBuilder(ch_HelloGoodbye);
  Grammar g_result = new Grammar(gb_result);
  return g_result;
}

In ähnlicher Weise birgt die Hilfsmethode, die ein "Grammar"-Objekt zum Festlegen des Texts im Windows Forms-TextBox-Steuerelement enthält, keine Überraschungen:

static Grammar GetTextBox1TextGrammar()
{
  Choices ch_Colors = new Choices();
  ch_Colors.Add(new string[] { "red", "white", "blue" });
  GrammarBuilder gb_result = new GrammarBuilder();
  gb_result.Append("set text box 1");
  gb_result.Append(ch_Colors);
  Grammar g_result = new Grammar(gb_result);
  return g_result;
}

Die Hilfsmethode erkennt die Formulierung "set text box 1 red". Der Benutzer muss diese Formulierung jedoch nicht exakt sprechen. Beispielsweise kann ein Benutzer sagen, "Please set the text in text box 1 to red" (Bitte mache den Text in Textfeld 1 rot), dann würde das Spracherkennungsmodul die Formulierung immer noch als "set text box 1 red" erkennen, allerdings mit einem geringeren Konfidenzwert als wenn der Benutzer dem Grammatikmuster exakt entsprochen hätte. Anders gesagt, beim Erstellen von Grammatiken müssen Sie nicht jede Variation einer Formulierung berücksichtigen. Das vereinfacht die Verwendung von Spracherkennung dramatisch.

Der CheckBox-Ereignishandler ist wie folgt definiert:

private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
  if (checkBox1.Checked == true)
    sre.RecognizeAsync(RecognizeMode.Multiple);
  else if (checkBox1.Checked == false) // Turn off
    sre.RecognizeAsyncCancel();
}

Das Objekt des Spracherkennungsmoduls, sre, bleibt während der gesamten Lebensdauer der Windows Forms-App ständig präsent. Das Objekt wird mithilfe der Methoden "RecognizeAsync" und "RecognizeAsync­Cancel" aktiviert und deaktiviert, wenn der Benutzer das CheckBox-Steuerelement ein- und ausschaltet.

Die Definition des speech-recognized-Ereignishandlers beginnt mit:

void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
  string txt = e.Result.Text;
  float conf = e.Result.Confidence;
  if (conf < 0.65) return;
...

Über die eigentlich fast immer verwendeten Eigenschaften "Result.Text" und "Result.Confidence" hinaus enthält das Objekt "Result" verschiedene andere nützliche, aber höher entwickelte, Eigenschaften, die Sie sich vielleicht näher ansehen sollten, wie etwa "Homophones" und "ReplacementWordUnits". Darüber hinaus sind im Spracherkennungsmodul verschiedene nützliche Ereignisse verfügbar, wie etwa "SpeechHypothesized".

Der Code des Ereignishandlers schließt mit:

...
  this.Invoke(new MethodInvoker(() =>
    { listBox1.Items.Add("I heard you say: " + txt); }));
  if (txt.IndexOf("text") >= 0 &&
    txt.IndexOf("box") >= 0 && txt.IndexOf("1")>= 0)
  {
    string[] words = txt.Split(' ');
    this.Invoke(new MethodInvoker(() =>
    { textBox1.Text = words[4]; }));
  }
}

Der erkannte Text wird im ListBox-Steuerelement mithilfe des MethodInvoker-Stellvertreters wiedergegeben. Da die Spracherkennung in einem anderen Thread als das Windows Forms-UI ausgeführt wird, schlägt ein direkter Zugriffsversuch auf das ListBox-Steuerelement, etwa in dieser Art:

listBox1.Items.Add("I heard you say: " + txt);

fehl und löst eine Ausnahme aus. Eine Alternative zu "Method­Invoker" besteht in der Verwendung des Action-Stellvertreters in dieser Weise:

this.Invoke( (Action)( () =>
  listBox1.Items.Add("I heard you say: " + txt)));

Theoretisch ist in dieser Situation die Verwendung des "MethodInvoker"-Stellvertreters geringfügig effizienter als die Verwendung des "Action"-Stellvertreters, da "MethodInvoker" zum Windows.Forms-Namespace gehört und daher für Windows Forms-Anwendungen spezifisch ist. Der "Action"-Stellvertreter ist allgemeiner. Dieses Beispiel zeigt, dass eine Windows Forms-Anwendung vollständig mithilfe von Spracherkennung gesteuert werden kann – unglaublich leistungsfähig und nützlich.

Zusammenfassung

Die in diesem Artikel vorgestellten Informationen sollen Ihnen zu einem sanften Einstieg verhelfen, wenn Sie die Spracherkennung und Sprachsynthese mit .NET-Anwendungen erkunden möchten. Das Beherrschen der Technologie selbst ist nicht zu schwierig, sobald Sie die Anfangshürden bei Installation und Lernen genommen haben. Das wirkliche Problem bei Spracherkennung und -synthese besteht darin, zu bestimmen, wann sie nützlich sind.

Bei Konsolenanwendungen können Sie interessante Ping-Pong-Dialoge erstellen, bei denen der Benutzer eine Frage stellt und die Anwendung antwortet, was zu einer Cortana-ähnlichen Umgebung führt. Sie müssen ein bisschen vorsichtig sein, denn wenn Ihr Computer spricht, wird diese Sprache vom Mikrofon aufgenommen und eventuell ebenfalls erkannt. Ich habe mich schon in einigen amüsanten Situationen befunden, in denen ich eine Frage stelle, die Anwendung sie erkennt und antwortet, die gesprochene Antwort dann aber ein weiteres Erkennungsereignis auslöst und ich in einer unterhaltsamen, endlosen Sprachschleife lande.

Eine andere mögliche Verwendung von Sprache in einer Konsolenanwendung besteht darin, Befehle wie "Starte Editor" und "Starte Word" zu erkennen. Anders ausgedrückt, eine Konsolenanwendung kann verwendet werden, um Aktionen auf Ihrem Hostcomputer auszuführen, die normalerweise mithilfe mehrerer Maus- und Tastaturinteraktionen durchgeführt würden.


Dr. James McCaffrey arbeitet für Microsoft Research in Redmond, Wash. Er arbeitet an verschiedenen Microsoft-Produkten wie Internet Explorer und Bing. Sie erreichen Dr. McCaffrey unter jammc@microsoft.com.

Unser Dank gilt den folgenden technischen Experten von Microsoft Research für die Durchsicht dieses Artikels: Rob Gruen, Mark Marron und Curtis von Veh