语音识别

使用 .NET 桌面应用程序进行语音识别

James McCaffrey

下载代码示例

自个人语音助理 Windows Phone Cortana(以及来自水果公司的那个不能直呼其名的类似产品)面世以来,支持语音的应用程序在软件开发方面发挥着越来越重要的作用。在本文中,我将介绍如何在 Windows 控制台应用程序、Windows Forms 应用程序和 Windows Presentation Foundation (WPF) 应用程序中开始进行语音识别和语音合成。

请注意,您还可以向 Windows Phone 应用程序、ASP.NET Web 应用程序、Windows 应用商店应用程序、Windows RT 应用程序和 Xbox Kinect 中添加语音功能,但实现这些功能的技术与本文所述的技术有所不同。

了解本文内容的最好方法是查看图 1图 2 中两个不同演示程序的屏幕截图。图 1 中的控制台应用程序启动之后,该应用程序会立刻说出“我醒了”这句话。当然,您在阅读这篇文章时无法听到演示所说的话,因此演示程序会以文本方式显示计算机所说的内容。接下来,用户说出命令“开启语音”。演示将回显识别的文本,然后在后台启用应用程序,以侦听并响应将两个数字相加的请求。

控制台应用程序中的语音识别和合成
图 1 控制台应用程序中的语音识别和合成

Windows Forms 应用程序中的语音识别
图 2 Windows Forms 应用程序中的语音识别

用户要求应用程序计算一加二,然后计算二加三。应用程序将识别这些语音命令并说出答案。稍后,我将说明使用语音识别的更有用的方法。

然后,用户发出命令“关闭语音”,此命令将停用侦听将数字相加的命令,但不会完全停用语音识别。语音关闭后,将忽略下一个要求计算一加二的语音命令。最后,用户重新打开语音并说出无意义的命令:“Klatu barada nikto”,应用程序将其识别为完全停用语音识别并退出应用程序的命令。

图 2 显示了支持语音的虚拟 Windows Forms 应用程序。该应用程序将识别语音命令,但不会以语音输出方式响应。该应用程序首次启用时,“开启语音”复选框控件为未选中状态,表示语音识别功能尚未激活。用户选中“开启语音”控件,然后说:“你好”。该应用程序将在应用程序底部的 ListBox 控件中回显识别的语音文本。

然后用户说:“将文本框 1 设置为红色”。该应用程序将识别“设置文本框 1 红色”,这与用户所说的内容几乎一致,但不完全一致。尽管在图 2 中未显示,但应用程序顶部的 TextBox 控件中的文本的确已设置为“红色”。

接下来,用户说:“请将文本框 1 设置为白色”。该应用程序将识别“设置文本框 1 白色”,并按此执行操作。用户最后说:“再见”,该应用程序将回显该命令,但并未操作 Windows Forms,尽管它可以(例如)通过取消选中“开启语音”复选框控件执行此操作。

在接下来的部分中,我将向您介绍创建这两种演示程序的过程,包括安装所需的 .NET 语音库。本文假设您至少具有中级编程技能,但并不假定您完全了解语音识别或语音合成。

将语音添加到控制台应用程序

为了创建图 1 中所示的演示,我启动了 Visual Studio 并创建了一个名为 ConsoleSpeech 的新 C# 控制台应用程序。我成功地在 Visual Studio 2010 和 2012 中使用了语音,任何最新版本应该都能正常使用。在将模板代码加载到编辑器之后,我在解决方案资源管理器窗口中将文件 Program.cs 重命名为更具描述性的 ConsoleSpeechProgram.cs,然后 Visual Studio 将重命名 Program 类。

接下来,我向 C:\ProgramFiles (x86)\Microsoft SDKs\Speech\v11.0\Assembly 中的文件 Microsoft.Speech.dll 添加了一个引用。我的主机上没有该 DLL,必须进行下载。安装向应用程序添加语音识别与合成所需的文件并不是一件容易的事。我将在本文的下一部分详细解释这一安装过程,但现在,假设我的计算机上有 Microsoft.Speech.dll。

