en / de
AI
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

Spracherkennung auf Raspberry PI 2 mit Windows 10 IoT

by it support 02. Dezember 2015· 9 Min. lesen

Windows 10 IoT Core auf dem Raspberry PI 2

Mit Windows 10 ist nun auch eine abgespeckte Version verfügbar, welche speziell auf IoT-Bedürfnisse (IoT = Internet of Things) zugeschnitten ist. Höchste Zeit also für uns, das Ganze mal genauer unter die Lupe zu nehmen.

Mit dem Raspberry PI 2 steht eine leistungsfähige Hardware-Plattform zur Verfügung mit wir einfach Prototypen erstellen können. Zu unserer Freude ist das nun mit Visual Studio 2015 in C#/.NET 4.6 möglich. Man kann entweder eine sogenannte «headless» Applikation erstellen, welche ohne User-Interface im Hintergrund läuft oder man erstellt eine Universal-App, welche bei angeschlossenem Bildschirm auch ein User-Interface darstellt. Eine Universal-App ist übrigens auch die einfachste Möglichkeit, wenn man Sprachausgabe realisieren will (nicht Teil dieses Artikels). Headless-Apps tun sich hier noch etwas schwer.

Wie man Windows 10 IoT auf das embedded Device, in unserem Falle den Raspberry PI 2, bekommt und anschliessend mit dem Visual Studio verbindet, findet man ausführlich auf dem ms-iot Github.

Demoprojekt

Nun zu unserem Demo-Projekt:
Mit Windows 10, das ja auf Phones, Tablets, Notebooks, Desktops und embedded Devices läuft, ist das Thema Sprachsteuerung wieder populärer geworden. Probieren wir also aus, ob sich mit Windows 10 IoT Core Devices mittels Sprachkommandos steuern lassen.
Leider ist das momentan nur in englischer Sprache möglich, da man das Windows 10 IoT Core Image noch nicht mit deutschen Sprachpaketen bekommt und leider auch nicht selbst nachrüsten kann.

Als Aufgabe stellen wir uns, dass wir zwei LEDs an den Ausgängen mittels Sprachkommandos ein- und ausschalten und unterschiedlich schnell blinken lassen können.

Wie das aussieht, zeigt das folgende Video:

Wir schalten also eine grüne LED an GPIO Pin 4 und eine rote LED an GPIO Pin 5.
Achtung: Die GPIO Nummerierung entspricht nicht der Stecker-Pin-Nummerierung.

Zu diesem Zweck erstellen wir eine Windows Universal App, welche eine Klasse SpeechController für die Spracherkennung, eine Klasse IoController für das Hardware Interface und eine Klasse Blinker für die Blink-Logik enthält. Diese Teile werden durch eine Logik-Klasse miteinander verheiratet.

SpeechController

Der SpeechController wird so initialisiert und konfiguriert, dass kontinuierliche Spracherkennung läuft. Es werden dann Events gefeuert, wenn Sprache erkannt wurde.

recognizer = new SpeechRecognizer();
recognizer.ContinuousRecognitionSession.ResultGenerated += RecognizerResultGenerated;
recognizer.ContinuousRecognitionSession.AutoStopSilenceTimeout = TimeSpan.MaxValue;

Damit der SpeechRecognizer versteht, was man ihm sagt, muss ihm die Grammatik mitgeteilt werden. Die Grammatik schränkt somit ein, was ein SpeechRecognizer versteht.
Es gibt unterschiedliche Arten von Grammatik:

Leider können die unterschiedlichen Grammatiken nur bedingt kombiniert werden. Dication Grammar und Web Search Grammar können nur einzeln benutzt werden und lassen sich nicht kombinieren. List grammar und SRGS grammar lassen sich hingegen gemeinsam nutzen, können aber auch nicht mit den ersten beiden kombiniert werden.

Für unseren Anwendungsfall eignet sich das SRGS grammar. Wir definieren also unsere Grammatik im XML-File, so dass folgende Kommandos möglich sind:

Die gesamte SRGS-Grammatik sieht wie folgt aus:

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

  <rule id="root" scope="public">
    <one-of>
      <item>
        <ruleref uri="#switchCommand"/>
        <tag> out.command="SWITCH"; </tag>
        <tag> out.params=rules.switchCommand; </tag>
      </item>
      <item>
        <ruleref uri="#blinkCommand"/>
        <tag> out.command="BLINK"; </tag>
        <tag> out.params=rules.blinkCommand; </tag>
      </item>
    </one-of>
  </rule>

  <rule id="switchCommand" scope="public">
    <item>
      <item> switch </item>
      <item>
        <ruleref uri="#ioStates" />
        <tag> out.state=rules.latest(); </tag>
      </item>
      <item> pin </item>
      <item>
        <ruleref uri="#ioNumber" />
        <tag> out.pin=rules.latest(); </tag>
      </item>
    </item>
  </rule>

  <rule id="ioStates">
    <one-of>
      <item>
        on <tag> out="ON"; </tag>
      </item>
      <item>
        off <tag> out="OFF"; </tag>
      </item>
    </one-of>
  </rule>

  <rule id="blinkCommand" scope="public">
    <item>
      <item> blink </item>
      <item> pin </item>
      <item>
        <ruleref uri="#ioNumber" />
        <tag> out.pin=rules.latest(); </tag>
      </item>
      <item repeat="0-1">
        <ruleref uri="#blinkSpeeds" />
        <tag> out.speed=rules.latest(); </tag>
      </item>
    </item>
  </rule>

  <rule id="blinkSpeeds">
    <one-of>
      <item>
        faster <tag> out="FASTER"; </tag>
      </item>
      <item>
        slower <tag> out="SLOWER"; </tag>
      </item>
    </one-of>
  </rule>

  <rule id="ioNumber">
    <one-of>
      <item>
        two <tag> out = 2; </tag>
      </item>
      <item>
        three <tag> out = 3; </tag>
      </item>
      <item>
        four <tag> out = 4; </tag>
      </item>
      <item>
        five <tag> out = 5; </tag>
      </item>
      <item>
        six <tag> out = 6; </tag>
      </item>
      <item>
        seven <tag> out = 7; </tag>
      </item>
      <item>
        eight <tag> out = 8; </tag>
      </item>
      <item>
        nine <tag> out = 9; </tag>
      </item>
      <item>
        ten <tag> out = 10; </tag>
      </item>
      <item>
        eleven <tag> out = 11; </tag>
      </item>
      <item>
        twelve <tag> out = 12; </tag>
      </item>
      <item>
        thirteen <tag> out = 13; </tag>
      </item>
      <item>
        fourteen <tag> out = 14; </tag>
      </item>
      <item>
        fifteen <tag> out = 15; </tag>
      </item>
      <item>
        sixteen <tag> out = 16; </tag>
      </item>
      <item>
        seventeen <tag> out = 17; </tag>
      </item>
      <item>
        eighteen <tag> out = 18; </tag>
      </item>
      <item>
        nineteen <tag> out = 19; </tag>
      </item>
      <item>
        twenty <tag> out = 20; </tag>
      </item>
      <item>
        twentyone <tag> out = 21; </tag>
      </item>
    </one-of>
  </rule>

</grammar>

Im root-Tag werden die Kommandos für switch und blink definiert. Hierarchisch geht es nun über die ruleref-Elemente zu den rules mit den entsprechenden IDs.
Die tag-Elemente definieren die Tags, welche nach der Erkennung dem Entwickler zur Verfügung stehen, um herauszufinden, welche Regeln bei der letzten Eingabe angewendet wurden.

<tag> out.command=»SWITCH»; </tag> liefert zum Beispiel ein Tag mit dem Key «command» und dem value «SWITCH».

Damit die Regeln angewandt werden, muss nun zuerst die Grammatik aus dem XML-File eingelesen und als Constraint zum SpeechRecognizer hinzugefügt werden.
Anschliessend müssen die Constraints im Recognizer kompiliert werden. Hat man sich im SGRS-File vertippt, gibt das einen Fehler, ansonsten ist der Status «Success» und damit kann die kontinuierliche Spracherkennung gestartet werden.

