Windows 10 IoT Serials 5 - 如何为树莓派应用程序添加语音识别与交互功能

都说语音是人机交互的重要手段,虽然个人觉得在大庭广众之下,对着手机发号施令会显得有些尴尬。但是在资源受限的物联网应用场景下(无法外接鼠标键盘显示器),如果能够通过语音来控制设备,与设备进行交互,那还是很实用的。继上一篇《Windows 10 IoT Serials 4 - 如何在树莓派上使用Cortana语音助手》之后,本文将详细讲述如何为运行Windows 10 IoT Core系统的树莓派添加语音识别和语音交互功能。

1. 硬件准备

  • 树莓派2/树莓派3、5V/2A电源、TF卡(8GB以上)
  • 麦克风:Microsoft LifeCam HD 3000(该摄像头集成了麦克风),也可以使用其他麦克风,如Blue Snowball iCE Condenser Microphone, Cardioid, Sound Tech CM-1000USB Table Top Conference Meeting Microphone
  • 受控对象:这里以两个LED灯为例。用户可以根据实际需求添加受控对象,比如添加继电器模块以后,可以控制强电设备。
  • 音频输出设备(可选):Windows 10 IoT Core系统的树莓派只支持3.5mm接口的音频输出,HDMI的音频输出不支持。所以,可以接一个普通的3.5mm接口的耳机就可以。
  • 显示设备(可选):可以接HDMI接口的显示器,或者使用有源HDMI转VGA模块,转接VGA接口的显示器。

注意,这里音频输出设备和显示设备是可选的,并不是必须的。

2. 硬件连接

这里将LED连接到树莓派的GPIO5和GPIO6两个引脚,同时,把麦克风设备插入到树莓派的USB接口。如果准备了音频输出设备(如耳机或音响)和显示设备(显示器),请连接到树莓派的3.5mm音频接口和HDMI接口。

3. 程序编写

本应用程序使用的开发环境是Windows 10+Visual Studio 2015 Community,注意,Visual Studio需要包含Universal Windows App Development Tools组件。

3.1 新建工程和添加资源

新建工程时,选用Universal模板,工程命名为RPiVoiceControl,如下图所示。

因为要用到GPIO引脚控制LED,所以需要为工程添加Windows IoT Extension for UWP引用,如下图所示。

由于需要使用Microphone,所以需要在工程的Package.appxmanifest文件中,勾选Microphone,如下图所示。

另外,由于需要使用到语音识别、LED和UI控件等资源,需要为应用程序引入命名空间,如下:

using System;
    using System.Diagnostics;
    此处省略若干…

using Windows.Devices.Gpio; //LED
    using Windows.Media.SpeechRecognition;//语音识别
    using Windows.Media.SpeechSynthesis;
    using Windows.Storage;
    using Windows.ApplicationModel;

3.2 新建语音指令定义文件

为项目添加新的xml文件,命名为Grammar.xml,用于定义语音指令。项目中用到的语音指令符合Speech Recognition Grammar Specification Version 1.0 (SRGS)标准,其具体协议可以参考MSDN上的这个文档:Create Grammars Using SRGS XML (Microsoft.Speech)

之后,打开该文件,为其添加如下语音指令。

<?xml version="1.0" encoding="utf-8" ?>
<grammar
  version="1.0"
  xml:lang="en-US"
  root="automationCommands"
  xmlns="http://www.w3.org/2001/06/grammar"
  tag-format="semantics/1.0">

<rule id="root">
    <item>
      <ruleref uri="#automationCommands"/>
      <tag>out.command=rules.latest();</tag>
    </item>
  </rule>

此处省略代码,具体请参考Github上项目的完整代码。

<rule id="deviceActions">
    <one-of>
      <item>
        light <tag> out="LIGHT"; </tag>
      </item>
      <item>
        led <tag> out="LED"; </tag>
      </item>
    </one-of>
  </rule>

</grammar>

3.3 程序界面设计

