WPF 的语音绑定





5.00/5 (5投票s)
展示了如何将语音识别与 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);
}
}
- 打开控制面板
- 转到辅助功能
- 选择语音识别
- 然后选择“训练你的计算机以更好地理解你”
从上面可以清楚地看出,开始使用语音识别和开发语音感知应用程序要容易得多。但人们可以想象,幕后有很多事情在发生。声波被转换为电信号,电信号被转换为数字并通过设备驱动程序访问。操作系统和语音引擎及 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 日。