65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 的语音绑定

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2014年12月20日

CPOL

6分钟阅读

viewsIcon

24448

downloadIcon

1529

展示了如何将语音识别与 WPF 结合使用。

引言

我想尝试一下这个叫做语音识别的伟大技术。幸运的是,C# 中有很多示例。语音识别技术是 Windows 的一部分,.Net 框架也支持这项功能。令人惊讶的是,这个很酷的功能非常容易使用。感谢 .net。它并不比在窗体上添加一个按钮并处理其点击事件难多少。由于我有一些 WPF 的经验,所以我尝试使用 WPF 创建了一个简单的应用程序,其中如果用户说“将颜色更改为绿色”,语音引擎就会识别它并将窗口背景颜色更改为绿色。该应用程序运行良好,但我对代码本身并不完全满意。由于 WPF 程序员习惯于声明式 XAML 和绑定,我觉得示例应用程序缺少这些。WPF 内置支持处理来自键盘、鼠标和笔等设备的输入。InputBinding 允许你在输入手势和命令之间创建关联。简而言之,我想要对语音的支持。类似以下的内容:

<Window.InputBindings>       
     <SpeechBinding Command="{Binding ChangeColour}" Gesture="Change Colour To Green"/>       
</Window.InputBindings>

这里的 Gesture 属性定义了句子的文本,Command 是当语音被引擎识别时处理操作的。让我们看看如何做到这一点。

.net 中的语音识别

如果你完全不熟悉 .net 中的语音识别,我建议阅读这篇很棒的文章。如果你熟悉它,可以跳过本节。让我们快速回顾一下语音识别是如何工作的。如果你想在你的应用程序中使用语音识别,你很可能会使用 `System.Speech.Recognition` 命名空间中的 `SpeechRecognitionEngine` 类,它定义在 `System.Speech.dll` 中。这个类可以处理来自音频设备甚至WAV 文件音频流或 WAV 流的输入。但是,你需要创建一个 Grammar 对象,它定义了需要引擎识别的单词和短语。引擎可以同步或异步工作。引擎基于事件模型工作。让我们看看设置 `SpeechRecognitionEngine` 所需的典型代码。

//Create instance of engine
 var voiceEngine = new SpeechRecognitionEngine(new System.Globalization.CultureInfo("en-US"));
 //Create and load grammar
 voiceEngine.LoadGrammar(new Grammar(new GrammarBuilder("Hello")) { Name = "TestGrammer" });
 //Hook to the SpeechRecognized event
 voiceEngine.SpeechRecognized += voiceEngine_SpeechRecognized;                
// set the input of the speech recognizer to the default audio device
 voiceEngine.SetInputToDefaultAudioDevice();
 // recognize speech asynchronous
 voiceEngine.RecognizeAsync(RecognizeMode.Multiple); 
void voiceEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
   {
      if (e.Result.Text == "Hello" ) // e.Result.Text contains the recognized text
         {
            Console.WriteLine("I know you said :" + e.Result.Text);
         }
   } 
上面的代码可以与任何控制台、WinForm 或 WPF 应用程序一起使用。请注意,如果引擎没有按预期识别语音,你可以尝试两件事:1)使用高质量的麦克风。
2)训练你的计算机来识别你的声音。要训练你的计算机,请按照以下步骤操作:
  1. 打开控制面板
  2. 转到辅助功能
  3. 选择语音识别
  4. 然后选择“训练你的计算机以更好地理解你”

从上面可以清楚地看出,开始使用语音识别和开发语音感知应用程序要容易得多。但人们可以想象,幕后有很多事情在发生。声波被转换为电信号,电信号被转换为数字并通过设备驱动程序访问。操作系统和语音引擎及 API 完成了处理、识别和引发多个事件的其余繁重工作。

WPF 中的输入绑定

InputBinding 代表一个InputGesture 与命令之间的绑定。让我们看看 XAML 中典型的输入绑定示例。

<Window.InputBindings>
  <KeyBinding Key="B"
              Modifiers="Control" 
              Command="ApplicationCommands.Open" />
</Window.InputBindings>

 

MSDN 文档说:

所有派生自UIElement的对象都有一个名为InputBindingCollection`InputBindings`。所有派生自ContentElement的对象都有一个名为InputBindingCollection`InputBindings`

但是,如果在 XAML 中设置了这些集合,则集合中的项必须是 `InputBinding` 的派生类,而不是直接的 `InputBinding` 对象。这是因为 `InputBinding` 不支持默认公共构造函数。因此,在 XAML 中设置的 `InputBindingCollection` 中的项通常是 `InputBinding` 的派生类,它支持默认公共构造函数,例如 `KeyBinding` 或 `MouseBinding`。

WPF 输入系统已关闭扩展

我希望 WPF 能够从麦克风接收输入。我的第一个想法是 WPF 可能有一个扩展点,允许从自定义设备注入输入。我找到了 `InputManager` 类。`InputManager` 类负责协调 WPF 中的所有输入系统。它提供了一个已注册输入提供程序的列表。但这个类没有提供添加自己的输入提供程序的方法。这是一个相关的链接。但根据这个链接,在 Avalon(WPF 在发布前名为 Avalon)的设计阶段,有提供添加新设备的选项。

实现语音绑定