如果不准备给树莓派接显示器的可以直接忽略这一步,如果需要在程序运行过程中查看状态的,可以加入一些简单的控件,这里只是加入了两个指示LED灯状态的Ellipse 控件、两个指示程序运行状态的TextBlock 控件和一个MediaElement 控件,代码如下。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
       <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
           <Ellipse x:Name="bedroomLED" Fill="LightGray" Stroke="White" Width="100" Height="100" Margin="10"/>
           <Ellipse x:Name="kitchenroomLED" Fill="LightGray" Stroke="White" Width="100" Height="100" Margin="10"/>
           <TextBlock x:Name="GpioStatus" Text="Waiting to initialize GPIO..." Margin="10,50,10,10" TextAlignment="Center" FontSize="26.667" />
           <TextBlock x:Name="VoiceStatus" Text="Waiting to initialize Microphone" Margin="10,50,10,10" TextAlignment="Center" TextWrapping="Wrap" />
           <MediaElement x:Name="mediaElement"></MediaElement>
       </StackPanel>
   </Grid>

3.4 后台代码

后台代码中,首先需要定义应用程序使用的资源对象,如GPIO、画刷、定时器、部分代码如下,

private const int BedRoomLED_PINNumber = 5;
private GpioPin BedRoomLED_GpioPin;
private GpioPinValue BedRoomLED_GpioPinValue;
private DispatcherTimer bedRoomTimer;

private const int kITCHENLED_PINNumber = 6;
private GpioPin kITCHENLED_GpioPin;
private GpioPinValue kITCHENLED_GpioPinValue;
private DispatcherTimer kITCHENTimer;

private SolidColorBrush redBrush = new SolidColorBrush(Windows.UI.Colors.Red);
private SolidColorBrush grayBrush = new SolidColorBrush(Windows.UI.Colors.LightGray);

然后,在MainPage的构造函数中,添加资源的初始化,部分代码如下:

public MainPage()
{
            this.InitializeComponent();
            Unloaded += MainPage_Unloaded;

// Initialize Recognizer
            initializeSpeechRecognizer();

InitBedRoomGPIO();
            InitKITCHENGPIO();

bedRoomTimer = new DispatcherTimer();
            bedRoomTimer.Interval = TimeSpan.FromMilliseconds(500);
            bedRoomTimer.Tick += BedRoomTimer_Tick;

kITCHENTimer = new DispatcherTimer();
            kITCHENTimer.Interval = TimeSpan.FromMilliseconds(500);
            kITCHENTimer.Tick += KITCHENTimer_Tick;
}

在initializeSpeechRecognizer函数中,完成语音识别状态改变事件的添加、语音指令文件的加载,部分代码如下:

private async void initializeSpeechRecognizer()
{
    // Initialize recognizer
    recognizer = new SpeechRecognizer();
    // Set event handlers
    recognizer.StateChanged += RecognizerStateChanged;
    recognizer.ContinuousRecognitionSession.ResultGenerated += RecognizerResultGenerated;
    // Load Grammer file constraint
    string fileName = String.Format(SRGS_FILE);
    StorageFile grammarContentFile = await Package.Current.InstalledLocation.GetFileAsync(fileName);
    SpeechRecognitionGrammarFileConstraint grammarConstraint = new SpeechRecognitionGrammarFileConstraint(grammarContentFile);

// Add to grammer constraint
    recognizer.Constraints.Add(grammarConstraint);

SpeechRecognitionCompilationResult compilationResult = await recognizer.CompileConstraintsAsync();
    Debug.WriteLine("Status: " + compilationResult.Status.ToString());

// If successful, display the recognition result.
    if (compilationResult.Status == SpeechRecognitionResultStatus.Success)
    {
        Debug.WriteLine("Result: " + compilationResult.ToString());

await recognizer.ContinuousRecognitionSession.StartAsync();
    }
    else
    {
        Debug.WriteLine("Status: " + compilationResult.Status);
    }
}

之后,添加RecognizerResultGenerated和RecognizerStateChanged两个事件的处理,主要用于语音识别结果和状态发生变化的处理。部分代码如下:
private async void RecognizerResultGenerated(SpeechContinuousRecognitionSession session, SpeechContinuousRecognitionResultGeneratedEventArgs args)
{
    // Check for different tags and initialize the variables
    String location = args.Result.SemanticInterpretation.Properties.ContainsKey(TAG_TARGET) ?
                    args.Result.SemanticInterpretation.Properties[TAG_TARGET][0].ToString() :
                    "";

String cmd = args.Result.SemanticInterpretation.Properties.ContainsKey(TAG_CMD) ?
                    args.Result.SemanticInterpretation.Properties[TAG_CMD][0].ToString() :
                    "";

String device = args.Result.SemanticInterpretation.Properties.ContainsKey(TAG_DEVICE) ?
                    args.Result.SemanticInterpretation.Properties[TAG_DEVICE][0].ToString() :
                    "";

Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        VoiceStatus.Text= "Target: " + location + ", Command: " + cmd + ", Device: " + device;
    });