将引用添加到语音 DLL 之后,我在源代码顶部删除了除指向顶级 System 命名空间的 using 语句之外的所有其他 using 语句。然后,我将 using 语句添加到命名空间 Microsoft.Speech.Recognition、Microsoft.Speech.Synthesis 和 System.Globalization。前两个命名空间与语音 DLL 相关联。注:有点令人困惑的是,还有 System.Speech.Recognition 和 System.Speech.Synthesis 命名空间。我随后会解释二者之间的不同。默认情况下,Globalization 命名空间可用,且无需向项目中添加新引用。

控制台应用程序演示的完整源代码如图 3 所示,且本文随附的代码下载部分提供完整源代码。我去掉了所有常规错误检查,以尽可能突出核心内容。

图 3 演示控制台应用程序源代码

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

在 using 语句后,演示代码的开头如下所示:

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)
    {
...

类作用域 SpeechSynthesizer 对象使应用程序具有朗读的功能。SpeechRecognitionEngine 对象使应用程序可以侦听并识别语音单词或短语。布尔型变量“done”确定整个应用程序何时完成。布尔型变量 speechOn 控制应用程序是否侦听除退出程序的命令之外的任何命令。

这里的思路是控制台应用程序不接受从键盘键入的输入,因此应用程序始终侦听命令。但是,如果 speechOn 为 false,则应用程序将只识别退出程序的命令并执行此命令;同时会识别但忽略其他命令。

Main 方法开始执行:

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

在声明 SpeechSynthesizer 对象时,将其实例化。使用合成器对象非常简单。SetOutputToDefaultAudioDevice 方法将输出发送到计算机的扬声器(也可将输出发送到文件)。Speak 方法将接受字符串,并朗读出来。就是这么简单。

语音识别要比语音合成困难得多。Main 方法继续创建识别器对象:

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

首先,在 CultureInfo 对象中指定要识别的语言,本例中为美国英语。CultureInfo 对象位于使用 using 语句引用的 Globalization 命名空间中。接下来,在调用 SpeechRecognitionEngine 构造函数之后,将语音输入设置为默认音频设备,在大多数情况下默认音频设备为麦克风。请注意,大多数笔记本电脑有内置麦克风,但大多数台式计算机需要外接麦克风(现在通常与耳机组合在一起)。

识别器对象的关键方法是 SpeechRecognized 事件处理程序。在使用 Visual Studio 时,如果您键入“sre.SpeechRecognized +=”并等待片刻,IntelliSense 功能将附加上“sre_SpeechRecognized”自动完成该语句,作为事件处理程序的名称。我建议点击 Tab 键来接受并使用该默认名称。

接下来,演示将设置识别将两个数字相加的命令的功能:

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

此处的三个关键对象是 Choices 集合、GrammarBuilder 模板和控制 Grammar。在我设计识别 Grammar 时,我首先列出了一些要识别的特定示例。例如,“一加二等于几?”以及“三加四等于几?”

然后确定相应的通用模板,例如,“<x> 加 <y> 等于几?”此模板是 GrammarBuilder,填入模板的特定值是 Choices。Grammar 对象将封装模板和 Choices。

在本演示中,我将要相加的数字限制为 1 到 4,并将它们作为字符串添加到 Choices 集合。更好的方法是:

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

我介绍创建 Choices 集合的较差的方法是出于以下两个原因。第一,一次添加一个字符串是我在其他语音示例中所看到过的唯一方法。第二,您可能认为一次添加一个字符串可能无法实现;实时 Visual Studio IntelliSense 表明其中一个 Add 重载将接受类型为“params string[] phrases”的参数。如果您没有注意 params 关键字,您可能认为 Add 方法仅接受字符串数组,而不是类型字符串数组或单个字符串。我建议传递数组。

创建连续数字的 Choices 集合是一个特例,允许如下所示的编程方法:

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

创建 Choices 并填充 GrammarBuilder 槽之后,演示将创建 GrammarBuilder 和控制 Grammar,如下所示:

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

演示使用类似的模式来为与开始和停止相关的命令创建 Grammar:

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

在定义语法时,有很大的灵活性。本文中,“开启语音”、“关闭语音”以及“klatu barada nikto”命令全部位于同一语法中,因为它们逻辑上相关。这三个命令无法在三个单独的语法中进行定义,或者您可以将“开启语音”和“关闭语音”命令放在一个语法中,“klatu barada nikto”命令放在第二个语法中。

创建所有 Grammar 对象之后,将它们传递到语音识别器,激活语音识别:

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

有多个语法时,需要 RecognizeMode.Multiple 参数,除了最简单的程序,所有其他程序都有此需要。Main 方法完成,如下所示:

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

外观奇怪的空 while 循环允许控制台应用程序外壳处于活动状态。当语音识别器的事件处理程序将布尔型类作用域变量“done”设置为 true 时,该循环将终止。

处理已识别语音

语音识别事件处理程序的代码开头如下所示:

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

已识别的实际文本存储在 SpeechRecognizedEventArgs Result.Text 属性中。您还可以使用 Result.Words 集合。Result.Confidence 属性包含一个介于 0.0 到 1.0 之间的值,该值是对语音文本与任何与识别器关联的语法匹配程度的粗略度量。演示将指示事件处理程序忽略任何识别置信度较低的文本。

置信度值可能有较大差异,具体取决于语法复杂性、麦克风质量等因素。例如,如果演示程序必须只识别 1 到 4,则计算机上的置信度值通常为约 0.75。但是,如果语法必须识别 1 到 100,则置信度值下降至约 0.25。简言之,您通常必须实验置信度值以获得良好的语音识别结果。

接下来,语音识别器事件处理程序将在识别开启和关闭之间切换:

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

请注意,语音识别引擎实际上可以识别无意义的单词。如果 Grammar 对象包含对象内置词典中没有的单词,则 Grammar 将使用语义试探法来尝试尽可能识别这些单词,且通常都会成功。这就是为什么我使用“klatu”而不是正确的“klaatu”(出自一部老的科幻电影)的原因。

还请注意,您无需处理已完整识别的 Grammar 文本 (“klatu barada nikto”),只需获取足够的信息来唯一标识语法短语(“klatu”和“barada”)。

接下来,将处理将两个数字相加的命令,然后处理事件处理程序,最后处理 Program 类和命名空间:

...
      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

请注意,Results.Text 中的文本区分大小写(“What”与 “what”)。识别短语之后,则可以解析出特定单词。在这种情况下,已识别的文本的格式为“x 加 y 等于几”,因此“几”在 words[0] 中,要相加的两个数字(作为字符串)分别在 words[2] 和 words[4] 中。

安装库

对演示程序的介绍假定您的计算机上已安装所有必需的语音库。要创建并运行演示程序,您需要安装四个程序包:用于在 Visual Studio 中创建演示的 SDK、用于在创建演示后执行演示的运行时、识别语言、以及合成(朗读)语言。

要安装 SDK,请在 Internet 上搜索“Speech Platform 11 SDK”。将打开 Microsoft 下载中心的正确页面,如图 4 所示。单击“下载”按钮后,您将看到如图 5 所示的选项。SDK 有 32 位和 64 位两种版本。我强烈建议使用 32 位版本,无论您的主机是什么版本。64 位版本无法与某些应用程序互操作。

Microsoft 下载中心的 SDK 安装主页
图 4 Microsoft 下载中心的 SDK 安装主页

 安装语音 SDK
图 5 安装语音 SDK

您只需要一个 x86(32 位).msi 文件。选择该文件并单击“下一步”按钮后,即可直接运行安装程序。语音库不会提供安装何时完成的反馈信息,因此,不要期待收到安装成功的消息。

接下来,要安装语音运行时。找到主页并单击“下一步”按钮之后,您将看到如图 6 所示的选项。

安装语音运行时
图 6 安装语音运行时

选择与 SDK 相同的平台版本(本演示为 11 )和位数版本(32 [x86] 或 64 [x64]),这一点非常重要。同样,我强烈建议使用 32 位版本,即使您在使用 64 位计算机。

接下来,您可以安装识别语言。下载页面如图 7 所示。此演示使用了文件 MSSpeech_SR_en-us_TELE.msi(美国英语)。SR 代表语音识别,TELE 代表电话,表示识别语言设计成在低质量音频输入下工作,例如识别来自电话或台式计算机麦克风的音频输入。

安装识别语言
图 7 安装识别语言

最后,您可以安装语音合成语言和语音。下载页面如图 8 所示。此演示使用文件 MSSpeech_TTS_en-us_Helen.msi。TTS 代表文本到语音,这实际上是语音合成的同义词短语。请注意,有两种英语可以使用美国语音。其他英语可以使用非美国语音。创建合成文件相当困难。您可以从几家公司购买并安装其他语音。

安装合成语言和语音
图 8 安装合成语言和语音

有趣的是,虽然语音识别语言和语音合成语音/语言完全不同,但两者的下载是同一个下载页面上的选项。可以从下载中心 UI 同时选中识别语言和合成语言,但尝试同时安装它们对我来说是个灾难,因此,我建议一次安装一个。

Microsoft.Speech 与 System.Speech

如果您不熟悉 Windows 应用程序的语音识别与合成,您很容易被这些文档弄糊涂,因为有多个语音平台。尤其是,除了本文演示所使用的 Microsoft.Speech.dll 库之外,还有作为 Windows 操作系统一部分的 System.Speech.dll 库。从 API 几乎相同(但并非完全相同)的意义上来讲,这两个库相似。因此,如果您联机搜索语音示例,找到了代码段而不是完整的程序,则您将很难判断示例引用的是 System.Speech 还是 Microsoft.Speech。

关键问题在于,如果您是语音开发方面的初学者,请使用 Microsoft.Speech 库而不是 System.Speech 库来将语音添加到 .NET 应用程序。

尽管两个库共享某些相同的核心基础代码,且 API 相似,但它们是完全不同的。图 9 的表中总结了一些主要的区别。

图 9 Microsoft.Speech 与 System.Speech

Microsoft.Speech.dll System.Speech.dll
必须单独安装 操作系统的一部分 (Windows Vista+)
可以与应用程序打包在一起 无法重新分发
必须构造 Grammar 使用 Grammar 或自由听写
没有用户培训 培训特定用户
托管代码 API (C#) 本机代码 API (C++)

System.Speech DLL 是操作系统的一部分,因此它安装在每个 Windows 计算机上。Microsoft.Speech DLL(以及关联的运行时和语言)必须下载并安装到计算机上。System.Speech 识别通常需要用户培训,其中用户朗读一些文本,系统学习理解特定用户的发音。Microsoft.Speech 识别可立即适用于任何用户。System.Speech 几乎可以识别任何单词(称为自由听写)。Microsoft.Speech 只识别程序定义的 Grammar 中的单词和短语。

将语音识别添加到 Windows Forms 应用程序

将语音识别与合成添加到 Windows Forms 或 WPF 应用程序的过程类似于将语音添加到控制台应用程序的过程。要创建如图 2 所示的虚拟演示程序,我启动了 Visual Studio 并创建了新的 C# Windows Forms 应用程序,将其命名为 WinFormSpeech。

将模板代码加载到 Visual Studio 编辑器之后,在解决方案资源管理器窗口中,我向文件 Microsoft.Speech.dll 中添加了一个引用,正如我在控制台应用程序演示中所做的操作一样。在源代码的顶端,我删除了不必要的 using 语句,只剩下对 System、Data、Drawing 和 Forms 命名空间的引用。我添加了两个 using 语句来将 Microsoft.Speech.Recognition 和 System.Globalization 命名空间引入作用域。

Windows Forms 演示不使用语音合成,因此,我没有使用对 Microsoft.Speech.Synthesis 库的引用。将语音合成添加到 Windows Forms 应用程序与将合成添加到控制台应用程序完全相同。

在 Visual Studio 设计视图中,我将 TextBox 控件、CheckBox 控件和 ListBox 控件拖放到 Form 上。在 CheckBox 控件上双击后,Visual Studio 自动创建了 CheckChanged 事件处理程序方法的框架。

请注意,控制台应用程序演示将立即开始侦听语音命令,并一直侦听直到退出应用程序。该方法可用于 Windows Forms 应用程序,但我决定允许用户通过使用 CheckBox 控件来在语音识别开启和关闭之间进行切换。

演示程序 Form1.cs 文件的源代码定义了局部类,如图 10 所示。将声明语音识别引擎对象,并实例化为 Form 成员。在 Form 构造函数中,我挂接了 SpeechRecognized 事件处理程序,创建并加载了两个 Grammar:

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
}

图 10 将语音识别添加到 Windows Forms

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

我可以像在控制台应用程序演示中一样直接创建两个 Grammar 对象,但为了简化操作,我定义了两个 Helper 方法 GetHelloGoodbyeGrammar 和 GetTextBox1TextGrammar 来执行此操作。

请注意,Form 构造函数不会调用 RecognizeAsync 方法,表示当应用程序启动时,不会立即激活语音识别。

Helper 方法 GetHelloGoodbyeGrammar 遵循本文前面所述的相同模式:

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

同样,创建 Grammar 对象以在 Windows Forms TextBox 控件中设置文本的 Helper 方法不存在任何惊喜:

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

Helper 将识别短语“设置文本框 1 红色”。但是,用户无需准确地说出这一短语。例如,用户可能会说“请将文本框 1 中的文本设置为红色”,语音识别引擎仍会将此短语识别为“设置文本框 1 红色”,尽管与用户完全匹配 Grammar 模式的情况相比,上述情况的置信度值较低。换句话说,当您创建 Grammar 时,无需考虑短语的每个变体。这极大地简化了语音识别的使用。

定义 CheckBox 事件处理程序,如下所示:

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

在 Windows Forms 应用程序的生存期期间,始终存在语音识别引擎对象 sre。用户切换 CheckBox 控件时,将使用 RecognizeAsync 和 RecognizeAsyncCancel 方法来激活和停用该对象。

语音识别的事件处理程序的定义的开头部分如下所示:

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

除了或多或少一直在使用的 Result.Text 和 Result.Confidence 属性之外,Result 对象还具有您可能想要研究的其他几个有用但更高级的属性,如 Homophones 和 ReplacementWordUnits。此外,语音识别引擎还具有几个有用的事件,如 SpeechHypothesized。

事件处理程序代码的结束部分如下所示:

...
  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]; }));
  }
}