我们已经知道如何使用语音识别,也知道如何将键盘手势绑定到命令。我们需要一种方法将语音手势绑定到命令。MSDN 文档清楚地说明,要将任何内容添加到`InputBindingCollection` ,它必须派生自 `InputBinding` 类。我们做同样的事情。

    public class SpeechBinding : InputBinding
    {
        protected static readonly SpeechRecognitionEngine recognizer
            = new SpeechRecognitionEngine(new System.Globalization.CultureInfo("en-US"));
        static bool recognizerStarted = false;
        static readonly Dispatcher mainThreadDispatcher = Dispatcher.CurrentDispatcher;

        static SpeechBinding()
        {
            recognizer.RecognizerUpdateReached += recognizer_RecognizerUpdateReached;
        }

        static void recognizer_RecognizerUpdateReached(object sender, RecognizerUpdateReachedEventArgs e)
        {
            var grammar = e.UserToken as Grammar;
            recognizer.LoadGrammar(grammar);
            Debug.WriteLine("Loaded :"+grammar.Name);
            StartRecognizer();            
        }

        [TypeConverterAttribute(typeof(SpeechGestureConverter))]
        public override InputGesture Gesture
        {
            get
            {
                return base.Gesture;
            }
            set
            {
                base.Gesture = value;
                LoadGrammar();               
            }
        }

        protected virtual void LoadGrammar()
        {
            var speechStr = Gesture.ToString();
            var grammarBuilder = new GrammarBuilder(speechStr);
            grammarBuilder.Culture = recognizer.RecognizerInfo.Culture;
            var grammar = new Grammar(grammarBuilder) { Name = speechStr };
            grammar.SpeechRecognized += grammar_SpeechRecognized;
            Debug.WriteLine("Loading " + speechStr);
            recognizer.RequestRecognizerUpdate(grammar);       
          
        }

        protected static void StartRecognizer()
        {
            if (recognizerStarted == false)
            {
                try
                {
                    recognizer.SetInputToDefaultAudioDevice();
                }
                catch (Exception ex)
                {
                    Messenger.Default.Send<Exception>(new Exception("Unable to set input from default audio device." + Environment.NewLine + "Check if micropone is availble and enabled.", ex));
                    return;
                }
                recognizer.RecognizeAsync(RecognizeMode.Multiple);
                recognizerStarted = true;
            }
        }

        protected void grammar_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            mainThreadDispatcher.Invoke(new Action(() =>
            {
                OnSpeechRecognized(sender, e);

            }), DispatcherPriority.Input);
        }

        protected virtual void OnSpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            if (Command != null)
            {
                Command.Execute(e.Result.Text);
            }
        }
    }
}

此类负责初始化 `SpeechRecognitionEngine`,并且此引擎实例是静态的。因此,所有 `SpeechBinding` 实例都使用相同的共享引擎。此类有两个重要的继承属性:`Gesture` 和 `Command`。`Gesture` 属性的类型为 `InputGesture`。`InputGesture` 是一个抽象类,代表来自设备的输入手势。由于它是抽象的,我们需要提供一个具体的实现来表示用户将要说的语音。`SpeechGesture` 派生自 `InputGesture`。`SpeechGestureConverter` 将 XAML 中指定的字符串转换为 `SpeechGesture`,而 `Gesture` 属性的 setter 从字符串创建 `Grammar` 对象。`Grammar` 的 `SpeechRecognized` 事件被处理。事件处理程序使用识别出的文本作为参数调用 `Command`。由于事件发生在非 UI 线程上,我们需要 UI 线程的 dispatcher 对象将命令调用委托给 UI 线程。

          使用该应用程序后,我意识到仅指定一个字符串不足以创建语法。例如,“将颜色更改为某种颜色”。如果需要支持 100 种颜色,则需要创建 100 个这样的 XAML 条目,每种颜色一个,这并不理想。幸运的是,可以使用 XML 文件创建语法对象。W3C 有一个名为语音识别语法规范 (SRGS) 的规范。因此,为了使用这些语法文件,我创建了另一个名为 `SRGSDocBinding` 的类,它派生自 `SpeechBiding`。它有一个 `GrammarFile` 属性,它是包含语法规则的 XML 文件的名称。用法如下。文件需要放在应用程序文件夹中。

 <Window.InputBindings>       
      <speech:SRGSDocBinding Command="{Binding OpenDotCom}"  GrammarFile ="Grammar.xml" />
 </Window.InputBindings>

关于示例应用程序

解决方案是用 VS2012 创建的,包含两个项目:一个 WPF 示例应用程序,另一个是实现 SpeechBiding 的库项目。WPF 应用程序是使用 MVVMLight 项目模板创建的。窗口的 ViewModel 定义了用于 SpeechBinding 的 `RelayCommands`。请下载并自行尝试。对应用程序说话,玩得开心。你需要一台带有麦克风和 .net 框架 4.5 的台式机/笔记本电脑。

一些观察

`SpeechRecognitionEngine` 可能会给你错误的识别结果。例如,你说“红色”,它却识别成“绿色”。我没有找到任何方法来确定识别的准确性。`RecognitionResult` 具有一个名为 `Confidence` 的 `float` 类型属性,其值在 0 到 1 之间。即使出现错误的识别结果,值也可能是 0.9。这意味着引擎有 90% 的置信度,但仍然 100% 错误。

关注点

对我来说,发现一种 MVVM 友好的方法来根据用户的语音输入调用命令很有趣。这可以节省编写重复代码来加载语法的麻烦。我期望 WPF 能原生支持 VoiceCommand。感谢您的反馈。

历史

首次发布于 2014 年 12 月 20 日。

© . All rights reserved.