Windows 10 IoT Serials 5 - 如何為樹莓派應用程式添加語音辨識與互動功能

來源:互聯網
上載者:User

標籤:運行   hone   main   無法   事件處理   --   usb   ioi   pat   

    都說語音是人機互動的重要手段,雖然個人覺得在大庭廣眾之下,對著手機發號施令會顯得有些尷尬。但是在資源受限的物聯網應用情境下(無法外接滑鼠鍵盤顯示器),如果能夠通過語音來控制裝置,與裝置進行互動,那還是很實用的。繼上一篇《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,歡迎下載使用。

Windows 10 IoT Serials 5 - 如何為樹莓派應用程式添加語音辨識與互動功能

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.