已识别的文本将使用 MethodInvoker 委托在 ListBox 控件中回显。由于语音识别器在不同于 Windows Forms UI 线程的其他线程中运行,直接尝试访问 ListBox 控件,如:

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

将会失败,并引发异常。Method­Invoker 的替代方法是使用 Action 委托,如下所示:

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

理论上,在这种情况下,使用 MethodInvoker 委托要比使用 Action 委托略高效,因为 MethodInvoker 是 Windows.Forms 命名空间的一部分,因此专用于 Windows.Forms 应用程序。Action 委托更通用。本示例表明您可以使用极其强大和有用的语音识别完全操控 Windows Forms 应用程序。

总结

如果您要研究使用 .NET 应用程序进行语音识别和语音合成,本文所提供的信息应该可以让您开始入门。您克服了最初的安装和学习障碍之后,掌握技术本身并不是特别困难。语音识别与合成的真正问题在于确定它们何时有用。

通过控制台应用程序,您可以创建有趣的一问一答对话,用户提出问题,应用程序回答,形成类似 Cortana 的环境。您要注意的是,当您的计算机朗读时,麦克风会拾取这些语音,并可能进行识别。我发现了一些有趣的情况,我询问一个问题,应用程序识别并回答,但语音回答又触发了另一个识别事件,我最终进入了一个有趣的无限语音循环。

控制台应用程序语音的另一个可能的用途是识别命令,如“启动记事本”和“启动 Word”。换句话说,控制台应用程序可用于在主机上执行通常需使用多个鼠标和键盘交互来执行的操作。


James McCaffrey 博士 任职于华盛顿州雷德蒙德市的 Microsoft 研究中心。他长期从事多个 Microsoft 产品(包括 Internet Explorer 和 Bing)的研发工作。可以在 jammc@microsoft.com 上联系 McCaffrey 博士。

衷心感谢以下 Microsoft Research 技术专家对本文的审阅:Rob Gruen、Mark Marron 和 Curtis von Veh