var grammarContentFile = await Package.Current.InstalledLocation.GetFileAsync(GrammarFile);
var grammarConstraint = new SpeechRecognitionGrammarFileConstraint(grammarContentFile);
recognizer.Constraints.Add(grammarConstraint);

var compilationResult = await recognizer.CompileConstraintsAsync();
if (compilationResult.Status == SpeechRecognitionResultStatus.Success)
{
      await recognizer.ContinuousRecognitionSession.StartAsync();
}

Nun hört der Raspberry PI laufend zu und versucht das Gesagte auf die definierten Regeln abzubilden.
Wenn ein Sprachfetzen erkannt wurde, wir nun das Event ResultGenerated des SpeechRecognizer aufgerufen. In args.Result.SemanticInterpretation.Properties sind nun die Tags der angewendeten Regeln als Key-Value-Pairs zu finden, wobei das Value jeweils eine Liste von Strings ist, da mehrere Werte möglich sind.

        // Action Handler, called when sucessfull speech results are available
        public Action>> ProcessCommand { get; set; }
        
        private void RecognizerResultGenerated(SpeechContinuousRecognitionSession session, SpeechContinuousRecognitionResultGeneratedEventArgs args)
        {
            Debug.WriteLine("status:     " + args.Result.Status);
            Debug.WriteLine("text:       " + args.Result.Text);
            Debug.WriteLine("confidence: " + args.Result.Confidence);

            switch (args.Result.Confidence)
            {
                case SpeechRecognitionConfidence.Low:
                    Debug.WriteLine("Sorry, I did not understand. Could you please repeat?");
                    break;
                case SpeechRecognitionConfidence.Rejected:
                    Debug.WriteLine("Sorry, this is not something I could do for you.");
                    break;
                case SpeechRecognitionConfidence.Medium:
                case SpeechRecognitionConfidence.High:
                    Debug.WriteLine("You said: " + args.Result.Text);
                    var count = args.Result.SemanticInterpretation.Properties.Count;

                    Debug.WriteLine("Count: " + count);
                    Debug.WriteLine("Tag: " + args.Result.Constraint.Tag);

                    foreach (var prop in args.Result.SemanticInterpretation.Properties)
                    {
                        Debug.WriteLine("Property " + prop.Key + ":");
                        foreach (var value in prop.Value)
                        {
                            Debug.WriteLine("   value = " + value);
                        }
                    }

                    ProcessCommand(args.Result.SemanticInterpretation.Properties);
                    break;
            }
        }

Im registrierten ResultGenerated-Eventhandler wird zuerst geprüft, ob überhaupt zuverlässige Resultate vorliegen. Die Confidence-Eigenschaft des Results liefert hier die Werte Low (schlechte Erkennungsqualität), Rejected (Spracheingabe wurde abgelehnt, passt nicht zur Grammatik), Medium (es wurde eine Spracheingabe passend zu Grammatik erkannt, aber nicht alles passt 100%-ig), High (zweifelsfrei auf Grammatik abgebildet).
Mit SpeechRecognitionConfidence gleich Medium oder High kann eine weitere Verarbeitung erfolgen, da die Tags dann abgefüllt wurden.