switch (device)
    {
        case "hiActivationCMD"://Activate device                  
            SaySomthing("hiActivationCMD", "On");
            break;

case "LIGHT":
            LightControl(cmd, location);
            break;

default:
            break;
    }
}

// Recognizer state changed
private async void RecognizerStateChanged(SpeechRecognizer sender, SpeechRecognizerStateChangedEventArgs args)
{
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        VoiceStatus.Text = "Speech recognizer state: " + args.State.ToString();
    });
}

定义函数SaySomthing,用于反馈的语音生成,这样,用户就可以听到树莓派的语音反馈了。部分代码如下:

private async void SaySomthing(string myDevice, string State, int speechCharacterVoice = 0)
{
    if (myDevice == "hiActivationCMD")
        PlayVoice($"Hi Jack What can i do for you");
    else
        PlayVoice($"OK Jack {myDevice}  {State}", speechCharacterVoice);
    await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        VoiceStatus.Text = $"OK -> ===== {myDevice} --- {State} =======";
    });
}
    最后,在两个定时器的溢出事件处理中,加入对LED灯的处理,部分代码如下:

private void BedRoomTimer_Tick(object sender, object e)
{
            if (BedRoomLED_GpioPinValue == GpioPinValue.High)
            {
                BedRoomLED_GpioPinValue = GpioPinValue.Low;
                BedRoomLED_GpioPin.Write(BedRoomLED_GpioPinValue);
                bedroomLED.Fill = redBrush;
            }
            else
            {
                BedRoomLED_GpioPinValue = GpioPinValue.High;
                BedRoomLED_GpioPin.Write(BedRoomLED_GpioPinValue);
                bedroomLED.Fill = grayBrush;
            }
}

4. 应用调试

在Visual Studio中设置编译的平台为ARM,调试设备为Remote Machine,在Debug选项卡中,设置树莓派的IP地址,点击调试。如下图所示。

程序运行以后,用户可以通过语音指令与树莓派进行交互。

首先,用户可以使用“Hi Jack”与设备交互,可以听到设备有回复,用于确认应用程序是否正确运行。

其次,用户可以使用“Turn On/Off Bedroom Light”和“Turn On/Off kitchen Light ”来控制两个LED灯,同时,在应用程序的界面上还可以看到灯的状态和语音识别的状态,如下图所示。


    应用程序运行的实物图如下:

5. 代码下载

本项目的代码已经发布到Github上,链接如下:https://github.com/shijiong/RPiVoiceControl,欢迎下载使用。

时间: 2024-12-13 14:19:08

Windows 10 IoT Serials 5 - 如何为树莓派应用程序添加语音识别与交互功能的相关文章

Windows 10 IoT Serials 4 - 如何在树莓派上使用Cortana语音助手

从Windows 10 IoT Core 14986版本开始,微软已经加入Cortana语音助手功能.之前,我们只能使用本地语音识别,需要编写应用程序,下载到设备中才能实现.从现在开始,微软已经从系统层面融入了Cortana语音助手,用户可以通过系统设置,开启Cortana,实时地与Cortana进行交互了.下面我们以树莓派为Windows 10 IoT Core设备,尝试一下使用Cortana的过程. 1. 硬件准备 树莓派2/树莓派3.5V/2A电源.TF卡(8GB以上) 麦克风:Micro

Windows 10 IoT Serials 2 - Windows 10 IoT RTM 升级教程

7月29日,微软推出了Windows 10 for PC的正式版,其版本号是Build 10240.近两天官方说已经有4700万的下载安装量,同时这个数字还在不断攀升.另外,除了Windows 10 for PC版本以外,还有针对手机的Windows 10 for Mobile版本,据说RTM也会很快到来,而网上也曝光了小米4刷Win10的一些机友的帖子.与PC和Mobile版本不同,IoT版本针对物联网领域的应用.几乎与PC RTM同时,Windows 10 IoT Core也RTM了,并且微

Windows 10 IoT Serials 9 – 如何利用IoTCoreAudioControlTool改变设备的音频设备