Um dem SpeechController generell zu halten, haben wir das in die Action ProcessCommand ausgelagert, welche dann anderswo implementiert und mit anderen Applikationsteilen wie dem IoController oder Blinker verbunden werden kann, wie das folgende Beispiel zeigt:

    public sealed class Logic
    {
        private readonly SpeechController speechController;
        private readonly IoController ioController;
        private readonly Blinker redBlinker;
        private readonly Blinker greenBlinker;

        public Logic()
        {
            speechController = new SpeechController();
            speechController.ProcessCommand = ProcessCommand;
            ioController = new IoController();
            ioController.ConfigureOutput("green", 4);
            ioController.ConfigureOutput("red", 5);

            greenBlinker = new Blinker(ioController, 4, 1000);
            redBlinker = new Blinker(ioController, 5, 1000);
        }

        private void ProcessCommand(IReadOnlyDictionary<string, IReadOnlyList<string>> tags)
        {
            if (tags.ContainsKey("command"))
            {
                if (tags["command"].Contains("SWITCH"))
                {
                    if (tags.ContainsKey("pin") && tags.ContainsKey("state"))
                    {
                        int pin = int.Parse(tags["pin"][0]);

                        if (pin == 4) greenBlinker.Stop();
                        if (pin == 5) redBlinker.Stop();

                        if (tags["state"][0] == "ON")
                        {
                            ioController.TurnOn(pin);
                        }
                        if (tags["state"][0] == "OFF")
                        {
                            ioController.TurnOff(pin);
                        }
                    }
                }
                else if(tags["command"].Contains("BLINK"))
                {
                    if (tags.ContainsKey("pin"))
                    {
                        int pin = int.Parse(tags["pin"][0]);

                        if (pin == 4) greenBlinker.Start();
                        if (pin == 5) redBlinker.Start();

                        if (tags.ContainsKey("speed"))
                        {
                            if (tags["speed"][0] == "FASTER")
                            {
                                if (pin == 4) greenBlinker.BlinkFaster();
                                if (pin == 5) redBlinker.BlinkFaster();
                            }
                            if (tags["speed"][0] == "SLOWER")
                            {
                                if (pin == 4) greenBlinker.BlinkSlower();
                                if (pin == 5) redBlinker.BlinkSlower();
                            }
                        }
                    }
                }
            }
        }
    }

Dieser Code verbindet die Spracherkennung im SpeechController mit den Funktionen für die Ansteuerung der LED’s, indem einfach auf das Vorhandensein von Tags und gegebenenfalls auf deren Werte geprüft wird. Da ein Tag grundsätzlich mehrere Werte haben könnte, wir aber immer nur einen Wert zurückliefern, wird hier direkt auf den Index 0 zugegriffen.

Der Vollständigkeit halber sollen hier noch die beiden Klassen für IoController und Blinker aufgeführt werden. Für das Verständnis der Spracherkennung sind sie nicht wichtig.
IoController ist eine Abstraktion der Raspberry PI GPIOs und Blinker bietet Blink Funktionalität für einen spezifischen GPIO Pin aufbauend auf dem IoController.

IoController:

using System.Collections.Generic;
using System.Linq;
using Windows.Devices.Gpio;

namespace SpeechConfigApp
{
    public sealed class IoController
    {
        private readonly IDictionary<string, GpioPin> ios = new Dictionary<string, GpioPin>(); 

        private static GpioController gpio;
        
        public IoController()
        {
            gpio = GpioController.GetDefault();
        }

        public void ConfigureInput(string name, int pin)
        {
            Configure(name, pin, GpioPinDriveMode.Input);
        }

        public void ConfigureOutput(string name, int pin)
        {
            Configure(name, pin, GpioPinDriveMode.Output);
        }

        public void TurnOn(string name)
        {
            WriteGpioPin(name, GpioPinValue.Low);
        }

        public void TurnOn(int pin)
        {
            GpioPin gpioPin = ios.Values.Single(io => io.PinNumber == pin);
            gpioPin.Write(GpioPinValue.High);
        }

        public void TurnOff(string name)
        {
            WriteGpioPin(name, GpioPinValue.Low);
        }

        public void TurnOff(int pin)
        {
            GpioPin gpioPin = ios.Values.Single(io => io.PinNumber == pin);
            gpioPin.Write(GpioPinValue.Low);
        }

        public bool? GetValue(string name)
        {
            GpioPin pin;
            if (ios.TryGetValue(name, out pin))
            {
                var value = pin.Read();
                return value == GpioPinValue.High;
            }
            return null;
        }

        private void Configure(string name, int pin, GpioPinDriveMode mode)
        {
            GpioPin gpioPin = gpio.OpenPin(pin);
            gpioPin.SetDriveMode(mode);
            ios.Add(name, gpioPin);
        }

        private void WriteGpioPin(string name, GpioPinValue value)
        {
            GpioPin pin;
            if (ios.TryGetValue(name, out pin))
            {
                pin.Write(value);
            }
        }
    }
}

Blinker:

using System;
using System.Diagnostics;
using Windows.System.Threading;