大家知道,在Windows 10 IoT Core上,如果用户外接了USB声卡.带有麦克风的摄像头之类的硬件,就会有多个音频设备可以用.但是,系统目前并没有提供直接的UI来设置音频的输入或者输出设备.经过查阅之后发现,我们可以使用命令行来更改默认的音频设备,具体方法如下. 以树莓派为例,笔者使用了一款USB声卡,再加上原来树莓派自带的3.5mm音频接口,就有两个音频输出和一个音频输入.系统默认是使用了树莓派自带的3.5mm Speaker作为音频输出,使用USB声卡的Microphone作为输入

玩转树莓派&mdash;&mdash;升级NOOBS离线安装介质到Raspbian 4.9和Windows 10 IoT C

为树莓派做系统升级是我一直想做的事.时间总是觉得不够,于是也好久没有碰. 直到前几天MVP群里有兄弟问大家的github来互相关注,我才突然想起之前写过的制作离线安装介质的文章:http://haohu.blog.51cto.com/2474833/1858600 因为之前把制作Windows 10 IoT Core需要的文件放到了github上.前不久刚把电脑的Windows 10更新到了1703,也是时候更新树莓派上的Windows 10 IoT Core了.(据说有不少新东西,比如Cort

玩转树莓派&mdash;&mdash;制作包含Windows 10 IoT Core和Raspbian的离线安装介质

How to make Windows 10 IoT Core offline install media for Raspberry Pi 前几天在树莓派上更新Receiver for Linux 13.4,又尝试在Pi上安装Visual Studio Code,结果觉得系统有点不正常了,于是打算重新刷一遍. 之前的系统使用了一张16 GB的TF卡,直接使用NOOBS lite进行在线安装.之前说了这样安装有个好处,就是安装文件本身不占太多的空间,更多的空间可以给系统使用. 可是--下载好慢啊

玩转树莓派&mdash;&mdash;安装 Windows 10 IoT Core

在树莓派上运行过Windows 3.1,自然也想运行Windows 10.于是准备在树莓派上安装一个Windows 10 IoT Core的系统. 写下这篇文字的时候,其实已经不是第一次安装运行Windows 10 IoT Core了.在我生日那天拿到树莓派之后,就立即尝试了一把Windows 10 IoT Core.当时正式的Pi 3的支持还没有发布,使用的是Windows Insider的版本.树莓派3和树莓派2最大的不同,是自带WiFi和蓝牙适配器.而当时Windows 10 IoT的版本

【Windows 10 IoT】为Win10 IoT镜像添加默认应用(树莓派)

[Windows 10 IoT]为Win10 IoT镜像添加默认应用(树莓派) 在Windows 10 IoT应用程序开发好之后,一般通过IoT WebManagement或者直接用vs将应用部署上去.并且执行命令iotstartup.exe add headed/headless AppxID,将应用设置为开机启动.但是,如果想基于一个开发板,量产某种硬件设备,这种方式肯定是不可行的. 我们会想到,是否可以将我们的应用直接打包到镜像中,并设置成为开机自启的默认应用呢?当然可以. 基本原理是这样

【Windows 10 IoT - 3】Windows 10 RTM安装及新特性(树莓派 Pi2)

在<[Window 10 IoT - 1]Window 10系统安装(树莓派 Pi2)>中,我们介绍了Windows 10 IoT预览版的安装,正式版Windows 10 IOT(OS版本号也是10.0.10240.16384)相对于预览版来说,安装简便了很多,功能也比较完善了,性能和稳定也得到了很大的提高. 一.ISO下载安装 下载链接:http://go.microsoft.com/fwlink/?LinkId=616847 IOT Core RPi.ISO 五百多兆大小,这是一个镜像文件

树莓派3 Windows 10 IoT Core

下载地址:https://developer.microsoft.com/zh-cn/windows/iot/Downloads 先下载安装 Windows 10 IoT 核心版仪表板: 安装完成后运行,准备好SD卡 选择好版本SD卡,设置好密码 提示SD卡会被格式化 然后是漫长的下载过程 下载完成 开始写入SD卡 SD卡插入树莓派,通电开机 选择语言 安装完成… 下面捣鼓下看看怎么把带了个3.5的LCD屏幕弄亮… 貌似人家豪都是用7寸的…