namespace SpeechConfigApp
{
    public sealed class Blinker
    {
        private readonly int pinNr;
        private readonly IoController ioController;
        private bool pinValue;
        private ThreadPoolTimer timer;

        public Blinker(IoController ioController, int pinNr, double intervall = 500)
        {
            this.pinNr = pinNr;
            this.ioController = ioController;
            Intervall = intervall;
        }

        public double Intervall { get; private set; }

        public void BlinkFaster()
        {
            Intervall = Intervall/2;
            Stop();

            if(Intervall >= 1)
            {
                Start();
            }
        }

        public void BlinkSlower()
        {
            Intervall = Intervall * 2;
            Stop();
            Start();
        }

        public void Start()
        {
            if (timer == null)
            {
                ioController.TurnOn(pinNr);
                
                timer = ThreadPoolTimer.CreatePeriodicTimer(TimerTick, TimeSpan.FromMilliseconds(Intervall));
                Debug.WriteLine("Start blinking on Pin {0}", pinNr);
            }
        }

        public void Stop()
        {
            if (timer != null)
            {
                timer.Cancel();
                timer = null;
                ioController.TurnOff(pinNr);
                Debug.WriteLine("Stop blinking on Pin {0}", pinNr);
            }
        }

        private void TimerTick(ThreadPoolTimer timer)
        {
            TogglePin();
        }
        
        private void TogglePin()
        {
            if (pinValue)
            {
                ioController.TurnOff(pinNr);
            }
            else
            {
                ioController.TurnOn(pinNr);
            }
            pinValue = !pinValue;
        }
    }
}

Zusammenfassung

Die Programmierung von sprachgesteuerten Geräten ist mit Windows 10 IoT Core ist nicht komplizierter als von sprachgesteuerten Apps auf Windows Phone. Dies ist vor allem der neuen Strukturierung des .NET Frameworks und den Universal Apps zu verdanken.
Die Entwicklung mit Visual Studio 2015 erwies sich als sehr angenehm, verglichen mit anderen Embedded IDEs. Anders als bei Windows Phone ist bei IoT leider keine Entwicklung mittels Emulator möglich. Man muss immer direkt mit dem embedded Device (hier Raspberry PI 2) über Ethernet-Kabel verbunden sein.
Leider ist es zum heutigen Zeitpunkt auch noch nicht möglich eine eigenes Image für Raspberry PI 2 mit Windows 10 IoT erstellen, so dass es uns nicht möglich war ein lokalisiertes (deutsches) Image zu erstellen. Hoffentlich ändert sich das bald.

Kommentare

2 Antworten zu “Spracherkennung auf Raspberry PI 2 mit Windows 10 IoT”

  1. Fabian sagt:

    Hallo,

    Vielen Dank für dieses gute Tutorial.
    Ich habe aber noch eine Frage.
    Wo muss ich denn die Programmfetzen hineinschreiben?
    Also in welche Dateien. Muss ich neue Dateien in meinem
    Projekt anlegen und dort dann den Code reinschreiben?
    Und muss ich den Dateien einen bestimmten Namen geben?
    Und in welche Datei muss ich dann welchen Code schreiben?
    Über eine positive Antwort würde ich mich sehr freuen.

    Viele Grüße
    Fabian

  2. Martin Weber sagt:

    Hallo Fabian,

    Danke, für dein Feedback. Freut mich.
    Ich habe eine Universal App erstellt und dann aus dem Konstruktor der MainPage heraus den SpeechController aufgesetzt. Du kannst aber auch alles gleich in eine Methode in MainPage packen und diese aus dem Konstruktor oder mittels einem Event, das nach dem Laden aufgerufen wird, ausführen.
    Noch zwei Tipp bei UniversalApps:
    – Im Manifest unter Cabilities nicht vergessen das Mikrophon freizuschalten.
    – Unter References / Universal Windows / Windows IoT Extensions for the UWP hinzufügen.

    Hoffe das hilf.

    Gruss,
    Martin

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden

Copyright © 2025 Noser Engineering AG – Alle Rechte vorbehalten.

NACH OBEN
Privacy Policy Cookie Policy
Zur Webcast Übersicht