05.11.2013 Aufrufe

Entwicklung einer Automatenbedienung mit einer leistungsfähigen ...

Entwicklung einer Automatenbedienung mit einer leistungsfähigen ...

Entwicklung einer Automatenbedienung mit einer leistungsfähigen ...

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<strong>Entwicklung</strong> <strong>einer</strong> <strong>Automatenbedienung</strong> <strong>mit</strong><br />

<strong>einer</strong> <strong>leistungsfähigen</strong> Spracherkennung und<br />

<strong>einer</strong> graphischen Benutzerschnittstelle<br />

unter C++<br />

Masterarbeit<br />

zur Erlangung des Grades "Master of Engineering"<br />

vorgelegt dem Fachbereich 03 Elektrotechnik und Informatik der<br />

Hochschule Niederrhein von<br />

Zitzer, Juri<br />

Bahnhofstr. 41<br />

41747 Viersen<br />

Matrikelnummer 605125<br />

abgegeben am: 20.07.2011<br />

Erstgutachter: Prof. Dr.-Ing. Hans-Günter Hirsch<br />

Zweitgutachter:<br />

Prof. Dr.-Ing. Roland Hoffmann


Vorwort<br />

An erster Stelle möchte ich mich ganz herzlich bei Herrn Prof. Dr.-Ing Hans-Günter<br />

Hirsch bedanken, der mich während m<strong>einer</strong> Masterarbeit wissenschaftlich betreut und<br />

bei der Suche nach Lösungswegen unterstützt hat. Einen weiteren Dank möchte ich<br />

Herrn Prof. Dr.-Ing. Roland Hoffmann für die Übernahme des Zweitgutachtens<br />

aussprechen. Außerdem möchte ich mich bei Herrn Dipl.-Ing. Andreas Kitzig, M. Eng.<br />

dafür bedanken, dass er mir bei der Durchführung m<strong>einer</strong> Masterarbeit <strong>mit</strong> seinen<br />

Ratschlägen und informativen Beiträgen zur Seite stand.<br />

Der praktische Teil dieser Masterarbeit wurde parallel zur <strong>Entwicklung</strong> vom „asrLib“-<br />

Projekt für die Erstellung von Dialogsystemen durchgeführt. Die dabei erzeugten<br />

Quelltexte wurden in das Projekt integriert und erweitern dieses. Diese Arbeit<br />

beschäftigt sich mehr <strong>mit</strong> der Weiterentwicklung des „asrLib“- Projektes, als <strong>mit</strong> der<br />

<strong>Entwicklung</strong> im Bereich Spracherkennung.<br />

Die vorliegende Masterarbeit wurde sorgfältig <strong>mit</strong> den Gedanken erstellt, dass ein<br />

Benutzer der „asrLib“- Bibliotheken ein Dialogsystem anhand dieser erstellen und<br />

betreiben kann. Deshalb kann man einen großen Teil dieser Arbeit als eine so <strong>einer</strong> Art<br />

Bedienungsanleitung ansehen. Dabei wurde diese so strukturiert, dass man selbständig<br />

ein neues Spracherkennungssystem oder eine neue graphische Oberfläche aufbauen<br />

kann oder schon vorhandene Elemente an entsprechenden Stellen ändern kann, um<br />

diesen neue Eigenschaften hinzuzufügen. Die Darstellung des Quelltextes, die<br />

(vielleicht auf den ersten Blick überflüssige) Beschreibung von vorhandenen<br />

Bibliotheken und eingesetzten Tools und eine lange Auflistung von benutzten<br />

Funktionen sollen dabei hilfreich sein.<br />

Falls der Leser nicht die ganze Masterarbeit lesen möchte oder kann und nur den<br />

Kapitel ansieht, dem er die gewünschte Information entnehmen möchte, kann es dazu<br />

kommen, dass ihm unbekannte Begriffe auftauchen oder der Zusammenhang einiger<br />

Stellen im Text unklar wird. Deshalb wurden die vielzähligen Verweise auf andere<br />

Stellen, wo es erklärt wird, worum es geht, in der gesamten Arbeit eingesetzt.


Ich versichere an Eides Statt durch meine Unterschrift, dass ich die vorstehende Arbeit<br />

selbständig und ohne fremde Hilfe angefertigt und alle Stellen, die ich wörtlich oder<br />

annähernd wörtlich aus Veröffentlichungen entnommen habe, als solche kenntlich<br />

gemacht habe, mich auch k<strong>einer</strong> anderen als der angegebenen Literatur oder sonstiger<br />

Hilfs<strong>mit</strong>tel bedient habe. Die Arbeit hat in dieser oder ähnlicher Form noch k<strong>einer</strong><br />

anderen Prüfungsbehörde vorgelegen.<br />

Krefeld, den 20.07.2011 ________________


Inhaltsverzeichnis<br />

1. Aufgabenstellung...........................................................................................................1<br />

2. Einleitung.......................................................................................................................2<br />

3. Grundlagen....................................................................................................................3<br />

3.1. Dialogsystem..........................................................................................................3<br />

3.1.1. Graphisch.......................................................................................................3<br />

3.1.2. Sprachbasiert..................................................................................................4<br />

3.1.3. Multimodal.....................................................................................................5<br />

3.2. Spracherkennung....................................................................................................6<br />

3.2.1. Spracherkennungssystem...............................................................................7<br />

3.2.2. Analyse...........................................................................................................8<br />

3.2.3. Erkennung......................................................................................................9<br />

4. Betriebs<strong>mit</strong>tel, Software..............................................................................................10<br />

4.1. FLTK 2.0..............................................................................................................10<br />

4.2. POCO...................................................................................................................14<br />

4.3. Hilfs<strong>mit</strong>tel............................................................................................................16<br />

5. asrLib...........................................................................................................................17<br />

5.1. asrLib und CMake................................................................................................18<br />

5.2. asrLib und Code::Blocks......................................................................................19<br />

5.3. Bibliotheken.........................................................................................................21<br />

5.4. Plugins..................................................................................................................25<br />

5.5. Wichtiges.............................................................................................................27<br />

6. Parallele Erkennung.....................................................................................................30<br />

7. GUI..............................................................................................................................51<br />

Tabellenverzeichnis<br />

Vorhandene Actionen eines Zustandsautomaten aus dem Plugin „Actions.so“.............28<br />

Mögliche Zustände <strong>einer</strong> Sprachdetektion <strong>mit</strong> HirschLib- Funktionen..........................41


Abbildungsverzeichnis<br />

Abbildung 1: Spracherkennungssystem............................................................................7<br />

Abbildung 2: Aufteilung des Audiosignals ......................................................................8<br />

Abbildung 3: Merkmalsextraktion ....................................................................................8<br />

Abbildung 4: FLUID Hauptfenster 1...............................................................................11<br />

Abbildung 5: FLUID Hauptfenster 2...............................................................................11<br />

Abbildung 6: FLUID „Widget Bin“................................................................................11<br />

Abbildung 7: "Hello, World!" <strong>mit</strong> FLTK........................................................................12<br />

Abbildung 8: Schriftart <strong>mit</strong> FLUID ...............................................................................13<br />

Abbildung 9: Callback <strong>mit</strong> FLUID .................................................................................13<br />

Abbildung 10: "Hello, World!" Projekt unter FLUID.....................................................13<br />

Abbildung 11: POCO- Übersicht....................................................................................14<br />

Abbildung 12: CMake GUI.............................................................................................18<br />

Abbildung 13: Code::Blocks Übersicht...........................................................................20<br />

Abbildung 14: Code::Blocks Einstellungen....................................................................20<br />

Abbildung 15: Beispiel Zustandsautomat........................................................................23<br />

Abbildung 16: Ablaufplan <strong>einer</strong> Spracherkennung.........................................................36<br />

Abbildung 17: Sequenzdiagramm Spracherkennungssystem..........................................40<br />

Abbildung 18: Möglicher Verlauf <strong>einer</strong> parallelen Spracherkennung............................46<br />

Abbildung 19: Fahrkartenautomat...................................................................................52<br />

Abbildung 20: GUI-Kalender..........................................................................................54<br />

Abbildung 21: Kommunikation zwischen GUI und StateMachine.................................56<br />

Quellenverzeichnis<br />

http://www.nuance.de/spracherkennung/speechtechnology.asp.......................................4<br />

http://de.opensuse.org/.....................................................................................................10<br />

http://www.fltk.org/.........................................................................................................10<br />

http://pocoproject.org/.....................................................................................................14<br />

http://www.cmake.org/....................................................................................................16<br />

http://www.codeblocks.org/.............................................................................................16


1. Aufgabenstellung<br />

Wie man dem Titel der vorliegenden Masterarbeit entnehmen kann, beschäftigt sich<br />

diese <strong>mit</strong> der Spracherkennung. Das Ziel dieser Arbeit ist, nicht ein neues Verfahren der<br />

Erkennung der gesprochenen Sprache zu entwerfen oder die aufwendige Algorithmen<br />

zur Berechnung der nützlichen Daten aus einem Audiosignal zu erstellen, sondern eine<br />

Möglichkeit zur Verfügung zu stellen, die Spracherkennung universeller einsetzen zu<br />

können. Dabei sollen die schon fertige Funktionen zur Analyse von Signalen und<br />

Erkennung der Sprache verwendet werden. Diese Funktionen wurden <strong>mit</strong> der<br />

Programmiersprache C erstellt und zu „HirschLib“- Bibliotheken zusammengefügt. Der<br />

Quellcode, der im Rahmen dieser Masterarbeit erstellt werden soll, wird in der<br />

erweiterten Programmiersprache C++ geschrieben, dabei sollen die Strukturen in<br />

„HirschLib“- Bibliotheken entsprechend angepasst werden.<br />

Die Aufgabe dieser Masterarbeit setzt sich aus zwei Teilen zusammen:<br />

Als Erstes, soll eine Methode entwickelt werden, <strong>mit</strong> der man eine schnellere und<br />

bessere Erkennung der Sprache realisieren kann. Diese wird dann bei der Bedienung<br />

von verschiedenen Automaten durch Sprache eingesetzt, z.B. bei einem automatisierten<br />

Auskunftssystem über Telefonleitung. Bei dieser Methode sollen mehrere Prozesse, die<br />

diese Erkennung durchführen, <strong>mit</strong> unterschiedlichen Konfigurationen gleichzeitig<br />

ausgeführt werden. Dabei erhöht sich die Erwartung, dass die gewünschten Ergebnisse,<br />

zumindest von einem dieser Prozessen, geliefert werden.<br />

Als Weiteres, soll eine graphische Oberfläche zur Steuerung von Automaten erstellt<br />

werden, die parallel zur Sprachbedienung eingesetzt werden kann, z.B. bei einem<br />

Fahrkartenautomat.<br />

Seite 1


2. Einleitung<br />

Diese Einleitung zur vorliegenden Masterarbeit soll als ihre kurze Übersicht dienen.<br />

Hier werden alle ihre Kapitel vorgestellt und deren Inhalte zusammengefasst.<br />

Im Kapitel „Grundlagen“ wird erklärt, was man unter einem Dialogsystem verstehen<br />

soll, welche Arten es davon gibt und welche bei dieser Masterarbeit verwendet wird.<br />

Dann wird ein Spracherkennungssystem <strong>mit</strong> allen seinen Komponenten kurz dargestellt<br />

und die meist eingesetzten Verfahren der Spracherkennung erwähnt.<br />

Das Kapitel „Betriebs<strong>mit</strong>tel, Software“ sollte nicht außer Acht gelassen werden, da die<br />

externen Bibliotheken und Tools, die bei dieser Arbeit eingesetzt wurden und deshalb<br />

auch wichtig sind, werden in diesem Kapitel beschrieben. In weiteren Kapiteln wird es<br />

immer wieder auf diese Beschreibung zurückgegriffen.<br />

Wie der Name des Kapitels „asrLib“ schon sagt, wird hier „asrLib“- Projekt dargestellt,<br />

in dem (und parallel zu dem) die im Rahmen dieser Masterarbeit erstellten<br />

Applikationen entwickelt wurden. In diesem Kapitel werden die wichtigsten Funktionen<br />

und die Grundlagen beschrieben, die für die beiden weiteren Kapitel entscheidend sind.<br />

Im Kapitel „Parallele Erkennung“ wird, als Erstes, gezeigt, wie eine Spracherkennung<br />

<strong>mit</strong> dem „asrLib“- Projekt überhaupt funktioniert. Dann wird beschrieben, wie eine<br />

Möglichkeit der parallelen Erkennung im Rahmen dieser Masterarbeit geschaffen wurde<br />

und wird erläutert, was man für einen parallelen Einsatz von mehreren<br />

Spracherkennungssystemen braucht und was man dabei beachten soll.<br />

Am Anfang des praktischen Teils dieser Masterarbeit wurde die Beschreibung von zu<br />

erstellenden graphischen Oberflächen im XML- Format erstellt. Mit dem von POCO *<br />

zur Verfügung gestellten Parser wurde diese Beschreibung in das FLTK * - Format<br />

während der Ausführungsphase der Applikation übersetzt. Dieses Verfahren hat sich<br />

nicht bewehrt und es wurde entschieden, auf das FLTK- eigenes Tool FLUID * zur<br />

Erstellung von graphischen Oberflächen umzusteigen. Im Rahmen dieser Arbeit wurden<br />

zwei graphische Oberflächen erstellt. Da<strong>mit</strong> der Benutzer von „asrLib“- Bibliotheken<br />

nicht auf diese zwei Oberflächen angewiesen ist und selbst solche schnell erstellen<br />

kann, wurden diese Bibliotheken entsprechend erweitert. Dazu im Kapitel „GUI“.<br />

* wird im Kapitel 4 beschrieben<br />

Seite 2


3. Grundlagen<br />

In diesem Kapitel wird es kurz erläutert, was ein Dialogsystem ist, welche Arten von<br />

Dialogsystemen es gibt und wie eine automatische Spracherkennung realisiert wird.<br />

3.1. Dialogsystem<br />

Als Dialog bezeichnet man eine schriftliche, mündliche oder visuelle Kommunikation<br />

zwischen mindestens zwei Personen. Wenn man eine natürliche Person <strong>mit</strong> einem<br />

Computer ersetzt, wird Dialog <strong>mit</strong> Hilfe von einem Dialogsystem geführt.<br />

Dialogsysteme werden immer dort eingesetzt, wo die teure menschliche Arbeitskraft<br />

nicht unbedingt notwendig ist. Solche Dialogsysteme werden immer beliebter und<br />

begegnen uns überall, meistens ohne dass wir es wahrnehmen. Dabei können Dialoge<br />

<strong>mit</strong>tels Sprache, <strong>mit</strong> Hilfe der graphischen Oberfläche oder sogar anhand von<br />

Gestenerkennung geführt werden.<br />

3.1.1. Graphisch<br />

Die graphischen Dialogsystemen sind die ältesten, beliebtesten und am meisten<br />

verbreitetsten Dialogsystemen. Die bekannteste Dialogführung ist die Kommunikation<br />

zwischen Mensch und Personal Computer. Dabei werden unbedingt ein Monitor oder<br />

ein Projektor für die Ausgabe von Information verwendet und eine Tastatur oder eine<br />

Computermaus oder am liebsten beides für die Informationseingabe benutzt. Die<br />

berührungsempfindliche Monitore können nicht nur die Daten ausgeben, sondern<br />

erlauben auch die Eingabe von Daten und Befehlen per Fingerdruck, was die<br />

herkömmliche Eingabegeräte ersetzt. Solche Monitore werden z.B. bei Dialogsystemen<br />

für die Fahrplanauskunft und Fahrkartenkauf auf vielen Bahnhöfen in Deutschland<br />

angewendet. Man liest eine Frage auf dem Bildschirm und gibt die Antwort <strong>mit</strong>tels<br />

Tastatur ein. Oder man tippt direkt die Felder an, die zur Auswahl auf dem<br />

Berührungsbildschirm angezeigt werden und wie Tasten funktionieren. Diese<br />

Dialogsysteme bieten rein graphische Dialogführung an. Die Eingaben des Benutzers<br />

werden fast immer zu 100 % richtig erkannt. Für sehbehinderte Menschen sind diese<br />

aber nutzlos, hier kann man nicht ohne sprachbasierte Dialogsysteme auskommen.<br />

Seite 3


3.1.2. Sprachbasiert<br />

Nach den graphischen kommen die sprachbasierten Dialogsysteme zum Einsatz, für<br />

deren Benutzung man nur die Lautsprecher und einen Mikrofon benötigt. Die Sprache<br />

ist die natürliche Art der Kommunikation für Menschen, deshalb werden diese<br />

Dialogsysteme immer beliebter. Die Bekanntesten sind die Anrufbeantworter der<br />

telefonischen Auskunfts- und Beratungs- Diensten. Der Anrufer wird in einem<br />

interaktiven Dialog aufgefordert, eine Menüauswahl per Sprache zu treffen. In<br />

Abhängigkeit davon, welche Auswahl getroffen wurde, wird der Anrufer <strong>mit</strong> einem<br />

entsprechenden Call-Center-Mitarbeiter verbunden oder der Dialog wird weiter geführt.<br />

Solche Dialogsysteme sind meistens auf die Ziffern-Erkennung und auf die Erkennung<br />

von bestimmten Sprachbefehlen begrenzt. Obwohl der Wortbestand klein ist, können<br />

die ausgesprochene Wörter oft falsch erkannt werden, weil der Anrufer z.B. einen<br />

Akzent oder Störgeräusche im Hintergrund hat. Es wird immer wieder versucht, die<br />

Dialogsysteme auf diese Abweichungen und Störungen anzupassen und die<br />

Erkennungsrate zu erhöhen.<br />

Ein anderes Beispiel für sprachbasierte Dialogsysteme ist die Sprachsteuerung eines<br />

personalen Computers, was z.B. bei Microsoft Windows Vista schon in Betriebssystem<br />

integriert ist. Die weltweit meistverkaufte Spracherkennungssoftware Dragon<br />

NaturallySpeaking von Nuance Communications erlaubt es, dem Anwender <strong>mit</strong> nur<br />

wenigen, einfachen Worten E-Mails zu versenden, Termine festzulegen, im Internet<br />

nach Informationen zu suchen oder Anwendungen des PC's zu starten, zu bedienen und<br />

zu beenden. Die Sprache kann <strong>mit</strong> <strong>einer</strong> Geschwindigkeit von bis zu 160 Wörtern pro<br />

Minute und Genauigkeitsraten von bis zu 99% ohne vorherigen Sprachtraining<br />

bearbeitet werden [http://www.nuance.de/spracherkennung/speechtechnology.asp].<br />

Die Sprachsteuerung kann sehr nützlich sein, wenn man sich dabei frei bewegen möchte<br />

oder wenn die Hände und Augen des Benutzers schon woanders beschäftigt sind, wie es<br />

beim Steuern des Autos der Fall ist.<br />

Seite 4


3.1.3. Multimodal<br />

Ein multimodales Dialogsystem ist eine Kombination aus beiden oben beschriebenen<br />

Dialogsystemen, dabei können auch die Gesten- und Mimik- Erkennung hinzukommen.<br />

Multimodal bedeutet, dass für die Kommunikation mehr als eine Modalität (Sehen,<br />

Hören, Fühlen) eingesetzt werden kann oder muss. Man unterscheidet zwischen den<br />

komplementären (ergänzenden) und den redundanten (alternativen) multimodalen<br />

Dialogsystemen.<br />

Bei komplementären Dialogsystemen kriegen oder liefern alle verwendeten Modalitäten<br />

ihre eigene Information und ergänzen einander, die können nicht einzeln sinnvoll<br />

eingesetzt werden. Man könnte zum Beispiel in <strong>einer</strong> Zeichnungssoftware den Befehl<br />

„Zeichne einen Kreis hier“ in den Mikrofon diktieren, den Mauszeiger auf die<br />

gewünschte Stelle ziehen und Maustaste betätigen. Dabei braucht man keine genauere<br />

Position zu nennen. Die Software bekommt die Aufgabe über zwei verschiedene<br />

Informationsquellen (Mikrofon und Maus), die jeweils nur einen Teil dieser Aufgabe<br />

liefern.<br />

Bei redundanten multimodalen Dialogsystemen haben alle Informationsquellen<br />

denselben Informationsgehalt. Das kann man am Beispiel von einem Fahrkartenautomat<br />

deutlich sehen. Die Frage „Wann möchten Sie fahren?“ wird auf dem Bildschirm<br />

angezeigt und gleichzeitig über die Lautsprecher ausgegeben. Der Benutzer hat<br />

s<strong>einer</strong>seits freie Wahl, die Frage per Sprache zu beantworten oder die Schaltfläche <strong>mit</strong><br />

dem gewünschten Datum auf dem Bildschirm <strong>mit</strong> der Maus zu wählen. Die<br />

Informationen in beiden Fällen sind vollständig und unabhängig voneinander und<br />

dienen demselben Zweck. Eine weitere Anwendungsmöglichkeit von redundanten<br />

multimodalen Dialogsystemen ist bei im vorherigen Kapitel erwähnten Telefondiensten<br />

sehr beliebt. Dem Anrufer wird es angeboten, die gewünschten Menüpunkte per<br />

Sprache auszuwählen oder alternativ die Eingabe über die Telefontastatur per Tonwahl<br />

zu machen. Allerdings wird die zweite Möglichkeit nur dann funktionieren, wenn das<br />

Telefongerät die Tastenwahlfunktion unterstützt.<br />

Im Rahmen dieser Arbeit sollte ein redundantes multimodales Dialogsystem entwickelt<br />

werden, das neben der Spracherkennung eine graphische Oberfläche <strong>mit</strong> der Maus- und<br />

Tastatur- Steuerung zur Bedienung von Automaten anbieten soll.<br />

Seite 5


3.2. Spracherkennung<br />

Die Spracherkennung ist heutzutage sehr beliebt und wird in vielen Bereichen <strong>mit</strong><br />

Erfolg eingesetzt. Die häufigste Anwendung findet in Programmen für die Sprache-<br />

Text-Umwandlung (text-to-speech) und im Kapitel 3.1.2 beschriebenen sprachbasierten<br />

Dialogsystemen statt. Es gibt zwei Arten der Spracherkennung: sprecherabhängige und<br />

sprecherunabhängige. Mit der Ersten muss die Anwendung zuerst an den jeweiligen<br />

Benutzer angepasst werden, dabei sollen einige Sprachaufnahmen erfolgen. Der<br />

Wortbestand bei dieser Art der Spracherkennung kann sehr groß sein. Deshalb wird die<br />

sprecherabhängige Erkennung meistens für die Sprache-Text-Umwandlung eingesetzt.<br />

Bei der sprecherunabhängigen Spracherkennung kann die Erkennung vom beliebigen<br />

Benutzer sofort und ohne vorheriges Trainieren gestartet werden, vorausgesetzt, der<br />

Benutzer spricht dabei auf der für die Erkennung vorgesehenen Sprache. Bei für<br />

mehrere Anwender gedachten, sprachbasierten Dialogsystemen wird ausschließlich<br />

diese Art der Spracherkennung <strong>mit</strong> einem festgesetztem Wortschatz verwendet. Deshalb<br />

kommt nur die sprecherunabhängige Spracherkennung bei dieser Arbeit in Betracht.<br />

Man muss zwischen Spracherkennung (was gesprochen wird) und Sprechererkennung<br />

(wer spricht) sowie Stimmerkennung (wie gesprochen wird) unterscheiden. Die<br />

Aufgaben und Ziele von diesen drei Teilgebieten der Sprachverarbeitung sind<br />

unterschiedlich, obwohl die Verfahren zu deren Realisierung sehr ähnlich sind. Man<br />

analysiert empfangene Signale und vergleicht er<strong>mit</strong>telte Werte <strong>mit</strong> den vorhandenen,<br />

vorher erstellten Mustern. Welche Daten dabei relevant sind und wie diese verglichen<br />

werden sollen, hängt vom jeweiligen Verfahren ab. Die Erstellung von Mustern ist <strong>mit</strong><br />

einem sehr großen Aufwand verbunden. Als Erstes müssen mehrere Sprachäußerungen<br />

von möglichst vielen männlichen und weiblichen Personen <strong>mit</strong> verschiedenen Akzenten<br />

aufgenommen werden, um die universellere Muster zu erhalten, die zu einem breiten<br />

Spektrum von Anwender passen werden. Die aufgenommene Äußerungen müssen<br />

analysiert werden. Die durch Analyse gewonnene Daten werden dann zu Mustern<br />

trainiert und im gewünschten Format abgespeichert. Die Art der Analyse bei dem<br />

Training und diese bei der Erkennung müssen natürlich gleich sein.<br />

Die ganze Applikation, bei der die Spracherkennung stattfindet, wird sehr oft als<br />

Spracherkenner bezeichnet. Bei der Echtzeit-Erkennung, d.h. während der<br />

Spracheingabe durch Benutzer, wird Analyse vor der Erkennung durchgeführt. Deshalb<br />

wird diese Applikation bei der vorliegenden Arbeit Spracherkennungssystem genannt.<br />

Seite 6


3.2.1. Spracherkennungssystem<br />

Ein Spracherkennungssystem besteht hauptsächlich aus zwei Komponenten, die bei<br />

dieser Arbeit als Analyzer und Erkenner bezeichnet werden. In Einem wird die<br />

Merkmalsextraktion (oder Analyse) durchgeführt und in Anderem findet die<br />

Mustererkennung statt. Es können noch zusätzliche Komponente dem System<br />

hinzugefügt werden, um bessere Ergebnisse der Erkennung zu erzielen. So können die<br />

Modelle, oder Muster, die für die Erkennung gebraucht werden, <strong>mit</strong> der Adaption an die<br />

Störgeräusche der Umgebung angepasst werden. Die Erkennung selbst ist ein zeit- und<br />

leistungs- aufwändiger Prozess. Um das System etwas zu entlasten, kann ein<br />

Sprachdetektor eingesetzt werden. Die Sprachdetektion sorgt dafür, dass Erkenner erst<br />

dann startet, wenn wirklich die Sprache kommt, oder, um genauer zu sein, wenn in den<br />

analysierten Daten Anfang der Sprache vom Sprachdetektor erkannt wird. Die<br />

optionalen Komponente sind in der Abbildung 1 punktiert dargestellt.<br />

Abbildung 1: Spracherkennungssystem<br />

Bei der Aufnahme von Audiosignalen kann es dazu kommen, dass die Grundspannung<br />

zum Audiosignal addiert wird. Um diesen kleinen aber unangenehmen Nebeneffekt, das<br />

DC-Offset genannt wird, zu beseitigen, kann DC-Filter verwendet werden. Manchmal<br />

ist dieses Filter ein Bestandteil der Analyse. Sehr oft wird es aber komplett ausgelassen.<br />

Deshalb ist DC-Filter als eine optionale Komponente des Spracherkennungssystems in<br />

der Abbildung 1 dargestellt.<br />

Seite 7


3.2.2. Analyse<br />

Die Analyse (oder Kurzzeitanalyse, oder Merkmalsextraktion) wird, ausgenommen die<br />

Energieberechnung, im spektralen Bereich durchgeführt. Dazu soll das Sprachsignal<br />

in kleine, sich überlappende Abschnitte geteilt und <strong>mit</strong> Hamming-Fenster gewichtet<br />

werden. Die Länge von 25 ms für diese Abschnitte und 15 ms für die Überlappung oder<br />

entsprechend 10 ms für die Verschiebung hat sich in der Praxis durchgesetzt.<br />

25 ms<br />

Abbildung 2:<br />

Aufteilung des<br />

Audiosignals<br />

10 ms<br />

Mit der Fast Fourier Transformation (FFT) wird jeder Abschnitt in den<br />

Frequenzbereich überführt. Die dabei entstandene Fouriertransformierte wird <strong>mit</strong> der<br />

aus 24 bis 26 sich zur Hälfte überlappenden Dreiecksfiltern bestehende Mel-Filterbank<br />

multipliziert, um das Mel-Spektrum zu berechnen. Aus Diesem kann nun das Mel-<br />

Cepstrum er<strong>mit</strong>telt werden. Dazu wird das Mel-Spektrum zuerst logarithmiert und<br />

anschließend <strong>mit</strong> der Kosinus-Funktion multipliziert. Die dabei gewonnene<br />

Merkmalsvektoren bilden die sogenannte Merkmalssequenz, oder MFCC (Mel<br />

Frequency Cepstral Coefficients), die zusammen <strong>mit</strong> der Energie die gewünschten<br />

Ergebnisse der Kurzzeitanalyse repräsentiert.<br />

Eine graphische Darstellung von diesem ganz grob beschriebenen Verfahren der<br />

Merkmalsextraktion ist in der Abbildung 3 zu sehen. Eine nähere Betrachtung von<br />

einzelnen Schritten wird hier nicht erfolgen, da es nicht die Aufgabe dieser Arbeit ist,<br />

eine Analyse durchzuführen.<br />

0. Sprachsignal 1. Aufteilung<br />

3. Hamming 4. FFT<br />

2. Energie<br />

5. Mel-Filterbank<br />

8. MFCC<br />

7. Kosinus<br />

6. LOG<br />

Abbildung 3: Merkmalsextraktion<br />

Seite 8


3.2.3. Erkennung<br />

Während der Erkennungsphase werden die durch Analyse gewonnene Merkmale des<br />

Eingangssignals <strong>mit</strong> den vorhandenen Mustern verglichen, um das Muster zu er<strong>mit</strong>teln,<br />

das diesem Sprachsignal am ähnlichsten ist. Es gibt verschiedene Verfahren, um diesen<br />

rechenintensiven Vergleich durchzuführen. Die drei Bekanntesten sind Dynamische<br />

Programmierung (Dynamic Time Warping, DTM), Künstliche Neuronale Netze (KNN<br />

oder Artificial Neural Network, ANN) und die Darstellung des Musters als Hidden-<br />

Markov-Modell (HMM).<br />

Bei Dynamischer Programmierung werden die analysierten Daten der zu erkennenden<br />

Äußerung an jedes vorhandene Muster zeitlich angepasst, um einen korrekten Vergleich<br />

zu ermöglichen. So können zwei unterschiedlich lange Äußerungen desselben Wortes<br />

richtig <strong>mit</strong>einander verglichen werden. Dabei werden die Abstände zwischen den<br />

einzelnen Werten der beiden Merkmalsfolgen berechnet. Das Muster <strong>mit</strong> dem kleinsten<br />

Abstand zur vorliegenden Äußerung wird ausgewählt. Die Zeitanpassung wird für jeden<br />

Wert der Merkmalsvektoren dynamisch durchgeführt.<br />

Neuronale Netze, oder anders genannt Künstliche Intelligenz, bestehen aus mehreren<br />

Schichten. An die Eingangsschicht werden die Merkmale des zu erkennenden Wortes<br />

gelegt, an die Ausgangsschicht die der vorhandenen Muster. In den Zwischenschichten<br />

werden die ersten Merkmale so lange verarbeitet, bis die den Merkmalen eines dieser<br />

Muster ähnlich sind. Das betroffene Muster wird als Ergebnis anerkannt. Sehr oft<br />

werden die Hidden-Markov-Modelle als Muster bei dieser Art der Mustererkennung<br />

eingesetzt, dabei entsteht eine neue, gemischte Technik.<br />

Bei dem dritten und wahrscheinlich am häufigsten benutzten Verfahren <strong>mit</strong> Hidden-<br />

Markov-Modellen werden die Übergangswahrscheinlichkeiten zwischen den Zuständen<br />

berechnet, aus denen diese HMM's bestehen. Man kann ein HMM als Zustandsautomat<br />

darstellen, dabei können die analysierten Daten von einem Wort oder sogar von einem<br />

Phonem in mehrere Zustände zerlegt werden. Die Verteilung von Merkmalsvektoren<br />

auf die Zustände wird <strong>mit</strong> Hilfe von Viterbi- Algorithmus realisiert. Nachdem alle diese<br />

Merkmale optimal verteilt wurden oder nachdem der beste Pfad durch den<br />

Zustandsautomat gefunden wurde, werden die Wahrscheinlichkeiten berechnet. Das<br />

Muster <strong>mit</strong> der höchsten Wahrscheinlichkeit wird der zu erkennenden Sprachäußerung<br />

zugewiesen. Im Labor für Digitale Nachrichtentechnik an der Hochschule Niederrhein<br />

wird sehr viel Wert auf dieses Verfahren gelegt.<br />

Seite 9


4. Betriebs<strong>mit</strong>tel, Software<br />

openSUSE wird auf nahezu allen Rechnern im DNT-Labor als<br />

Betriebssystem eingesetzt. Diese freie Distribution ist in<br />

Deutschland sehr verbreitet und beliebt. Obwohl openSUSE<br />

kostenlos ist, kann man eine kommerzielle Version <strong>mit</strong> dem<br />

vollständigen Handbuch und <strong>mit</strong> <strong>einer</strong> 90-Tägigen telefonischen Installations-<br />

Unterstützung erwerben. Um das Betriebssystem unbeschränkt nutzen zu können, muss<br />

man auf die Produktlebenszeit achten. 18 Monate lang ab Erscheinungsdatum von<br />

jeweiliger Version stehen die Aktualisierungen und zugehörige Repositories zur<br />

Verfügung. Man soll also rechtzeitig auf die neue Version umsteigen. Die<br />

Softwareentwicklung bei dieser Masterarbeit wurde ausschließlich unter openSUSE<br />

durchgeführt. [http://de.opensuse.org/] Die verwendeten <strong>Entwicklung</strong>swerkzeuge und<br />

Bibliotheken sind sowohl unter Linux als auch unter Microsoft Windows und Mac OS<br />

einsetzbar, wie z.B. das Toolkit FLTK für die GUI-<strong>Entwicklung</strong>.<br />

4.1. FLTK 2.0<br />

Es gibt viele kostenfreie GUI-Frameworks, die<br />

einen breiten Funktionsumfang zur Verfügung<br />

stellen. Bei dieser Arbeit ist die Wahl auf Fast<br />

Light Toolkit (FLTK) Version 2.0 gefallen. FLTK ist eine plattformunabhängige C++<br />

Bibliothek, bei der auf die Anbindungen an andere Programmiersprachen verzichtet<br />

wurde. Aus diesem Grund und weil es nur auf die GUI konzentriert wurde und alle<br />

anderen extra Klassen ausgelassen wurden, ist FLTK, wie auch der Name schon sagt,<br />

sehr schnell und leicht im Vergleich <strong>mit</strong> anderen Toolkits. Außerdem wird 3D-Grafik<br />

<strong>mit</strong> OpenGL-Anbindung bei FLTK unterstützt. [http://www.fltk.org/]<br />

Zusammen <strong>mit</strong> FLTK wird ein FLTK eigener graphischer Editor „Fast Light User-<br />

Interface Designer“ (FLUID) installiert, <strong>mit</strong> dem man nicht nur eine graphische<br />

Oberfläche sondern große Projekte <strong>mit</strong> Klassen und Namensräumen ganz leicht<br />

erstellen kann. FLUID verwaltet seine Projekte als Textdateien <strong>mit</strong> eigenem Dateisuffix<br />

'.fl', die man bei Bedarf <strong>mit</strong> beliebigem Texteditor editieren kann. Mit FLUID kann man<br />

die neu erstellte oder aus der '.fl'-Datei geladene Projekte in C++ Code konvertieren,<br />

Seite 10


(„Write code“ in der Abbildung 4) dabei werden jeweils eine '.cxx' und eine '.h' Datei<br />

erstellt, die den gesamten Quellcode enthalten, der nur noch kompiliert werden soll.<br />

Nach dem Start von FLUID öffnen sich zwei Fenster. In dem Hauptfenster, das auf den<br />

Abbildungen 4 und 5 zu sehen ist, kann man alle Konfigurationen vornehmen, den<br />

Quellcode erstellen und graphische Elemente hinzufügen.<br />

Abbildung 4: FLUID Hauptfenster 1 Abbildung 5: FLUID Hauptfenster 2<br />

Das zweite Fenster ermöglicht einen schnellen Zugriff auf alle vorhandene Elemente:<br />

Abbildung 6: FLUID „Widget Bin“<br />

Wenn man ein graphisches Objekt erstellt, öffnet sich ein drittes Fenster, das dieses<br />

Objekt darstellt. So ist ein Fenster von der Applikation „Hello World!“ in der<br />

Abbildung 7 auf der nächsten Seite als ein FLUID-Objekt dargestellt. Mann kann die<br />

einzelne Elemente direkt im Fenster verschieben und deren Größe <strong>mit</strong> dem Mauszeiger<br />

ändern. Das Fenster wird nach der Kompilierung des Quellcodes genau so aussehen,<br />

wenn man die Applikation startet. So schnell und übersichtlich kann man beliebig<br />

komplizierte graphische Oberflächen <strong>mit</strong> FLUID erstellen.<br />

Seite 11


Abbildung 7:<br />

"Hello, World!"<br />

<strong>mit</strong> FLTK<br />

Um die Vorteile von FLUID besser zu erkennen, soll man den von Hand erstellten<br />

Quellcode für die Applikation „Hello, World!“ ohne Headerfile <strong>mit</strong> den Abbildungen 8<br />

bis 10 auf der Seite 13 vergleichen:<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

using namespace fltk;<br />

// für exit()<br />

void my_callback(fltk::Button*, void*) // Abbildung 10<br />

{<br />

exit(0);<br />

}<br />

int main(int argc, char **argv)<br />

{<br />

Window *window = new Window(300, 200, "hello");<br />

window->begin();<br />

}<br />

Widget *box = new Widget(20, 20, 260, 80, "Hello, World!");<br />

box->box(UP_BOX);<br />

// Schriftart<br />

box->labelfont(HELVETICA_BOLD_ITALIC); // in<br />

box->labelsize(36); // der<br />

box->labeltype(SHADOW_LABEL); // Abbildung 8<br />

Button *button = new Button(70, 120, 160, 60, "Exit");<br />

button->labelsize(24);<br />

button->callback((Callback*)my_callback); // Abbildung 9<br />

window->end();<br />

window->show(argc, argv);<br />

return run();<br />

Für einen Anfänger in der GUI- Programmierung <strong>mit</strong> FLTK ist es sehr aufwändig z.B.<br />

die Schriftart für einen Text zu ändern, ohne in der Dokumentation zu suchen. Mit<br />

FLUID muss man nur die gewünschte aus allen vorhandenen Möglichkeiten auswählen.<br />

In der Abbildung 8 sind die geänderten Parameter rot dargestellt. Alle benötigten FLTK<br />

eigene Headerfiles werden automatisch bei der Quellcode-Generierung eingefügt.<br />

FLUID erstellt alle Elemente als kleinen Buchstaben „o“. Ein Abschnitt des oben<br />

Seite 12


angegebenen Quellcodes könnte vereinfacht bei FLUID wie folgt aussehen:<br />

Window *o = new Window(300, 200, "hello");<br />

{<br />

Widget *o = new Widget(20, 20, 260, 80, "Hello, World!");<br />

}<br />

{<br />

Button *o = new Button(70, 120, 160, 60, "Exit");<br />

}<br />

Deshalb ist Button-Callback in der Abbildung 9 als „my_callback(o, 0)“ angegeben.<br />

Der Buchstabe „o“ ist der Button selbst und die Ziffer 0 steht für „Übergabeparameter:<br />

keine“. Das ganze Projekt unter FLUID sieht dann wie in der Abbildung 10 aus.<br />

Abbildung 8: Schriftart <strong>mit</strong> FLUID<br />

Abbildung 9: Callback <strong>mit</strong> FLUID<br />

Abbildung 10:<br />

"Hello, World!"<br />

Projekt unter<br />

FLUID<br />

Seite 13


4.2. POCO<br />

POCO ist eine Sammlung von modernen, <strong>leistungsfähigen</strong>, freien C++<br />

Klassenbibliotheken und Frameworks für die Netzwerk- und Internetbasierten<br />

Anwendungen, die auf Desktop-, Server- und Embedded-<br />

Systemen ausgeführt werden. [http://pocoproject.org/]<br />

Die POCO- Bibliotheken werden weltweit <strong>mit</strong> dem Ziel entwickelt, eine möglichst<br />

große plattformunabhängige Sammlung von fertigen Lösungen zu verschiedensten<br />

Aufgaben, auf die man bei der Programmierung in unterschiedlichen Bereichen stoßen<br />

kann, zusammen zu stellen. Diese sehr empfehlenswerte Bibliotheken ermöglichen den<br />

Entwicklern, die Zeit für die <strong>Entwicklung</strong> von schon Entwickeltem zu sparen.<br />

Eine grobe Übersicht der POCO- Bibliotheken, die aus der kurzen Zusammenfassung<br />

unter http://pocoproject.org/documentation/PoCoOverview.pdf kopiert wurde:<br />

Abbildung 11: POCO- Übersicht<br />

Die Zip- Bibliothek enthält Klassen zum Erstellen, Bearbeiten und Entpacken von Ziparchivierten<br />

Daten.<br />

Die Klassen aus Util- Bibliothek sind für die Verarbeitung von Konfigurationsdateien<br />

und für die Behandlung von Kommandozeilenparameter, sowie für die Erstellung von<br />

Seite 14


Server- Anwendungen wie Unix-Dämonen oder Windows-Diensten zuständig.<br />

Wie der Name von XML- Bibliothek schon sagt, ist diese Bibliothek für die Erstellung<br />

und Analyse von XML- Dokumenten verantwortlich. Dabei werden die W3C (World<br />

Wide Web Consortium) Standards SAX2 und DOM unterstützt.<br />

Die Net- Bibliothek bietet Implementierungen verschiedener Netzwerkprotokolle und<br />

Server wie HTTP, FTP, SMTP und anderen. Da<strong>mit</strong> kann man z.B. IP-Adressen<br />

verwalten, e-Mails versenden oder kompletten Webserver erstellen.<br />

Eine Erweiterung von dieser Bibliothek wird als NetSSL bezeichnet und verwendet die<br />

SSL- (Secure Sockets Layer) und TLS- (Transport Layer Security) Verschlüsselung für<br />

eine sichere Kommunikation. Um diese Erweiterung einsetzen oder überhaupt<br />

installieren zu können, müssen zusätzliche externe OpenSSL- Pakete auf dem Rechner<br />

schon vorhanden sein. Deshalb wird es empfohlen, diese Erweiterung nur dann zu<br />

installieren, wenn sie wirklich benötigt wird. Unter Linux kann man diese Bibliothek<br />

vor der Installation von POCO folgendermaßen auslassen:<br />

./configure --o<strong>mit</strong>=NetSSL_OpenSSL<br />

Mit „--o<strong>mit</strong>=Crypto“ kann man eine weitere Bibliothek ausschließen, die OpenSSL<br />

benötigt - Crypto. Diese Bibliothek bietet die Ver- und Entschlüsselung von Daten an.<br />

Die Data- Bibliothek erlaubt einen einfachen Zugriff auf verschiedene Datenbanken<br />

über ODBC (Open Database Connectivity) und auf die Datenbankverwaltungssysteme<br />

wie MySQL oder SQLite. Dafür müssen Microsoft ODBC für Windows oder<br />

unixODBC / iODBC für Linux und MySQL client installiert werden.<br />

Um alle von externen Paketen abhängige POCO- Bibliotheken bei der Installation zu<br />

ignorieren, kann man diesen Befehl anwenden:<br />

./configure -–o<strong>mit</strong>=NetSSL_OpenSSL,Crypto,Data/ODBC,Data/MySQL<br />

Das „Herz“ von POCO ist Foundation, eine Bibliothek <strong>mit</strong> vielen abstrakten Klassen<br />

und fertigen Strukturen für die Arbeit <strong>mit</strong> Ausnahmen und Ereignissen, <strong>mit</strong> parallelen<br />

Prozessen und deren Kommunikation <strong>mit</strong>einander, <strong>mit</strong> Datum und Zeit, <strong>mit</strong> Dateien und<br />

Ordner, <strong>mit</strong> dynamischen Bibliotheken und vielem mehr.<br />

Seite 15


4.3. Hilfs<strong>mit</strong>tel<br />

Um die Makefiles zu generieren und um alle bei dieser Arbeit<br />

erstellten Quelltexte zu einem Projekt zusammenzufügen, wurde das<br />

plattformunabhängige Programmierwerkzeug CMake (cross-platform<br />

make) verwendet. [http://www.cmake.org/]<br />

Mit CMake kann man schnell und leicht Projekte aus beliebig komplexer Ordner-<br />

Architektur erstellen. Dazu muss eine einfache Konfigurationsdatei „CMakeLists.txt“ in<br />

jedem Ordner <strong>mit</strong> Quellcode vorhanden sein. Auf der offiziellen Webseite von CMake<br />

findet man sehr gute Dokumentation und viele Konfigurations- und Anwendungs-<br />

Beispiele von diesem Toolkit. Neben der kommandozeilenorientierten Anwendung<br />

„cmake“ gibt es einen graphischen Editor „cmake-gui“, <strong>mit</strong> dem man ganz bequem die<br />

wichtigsten Konfigurationen vornehmen und externe Bibliotheken anbinden kann.<br />

Mit CMake erstellte Makefiles können direkt vom „make“- Programm gelesen werden,<br />

um den Quelltext zu kompilieren. Alternativ kann das zusammen <strong>mit</strong> Makefiles erstellte<br />

Projekt in eine <strong>Entwicklung</strong>sumgebung für die weitere Verarbeitung geladen werden.<br />

Für welche <strong>Entwicklung</strong>sumgebung das Projekt erstellt werden soll, muss man vorher<br />

bestimmen.<br />

Bei dieser Arbeit wurde eine freie, plattformunabhängige C++<br />

Programmierumgebung <strong>mit</strong> dem Namen Code::Blocks verwendet,<br />

bei der beliebige Compiler und Debugger eingesetzt werden können.<br />

Code::Blocks kann <strong>mit</strong> weiteren zusätzlichen Erweiterungsmodulen<br />

(Plugins) versehen werden. Diese Programmierumgebung hat ein deutsches Handbuch<br />

<strong>mit</strong> ausführlicher Beschreibung. [http://www.codeblocks.org/] Code::Blocks hat viele<br />

Einstellungsmöglichkeiten und eine übersichtliche graphische Oberfläche, die die<br />

Arbeit <strong>mit</strong> dem Quellcode leichter und angenehmer werden lassen, besonders wenn es<br />

um größere Projekte geht. Wenn man die erstellten Anwendungen <strong>mit</strong> Code::Blocks<br />

testen will, kann man unter „Project → Set program's arguments” die gewünschten<br />

Übergabeparameter angeben. Unter „Project → Properties → Build targets” kann man<br />

den Ordner festlegen, aus dem die Anwendung starten soll (s.a. Abbildung 14 auf der<br />

Seite 20). Das Projekt selbst kann man <strong>mit</strong> dem folgenden Aufruf von CMake erstellen:<br />

cmake -G "CodeBlocks - Unix Makefiles"<br />

Seite 16


5. asrLib<br />

asrLib (Automatic Speech Recogition Library) ist ein Projekt, das im Labor für Digitale<br />

Nachrichtentechnik an der Hochschule Niederrhein entwickelt wurde und das eine<br />

Sammlung von Bibliotheken zur Erstellung von Applikationen für automatische<br />

Spracherkennung zur Verfügung stellt. Mit diesen Bibliotheken kann man nicht nur<br />

lokale sprachbasierte Dialogsysteme, d.h. <strong>mit</strong> dem Einsatz von Mikrofon und<br />

Lautsprecher, realisieren, sondern auch solche, die eine Kommunikation <strong>mit</strong> dem<br />

System über beliebige Telefongeräte per Telefonleitung ermöglichen. Das Projekt<br />

asrLib wurde parallel zu dieser Masterarbeit weiter entwickelt und um die Möglichkeit,<br />

multimodale Dialogsysteme <strong>mit</strong> der graphischen Oberfläche zu entwerfen, erweitert.<br />

Das Projekt befindet sich im gleichnamigen Ordner und ist in mehrere Verzeichnisse<br />

unterteilt. Die hier dargestellte (vereinfachte) Verzeichnisstruktur gibt einen kurzen<br />

Überblick über „asrLib“, die etwas nähere Beschreibung von einzelnen Ordner findet<br />

man in den nächsten Kapiteln.<br />

asrLib<br />

| --- applications // beinhaltet Ordner <strong>mit</strong> Quellcode für verschiedene Applikationen<br />

| --- DialogHGH // Quellcode <strong>einer</strong> Applikation für sprachbasierte Dialogsysteme<br />

| --- GuiApp // Quellcode <strong>einer</strong> Applikation für multimodale Dialogsysteme<br />

| --- build.Codeblocks // Ordner für alle Makefiles, wird von CMake erstellt<br />

| --- bin // Ordner für ausführbare asrLib- Binärdateien<br />

| --- lib // Ordner für asrLib- Basis- Bibliotheken<br />

| --- CMakeModules // Ordner <strong>mit</strong> zusätzlichen Konfigurationsdateien für CMake<br />

| --- data // enthält alle Daten und Konfigurationsdateien für Dialogsysteme<br />

| --- plugins // fertige Erweiterungsmodule als dynamische Bibliotheken<br />

| --- doc // Dokumentation von asrLib-Projekt, erstellt <strong>mit</strong> Doxygen<br />

| --- include // Ordner <strong>mit</strong> Headerfiles für Grundbibliotheken von asrLib<br />

| --- lib // Ordner <strong>mit</strong> externen Bibliotheken, die bei asrLib eingesetzt werden<br />

| --- CapiLib // ISDN-IO Bibliothek<br />

| --- HirschLib // Bibliotheken für automatische Spracherkennung<br />

| --- plugins // Ordner <strong>mit</strong> Quellcode für alle Erweiterungsmodule<br />

| --- src // Ordner <strong>mit</strong> Quellcode für Grundbibliotheken von asrLib<br />

Seite 17


5.1. asrLib und CMake<br />

Um die mühsame manuelle Erstellung von Makefiles und deren Korrektur bei jeder<br />

Änderung im Projekt zu ersparen, wurde bei dieser Arbeit CMake eingesetzt (Kapitel<br />

4.3). Um noch einfacher und angenehmer zu arbeiten, wurde der leicht zu bedienende<br />

graphische Editor von CMake verwendet, der nur zu empfehlen ist. Dieser Editor kann<br />

über Kommandozeile <strong>mit</strong> „cmake-gui“ aufgerufen werden. Nach dem Start öffnet sich<br />

ein Auswahlfenster für Makefiles- Generator *. Es wird empfohlen, „CodeBlocks -<br />

Unix Makefiles“ als Generator auszuwählen, um das Projekt für die weitere Bearbeitung<br />

in Programmierumgebung Code::Blocks vorzubereiten, die ebenfalls im Kapitel 4.3<br />

kurz beschrieben wurde. Als Weiteres gibt man den vollständigen Pfad zum asrLib-<br />

Ordner im entsprechenden Eingabefeld (1. in der Abbildung 12) für Verzeichnis <strong>mit</strong><br />

dem Quellcode ein. Als Ausgabeordner (2.) wird z.B. „build.Codeblocks“ ausgewählt<br />

(s. Ordnerstruktur auf vorheriger Seite) oder ein neuer Ordner erstellt, in dem alle<br />

Makefiles geschrieben werden sollen. Wie der Name schon sagt, wird ein Projekt für<br />

Code::Blocks zusammen <strong>mit</strong> Makefiles in diesem Ordner angelegt.<br />

1. →<br />

2. →<br />

3. →<br />

Abbildung 12: CMake GUI<br />

* Falls Auswahlfenster nicht automatisch geöffnet wird, kann man das erzwingen, indem man den<br />

Menüpunkt „File → Delete Cache” wählt (oder beide Eingabefelder leer lässt, die <strong>mit</strong> Ziffern 1 und 2 in<br />

der Abbildung 12 gekennzeichnet sind) und den Button „Configure“ (3.) betätigt.<br />

Seite 18


Mit der Schaltfläche „Configure“ (3.) wird die Konfigurationsdatei „CMakeLists.txt“<br />

im asrLib- Ordner eingelesen und alle Einstellungen und Zuweisungen werden in<br />

CMake übernommen. Falls nötig, können Diese im Editor angepasst werden. Wenn<br />

keine Fehler bei diesem Konfigurationsschritt auftreten, bzw. nach deren Beseitigung,<br />

kann man den gewählten Generator <strong>mit</strong> der Taste „Generate“ starten.<br />

Die Konfigurationsdatei ist leicht zu verstehen und kann nach Bedarf angepasst werden.<br />

So kann der Zielordner für ausführbare Binärdateien <strong>mit</strong> Variable „OUTPUT_BINDIR“<br />

geändert werden. Standardmäßig ist das der Ordner „bin“, der sich im vom Benutzer<br />

festgesetzten Ausgabeordner (2. in der Abbildung 12, z.B. build.Codeblocks) befindet.<br />

Der Ordner für alle bei asrLib erzeugten Bibliotheken ist <strong>mit</strong> der Variable<br />

„OUTPUT_LIBDIR“ zu setzten, standardmäßig „build.Codeblocks/lib“. Welche Ordner<br />

<strong>mit</strong> Quellcode bei der Kompilation berücksichtigt werden sollen, kann man <strong>mit</strong><br />

„add_subdirectory()“ festlegen. Man kann den Namen des Projektes für die<br />

Programmierumgebung <strong>mit</strong> „PROJECT(asrLib)“ bestimmen.<br />

Um die Konfigurationsdatei übersichtlich zu halten, kann man zusätzliche Einstellungen<br />

in separaten Modulen abspeichern. So sind einige Module im Ordner CMakeModules<br />

abgelegt, die angeben, welche externe Bibliotheken geladen werden sollen und wo diese<br />

zu finden sind. Jeder Ordner <strong>mit</strong> Quellcode enthält seine eigene Konfigurationsdatei <strong>mit</strong><br />

lokalen Zuweisungen. Zum Beispiel ist der Ausgabeordner für Bibliotheken in allen<br />

„CMakeLists.txt“ Dateien in allen Verzeichnissen unter „plugins“ (s. Ordnerstruktur auf<br />

der Seite 17) als „data/plugins“ definiert, im Gegensatz zur oben erwähnten Variable<br />

„OUTPUT_LIBDIR“ („build.Codeblocks/lib“), um diese Bibliotheken von denen, die<br />

aus dem Quellcode unter „src“ erstellt werden, zu trennen. Worin der Unterschied liegt<br />

und wofür dieser gut ist, wird im Kapitel 5.4 erläutert.<br />

5.2. asrLib und Code::Blocks<br />

Code::Blocks braucht nicht besonders vorgestellt zu werden. Das Wichtigste wurde<br />

schon im Kapitel 4.3 geschrieben. Einige Bemerkungen müssen in diesem Kapitel doch<br />

noch gemacht werden. Nach dem Start von Code::Blocks wird es vorgeschlagen, ein<br />

neues Projekt anzulegen oder schon vorhandenes zu öffnen. Hier öffnet man vom<br />

CMake erstelltes Projekt, z.B. „asrLib/build.Codeblocks/asrLib.cbp“. Wie viele andere<br />

Programmierumgebungen kann Code::Blocks nicht nur ein Projekt editieren und<br />

kompilieren, sondern auch ausführen und debuggen. In der Abbildung 13 ist die<br />

Seite 19


Symbolleiste <strong>mit</strong> den Werkzeugen zum Debuggen <strong>mit</strong> der Ziffer 1 gekennzeichnet. Auf<br />

der rechten Seite von dieser Leiste ist ein weißes Fenster abgebildet, hier kann man z.B.<br />

den Speicher ansehen oder Variablen beobachten. Die kurzen Popup Meldungen geben<br />

eine verständliche Beschreibung zum jeden Symbol.<br />

1. →<br />

2. →<br />

Abbildung 13: Code::Blocks Übersicht<br />

Mit 2. sind die Werkzeuge zum Ausführen und Kompilieren vom ganzen Projekt oder<br />

seinen einzelnen Teilen markiert. Mit „Build target:“ wählt man den zu kompilierenden<br />

Teil aus („All“ für ganzes Projekt). Falls man irgendwelche Änderungen im Quellcode<br />

außerhalb von Code::Blocks macht, muss das Projekt <strong>mit</strong> dem Symbol <strong>mit</strong> zwei blauen<br />

Pfeilen neu geladen werden. Wichtig für das Ausführen von jedem Projekt ist die in der<br />

Abbildung 14 markierte Einstellung (Ordner, aus dem die Anwendung starten soll).<br />

Abbildung 14: Code::Blocks Einstellungen<br />

Seite 20


5.3. Bibliotheken<br />

Wie schon im Kapitel 5 geschrieben wurde, besteht das Projekt asrLib aus <strong>einer</strong><br />

Sammlung von Bibliotheken. Diese Sammlung setzt sich aus sechs sozusagen stetigen<br />

Bibliotheken zusammen, die eine Basis für asrLib bilden, und aus von diesen<br />

abgeleiteten Erweiterungsmodulen, die „Plugins“ genannt werden. Bei dem Aufruf von<br />

Applikationen kann man festlegen, welche Plugins benötigt werden. Man kann Diese<br />

bei Bedarf auch während der Laufzeit laden. Der Quellcode (die Ordner „src“ und<br />

„include“ in asrLib- Ordnerstruktur auf der Seite 17) für die sechs Grundbibliotheken ist<br />

in Gruppen aufgeteilt, die zu einem gemeinsamen Namensraum „asrlib“ gehören:<br />

Base (libBase.so) ist die Grundlage für alle anderen Gruppen. *<br />

StateMachine (libStateMachine.so) ist der Kern von allen Dialogsystemen. Stellt ein<br />

Zustandsautomat dar, dessen Aufgabe ist, nicht nur die Zustände eines Systems unter<br />

gegebenen Bedienungen zu wechseln, sondern auch die Konfigurationsdateien<br />

einzulesen und Information dynamisch zu speichern.<br />

Audio (libAudio.so) ist für das Einlesen und Bearbeiten von Audiodateien, für die<br />

Konfiguration von Audiotreiber und für den Austausch von Audiodaten zwischen<br />

Soundkarte und Komponenten eines Spracherkennungssystems zuständig. *<br />

ISDN (libISDN.so) basiert auf ISDN-IO Bibliothek, oder CapiLib, einem Treiber für<br />

ISDN-Komponente. Da<strong>mit</strong> kann der Datenaustausch <strong>mit</strong>tels ISDN realisiert werden. *<br />

ASR (libASR.so) beinhaltet die (virtuellen) Basisklassen für alle Komponente eines<br />

Spracherkennungssystems (Kapitel 3.2.1). Man kann mehrere von ASR abgeleitete und<br />

voneinander unabhängige Spracherkennungssysteme <strong>mit</strong> verschiedenen Methoden der<br />

Erkennung erstellen und als Plugins bei Dialogsystemen einsetzen.<br />

GUI (libGUI.so) ermöglicht eine einfachere Erstellung von graphischen Oberflächen<br />

und deren Einbindung in Dialogsysteme. GUI gründet auf FLTK 2.0 (Kapitel 4.1) und<br />

hat ihre eigene von FLTK abgeleitete und erweiterte Klassen für Widgets. Zur<br />

Kommunikation zwischen der graphischen Oberfläche und StateMachine wird ein<br />

Controller verwendet. Weiter dazu im Kapitel 7.<br />

* wird bei dieser Arbeit nicht näher betrachtet<br />

Seite 21


Die Bibliothek „libStateMachine.so“ wurde nach in ihr enthaltener Singleton-Klasse<br />

genannt, deren Objekt (weiter einfach StateMachine) alle Prozesse in Dialogsystemen<br />

steuert und die graphische Oberfläche kontrolliert. Für jede Applikation von asrLib<br />

muss eine Konfigurationsdatei vorhanden sein, die das Verhalten von StateMachine<br />

beschreibt. Ein Beispiel für solche Konfigurationsdatei könnte wie folgt aussehen:<br />

<br />

frage1<br />

<br />

AUDIO_OUT<br />

1 fahren_sie_allein.raw<br />

0<br />

<br />

erkennung_ja_nein<br />

<br />

erkennung_ja_nein<br />

<br />

RECOGNIZE<br />

2 jn.syn M_rec<br />

2 w_nein w_ja<br />

frage2 goodbye<br />

goodbye<br />

<br />

frage2<br />

<br />

AUDIO_OUT<br />

1 wieviel_personen.raw<br />

0<br />

<br />

erkennung_zahl<br />

<br />

erkennung_zahl<br />

<br />

RECOGNIZE<br />

3 number.syn M_rec B_nummer<br />

0<br />

<br />

goodbye<br />

<br />

goodbye<br />

<br />

AUDIO_OUT<br />

1 goodbye.raw<br />

0<br />

<br />

exit<br />

Die oben angegebene Konfiguration eines Dialogsystems ergibt keinen wirklichen Sinn,<br />

es fehlt der Anfang und das Ende. Diese Konfiguration besteht aus fünf Absätzen <strong>mit</strong><br />

jeweils sechs Zeilen. Jeder Absatz stellt einen Zustand des Zustandsautomaten dar.<br />

„STATE“ enthält den Namen vom Zustand und „ACTION“ ist sein Verhalten oder<br />

Funktion, die ausgeführt werden soll, während StateMachine sich in diesem Zustand<br />

befindet. „ACTION_PARS“ und „TRANS_SEL“ sind entsprechend die Übergabe- und<br />

mögliche Rückgabe- Parameter dieser Funktion, wobei die ersten Ziffer deren Anzahl<br />

angeben. Die Rückgabeparameter sind Bedingungen für den Zustandsübergang der<br />

StateMachine. In welche Zustände der Zustandsautomat vom aktuellen Zustand<br />

Seite 22


übergehen kann, ist unter „TRANS_STATE“ festgesetzt. Zu jedem Übergang soll also<br />

genau eine Bedingung vorhanden sein. Falls gar keine Rückgabeparameter von der<br />

aktuellen Action vorgesehen sind oder falls ein Parameter zurückgegeben wird, der<br />

nicht unter „TRANS_SEL“ aufgelistet ist, kommt „DEFAULT_TRANS“ in Betracht,<br />

das den Namen des Zustandes enthält, in den StateMachine vom aktuellen Zustand bei<br />

default wechseln soll. Wenn der aktuelle Zustand hier seinen eigenen Namen hat, bleibt<br />

StateMachine in diesem Zustand und die Action wird nochmal ausgeführt.<br />

Da<strong>mit</strong> man diese Beschreibung des Verhaltens von StateMachine besser nachvollziehen<br />

kann, soll man diese in einem Zustandsübergangsdiagramm ansehen:<br />

STATE: frage1<br />

ACTION: AudioOut<br />

w_nein<br />

STATE: frage2<br />

ACTION: AudioOut<br />

STATE: erkennung_ja_nein<br />

ACTION: Recognize<br />

STATE: erkennung_zahl<br />

ACTION: Recognize<br />

w_ja<br />

STATE: goodbye<br />

ACTION: AudioOut<br />

Abbildung 15: Beispiel Zustandsautomat<br />

In Wirklichkeit sind diese Actionen keine ausführbare Funktionen, sondern Klassen, die<br />

von „asrlib::Action“ aus der StateMachine- Bibliothek abgeleitet wurden.<br />

Class AudioOut : public asrlib::Action<br />

{<br />

void execute();<br />

}<br />

Die States sind Objekte der Klasse „asrlib::State“, die jeweils ein entsprechendes Objekt<br />

von Action besitzen und sich anhand der Namen unterscheiden.<br />

Class State<br />

{<br />

State(std::string name) {};<br />

Action* action;<br />

}<br />

State* state1 = new State(„frage1“);<br />

state1->action = new AudioOut();<br />

State* state2 = new State(„erkennung_ja_nein“);<br />

state2->action = new Recognize();<br />

Seite 23


Bevor man StateMachine startet, soll die Konfigurationsdatei eingelesen werden. Es<br />

sollen so viele Objekte der Klasse asrlib::State erstellt und in StateMachine in einem<br />

Map gespeichert werden, wie viele States diese Datei enthält. In unserem Beispiel sind<br />

das 5 Objekte. Nach dem Start von StateMachine wird die Funktion execute() des<br />

Action-Objektes vom ersten State aus dem Map aufgerufen. Genau diese Funktion führt<br />

die gewünschte Aktion durch, z.B. die Wiedergabe <strong>einer</strong> Audiodatei. Diese Schritte<br />

könnten in <strong>einer</strong> vereinfachter Form so aussehen:<br />

stateMap[„frage1“] = new State(„frage1“);<br />

stateMap[„frage1“]->action->execute();<br />

// Ein STATE erstellen<br />

// ACTION ausführen<br />

Nicht nur die States, sondern auch die zugehörige „TRANS_STATE“ und<br />

„DEFAULT_TRANS“ werden in StateMachine gespeichert, da<strong>mit</strong> diese weiß, welches<br />

State als nächstes aus dem Map genommen werden soll.<br />

Das war eine kurze Beschreibung der Hauptaufgaben von StateMachine- Bibliothek.<br />

Eine weitere Bibliothek, die in diesem Kapitel noch ganz kurz beschrieben werden soll,<br />

ist ASR, oder „libASR.so“. Wie schon früher geschrieben wurde, stellt diese Bibliothek<br />

eine abstrakte Basisklasse für ein komplettes Spracherkennungssystem <strong>mit</strong> allen seinen<br />

Komponenten zur Verfügung. Der Benutzer dieser Bibliothek kann selbst entscheiden,<br />

welche Komponente beim jeweiligen System gebraucht werden und muss ihr Verhalten<br />

selbst beschreiben, d.h. den Quellcode, für z.B. Erkennung, erstellen, dabei können<br />

beliebige Verfahren eingesetzt werden und nicht nur die, die in Kapiteln „Analyse“ und<br />

„Erkennung“ auf den Seiten 8 und 9 erwähnt wurden. Alle Spracherkennungssysteme<br />

sollen im Ordner „plugins“ erstellt werden (s. Ordnerstruktur auf der Seite 17). Für die<br />

bessere Übersicht wird es empfohlen, deren Bibliotheken getrennt von den Basis-<br />

Bibliotheken abzuspeichern. Es gibt schon ein fertiges Spracherkennungssystem <strong>mit</strong><br />

dem Namen „AsrHGH“ (Bibliothek „AsrHGH.so“ unter „data/plugins“), das <strong>mit</strong><br />

Hidden-Markov-Modellen arbeitet und HirschLib- Bibliotheken verwendet. Dieses<br />

System und seine Komponente werden im Kapitel 6 beschrieben.<br />

ASR hat eine kleine Abweichung vom in der Abbildung 1 auf der Seite 7 dargestellten<br />

Spracherkennungssystem. DC-Filter gehört nicht zu ASR- Komponenten, wie auf dem<br />

Bild gezeichnet, sondern wurde als ein Teil des Erweiterungsmoduls „AudioFX“ unter<br />

„plugins“ erstellt, dessen Bibliothek „AudioFX.so“ schon nach dem Start vom<br />

Dialogsystem bei Bedarf geladen werden kann.<br />

Seite 24


5.4. Plugins<br />

Die Erweiterungsmodule, oder Plugins, unterscheiden sich von Basis- Bibliotheken,<br />

indem sie zum beliebigen Zeitpunkt während der gesamten Laufzeit der jeweiligen<br />

Applikation vom beliebigen Ort geladen werden können. Gewünschte Plugins können,<br />

und in bestimmten Fällen sogar müssen, bei dem Start von Applikation als Parameter<br />

übergeben werden. Wie man solche Applikationen startet, kann man am Beispiel vom<br />

multimodalen Dialogsystem „GuiApp“ sehen:<br />

./GuiApp -s StatFile -l ASRActions -l PluginGUI [-d Path] [-l PluginN]<br />

StatFile Konfigurationsdatei für Dialogsystem<br />

ASRActions Plugin <strong>mit</strong> Actionen des gewünschten Spracherkennungssystems<br />

PluginGUI Plugin für graphische Oberfläche<br />

PluginN Beliebige weitere Plugins, optional<br />

Path Der optionale Pfad zum StatFile ('../../data' bei default)<br />

Bei dem sprachbasierten Dialogsystem „DialogHGH“ wird der Parameter „PluginGUI“<br />

ausgelassen. Alle zu ladende Plugins werden unter <strong>mit</strong> dem Parameter „Path“ gesetztem<br />

Pfad (einschließlich Pfad/plugins und Pfad/lib) gesucht. Unter diesem Pfad sollten sich<br />

neben der Konfigurationsdatei alle verwendeten Daten befinden (Listfiles, Audiodateien<br />

u.s.w.), da<strong>mit</strong> die Konfiguration leichter und übersichtlicher wird. Der Pfad ist<br />

standardmäßig auf „../../data/“ von der Sicht des Stammordners der Applikation (z.B.<br />

build.Codeblocks/bin, Ordnerstruktur auf der Seite 17) gesetzt. Falls die Plugins unter<br />

dem Pfad nicht gefunden werden, wird dieser Stammordner (einschließlich ./plugins<br />

und ./lib, falls vorhanden) durchsucht. Dies ermöglicht eine beliebige Strukturierung<br />

von Dialogsystemen und eine trotzdem automatische Zuweisung von allen benötigten<br />

Plugins. Beim Laden von Plugins reicht es, nur deren Namen <strong>mit</strong> Suffix anzugeben.<br />

Falls ein Plugin aus einem von oben angegebenen Pfaden abweichenden Ordner geladen<br />

werden soll, muss dessen Name <strong>mit</strong> dem vollständigen Pfad angegeben werden. In der<br />

Plugin- Übersicht auf der nächsten Seite kann man alle schon verfügbare Plugins<br />

kennenlernen. Diese Übersicht zeigt auch, welche von Diesen automatisch geladen<br />

werden und welche man explizit bei dem Aufruf von Applikation laden muss.<br />

Die im vorherigen Kapitel vorgestellte Basis- Bibliotheken werden noch während der<br />

Kompilation in die Applikation eingebunden und dürfen nicht aus dem Ordner<br />

verschoben werden, in dem diese erstellt wurden (s. Ordnerstruktur auf der Seite 17<br />

Seite 25


oder Variable „OUTPUT_LIBDIR“ in der CMake- Konfigurationsdatei). Diese<br />

Bibliotheken sollen also noch vor dem Start des Dialogsystems aus dem bekannten<br />

Ordner geladen werden können. Um Basis- Bibliotheken etwas Dynamik zu verleihen,<br />

kann man <strong>mit</strong> „link_directories()“ in „CMakeLists.txt“ einen (lieber zum Stammordner<br />

der Applikation relativen) Pfad zum einen zusätzlichen Verzeichnis setzen, aus dem die<br />

Bibliotheken alternativ zu laden sind. Plugins können dagegen nach der Kompilation,<br />

wie oben beschrieben wurde, beliebig verschoben werden. Im Gegensatz zu Basis-<br />

Bibliotheken hat jedes Plugin natürlich seinen eigenen Namensraum.<br />

Actions (Actions.so) sind alle von asrlib::Action abgeleitete Klassen, die man zum<br />

einen Zustandsautomaten <strong>mit</strong> einem breiten Funktionsumfang zusammenstellen kann<br />

und die jeweils eine ACTION von StateMachine darstellen, z.B. „AudioOut“ in der<br />

Abbildung 15. Namensraum: „asrActions“. *<br />

ActionsHGH (ActionsHGH.so) ist eine Ergänzung von Actions um die Klassen zur<br />

Initialisierung und Steuerung vom Spracherkennungssystem namens AsrHGH (s.u.),<br />

z.B. Klasse „Recognize“, Abbildung 15. Namensraum: „actionsHGH“.<br />

AsrHGH (AsrHGH.so) ist ein Spracherkennungssystem, das <strong>mit</strong> HMM's arbeitet und<br />

HirschLib- Bibliotheken verwendet. Namensraum: „asrHGH“. *<br />

AudioFX (AudioFX.so) stellt verschiedene Tools zum Bearbeiten von Audiosignalen<br />

zur Verfügung, Zum Beispiel den schon früher erwähnten DC-Filter zur Beseitigung<br />

von DC-Offset. Weitere Filter können hier erstellt werden. Namensraum: „audioFX“. *<br />

GuiFKA (GuiFKA.so) stellt eine graphische Oberfläche zur Steuerung von einem<br />

Fahrkartenautomaten dar. Soll zusammen <strong>mit</strong> einem Spracherkennungssystem- Plugin<br />

aufgerufen werden. Namensraum: „guiFKA“.<br />

GuiPHA (GuiPHA.so) ist ein weiteres Plugin für graphische Oberfläche. Da<strong>mit</strong> kann<br />

man einen Parkhausautomaten steuern. Namensraum: „guiPHA“.<br />

* diese Plugins werden automatisch geladen und müssen nicht extra als Parameter bei dem Start der<br />

jeweiligen Applikation übergeben werden. Voraussetzung dafür ist, dass die Plugins sich unter einem der<br />

auf der vorherigen Seite beschriebenen Pfaden befinden. Falls das nicht der Fall ist, müssen auch diese<br />

Plugins explizit vom Benutzer geladen werden.<br />

Seite 26


5.5. Wichtiges<br />

In diesem Kapitel werden noch einige wichtige Bemerkungen als Abschluss der<br />

Beschreibung von asrLib gemacht, die für die beiden weiteren Kapitel nützlich sein<br />

können. Die Tabelle auf der nächsten Seite gibt einen Überblick über die Actionen aus<br />

dem Plugin „Actions“, die man bei den Konfigurationsdateien für Dialogsysteme<br />

verwenden kann. Wie schon oben erwähnt wurde, muss für jedes Dialogsystem<br />

mindestens eine solche Datei vorhanden sein. Man kann aber auch mehrere<br />

Konfigurationsdateien erstellen und zwischen diesen <strong>mit</strong> „LOAD_STAT“ (s. Tabelle)<br />

wechseln während das System läuft. Dabei werden die meisten Daten gelöscht, die bei<br />

der Verarbeitung der aktuellen Konfigurationsdatei erstellt und im Speicher von<br />

StateMachine abgelegt wurden. Deshalb gibt es bei diesem Projekt zwei Möglichkeiten,<br />

die Daten währen der Laufzeit <strong>einer</strong> Applikation abzuspeichern.<br />

Die erste Möglichkeit ist ein „lokaler“ Speicher, der die Daten nur für die aktuelle<br />

Konfigurationsdatei behält. Wird eine andere '*.stat'- Datei geladen, wird ein neues<br />

Zustandsautomat erzeugt und StateMachine vernichtet die ganze Information in diesem<br />

Speicher. Die Klasse „Buffer“ aus der „StateMachine“- Bibliothek ist für diesen<br />

Speicher zuständig, der im Weiteren einfach als Buffer bezeichnet wird. Buffer ist an<br />

StateMachine gebunden und kann Daten von allen möglichen Typen abspeichern. In<br />

<strong>einer</strong> Konfigurationsdatei sind diese Daten meistens vom Typ „std::string“ und<br />

„std::vector“ (oder auch 2- und 3- dimensionale Vektoren). Dabei müssen<br />

die Namen vom Buffer <strong>mit</strong> „B_“ anfangen, z.B. „B_syntax“ für ein Buffer, in dem der<br />

Pfad zu <strong>einer</strong> Syntax- Datei gespeichert ist. Bei Verwendung des Buffers im Quellcode<br />

werden diese Regeln nicht beachtet, man kann beliebige Namen und Typen nehmen.<br />

Die zweite Art des Speichers wird vorwiegend im Quellcode benutzt und kann bei<br />

jedem Objekt angewendet werden, der von der Klasse „Base“ abgeleitet wurde. In der<br />

Konfigurationsdatei wird dieser Speicher zum Anlegen vom Spracherkennungssystem<br />

verwendet, dessen Name <strong>mit</strong> „M_“ anfangen soll, um sich vom Buffer zu<br />

unterscheiden. Wird eine andere Konfigurationsdatei geladen, bleibt dieses System<br />

erhalten. Der Speicher muss extra gelöscht werden, wenn der nicht mehr benutzt wird.<br />

stateMachine->addBuffer(new Buffer("B_Name", "Wert"));<br />

stateMachine->addProperty("M_Name", "Wert");<br />

stateMachine->rmProperty("M_Name");<br />

Seite 27


Diese Tabelle enthält alle Namen der zur Zeit der Erstellung dieser Masterarbeit<br />

vorhandenen Klassen aus dem Plugin „Actions“ und deren kurze Beschreibung.<br />

Name der Action (Klasse)<br />

AUDIO_OUT (AudioOut)<br />

CALCULATOR (Calculator)<br />

COMPARATOR (Comparator)<br />

CONVERT_TO_DATE (ConvertToDate)<br />

CONVERT_TO_MONEY<br />

(ConvertToMoney)<br />

CONVERT_TO_TIME (ConvertToTime)<br />

EXIT (Exit)<br />

INIT (Init)<br />

ISDN_EXIT (ISDNExit)<br />

ISDN_INIT (ISDNInit)<br />

LOAD_COUNTER (LoadCounter)<br />

LOAD_STAT (LoadStat)<br />

PRINT_BUFFER (PrintBuffer)<br />

RANDOM_NUMBER (RandomNumber)<br />

READ_FROM_FILE (ReadFromFile)<br />

READ_LIST_FILE (ReadListFile)<br />

REPLACE (Replace)<br />

SAVE_COUNTER (SaveCounter)<br />

SEARCH_IN_BUFFER (SearchInBuffer)<br />

SEARCH_IN_FILE (SearchInFile)<br />

SET_TRANS_STATE (SetTransState)<br />

TEST (Test)<br />

TRANSCEIVER (Transceiver)<br />

WAIT (Wait)<br />

WAIT_FOR_CALL (WaitForCall)<br />

WRITE_TO_BUFFER (WriteToBuffer)<br />

Funktion<br />

Gibt Audiodateien über die Lautsprecher oder über<br />

ISDN wieder<br />

Ein Taschenrechner <strong>mit</strong> Grundrechenarten<br />

Vergleicht zwei Werte (Text, Zahlen, Datum)<br />

Konvertiert den gegebenen Wert in Format<br />

"tt.mm.jjjj"<br />

Konvertiert den gegebenen Wert in Vector im<br />

Format "[x euro y cent]"<br />

Konvertiert den gegebenen Wert in Vector im<br />

Format "[ss hour mm minutes]"<br />

Löscht das gesamte Buffer und stoppt Applikation<br />

Legt Parameter für Audioausgabe über die<br />

Soundkarte fest<br />

Entfernt alle Konfigurationen für<br />

Sprachübertragung <strong>mit</strong>tels ISDN<br />

Legt Parameter für die Sprachübertragung <strong>mit</strong>tels<br />

ISDN fest<br />

Liest eine Zahl aus der gegebenen Textdatei und<br />

speichert diese intern als „SM_COUNTER“<br />

Liest eine Konfigurationsdatei ('*.stat') und<br />

verarbeitet deren Actionen<br />

Gibt den Inhalt eines Buffers auf der Konsole aus<br />

Zufallszahlengenerator<br />

Liest die Daten aus <strong>einer</strong> Textdatei ein<br />

Liest ein Listenfile komplett oder zeilenweise ein<br />

Ersetzt oder löscht Inhalte eines Buffers<br />

Schreibt eine Zahl in gegebene Textdatei<br />

Durchsucht Buffer nach bestimmten Inhalten<br />

Durchsucht eine Textdatei nach bestimmten<br />

Inhalten<br />

Gibt den Inhalt eines Buffers zurück<br />

Testet das aktuelle stat-File auf Vollständigkeit<br />

Schnittstelle für Kommunikation <strong>mit</strong> GUI<br />

Wartet eine (un-)bestimmte Zeit auf ein Ereignis<br />

Wartet eine bestimmte Zeit auf einen Anruf<br />

Erstellt, kopiert, belegt und bearbeitet ein Buffer<br />

Seite 28


Noch eine Klasse aus der Base- Bibliothek, deren Funktionen bei dieser Arbeit noch<br />

erwähnt werden, ist „CallbackInterface“. Mit dieser Klasse kann eine Kommunikation<br />

in der Form von Ereignissen, oder Events, zwischen beliebigen Klassen im asrLib-<br />

Projekt realisiert werden. Die drei wichtigsten Funktionen, die dabei verwendet werden,<br />

sind „registerCallback()“, „executeCallback()“ und „removeCallbak()“ aus<br />

„include/Base/Callbak.h“. Wie der Name der ersten Funktion schon vermuten lässt,<br />

kann man diese <strong>mit</strong> der Registrierung eines Postfaches vergleichen. Mit der zweiten<br />

Funktion wird das Senden <strong>einer</strong> Nachricht durchgeführt. Im Weiteren wird so eine<br />

Nachricht einfach als Callback bezeichnet, weil diese in Wirklichkeit ein Objekt der<br />

Klasse „Callback“ ist. Um den belegten Speicher wieder freizugeben, kann man das<br />

registrierte Callback <strong>mit</strong> der dritten Funktion löschen. Die Klasse „CallbackInterface“<br />

wurde als Basisklasse für die Klasse „Base“ genommen, deshalb kann man Callbacks<br />

direkt bei Objekten der von „Base“ abgeleiteten Klassen registrieren. In diesem Fall<br />

sind die registrierten „Postfächer“ nur für diese Objekte zu sehen. Man kann die<br />

Callbacks auch so registrieren, dass die aus allen Klassen gesendet werden können.<br />

Dafür ist die Klasse „CallbackBuffer“ <strong>mit</strong> ihrem globalen Singleton- Objekt gedacht.<br />

1. Callback* cb =<br />

new Callback(_adaption, &AdaptionHGH::adaptionCallback);<br />

2. _recognizer->registerCallback("adaptionCallback", cb);<br />

3. _recognizer->executeCallback("adaptionCallback", (void*)(data));<br />

4. _recognizer->removeCallback("adaptionCallback", _adaption);<br />

In diesem Beispiel- Quellcode wird, als Erstes, ein Callback erstellt. Sein Konstruktor<br />

soll zwei Parameter erhalten: ein Objekt, an den das Callback später gesendet werden<br />

soll, und eine Referenz auf die Funktion von diesem Objekt, die dabei aufgerufen<br />

werden soll. Als Zweites wird dieses Callback <strong>mit</strong> einem speziellen Namen registriert.<br />

Man kann Callback bei einem Objekt registrieren, dann wird es an dieses Objekt<br />

gebunden, wie in diesem Beispiel. Da<strong>mit</strong> Callback nicht objektbezogen ist, soll man<br />

„CallbackBuffer->getInstance()“ statt „_recognizer“ verwenden. Das gilt auch für die<br />

letzten zwei Zeilen. Als Drittes wird Callback gesendet. Die zu sendenden Daten<br />

werden (standardmäßig implizit) zum Typ „Zeiger auf void“ konvertiert. Man kann<br />

mehrere Callbacks <strong>mit</strong> gleichen Namen registrieren, die an unterschiedliche Objekte<br />

gesendet werden. So kann man <strong>mit</strong> <strong>einer</strong> einzigen Nachricht mehrere Funktionen<br />

aufrufen. Deshalb soll man beim Löschen vom Callback auch den Empfänger eingeben.<br />

Seite 29


6. Parallele Erkennung<br />

Wie es schon in der Aufgabenstellung geschrieben wurde, sollen mehrere Erkenner bei<br />

<strong>einer</strong> Spracherkennung gleichzeitig eingesetzt werden, um eine schnellere und, was sehr<br />

wichtig ist, bessere Erkennung gewährleisten zu können. In diesem Kapitel geht es um<br />

die Realisierung von dieser Methode, um die dabei aufgetretene Schwierigkeiten und<br />

um die Problematik der Implementierung. Der Einblick in den Quellcode wird von <strong>einer</strong><br />

Erläuterung gefolgt, wieso es gerade so gemacht wurde und nicht anders.<br />

Bevor wir uns <strong>mit</strong> der parallelen Erkennung beschäftigen, soll die Funktionsweise eines<br />

Spracherkennungssystems dargestellt werden. Das einzig zum Zeitpunkt der Erstellung<br />

dieser Dokumentation vorhandene Spracherkennungssystem ist „AsrHGH“. Das in<br />

vorherigen Kapiteln schon erwähnte Plugin wird hier etwas ausführlicher beschrieben.<br />

Bis jetzt wurde das Plugin „AsrHGH“ einfachheitshalber als Spracherkennungssystem<br />

bezeichnet. In Wirklichkeit ist das nur ein Erweiterungsmodul oder eine Bibliothek, in<br />

der das in der Abbildung 1 auf der Seite 7 dargestellte Spracherkennungssystem<br />

enthalten ist. Dieses System und alle seine Komponenten haben ihre eigenen Namen,<br />

<strong>mit</strong> denen diese auch angesprochen werden können. So hat die Komponente, die für die<br />

Analyse des Eingangssignals zuständig ist, den Namen „AnalyzerHGH“ und der<br />

Erkenner heißt „RecognizerHGH“. Diese Komponenten und alle Bestandteile von<br />

anderen Plugins sind Objekte bestimmter Klassen, anhand denen die Namen vergeben<br />

wurden. Dateien <strong>mit</strong> Quellcode und zugehörige Headerfiles befinden sich bei Plugins in<br />

einem Ordner (Diese von Grundbibliotheken wurden in „src“ und „include“ aufgeteilt,<br />

s. Ordnerstruktur auf der Seite 17). Eine Zusammenfassung des Plugins „AsrHGH“ im<br />

Zusammenhang <strong>mit</strong> der Abbildung 1:<br />

asrLib<br />

| --- plugins // Ordner <strong>mit</strong> Quellcode für alle Erweiterungsmodule<br />

| --- AsrHGH // Plugin für Spracherkennungssystem<br />

| --- AdaptionHGH // Adaption<br />

| --- AnalyzerHGH // Analyse<br />

| --- ApplicationHGH // Spracherkennungssystem<br />

| --- NoiseEstimator // Sprachdetektion<br />

| --- RecognizerHGH // Erkennung<br />

Seite 30


Um das Spracherkennungssystem benutzen zu können, muss sein Objekt erstellt<br />

werden. Das soll in <strong>einer</strong> der Actionen von StateMachine passieren. Wie auf den Seiten<br />

22 und 23 bei der Beschreibung von StateMachine geschrieben wurde, sind Actionen<br />

die von asrlib::Action abgeleitete Klassen. Man kann beliebig viel solcher Klassen, z.B.<br />

für Audioausgabe (wie „AudioOut“) oder für den Start eines Spracherkennungssystems<br />

(wie „Recognize“), erstellen und als Plugins der asrLib- Sammlung beifügen. So wurde<br />

die schon mehrmals erwähnte Klasse „Recognize“ zusammen <strong>mit</strong> anderen Klassen für<br />

die Steuerung des Spracherkennungssystems „ApplicationHGH“ (Plugin „AsrHGH“)<br />

unter dem separaten Plugin <strong>mit</strong> dem Namen „ActionsHGH“ erstellt:<br />

asrLib<br />

| --- plugins // Ordner <strong>mit</strong> Quellcode für alle Erweiterungsmodule<br />

| --- ActionsHGH // Plugin für die Steuerung von „AsrHGH“<br />

| --- Recognize // Hier findet die gesamte Spracherkennung statt<br />

| --- RecognizeExit // Löscht das Spracherkennungssystem<br />

| --- RecognizeInit // Erstellt und Initialisiert das Spracherkennungssystem<br />

| --- RecognizeTest // Hier wird die Spracherkennung getestet<br />

Der gesamte Quelltext von diesem Plugin befindet sich in den Headerfiles. Alle diese<br />

Klassen und die Klassen aus dem Plugin „Actionen“ (s. Plugin- Übersicht auf der Seite<br />

26) werden in den Konfigurationsdateien für Dialogsysteme jeweils als eine ACTION<br />

dargestellt. Wie man im Beispiel für so eine Konfigurationsdatei auf der Seite 22 sehen<br />

kann, haben diese Actionen andere Namen als die zugehörige Klassen. Das wurde extra<br />

für eine bessere Darstellung gemacht. Die Übersetzung der Namen ist in <strong>einer</strong> einzelnen<br />

bei dieser Art von Plugins Quellcodedatei „export.cpp“ <strong>mit</strong> POCO festgelegt, z.B.:<br />

POCO_EXPORT_NAMED_CLASS(Recognize,RECOGNIZE)<br />

Das Spracherkennungssystem „ApplicationHGH“ wird in der Klasse „RecognizeInit“<br />

erstellt. Zuerst sollte aber seine Bibliothek geladen werden, falls noch nicht geschehen.<br />

Die beiden Schritte sind in einem Auszug aus „RecognizeInit.h“ zu sehen:<br />

PluginFactory::get()->loadLibrary("AsrHGH.so");<br />

// vereinfacht, s. Quelltext<br />

ASRApplication* asrApp =<br />

dynamic_cast(PluginFactory::get()->create("ApplicationHGH"));<br />

Seite 31


„PluginFactory“ ist eine Singleton- Klasse aus der Base- Bibliothek, die für das<br />

Einbinden von Bibliotheken <strong>mit</strong> Hilfe von POCO zuständig ist. „ASRApplication“ aus<br />

der ASR- Bibliothek ist die Basisklasse für alle Spracherkennungssysteme. Wie der<br />

Name „RecognizeInit“ schon sagt, wird das Spracherkennungssystem hier nicht nur<br />

erstellt, sondern auch konfiguriert. Die gewünschte Konfiguration kann man in der<br />

Konfigurationsdatei vornehmen (vgl. diese auf der Seite 22):<br />

<br />

<br />

RECOGNIZE_INIT<br />

[SM_DC_BLOCK] [] [] [] [] <br />

<br />

Anzahl der folgenden Komponenten<br />

<br />

Listenfile <strong>mit</strong> allen zu verwendeten HMM- Mustern<br />

SM_DC_BLOCK DC-Filter einschalten, optional(AudioFX.so wird geladen)<br />

<br />

Erkennung <strong>mit</strong> zusätzlichen Funktionen durchführen, optional<br />

SM_ADAPTION Erkennung <strong>mit</strong> Adaption der Muster<br />

SM_ROBUST Robuste Erkennung<br />

<br />

SampleRate in Hz (8000, 11000, 16000), optional<br />

<br />

SampleDataFormat (INT16, INT32, FLOAT32), optional<br />

<br />

VectorType (MFCC_E, MFCC_E_0, MFCC_E_D_A, MFCC_E_D_A_0) opt<br />

<br />

Der Name des zu erstellenden Spracherkenners<br />

Mit dem Spracherkennungssystem können die direkt vom Mikrophon aufgenommene<br />

Daten, die Daten über eine Telefonleitung oder sogar Audiodateien verarbeitet werden.<br />

Im letzten Fall soll man die zu erkennende Audiodatei an „Recognize“ übergeben.<br />

<br />

<br />

RECOGNIZE<br />

[] [] [] []<br />

<br />

<br />

SM_GUI<br />

SM_BUTTON<br />

SM_KEY<br />

<br />

<br />

<br />

<br />

<br />

Anzahl der folgenden Komponenten<br />

Erkennung darf extern unterbrochen werden, optional<br />

Unterbrechung durch GUI<br />

Unterbrechung durch den Knopf am Infokasten<br />

Unterbrechung durch eine Taste auf der PC-Tastatur<br />

Syntaxfile<br />

Die zu erkennende Audiodatei, optional<br />

Der Name des zu verwendeten Spracherkenners<br />

Weitere Spracherkenner zum parallelen Betrieb, optional<br />

Ein Speicher für die Erkennungsergebnisse, optional<br />

Für die Spracherkennung <strong>mit</strong>tels ISDN soll die ISDN-Schnittstelle <strong>mit</strong> ISDN_INIT aus<br />

dem Plugin „Actions“ noch vor RECOGNIZE_INIT konfiguriert werden. Während der<br />

Seite 32


Erkennungsphase wird es automatisch erkannt, um welche Quelle es sich handelt. Bei<br />

der Erkennung von <strong>einer</strong> Audiodatei wird diese gleichzeitig über die Lautsprecher oder<br />

Telefonhörer wiedergegeben. Diese Funktion kann man nur durch die Änderung im<br />

Quellcode abschalten. Falls man die Erkennung von mehreren Audiodateien einfach<br />

testen möchte, könnte es sehr lange dauern, bis alle Dateien abgespielt werden. In<br />

diesem Fall greift man zur einen abgekürzten Version von „Recognize“, die<br />

„RecognizeTest“ heißt und die nur Audiodateien bearbeiten kann und keine Verbindung<br />

zur ISDN- oder Sound- Karte hat. „RecognizeTest“ unterstützt parallele Erkennung<br />

nicht und kann nicht extern (z.B. durch GUI) unterbrochen werden.<br />

<br />

<br />

RECOGNIZE_TEST<br />

[]<br />

<br />

<br />

<br />

<br />

<br />

Anzahl der folgenden Komponenten<br />

Syntaxfile<br />

Die zu erkennende Audiodatei<br />

Der Name des zu verwendeten Spracherkenners<br />

Ein Speicher für die Erkennungsergebnisse, optional<br />

Ein Spracherkennungssystem kann nur einmal bei der Erstellung konfiguriert werden,<br />

deshalb passiert nichts, wenn man es nochmals an „RecognizeInit“ übergibt. Um es<br />

umkonfigurieren zu können, muss man es <strong>mit</strong> „RecognizeExit“ komplett löschen.<br />

<br />

<br />

<br />

RECOGNIZE_EXIT<br />

1 <br />

Der Name des Spracherkenners, der gelöscht werden soll<br />

Das war eine kurze Einführung in die Möglichkeiten eines Spracherkennungssystems,<br />

da<strong>mit</strong> man eine Vorstellung hat, wie ein Spracherkenner erstellt und betrieben werden<br />

kann. Für etwas ausführlichere Beschreibung von beiden Plugins „ActionsHGH“ und<br />

„Actions“ soll man Dokumentation des Projektes (s. Ordnerstruktur auf der Seite 17)<br />

lesen und/oder den Quellcode ansehen. Im Weiteren wird nur noch der Quellcode von<br />

„Recognize“ und „RecognizeInit“ im Zusammenhang <strong>mit</strong> der Darstellung der<br />

Funktionsweise eines Spracherkennungssystems betrachtet.<br />

Wir wissen jetzt, wie man ein Spracherkennungssystem <strong>mit</strong> <strong>einer</strong> Konfigurationsdatei<br />

steuern kann. Nun kommen wir zu s<strong>einer</strong> Beschreibung zurück und werden uns den<br />

inneren Ablauf anschauen.<br />

Seite 33


Da „ASRApplication“ die Basisklasse für alle mögliche Spracherkennungssysteme im<br />

asrLib- Projekt ist, werden auch die grundlegende Funktionen und Zwischenschritte, die<br />

für diese Systeme gleich sind, hier ausgeführt. In dem Quellcode des von dieser Klasse<br />

abgeleiteten Spracherkennungssystems wird nur seine spezifische Bearbeitungsweise<br />

von Daten beschrieben, wie z.B. die Erkennung von Mustern selbst. Bei jedem System<br />

muss ein Objekt erstellt werden, der für alle Audioeinstellungen zuständig ist und den<br />

Austausch von Audiodaten zwischen einzelnen Schnittstellen kontrolliert, sozusagen ein<br />

Manager (weiter einfach AudioManager). Dieser wird in „RecognizeInit“ bzw. in<br />

„ISDNInit“, wenn die Datenübertragung über ISDN geführt werden soll, erstellt und<br />

speichert alle dabei erhaltene Einstellungen, so wie Datenformat, Frequenz und die<br />

Länge eines Signalabschnittes (s. Kapitel 3.2.2), oder, um genauer zu sein, die Anzahl<br />

der Abtastwerte in einem Abschnitt. Bei einem 8 kHz Signal und <strong>einer</strong> Länge von 25<br />

ms sind das 200 Werte, bei 11 kHz und 16 kHz sind das entsprechend 275 und 400<br />

Abtastwerte. In der Zeit der Erstellung dieser Masterarbeit werden nur die drei<br />

Frequenzbereiche vom asrLib- Projekt unterstützt. Falls AudioManager bei der ISDN-<br />

Initialisierung erstellt wurde, wird dieser vom Spracherkennungssystem während s<strong>einer</strong><br />

Konfigurierung übernommen. Das Ganze funktioniert folgendermaßen:<br />

Allen selbständigen Komponenten eines Dialogsystems bei asrLib (sei das ein Objekt,<br />

das eine Audiodatei darstellt, eine Schnittstelle für die Audioausgabe über die<br />

Lautsprecher oder ein Spracherkenner) muss derselbe AudioManager zugewiesen<br />

werden, um eine reibungslose Kommunikation und eine richtige Ein- bzw. Ausgabe von<br />

Daten realisieren zu können. Eine Übergabe vom Audiomanager könnte so aussehen:<br />

audioDatei->setAudioManager(asrApp->getAudioManager());<br />

Bei ISDN_INIT wird eine Schnittstelle namens „ISDN_FRONTEND“ für die<br />

Datenübertragung <strong>mit</strong>tels ISDN <strong>mit</strong> eigenem AudioManager erstellt und als Property<br />

(ein „globaler“ Speicher, s. Kapitel 5.5) bei StateMachine gespeichert. Jedes Mal, wenn<br />

Audiodaten ausgegeben oder aufgenommen werden sollen, wird es auf das<br />

Vorhandensein dieser Schnittstelle geprüft. Bei Erfolg wird Diese verwendet und ihr<br />

AudioManager wird übernommen. (sm = StateMachine, a = AudioManager,<br />

getProperty() = gib Speicher, get() = gib Objekt in diesem Speicher)<br />

if (sm->getProperty("ISDN_FRONTEND"))<br />

a = sm->getProperty("ISDN_FRONTEND")->get()->getAudioManager();<br />

Seite 34


Hier könnte man auf die Idee kommen, AudioManager im asrLib- Projekt global zu<br />

deklarieren, da<strong>mit</strong> alle Komponente ihn bei Bedarf verwenden können, ohne diese<br />

aufwendige Übergabe machen zu müssen. Es kann aber vorkommen, dass man die<br />

Daten über Mikrofon <strong>mit</strong> 8 kHz aufnehmen möchte, dabei aber nur die 16 kHz- Daten<br />

für die Ausgabe über die Lautsprecher zur Verfügung hat. In diesem Fall müssen<br />

mindestens zwei AudioManager erstellt werden, einen für die Aufnahme und einen für<br />

die Ausgabe. Bei dem Start von jedem Dialogsystem wird StateMachine erstellt und<br />

initialisiert, dabei wird eine Schnittstelle für die Audioausgabe über die Lautsprecher in<br />

der Action „INIT“ (ist in der Funktion „StateMachine::initialize()“ festgelegt, unter<br />

„src/StateMachine/StateMachine.cpp“, muss also in der Konfigurationsdatei nicht extra<br />

angegeben werden) als Property „SOUNDCARD_FRONTEND“ an StateMachine<br />

automatisch angehängt. AudioManager bekommt hier Standardeinstellungen (Frequenz<br />

8000, Datentyp INT16, Abtastwerte 200). Man kann diese Einstellungen jede Zeit in der<br />

Konfigurationsdatei <strong>mit</strong> „INIT“ ändern. „ISDN_FRONTEND“ hat eine höhere Priorität,<br />

„SOUNDCARD_FRONTEND“ wird erst dann für die Audioausgabe verwendet, wenn<br />

die erste Komponente nicht vorhanden ist.<br />

Es ist schon klar, dass man zwei unterschiedliche AudioManager für die Aufnahme und<br />

Audioausgabe verwenden soll. Ein weiteres Problem tritt auf, wenn man zwei, oder<br />

noch schlimmer, mehrere, Spracherkenner <strong>mit</strong> Modellen <strong>mit</strong> unterschiedlichen<br />

Frequenzen bei <strong>einer</strong> parallelen Erkennung einsetzen möchte. Die Lösung ist, bei jedem<br />

Spracherkennungssystem ein eigenes „SOUNDCARD_FRONTEND“ <strong>mit</strong> eigenem<br />

AudioManager zu erstellen. Dabei wird es als Property <strong>mit</strong> dem Namen<br />

„AudioFrontend“ (für die bessere Unterscheidung) dem Spracherkennungssystem<br />

zugewiesen und nicht mehr der StateMachine. Alle Bedingungen bleiben erhalten.<br />

Eine weitere wichtige Einstellung für AudioManager ist der Zeitintervall für einen<br />

iterativen „Wecker“, der wie folgt gesetzt wird („* 1000.0“ für Millisekunden):<br />

audioManager->setTimeStep((float)frameSize / (float)sampleRate * 1000.0);<br />

Bei einem Verhältnis der Anzahl der Abtastwerte zur Frequenz von 1 zu 40 (200/8000)<br />

wird die Funktion „ASRApplication::onTimer()“ unter „src/ASR/ASRApplication.cpp“<br />

alle 25 ms von diesem Wecker aufgerufen. Der Wecker wird zusammen <strong>mit</strong> dem<br />

Spracherkennungssystem gestartet und gestoppt. Das ist ein selbständiger Prozess, der<br />

<strong>mit</strong> Hilfe von POCO ausgeführt wird und parallel zum Dialogsystem läuft. Die von<br />

Seite 35


diesem Prozess angesprochene Funktion muss komplett abgearbeitet werden, bevor sie<br />

wieder vom Wecker aufgerufen werden kann, auch wenn die Bearbeitungszeit länger als<br />

25 ms dauert. Das muss man bei der Programmierung beachten. Diese Funktion führt<br />

eine andere, abstrakte Funktion „tick()“ aus, die in <strong>einer</strong> abgeleiteten Klasse (in diesem<br />

Fall „ApplicationHGH“) erstellt werden soll und den ganzen Prozess der<br />

Spracherkennung beschreiben soll.<br />

Aber bevor ein Spracherkenner startet, soll sein vorher initialisiertes „AudioFrontend“<br />

gestartet werden, bei dem ein zweites Wecker genauso als paralleler Prozess in<br />

Funktion tritt. Diesmal ist der Wecker die Soundkarte des Rechners, oder ihr Treiber,<br />

der von dem externen Tool „portaudio“ zur Verfügung gestellt wird. Jedes Mal, wenn<br />

der für die aufgenommenen Daten reservierte Platz voll ist, wird die Funktion<br />

„SoundcardFrontend::paCallbackFun()“ unter „src/Audio/SoundcardFrontend.cpp“ in<br />

Gang gesetzt. Bei 8 kHz und 200 Werten ist das ein Zeitintervall in genau 25 ms.<br />

Um besser zu verstehen, wozu diese zwei „Wecker“ benötigt werden, sollte man die<br />

Abbildung 16 ansehen. Die im AudioManager registrierte Komponente kann z.B. DC-<br />

Filter sein. In diesem Fall werden die aufgenommenen Daten von „SoundcardFrontend“<br />

direkt an „DCBlocker“ übergeben und dieser stellt dann die von ihm bearbeiteten Daten<br />

als Signalabschnitte zur Verfügung.<br />

alle 25 ms<br />

alle 25 ms<br />

SoundcardFrontend::<br />

paCallbackFun()<br />

ASRApplication::<br />

onTimer()<br />

ApplicationHGH::<br />

tick()<br />

Alle Komponenten des<br />

Spracherkennungssystems<br />

AnalyzerHGH::analyze()<br />

NoiseEstimator::estimate()<br />

[ AdaptionHGH::adapt() ]<br />

RecognizerHGH::process()<br />

0 1 2 3 4 5 6 ...<br />

Signalabschnitte<br />

Abtastwerte<br />

0 1 2 3 4 5<br />

Merkmalsvektoren<br />

AudioManager::<br />

tickCb()<br />

Für alle im AudioManager<br />

registrierten Komponenten:<br />

tick()<br />

BufferedIOAdaptor::tick()<br />

[ DCBlocker::tick() ]<br />

...<br />

Abbildung 16: Ablaufplan <strong>einer</strong> Spracherkennung<br />

Seite 36


Auch „AudioFile“ kann als eine weitere Komponente im AudioManager registriert<br />

werden. Dabei werden die Daten vom „SoundcardFrontend“ ignoriert und durch die<br />

Daten aus <strong>einer</strong> Audiodatei ersetzt, die weiter bearbeitet werden können. Man kann also<br />

beliebige Komponente, die von der Klasse „AudioComponent“ (libAudio.so) abgeleitet<br />

wurden, im AudioManager registrieren, indem man diese Komponenten einfach startet.<br />

audioFrontend->getInput(0)->setSource(audioFile->getOutput(0));<br />

audioFrontend->getInput(1)->setSource(audioFile->getOutput(0));<br />

audioFile->start();<br />

dcBlocker->getInput(0)->setSource(audioFile->getOutput(0));<br />

dcBlocker->start();<br />

asrApp->getInputAdaptor()->getInput(0)->setSource(dcBlocker->getOutput(0));<br />

audioFrontend->start();<br />

asrApp->start();<br />

In diesem Beispiel für einen möglichen Quellcode werden die Daten aus <strong>einer</strong><br />

Audiodatei (0 = links, mono) an „audioFrontend“, das sowie ein Objekt der Klasse<br />

„SoundcardFrontend“ als auch der Klasse „ISDNFrontend“ sein kann, zur Ausgabe über<br />

die Lautsprecher (1 = rechts) bzw. Telefonhörer übergeben. Diese Daten werden <strong>mit</strong><br />

dem DC- Filter bearbeitet. Von diesem Filter werden die Daten als Signalabschnitte an<br />

„BufferedIOAdaptor“ geleitet (die dritte Zeile von unten). Mit „audioFrontend“ und<br />

„asrApp“ wird der ganze Prozess gestartet, entsprechend rechte und linke Seiten der<br />

Abbildung 16. Mit diesem vereinfachten Quelltext kann man also eine Audiodatei<br />

wiedergeben und gleichzeitig <strong>mit</strong> dem Spracherkennungssystem erkennen lassen. Da<br />

diese Art der Datenübergabe bidirektional funktioniert, kann man eine Spracherkennung<br />

über Mikrofon während der Audiodatei- Wiedergabe durchführen.<br />

Die in der Abbildung 16 punktiert dargestellten Bereiche von Abtastwerten und<br />

Merkmalsvektoren sind gelöschte Werte. Die Abtastwerte werden in <strong>einer</strong> Reihe<br />

abgespeichert, so wie das ganze Signal aussehen würde. Analyzer nimmt jedes Mal die<br />

ersten 200 Werte (bei <strong>einer</strong> Abtastfrequenz von 8000 Hz) und verarbeitet diese, die<br />

ersten 80 davon werden dann gelöscht. So kommt es zu <strong>einer</strong> Überlappung (s.<br />

Abbildung 2 auf der Seite 8). Das wird <strong>mit</strong> dem folgenden Quellcode aus<br />

„plugins/AsrHGH/AnalyzerHGH.cpp“ realisiert:<br />

AudioFrame* frame1 = _inputBuffer->getFrame(200);<br />

AudioFrame* frame2 = _inputBuffer->removeFrame(80);<br />

Seite 37


„_inputBuffer“ ist ein Teil vom „BufferedIOAdaptor“ (wurde bei dem Start von asrApp<br />

zugewiesen), der zweite Teil ist „_outputBuffer“. Man kann also die Daten für die<br />

Ausgabe auch bei Analyzer angeben, soweit das sinnvoll ist. „frame1“ wird analysiert<br />

und „frame2“ wird <strong>mit</strong> seinen 80 Werten in einem Stack („_frameContainer“) für die<br />

weitere Verwendung abgelegt, z.B. für die Abspeicherung des aufgenommenen Signals<br />

als eine '*.wav' Datei. Gespeichert wird nicht das komplette Eingangssignal, sondern<br />

nur ein Teil davon, bei dem die Sprache erkannt wird, evtl. eine halbe Sekunde vor und<br />

nach der Sprache. Ein Auszug aus „plugins/ActionsHGH/Recognize.h“ (af = audioFile):<br />

af->open(MODE_WRITE, FMT_WAV, 1, asrApp->getAnalyzer()->getFrameContainer());<br />

af->save(asrApp->getRecognizer()->_speechBegin,<br />

asrApp->getRecognizer()->_speechEnd);<br />

Die Größe vom in der Abbildung 16 dargestellten Stack für die Merkmalsvektoren kann<br />

maximal 6 sein. Die im „ApplicationHGH“ eingesetzte Sprachdetektion, die immer<br />

angewendet wird, arbeitet zeitversetzt. Wenn diese Komponente das Vorhandensein der<br />

Sprache in einem Signalabschnitt erkennt, wird das erst nach 4 weiteren Abschnitten<br />

gemeldet (falls diese auch Sprache enthalten), um sicher zu sein, dass es keine Störung<br />

war. Da<strong>mit</strong> Recognizer keine wichtigen Daten verpasst, werden diese für kurze Zeit<br />

(~25ms * 5) aufbewahrt. Sobald Analyzer den sechsten Objekt <strong>mit</strong> Merkmalsvektoren<br />

in den Stack schiebt, wird das erste Objekt aus dem Stack vom Recognizer entfernt. Mit<br />

diesem Trick wurde eine Zeitversetzung realisiert.<br />

Die Abbildung 16 zeigt, wie der Datenaustausch zwischen einzelnen Komponenten<br />

funktioniert. Aber wie Analyzer die Daten an den Sprachdetektor übergibt, wurde nicht<br />

gezeichnet. Das passiert <strong>mit</strong> einem Callback (s. auf der Seite 29). Um nicht jeden Schritt<br />

<strong>mit</strong> Worten zu beschreiben, wird hier ein sehr vereinfachter Quelltext der Funktion<br />

„AnalyzerHGH::analyze()“ angegeben:<br />

AudioFrame* frame1 = _inputBuffer->getFrame(_frameSize); // 200 Werte nehmen<br />

anal_cep_frame(frame1, &_featurePar);<br />

// und analysieren<br />

// die gewonnene Merkmale aus _featurePar zu einem Objekt kopieren,<br />

// der im Stack für Recognizer abgespeichert werden soll<br />

FeatureVector v(_featurePar.vector, _featurePar.vector+_featurePar.vec_size);<br />

pushFeatures(v);<br />

// Objekt in den Stack schieben<br />

// die Merkmale <strong>mit</strong> Callback zum Sprachdetektor senden<br />

this->executeCallback("estimateCallback", &_featurePar);<br />

// "estimateCallback" ruft Funktion "NoiseEstimator::estimate()" auf<br />

Seite 38


In diesem Quelltext wurde zum ersten Mal eine Funktion, „anal_cep_frame()“, aus den<br />

HirschLib- Bibliotheken erwähnt, die eigentlich der Kern des Spracherkennungssystems<br />

„ApplicationHGH“ sind. In dieser Funktion werden die Daten komplett analysiert und<br />

die dabei gewonnene Merkmale werden im Objekt „_featurePar“ zurückgeliefert. Für<br />

die Analyse bei <strong>einer</strong> robusten Spracherkennung soll eine Funktion <strong>mit</strong> Reduzierung der<br />

Störgeräusche verwendet werden:<br />

anal_cep_nr_frame(frame1, &_featurePar, &_nest_hgh);<br />

Um die beiden Funktionen für die Merkmalsextraktion überhaupt verwenden zu<br />

können, muss Analyzer vorher initialisiert werden („AnalyzerHGH::initialize()“):<br />

anal_cep_init(_sampleRate, DELTA_HTK, &_featurePar);<br />

// bzw. für eine robuste Spracherkennung:<br />

estimate_noise_spec_init(&_nest_hgh, 256);<br />

anal_cep_nr_init(_sampleRate, DELTA_ETSI, &_featurePar);<br />

Der da<strong>mit</strong> reservierte Speicher wird später freigegeben („AnalyzerHGH::reset()“):<br />

anal_cep_exit(&_featurePar);<br />

// bzw. nach <strong>einer</strong> robusten Spracherkennung:<br />

estimate_noise_spec_exit(&_nest_hgh);<br />

anal_cep_nr_exit(&_featurePar);<br />

Um die rechtzeitige Initialisierung und um das abschließende Rücksetzen (einige<br />

Komponenten initialisieren sich nach dem Rücksetzen neu) muss man sich bei der<br />

Erstellung von Plugins nicht kümmern. Das alles wird automatisch in der Basisklasse<br />

für Spracherkennungssysteme erledigt. Die Initialisierung von allen Komponenten wird<br />

in „RECOGNIZE_INIT“ <strong>mit</strong> „asrApp->initialize()“ durchgeführt. Am Ende des<br />

gesamten Spracherkennungsprozesses, wenn keine Daten mehr benötigt werden, soll<br />

nur „asrApp“ zurückgesetzt werden. „src/ASR/ASRApplication.cpp“:<br />

ASRApplication::reset() {<br />

_inputAdaptor->resetBuffers();<br />

_analyzer->reset();<br />

_recognizer->reset();<br />

_noiseEstimator->reset();<br />

_adaption->reset();<br />

_audioManager->reset();<br />

}<br />

Seite 39


Auf der vorherigen Seite wurde schon geschrieben, was beim Rücksetzen des<br />

Spracherkennungssystems nach der Spracherkennung passiert, aber was dieses System<br />

während der Spracherkennung macht, wurde noch nicht erklärt. Das kann <strong>mit</strong> der<br />

folgenden Abbildung gemacht werden:<br />

Abbildung 17: Sequenzdiagramm Spracherkennungssystem<br />

Die iterative Funktion „ApplikationHGH::tick()“ wird alle 25 ms aufgerufen (vgl.<br />

Abbildung 16 auf der Seite 36). Nach der erfolgten Analyse <strong>mit</strong> der Sprachdetektion<br />

wird der Zustand des Sprachdetektors abgefragt. In Abhängigkeit davon, was Dieser in<br />

Seite 40


analysierten Daten erkannt hat, nimmt er einen von insgesamt sechs möglichen<br />

Zuständen an, die in s<strong>einer</strong> Basisklasse „VAD“ (Voice Activity Detector) unter<br />

„include/ASR/VAD.h“ deklariert und in folgender Tabelle aufgelistet sind:<br />

Nr Zustand Bedeutung Folgezustand<br />

1 VAD_NO_SPEECH Keine Sprache erkannt, pause 1, 2, 6<br />

2 VAD_SPEECH_BEGIN Anfang der Sprache erkannt, zeitversetzt 3, 5<br />

3 VAD_SPEECH Sprache erkannt 3, 4, 6<br />

4 VAD_SPEECH_END Ende der Sprache erkannt, pause 1<br />

5 VAD_SPEECH_SHORT Ende der Sprache zu früh erkannt, pause 1<br />

6 VAD_OTHER Andere Ergebnisse der Sprachdetektion 1<br />

Falls der Sprachdetektor sich im Zustand „VAD_OTHER“ befindet, weil z.B. entweder<br />

nur die Pause oder nur die Sprache seit zu langer Zeit erkannt wird, wird das gesamte<br />

Spracherkennungsprozess gestoppt, um nicht in der Schleife hängen zu bleiben. Welche<br />

Zeiten als „zu lang“ gelten sollen, ist im Konstruktor des Sprachdetektors unter<br />

„plugins/AsrHGH/NoiseEstimator.cpp“ festgelegt, z.B. 5 Sekunden für die Vorpause<br />

und 10 Sekunden für die Dauer der Sprache. Bei allen anderen Zuständen vom<br />

„NoiseEstimator“ wird Erkenner nach eventueller Adaption der Muster angesprochen.<br />

Da die Erkennung in Funktionen der HirschLib- Bibliothek durchgeführt wird, wie<br />

übrigens alle Prozesse von Spracherkennungssystemkomponenten, und in diesen<br />

Komponenten selbst nur die Vorbereitungen, Zwischenschritte und Auswertungen<br />

gemacht werden, wird es hier nicht auf die Details eingegangen. Die Beschreibung von<br />

diesen Funktionen wird bei dieser Arbeit ausgelassen. Um alle nötigen Zwischenschritte<br />

hier zu beschreiben oder um den Quelltext darzustellen, müssen mehrere Seiten in<br />

Anspruch genommen werden. Deshalb werden nur die Namen von verwendeten<br />

HirschLib- Funktionen <strong>mit</strong> kurzen Kommentaren im Weiteren angegeben. Wofür genau<br />

diese Funktionen gebraucht werden und was in diesen passiert, kann man unter<br />

„lib/HirschLib/sources/recog“ nachsehen. Im Quellcode vom Plugin „AsrHGH“ kann<br />

man sehen, welche Übergabeparameter dabei eingegeben werden, wie diese vorbereitet<br />

werden und wie die Rückgabeparameter ausgewertet werden.<br />

Genau wie Analyzer muss man auch andere Komponenten vor der Verwendung<br />

initialisieren. Bevor der Erkennungsprozess weiter beschrieben wird, wird eine kleine<br />

Übersicht dieser Maßnahmen gezeigt.<br />

Seite 41


Die Initialisierung des Sprachdetektors <strong>mit</strong> „NoiseEstimator::initialize()“:<br />

estimate_noise_init(_nest, _featurePar);<br />

Wenn Analyzer die Funktion „NoiseEstimator::estimate()“ aufruft:<br />

estimate_noise(_nest, _featurePar, _frameIndex);<br />

Die Rücksetzung nach jeder erfolgten Spracherkennung, „NoiseEstimator::reset()“:<br />

estimate_noise_exit(_nest);<br />

Dabei wurden folgende Parameter verwendet:<br />

_nest<br />

_featurePar<br />

_frameIndex<br />

Objekt, in dem die Ergebnisse der Sprachdetektion<br />

zurückgegeben werden, z.B. der Zustand<br />

Objekt <strong>mit</strong> Merkmalen, die Analyzer erzeugt<br />

Nummer des aktuellen Signalabschnittes<br />

Die Initialisierung der Adaption <strong>mit</strong> „AdaptionHGH::initialize()“ (Syntaxfile benötigt):<br />

adapt_hmm_all_init(&_adaptionData, _featurePar, _originalReferences, MSF);<br />

adapt_hmm_all_set(&_adaptionData, _originalReferences);<br />

adapt_hmm_all_reset(&_adaptionData, _syntax);<br />

„AdaptionHGH::adapt()“ beim Zustand „VAD_SPEECH_BEGIN“:<br />

adapt_copy_noise_spec(&_adaptionData, _nest);<br />

adapt_copy_pars(&_adaptionData, _originalReferences);<br />

adapt_hmm_all_doit(&_adaptionData, _originalReferences);<br />

_owner->getRecognizer()->setReferences(_adaptionData.ref_adapt);<br />

extract_c0_ref(_adaptionData.ref_adapt, _featurePar->ncep4);<br />

In der Funktion „AdaptionHGH::adapt()“ beim Zustand „VAD_SPEECH“ werden die<br />

Ableitungen des nullten Koeffizienten aus dem Vektor entfernt und der Koeffizient<br />

selbst wird auf das Ende des Vektors verschoben:<br />

C1 ... C12 C0 E ... ΔC12 ΔC0 ΔE ... ΔΔC12 ΔΔC0 ΔΔE ...<br />

C1 ... C12 E ... ΔC12 ΔE ... ΔΔC12 ΔΔE ...<br />

C0<br />

Seite 42


Als Weiteres wird Adresse des Vektors an die HirschLib- Funktion übergeben:<br />

adapt_hmm_all_buf_spec(&_adaptionData, &(fb->front())[0], MSF, _frameIndex);<br />

Bei einem Callback vom Recognizer, „AdaptionHGH::adaptionCallback()“:<br />

adapt_hmm_all_estim_h(&_adaptionData, _originalReferences, dataHGH->bestPath,<br />

_syntax, dataHGH->viterbiData, dataHGH->nvec);<br />

adapt_hmm_all_t60(&(dataHGH->results), _originalReferences, _syntax,<br />

&_adaptionData, _nest, dataHGH->nvec);<br />

Beim Entfernen des Objektes im Destruktor, „AdaptionHGH::reset()“:<br />

adapt_hmm_all_exit(&_adaptionData, MSF);<br />

Dabei wurden folgende Parameter verwendet:<br />

_adaptionData<br />

_featurePar<br />

_originalReferences<br />

MSF<br />

_syntax<br />

_nest<br />

fb->front()<br />

_frameIndex<br />

dataHGH<br />

Objekt, in dem die Ergebnisse der Adaption<br />

zurückgegeben werden, z.B. adaptierte Modelle<br />

Objekt <strong>mit</strong> Merkmalen, die Analyzer erzeugt<br />

die vom Recognizer eingelesene unbearbeitete Modelle<br />

MaxSpeechFrames, maximale Anzahl von Abschnitten <strong>mit</strong><br />

Sprache, 1000 bei 10 Sekunden (s. vorletzte Seite)<br />

geladene Daten aus der Syntax- Datei<br />

Objekt <strong>mit</strong> Ergebnissen der Sprachdetektion<br />

der erste Merkmalsvektor im Stack für Recognizer<br />

Nummer des aktuellen Signalabschnittes<br />

Daten vom Recognizer nach erfolgreicher Erkennung<br />

Die Initialisierung vom Recognizer läuft etwas anders ab. Dabei werden nur die HM-<br />

Modelle eingelesen. Erst bei dem Start vom Spracherkennungssystem wird auch die<br />

Syntax- Datei geladen (falls diese Datei schon beim letzten Start geladen wurde,<br />

passiert das nicht noch einmal, um etwas Zeit zu sparen).<br />

_references = load_hmm(Pfad);<br />

_syntax = load_syn(Pfad, _references);<br />

Erst jetzt kann auch die Adaption initialisiert werden. Im Kapitel 3.2.1 wurde<br />

geschrieben, dass Recognizer nur dann startet, wenn der Sprachdetektor die Sprache<br />

erkennt. In Wirklichkeit läuft „RecognizerHGH“, wie die anderen Komponenten, immer<br />

Seite 43


<strong>mit</strong>. Dabei rechnet er aber nichts aus und sorgt nur noch dafür, dass die Größe vom<br />

Stack <strong>mit</strong> Merkmalsvektoren nicht größer als 5 ist. Erst wenn der Sprachdetektor den<br />

Zustand „VAD_SPEECH_BEGIN“ annimmt, werden die Objekte für die Berechnungen<br />

vom Viterbi- Algorithmus und vom besten Pfad initialisiert (s. Kapitel 3.2.3).<br />

Funktion „RecognizerHGH::process()“ bei dem Zustand „VAD_SPEECH_BEGIN“:<br />

viterbi_syn_init(_viterbi, _references, _syntax);<br />

viterbi_syn_set(_viterbi, _references);<br />

viterbi_syn_reset(_viterbi, _references, _syntax);<br />

best_path_init(_bestPath, _references, _syntax, MaxSpeechFrames);<br />

Im Zustand „VAD_SPEECH“ wird der aufwendige Viterbi- Algorithmus <strong>mit</strong> der<br />

Bestimmung des besten Pfades für die Mustererkennung angewendet.<br />

viterbi_syn_calc(&(_fb.front())[0], _viterbi, _references, _syntax, 0,<br />

_speechFrame);<br />

best_path_copy(_bestPath, _references, _syntax, _viterbi, _speechFrame);<br />

Nachdem ein Merkmalsvektor aus dem Stack <strong>mit</strong> Viterbi- Funktion verarbeitet wurde,<br />

wird geprüft, welche Modelle in diesen Merkmalen erkannt wurden. Falls es sich um<br />

die Modelle für die Pause oder Störung handelt, wird der Pausen- Zähler inkrementiert.<br />

Andernfalls wird der zugehörige Signalabschnitt als ein Abschnitt <strong>mit</strong> der Sprache<br />

anerkannt. Wenn der Zähler für die „leere“ Abschnitte einen bestimmten, in der Syntax-<br />

Datei festgelegten Wert erreicht hat, nachdem mindestens ein Sprachabschnitt anerkannt<br />

wurde, wird es bei allen bisherigen Ergebnissen zusammen <strong>mit</strong> der folgenden Funktion<br />

geprüft, ob eine sinnvolle Erkennung doch vorhanden sein könnte:<br />

res_syn = backtrack_viterbi_score(bestref, fromframe, _viterbi, _bestPath,<br />

_syntax, _references, _speechFrame);<br />

Falls „res_syn“ mindestens ein Model enthält, das weder der Pause noch der Störung<br />

gehört, werden alle Ergebnisse <strong>mit</strong> der Funktion „RecognizerHGH::recognize()“<br />

ausgewertet und das gesamte Spracherkennungsprozess abgebrochen, weil das Ende der<br />

Sprache vom Recognizer erfolgreich erkannt wurde.<br />

Dasselbe passiert, wenn der Sprachdetektor als Erster das Ende der Sprache erkannt hat<br />

und in den Zustand „VAD_SPEECH_END“ gewechselt hat. Falls aber „res_syn“ kein<br />

für die Spracherkennung sinnvolles Model enthält oder sogar kein Signalabschnitt als<br />

Sprache vom Recognizer erkannt wurde, wird alles sofort zurückgesetzt:<br />

Seite 44


viterbi_syn_exit(_viterbi, _references, _syntax);<br />

best_path_exit(_bestPath, _syntax, MaxSpeechFrames);<br />

Diese Funktionen werden auch bei allen verbleibenden Zuständen aufgerufen.<br />

Es kann auch sein, dass das Spracherkennungssystem von Außerhalb gestoppt wurde,<br />

weil z.B. die zu erkennende Audiodatei zu Ende abgespielt wurde oder der<br />

Sprachdetektor in den Zustand „VAD_OTHER“ gewechselt hat, Recognizer aber einige<br />

sinnvolle Ergebnisse ausgerechnet hat und nicht geschafft hat, diese auszuwerten. Um<br />

die Verluste von Ergebnissen auszuschließen, wartet das System in dessen Funktion<br />

„ASRApplication::stop()“ bis Recognizer fertig ist, „RecognizerHGH::wait()“:<br />

void RecognizerHGH::wait()<br />

{<br />

if (!_speech_detected && _speechFrameDetected)<br />

recognize();<br />

}<br />

Mit „_speech_detected“ wird geprüft, ob das Ende der Sprache vielleicht doch richtig<br />

erkannt wurde und die Ergebnisse ausgewertet wurden, um die doppelte Auswertung zu<br />

vermeiden. Falls das nicht der Fall ist und in mindestens einem Signalabschnitt die<br />

Sprache erkannt wurde, wird die Funktion „RecognizerHGH::recognize()“ aufgerufen:<br />

_realResults = backtrack_viterbi_syn(bestref, fromframe, _viterbi, _syntax,<br />

_references, _speechFrame);<br />

best_path_calc(_bestPath, _references, _syntax, _viterbi, _speechFrame);<br />

Alle berechneten Objekte werden in „dataHGH“ gepackt und an Adaption als Callback<br />

gesendet (s. Seite 43, oben). „_realResults“ enthält alle Informationen über die<br />

erkannten Äußerungen und wird in der Funktion „RecognizerHGH::saveLabel(Pfad)“<br />

für das Abspeichern von Labeln verwendet.<br />

create_label(Pfad, &_realResults, _references, _syntax);<br />

Nachdem das Spracherkennungssystem „ApplicationHGH“ erfolgreich beendet aber<br />

noch nicht zurückgesetzt wurde, kann Funktion „RecognizerHGH::processResults()“<br />

aufgerufen werden, die nur die Namen der erkannten Wortmodellen zurückliefert, ohne<br />

diesen der Modellen für die Pause und Störung. Das passiert z.B. in der Funktion<br />

„Recognize::startThread()“, in denen alle Schnittstellen wie Spracherkennungssystem,<br />

„audioFrontend“, DC-Filter usw. aneinander angepasst, gestartet und beendet werden.<br />

Seite 45


Nach der Darstellung des Plugins „AsrHGH“, nach der Erläuterung der Einsatzmöglichkeiten<br />

von Actionen aus dem Plugin „ActionsHGH“, nach der Beschreibung der<br />

Funktionsweise des Spracherkennungssystems „ApplicationHGH“ und nach der<br />

Auflistung von allen bei diesem System verwendeten HirschLib- Funktionen kommen<br />

wir endlich auf die Frage zur parallelen Spracherkennung zurück.<br />

Bei der Arbeit am asrLib- Projekt wurde ein sehr großer Wert darauf gelegt, die<br />

möglichst universell einsetzbaren Bibliotheken zu erzeugen und deren Benutzern eine<br />

Möglichkeit zu geben, nicht nur verschiedene Spracherkennungssysteme <strong>mit</strong> vielseitiger<br />

Anwendung ohne großen Aufwand zu erstellen, sondern auch diese <strong>mit</strong>einander nach<br />

Belieben kombinieren zu können. Ein Beispiel für einen möglichen<br />

Spracherkennungsprozess bei <strong>einer</strong> Kombination aus mehreren Spracherkennungssystemen<br />

ist in der Abbildung 18 gegeben, vgl. <strong>mit</strong> der Abbildung 16 auf der Seite 36.<br />

Wie man sieht, gibt es hier nur eine Schnittstelle für die Kommunikation <strong>mit</strong> der<br />

Soundkarte, dafür aber zwei Analyzer und gleich 4 Recognizer.<br />

alle 25 ms<br />

Signalabschnitte<br />

0 1 2 3 4 5 6 ...<br />

SoundcardFrontend::<br />

paCallbackFun()<br />

Signalabschnitte<br />

0 1 2 3 4 5 6 ...<br />

BufferedIOAdaptor::tick()<br />

Abtastwerte<br />

Abtastwerte<br />

AnalyzerHGH::analyze()<br />

AnalyzerHGH::analyze()<br />

0 1 2 3 4 5 0 1 2 3 4 5<br />

0 1 2 3 4 5 0 1 2 3 4 5<br />

Merkmalsvektoren<br />

Merkmalsvektoren<br />

RecognizerHGH::process()<br />

RecognizerHGH::process()<br />

RecognizerHGH::process()<br />

RecognizerHGH::process()<br />

Abbildung 18: Möglicher Verlauf <strong>einer</strong> parallelen Spracherkennung<br />

Seite 46


Wir haben hier also 4 Spracherkennungssysteme. Wie im Kapitel 3.2.1 geschrieben<br />

wurde, muss ein Spracherkennungssystem aus mindestens zwei Komponenten bestehen:<br />

Analyzer und Erkenner. Genau wie ein AudioManager auf der Seite 34 können alle<br />

Komponente eines Systems dem anderen zugewiesen werden. Wie schon oben<br />

geschrieben, kann man bei einem Analyzer die Callbacks für mehrere Sprachdetektoren<br />

registrieren. Für den Datenaustausch zwischen Analyzer und Recognizer (und so<strong>mit</strong> der<br />

Adaption) werden die Stacks für Merkmalsvektoren registriert.<br />

_analyzer1->registerCallback("estimateCallback", cb1);<br />

_analyzer1->registerCallback("estimateCallback", cb2);<br />

_analyzer1->registerFeatureBuffer(_recognizer1->getFeatureBuffer());<br />

_analyzer1->registerFeatureBuffer(_recognizer2->getFeatureBuffer());<br />

Es gibt also fast keine Grenzen. Man kann verschiedene Spracherkennungssysteme <strong>mit</strong><br />

beliebiger Struktur erstellen und für eine gleichzeitige, oder parallele, Spracherkennung<br />

einsetzen. Es gibt aber eine einzige Voraussetzung: die Daten, die von den kombinierten<br />

Komponenten bearbeitet werden, müssen zueinander passen. Man darf nicht einen<br />

Erkenner, der für eine robuste Erkennung erstellte Muster geladen hat, <strong>mit</strong> einem<br />

Analyzer kombinieren, deren Merkmalsvektoren nicht dafür vorgesehen wurden. Man<br />

muss auch auf die Frequenzen achten. Bei der Erstellung von solchen<br />

Spracherkennungssystemen (oder bei deren Initialisierung oder Start) muss man schon<br />

überlegen, wie man deren Komponente am Besten kombinieren soll und welche Art von<br />

Daten für die Spracherkennung verwendet wird.<br />

Plugins „AsrHGH“, für ein Spracherkennungssystem, und „ActionsHGH“, für dessen<br />

Actionen, wurden für den Einsatz von kompletten Spracherkennungssystemen gedacht,<br />

also ohne Kombinationen. Man kann verschiedene Varianten von „ApplicationHGH“ in<br />

„RecognizeInit“ erstellen und in „Recognize“ starten. Dabei können maximal 10<br />

Systeme gleichzeitig ausgeführt werden, jedes von denen wie in der Abbildung 16<br />

strukturiert ist. Weil diese Systeme unabhängig voneinander laufen, können die beliebig<br />

konfiguriert werden (z.B. beliebige zulässige Frequenzen).<br />

Um zu sehen, wie eine parallele Erkennung funktioniert, vertiefen wir uns in den<br />

Quellcode der Klasse „Recognize“ unter „plugins/ActionsHGH/Recognize.h“.<br />

Als Erstes werden alle in der Konfigurationsdatei eingegebenen Parameter eingelesen<br />

(s. Seite 32). Mit Parameter „SM_BUTTON“ wird Funktion „Recognize::<br />

waitForButton()“ als ein Thread (selbständiger, paralleler Prozess) gestartet, die in <strong>einer</strong><br />

„ewigen“ Schleife den Zustand des parallelen Ports des Rechners prüft. Falls der Knopf<br />

Seite 47


am Infokasten, der <strong>mit</strong> diesem Port verbunden ist, betätigt wird, wird die Variable<br />

„_breakAction“, die standardmäßig „0“ ist, auf „1“ gesetzt (das Abbrechen der<br />

Erkennung von Außen erzwingen) und die Funktion wird verlassen. Was bei dem<br />

Parameter „SM_GUI“ passiert, wird im nächsten Kapitel geschrieben.<br />

Bei jedem Parameter, der <strong>mit</strong> „M_“ anfängt (Zeichen für ein Spracherkennungssystem),<br />

wird die Funktion „Recognize::startThread()“ als ein neuer Thread gestartet. Also wenn<br />

auch nur noch ein Spracherkennungssystem vorhanden ist, wird der Erkennungsprozess<br />

als ein separater Prozess ausgeführt.<br />

for (nrOfThread = 0; nrOfThread < 10; nrOfThread++)<br />

if (_parameterVector[count].substr(0,2) == "M_")<br />

pthread_create(&thread[nrOfThread], NULL, startThread, (void*) this);<br />

else<br />

beak;<br />

Nachdem alle Threads gestartet wurden, wird einfach gewartet, bis „_breakAction“<br />

einen von „0“ abweichenden Wert annimmt oder bis alle Threads beendet wurden.<br />

Weitere Beschreibung gilt für alle parallel laufenden Spracherkennungsprozesse.<br />

In der Funktion „Recognize::startThread()“ wird zuerst das aktuelle Spracherkennungssystem<br />

„asrApp“ geladen und im Vektor „_asrAppContainer“ abgelegt. Dann wird die<br />

als Parameter übergebene Syntaxdatei eingelesen, falls das noch nicht geschehen ist:<br />

asrApp->getRecognizer()->initSyntax(_syntaxFile);<br />

Falls die Erkennung <strong>mit</strong> der Adaption erfolgen soll, wird diese initialisiert:<br />

asrApp->getAdaption()->initialize();<br />

Beim ersten Aufruf vom aktuellen System wird „audioFrontend“ (ISDN_FRONTEND<br />

oder SOUNDCARD_FRONTEND, s. Seiten 34 - 36) erstellt und als Property im<br />

„asrApp“ gespeichert, andernfalls wird „audioFrontend“ aus Property geladen.<br />

if (asrApp->getProperty("AudioFrontend") == NULL) {<br />

if (StateMachine::getInstance()->getProperty("ISDN_FRONTEND")) {<br />

audioFrontend = sm->getProperty("ISDN_FRONTEND")->get();<br />

asrApp->addProperty("AudioFrontend", audioFrontend);<br />

}<br />

else {<br />

audioFrontend = new SoundcardFrontend();<br />

audioFrontend->setAudioManager(asrApp->getAudioManager());<br />

audioFrontend->initialize();<br />

asrApp->addProperty("AudioFrontend", audioFrontend);<br />

}<br />

}<br />

Seite 48


Falls eine Audiodatei als Parameter eingegeben wurde, wird diese als Datenquelle<br />

gesetzt (s. Quelltext auf der Seite 37). Andernfalls ist „audioFrontend“ die Datenquelle:<br />

asrApp->getInputAdaptor()->getInput(0)->setSource(audioFrontend->getOutput(0))<br />

Nachdem alle Komponente (Audiodatei, DC-Filter, „audioFrontend“ und „asrApp“)<br />

gestartet wurden, läuft alles ab, wie oben in diesem Kapitel beschrieben wurde. Es wird<br />

in der Funktion „ASRApplication::start()“ auf das Ende der Spracherkennung gewartet.<br />

Wenn „asrApp“ gestoppt wird, weil: die Audiodatei zu Ende ist; das Ende der Sprache<br />

vom Erkenner erkannt wurde; der Sprachdetektor in den Zustand „VAD_OTHER“<br />

gewechselt hat; das parallel laufende Spracherkennungssystem etwas erkannt hat; die<br />

Spracherkennung von Außen unterbrochen wurde, dann werden alle noch laufenden<br />

Komponente gestoppt und die gesetzten Quellen entfernt.<br />

audioFrontend->stop();<br />

audioFrontend->getInput(0)->setSource(NULL);<br />

if (audioFrontend->getInput(1))<br />

audioFrontend->getInput(1)->setSource(NULL);<br />

dcblocker->stop();<br />

asrApp->getInputAdaptor()->getInput(0)->setSource(NULL);<br />

Falls die Spracherkennung von Außen unterbrochen wurde (_breakAction = 1), wird<br />

das gesamte Spracherkennungssystem <strong>mit</strong> allen seinen Komponenten zurückgesetzt und<br />

Funktion „Recognize::startThread()“ wird verlassen, der aktuelle Thread wird beendet.<br />

asrApp->reset();<br />

In allen anderen Fällen werden die möglichen Ergebnisse des Erkenners ausgewertet:<br />

asrApp->getRecognizer()->processResults();<br />

std::vector results = asrApp->getRecognizer()->getResults();<br />

Wenn „results“ leer ist, wird „asrApp“ zurückgesetzt, wenn nicht, wird „_breakAction“<br />

den Wert „2“ annehmen als Zeichen dafür, dass etwas erkannt wurde.<br />

Zurück zur „Recognize::execute()“. Alle noch laufende Spracherkennungssysteme<br />

werden gestoppt und es wird noch gewartet, bis alle Threads beendet wurden.<br />

for (int j = 0; j < (int)_asrAppContainer.size(); j++)<br />

_asrAppContainer.at(j)->stop();<br />

for (int j = 0; j < nrOfThread; j++)<br />

pthread_join(thread[j], NULL);<br />

Seite 49


Falls „_breakAction“ den Wert 0 hat (keines von Spracherkennungssystemen hat etwas<br />

erkannt und auch keine Unterbrechung von Außen), wird „SM_TIMEOUT“ als<br />

Ergebnis der Action „Recognize“ zurückgegeben (bei drei nacheinander folgenden<br />

„SM_TIMEOUT“ wird „SM_MAX_TIMEOUT“ ausgegeben). Alle Systeme wurden<br />

also schon vorher zurückgesetzt, muss nur noch „_asrAppContainer“ geleert werden.<br />

Bei „_breakAction“ = 2 werden die Ergebnisse eines der Spracherkennungssysteme als<br />

Rückgabe der Action „Recognize“ genommen (evtl. als Buffer, s. Kapitel 5.5):<br />

this->setResult(results.front());<br />

Bevor „asrApp“ zurückgesetzt und „_asrAppContainer“ geleert werden, können noch<br />

die gewonnenen Daten abgespeichert werden, z.B. die aufgenommene Sprachäußerung<br />

als eine Audiodatei (s. Seite 38, oben) oder die Labels (s. Seite 45, unten).<br />

Das war eine oberflächliche Beschreibung der parallelen Erkennung ohne der<br />

Berücksichtigung von allen möglichen Sicherheitsabfragen und kleinen Details. Wenn<br />

man den Quellcode von „Recognize“ anguckt, sieht man, dass ein großer Teil des<br />

Quellcodes aus diesen Abfragen und aus dem Setzen und Entfernen von Mutex besteht.<br />

Wenn man <strong>mit</strong> parallelen Prozessen arbeitet (bei „asrLib“ wird eine große Menge davon<br />

erzeugt), muss man darauf achten, dass auf ein Objekt zur gleichen Zeit aus<br />

verschiedenen Prozessen zugegriffen werden kann. Wenn man keine Maßnahmen<br />

vornimmt, könnte es zu einem Absturz der Applikation führen. Deshalb sollte man<br />

solche Objekte <strong>mit</strong> Mutex schützen. Ein Prozess, das auf ein Objekt oder auf eine<br />

Funktion zugreifen möchte, sperrt Mutex. Der nächste Prozess wartet, bis Mutex<br />

entsperrt wird, dabei kann eine große „Schlange“ entstehen. Mutex soll global entweder<br />

in <strong>einer</strong> Klasse oder in einem Namensraum oder sogar im gesamten Projekt deklariert<br />

werden. Mutex wird auf mehreren Stellen im gesamten asrLib- Projekt zum Schutz von<br />

vielen Objekten verwendet. Auch die Konsolenausgabe wird geschützt, wie in diesem<br />

Beispiel aus „plugins/ActionsHGH/Recognize.h“:<br />

// globale Deklaration im Namensraum „actionsHGH“<br />

static pthread_mutex_t mutex_report_recognize = PTHREAD_MUTEX_INITIALIZER;<br />

// Anwendung in den Funktionen<br />

pthread_mutex_lock(&mutex_report_recognize);<br />

std::cout


7. GUI<br />

Die graphische Oberfläche ist ein Bestandteil des multimodalen Dialogsystems. Das ist<br />

eine zusätzliche, in dieses System integrierte Applikation, die auf FLTK der Version 2.0<br />

basiert und parallel zur StateMachine (Kapitel 5) läuft. Die beiden Komponenten<br />

werden von <strong>einer</strong> Schnittstelle gestartet, die Controller genannt wird, und über die diese<br />

<strong>mit</strong>einander auch kommunizieren können. Controller ist ein Objekt der von<br />

„asrlib::GUIController“ abgeleiteten Klasse, das in der Hauptfunktion erstellt werden<br />

soll. Jede graphische Oberfläche muss ihren eigenen Controller haben, der deren<br />

Verhalten bestimmt und entscheidet, was passieren soll, wenn z.B. ein Button (= Taste<br />

oder Schaltfläche) betätigt wird. Die Klasse des Controllers soll zusammen <strong>mit</strong> anderen<br />

Klassen von allen GUI- Komponenten als Plugin erstellt werden, das beim Aufruf eines<br />

Dialogsystems als Parameter übergeben wird. Eine Beschreibung von diesem Aufruf ist<br />

im Kapitel „Plugins“ auf der Seite 25 gegeben. Die Applikation „GuiApp“ ist extra für<br />

die Arbeit <strong>mit</strong> den graphischen Oberflächen gedacht und kann beliebige Plugins laden.<br />

Eins von denen muss unbedingt ein GUI- Plugin sein. Der etwas vereinfachte Quellcode<br />

der Hauptfunktion dieser Applikation unter „applications/GuiApp/GuiApp.cpp“ zeigt<br />

die notwendigen Schritte für den Start eines Dialogsystems <strong>mit</strong> GUI:<br />

int main()<br />

{<br />

StateMachine* sm = StateMachine::getInstance();<br />

sm->setDataPath("../../data");<br />

PluginFactory::get()->loadLibrary(sm->getDataPath()+"/Actions.so");<br />

PluginFactory::get()->loadLibrary(sm->getDataPath()+"/"+ASRActions);<br />

PluginFactory::get()->loadLibrary(sm->getDataPath()+"/"+PluginGUI);<br />

asrlib::StatReader* statReader = new asrlib::StatReader();<br />

if(!statReader->initStateMachine(StatFile, sm))<br />

return 0;<br />

GUIController* myController = dynamic_cast<br />

(PluginFactory::get()->create("GUIController"));<br />

myController->start();<br />

};<br />

sm->destroy();<br />

delete statReader;<br />

delete myController;<br />

return 0;<br />

Plugin „Actions.so“ <strong>mit</strong> allgemeinen Actionen für StateMachine (s. Tabelle auf der<br />

Seite 28) wird immer automatisch geladen. Nachdem auch das gewünschte Plugin <strong>mit</strong><br />

Seite 51


GUI geladen wurde, kann sein Controller <strong>mit</strong> „GUIController“ aufgerufen werden. Im<br />

Rahmen dieser Masterarbeit wurden zwei GUI- Plugins erstellt, deren Quellcode unter<br />

„plugins/GuiFKA“ und „plugins/GuiPHA“ zu finden ist. „GuiFKA“ wurde für die<br />

graphische Darstellung eines Fahrkartenautomaten geschrieben, der in diesem Kapitel<br />

dargestellt wird. Sein Controller ist die Klasse „FKAController“. Mit „PHAController“<br />

wird die graphische Oberfläche des Parkhausautomaten aus „GuiPHA“ gesteuert.<br />

Da<strong>mit</strong> beliebige Controller in der Hauptfunktion, wie im Quellcode auf der vorherigen<br />

Seite, erstellt werden können, müssen deren Namen in „export.cpp“ übersetzt werden:<br />

POCO_EXPORT_NAMED_CLASS(FKAController,GUIController)<br />

Bevor wir uns <strong>mit</strong> dem Fahrkartenautomaten beschäftigen, soll die Basis- Bibliothek<br />

„libGUI.so“ dargestellt werden, deren Quellcode sich unter „include/GUI“ befindet und<br />

aus drei Headerfiles besteht: „Windows.h“, „Widgets.h“ und „GUIController.h“.<br />

Die Klasse „asrlib::Windows“ wurde aus „asrlib::Base“ abgeleitet und ist eine abstrakte<br />

Basisklasse für die Verwaltung von allen „Graphical User Interfaces” des „asrLib“-<br />

Projektes. So ein GUI <strong>mit</strong> allen seinen Komponenten, zu denen alle Buttons, Bilder,<br />

Ein- und Ausgabefelder und natürlich das Fenster selbst gehören, ist hier dargestellt:<br />

Abbildung 19: Fahrkartenautomat<br />

Seite 52


Alle GUI- Objekte werden also in der Klasse „asrlib::Windows“ verwaltet. Erstellt<br />

müssen diese in der abstrakten Funktion „make_window()“ in den abgeleiteten Klassen.<br />

Dabei soll im Kapitel 4.1 beschriebenes FLTK- Tool „FLUID“, das für die Erstellung<br />

von graphischen Oberflächen gedacht ist, <strong>mit</strong> der Projektdatei „prototyp.fl“ unter<br />

„plugins/GuiPrototyp“ verwendet werden, die extra für die von „asrlib::Windows“<br />

abgeleitete Klassen vorbereitet wurde und als Prototyp für alle FLUID- Projekte dienen<br />

kann. In dem geladenen Projekt soll man den Namen der Klasse ändern und die<br />

gewünschten Objekte in „make_window()“ erstellen. Die Erstellung selbst ist ganz<br />

einfach, wie man an Beispielen im Kapitel 4.1 sieht. Das Einzige, was man beachten<br />

soll, ist, dass FLUID nicht <strong>mit</strong> relativen Pfaden arbeiten kann. Wenn man ein Bild ins<br />

Projekt geladen hat (was übrigens <strong>mit</strong> dem „Widget“- Objekt gemacht werden kann),<br />

wird dieses <strong>mit</strong> dem absoluten Pfad in der Projektdatei und im Quellcode gespeichert.<br />

Bevor man den Quelltext kompiliert, sollen diese Pfade <strong>mit</strong> relativen Pfaden oder <strong>mit</strong><br />

dem ähnlichen Quelltext ersetzt werden:<br />

asrlib::StateMachine* sm = asrlib::StateMachine::getInstance();<br />

o->image(fltk::SharedImage::get(sm->getDataPath()+"/Images/50.jpg");<br />

Das Fenster, das wir in der Abbildung 19 sehen, ist nicht das „Windows“ Objekt,<br />

sondern nur sein Bestandteil. Ein „Windows“ Objekt kann gleich mehrere Fenster<br />

enthalten, soweit das sinnvoll ist. Das Hauptfenster wird „_ownWindow“ zugewiesen.<br />

Jedes zusätzliche Fenster soll nach s<strong>einer</strong> Erstellung in <strong>einer</strong> dafür vorgesehenen Map<br />

<strong>mit</strong> seinem Namen abgespeichert werden, da<strong>mit</strong> es später angesprochen werden kann:<br />

_ownWindow = o;<br />

insertWindow(o, "Zusätzliches Fenster");<br />

// Hauptfenster setzen<br />

// Nebenfenster speichern<br />

Das soll auch <strong>mit</strong> allen anderen GUI- Objekten passieren, deren Zustand während der<br />

Ausführungsphase durch den Benutzer (bzw. durch StateMachine) geändert werden<br />

kann. Das sind z.B. fast alle Buttons und Ein- und Ausgabefelder. Falls mehrere<br />

Objekte gleich behandelt werden sollen, wie z.B. das Ausblenden der ganzen Tastatur in<br />

der Abbildung 19, können diese in <strong>einer</strong> Gruppe („fltk::Group“) erstellt werden, die in<br />

der Map abgelegt wird und bei Bedarf daraus <strong>mit</strong> ihrem Namen aufgerufen wird:<br />

insertGroup(fltk::Group* group, std::string name)<br />

std::string getGroup(fltk::Group *group)<br />

fltk::Group* getGroup(std::string name)<br />

// in der Map ablegen<br />

// Name ausgeben<br />

// aus der Map holen<br />

Seite 53


Um den Benutzer bei der Erstellung von solchen GUI- Objekten, wie die oben erwähnte<br />

Tastatur, etwas zu entlasten, wurden in „include/GUI/Widgets.h“ einige Klassen für die<br />

Gruppen fertiggestellt. Man erstellt ein Gruppen-Objekt in FLUID und ersetzt<br />

„fltk::Group“ <strong>mit</strong> der Klasse „asrlib::KeyboardDeuGroup“ für eine fertige Tastatur <strong>mit</strong><br />

deutschem Layout. Man setzt nur die gewünschte Größe der Gruppe, die Größe und<br />

Schrift von jedem Button werden automatisch durch die Berechnungen im Quellcode<br />

angepasst. Diese Tastatur ist erstmal nicht sichtbar, weil die nicht <strong>mit</strong> FLUID erstellt<br />

wurde. Erst nach der Kompilation wird diese während der Ausführung erstellt. Eine<br />

weitere fertige Gruppe von GUI- Objekten ist die Klasse „asrlib::CalendarGroup“, <strong>mit</strong><br />

der man einen deutschen Kalender hat, dessen Größe sich ebenfalls dynamisch anpasst.<br />

Abbildung 20:<br />

GUI-Kalender<br />

Die Basisklasse von beiden oben erwähnten Klassen ist „asrlib::OwnGroups“, die<br />

ihrerseits von der Klasse „fltk::Group“ abgeleitet wurde und einige neue Funktionen zur<br />

Verfügung stellt, z.B. zwei Funktionen zur unterschiedlichen Bearbeitung von Button-<br />

Callbacks. Wenn ein Ereignis beim Betätigen des Buttons für die Auswahl des Monats<br />

in der obigen Abbildung auftritt, soll dieses intern im Kalender bearbeitet werden. Nur<br />

die Nachrichten von die Kalendertage repräsentierenden Buttons werden nach draußen<br />

verschickt. Bei den meisten Schaltflächen reicht es aus, wenn deren Beschriftung als<br />

Information in den Nachrichten gesendet wird, z.B. „OK“ oder „Abbrechen“. Da<strong>mit</strong><br />

kommt man bei diesem Kalender nicht aus. Es ist in der Klasse „fltk::Button“ nicht<br />

vorgesehen, irgendwelche zusätzliche Information abzuspeichern. Deshalb werden die<br />

Tasten in den graphischen Oberflächen des asrLib- Projektes als Objekte der eigenen<br />

Klasse „asrlib::OwnButton“ erstellt, die erlaubt, zusätzliche Information vom Typ<br />

„std::string“ <strong>mit</strong> der Funktion „setData(std::string data)“ anzulegen und diese <strong>mit</strong><br />

„getData()“ abzufragen. Alle von Buttons gesendete Ereignisse werden <strong>mit</strong> der<br />

Seite 54


Funktion „Windows::callbackButton()“ abgefangen. So könnte der Verlauf eines<br />

Ereignisses beim Betätigen des Buttons „21“ in der Abbildung 20 vereinfacht aussehen:<br />

o->setData(Tag+"."+Monat+"."+Jahr);<br />

_owner->callbackButton(o, (void*)(o->getData());<br />

// Information speichern<br />

// Information senden<br />

„_owner“ ist ein Objekt der von „asrlib::Windows“ abgeleiteten Klasse, dem die<br />

Kalender- Gruppe hinzugefügt wurde. Für Buttons, die in FLUID erstellt wurden, muss<br />

dieses Objekt nicht eingegeben werden. Die abgefangenen Nachrichten werden als<br />

Callbacks, die uns aus dem Kapitel 5.5 bekannt sind, zum Controller gesendet.<br />

void callbackButton(fltk::Widget* button, void* data)<br />

{<br />

this->executeCallback("callbackButton", data);<br />

};<br />

Controller ist eine Schnittstelle zwischen dem „Windows“- Objekt (und allen seinen<br />

Komponenten) und StateMachine. Die Basisklassen für alle solche Controller ist<br />

„asrlib::GUIController“. Nach dem Start vom Controller werden zuerst alle Standard-<br />

Callbacks für die grundlegende Steuerung von GUI- Objekten, z.B. zum Aktivieren<br />

oder Deaktivieren von Buttons oder selbst zum Starten von GUI, automatisch <strong>mit</strong><br />

„registerOwnCallbacks()“ registriert, da<strong>mit</strong> diese später von beliebiger Stelle zu jeder<br />

Zeit gesendet werden können. Alle von diesen Callbacks aufzurufenden Funktionen,<br />

außer „updateFromSM()“, befinden sich unter „include/GUI/GUIController.h“. Die<br />

abstrakte Funktion „updateFromSM()“, die <strong>mit</strong> dem oben genannten Callback für<br />

irgendwelche, vom Benutzer definierte Aufgaben von StateMachine aufgerufen wird,<br />

soll in der abgeleiteten Klasse erstellt werden. Dazu kommen wir etwas später.<br />

CallbackBuffer::getInstance()->registerCallback("startGUI",<br />

new Callback(this, &GUIController::startGUI));<br />

Nach der Registrierung von allen Callbacks wird StateMachine initialisiert und in einem<br />

Thread parallel zur laufenden Applikation gestartet. Nachdem auch GUI gestartet wurde<br />

(dazu später, Seite 58), kommt es zu <strong>einer</strong> endlosen Schleife, „GUIController::start()“:<br />

while (1) {<br />

updateController();<br />

pthread_mutex_lock(&mutex_redraw);<br />

fltk::check();<br />

pthread_mutex_unlock(&mutex_redraw);<br />

usleep(10000);<br />

}<br />

// abstrakte Funktion für irgendwas<br />

// Ereignisse prüfen und abarbeiten<br />

Seite 55


Mit der Funktion „updateController()“ kann man festlegen, was alle 10 Millisekunden<br />

passieren soll. Bei „FKAController“ wird diese Funktion für die Ausgabe vom Datum<br />

<strong>mit</strong> Zeit verwendet, wie in der Abbildung 19, unten. Wie man im oberen Quellcode<br />

sieht, wird auf der Seite 50 beschriebenes Mutex auch hier eingesetzt. Wenn das<br />

Aussehen des aktuellen Fensters geändert werden soll, z.B. wegen der Deaktivierung<br />

eines Buttons, soll dieses Fenster <strong>mit</strong> „redraw()“ neu gezeichnet werden. Da die meisten<br />

solchen Änderungen durch den Aufruf von entsprechenden Funktionen <strong>mit</strong> Callbacks<br />

von StateMachine durchgeführt werden, wird die Funktion „redraw()“ parallel zu<br />

„check()“ ausgeführt. Wenn die beiden Funktionen sich überschneiden, wird das Fenster<br />

nicht korrekt dargestellt, deshalb soll Mutex bei diesen verwendet werden.<br />

Jetzt wissen wir, wie man eine graphische Oberfläche erstellt, wie diese <strong>mit</strong> dem<br />

Controller kommuniziert und was man bei Callbacks von StateMachine beachten soll.<br />

Nun kommen wir zum Teil dieses Kapitel, in dem erklärt wird, wie eine graphische<br />

Oberfläche überhaupt gestartet wird und wie genau eine Kommunikation zwischen dem<br />

Controller und StateMachine abläuft. Dazu betrachten wir folgende Abbildung und<br />

nehmen Action „TRANSCEIVER“ aus der Tabelle auf der Seite 28 „unter die Lupe“.<br />

start()<br />

myController:<br />

GUIController*<br />

startSM()<br />

sm:<br />

StateMachine*<br />

„createWindow“<br />

createWindow()<br />

„GUI_OK“<br />

_currentWindows:<br />

asrlib::Windows*<br />

make_window()<br />

_mainWindow:<br />

fltk::Window*<br />

„startGUI“<br />

startGUI()<br />

„GUI_OK“<br />

show()<br />

callbackButton()<br />

„callbackButton“<br />

„ … “<br />

Abbildung 21: Kommunikation zwischen GUI und StateMachine<br />

Seite 56


Zuerst wird der Controller gestartet, wie in der „main()“- Funktion auf der Seite 51<br />

dargestellt. Weil dieser Controller ein Objekt der von „asrlib::GUIController“<br />

abgeleiteten Klasse ist, wird zuerst ihre Start- Funktion ausgeführt (in dem Beispiel <strong>mit</strong><br />

dem Fahrkartenautomat ist das „FKAController::start()“), die ihrerseits die oben<br />

beschriebene Funktion der Basisklasse aufruft, nachdem alle in der Basisklasse<br />

fehlenden Callbacks <strong>mit</strong> der abgeleiteten Funktion „registerOwnCallbacks()“ registriert<br />

wurden. Diese Callbacks sind spezielle Nachrichten des Fahrkartenautomaten. In der<br />

Abbildung 19 dargestelltes Fenster hat mehrere Felder für die Textausgabe. Für jedes<br />

Ausgabefeld, dessen Inhalt von StateMachine bestimmt wird, soll ein eigenes Callback<br />

registriert werden. Die entsprechende Funktionen erstellt man in der abgeleiteten<br />

Controller- Klasse. Das Setzten des gewünschten Inhaltes wird wie folgt realisiert:<br />

GUIController::setOutput(NameVomAusgabefeld, Inhalt);<br />

Nachdem StateMachine gestartet wurde, wird die Konfigurationsdatei für Dialogsystem<br />

abgearbeitet. Für die Kommunikation <strong>mit</strong> dem Controller wird die Action der Klasse<br />

„asrActions::Transceiver“ eingesetzt. Als Erstes soll jedes „Windows“- Objekt erstellt<br />

werden, dessen Fenster beim aktuellen Dialogsystem angezeigt werden sollen. Wie<br />

schon oben geschrieben wurde, soll jedes „Windows“- Objekt ein Hauptfenster haben<br />

und kann mehrere Nebenfenster enthalten. Bei unserem Beispiel <strong>mit</strong> dem Fenster vom<br />

Fahrkartenautomat in der Abbildung 19 ist dieses Objekt „FKA_deu“.<br />

<br />

create_window_fka_deu<br />

<br />

TRANSCEIVER<br />

4 SM_SEND FKA_deu createWindow SM_RECEIVE<br />

1 GUI_OK<br />

start_window_fka_deu<br />

exit<br />

Statt diesen Auszug aus der Konfigurationsdatei zu erklären, wird eine allgemeine<br />

Beschreibung der Action „TRANSCEIVER“ hier gegeben:<br />

<br />

TRANSCEIVER<br />

[SM_SEND ] | [SM_RECEIVE [] ]<br />

<br />

<br />

<br />

<br />

<br />

Das Schlüsselwort für das Senden von Nachrichten<br />

Die Information, die gesendet werden soll<br />

Der Name vom Callback, <strong>mit</strong> dem gesendet werden soll<br />

Das Schlüsselwort für den Empfang von Nachrichten<br />

Ein Speicher für die empfangene Nachrichten, optional<br />

Seite 57


Das Senden und das Empfangen kann man beliebig kombinieren und beliebig oft in<br />

<strong>einer</strong> einzigen Action einsetzten.<br />

Die Erstellung vom „Windows“- Objekt <strong>mit</strong> allen seinen Komponenten kann einige Zeit<br />

in Anspruch nehmen, deshalb sollten alle Objekte, die benutzt werden, noch vor dem<br />

Start des ersten Objektes erstellt werden. Dabei werden diese in einem Stack abgelegt.<br />

Ein Auszug aus „GUIController::createWindow()“:<br />

Windows* myWindows =<br />

dynamic_cast(PluginFactory::get()->create(name));<br />

myWindows->make_window();<br />

_allWindows.insert(std::pair(name, myWindows));<br />

Nachdem „Windows“- Objekt erstellt wurde, kann es genauso <strong>mit</strong> Callback „startGUI“<br />

gestartet werden, „GUIController::startGUI()“:<br />

asrlib::Windows* _currentWindows = getWindows(name);<br />

fltk::Window* _mainWindow = _currentWindows->getWindow();<br />

_mainWindow->show();<br />

registerSMCallbacks();<br />

Mit „GUIController::getWindows(name)“ wird das gewünschte „Windows“- Objekt aus<br />

dem Stack geholt. Mit „Windows::getWindow()“ wird sein Hauptfenster zurück<br />

gegeben, das in FLUID „_ownWindow“ zugewiesen wurde (Seite 53, zweiter Absatz).<br />

Alle zusätzlichen Fenster können <strong>mit</strong> Callbacks „showWindow“ bzw. „hideWindow“<br />

von StateMachine aus gesteuert werden. Alle oben erwähnten Callbacks beziehen sich<br />

auf die Controller- eigene Funktionen und wurden schon vorher registriert, <strong>mit</strong><br />

„GUIController::registerOwnCallbacks()“. „GUIController::registerSMCallbacks()“ aus<br />

dem obigen Quelltext registriert alle Callbacks, die vom Controller zu StateMachine<br />

gesendet werden sollen, also in andere Richtung. Objekte, an die diese Callbacks<br />

registriert werden, sind alle aus der Konfigurationsdatei eingelesenen Zustände, deren<br />

Actionen in der Gruppe „GUIcompatible“ sind (nicht <strong>mit</strong> GUI- Gruppen verwechseln).<br />

std::string getGroupName() { return "GUIcompatible";}<br />

Diese Objekte empfangen die Nachrichten vom Controller über die Funktion<br />

„setUserData(void* data)“. Erst jetzt, nachdem das Senden von Nachrichten in beiden<br />

Richtungen eingerichtet wurde, kommt es zu <strong>einer</strong> endlosen Schleife (auf der Seite 55).<br />

Das gesamte Dialogsystem <strong>mit</strong> der graphischen Oberfläche wird automatisch beendet,<br />

Seite 58


nachdem StateMachine <strong>mit</strong> der Action „EXIT“ („asrActions::Exit“) gestoppt wird.<br />

Bevor dieses Kapitel abgeschlossen wird, kommen wir nach der Beschreibung der<br />

Kommunikation zwischen verschiedenen Prozessen zur Abbildung 21 zurück. Wenn ein<br />

Ereignis bei <strong>einer</strong> Schaltfläche auftritt, wird die Funktion „Windows::callbackButton()“<br />

aufgerufen, die eine Nachricht als Callback zum jeweiligen Controller sendet (in<br />

unserem Fall ist das „FKAController“). In der von diesem Callback aufgerufenen<br />

Funktion „FKAController::callbackButton()“ wird es entschieden, was <strong>mit</strong> dieser<br />

Information passieren soll. Hier wird diese einfach erneut versendet, diesmal als<br />

Callback zum aktuellen Zustand von StateMachine. Wenn man an die in der Abbildung<br />

19 dargestellte Tastatur denkt, dann sieht es nicht mehr sinnvoll aus, eine Mitteilung bei<br />

jedem Ereignis an StateMachine zu senden, weil die aktuell laufende Action dabei<br />

abgebrochen wird. Für Ereignisse, die von dieser Tastatur kommen, ist eine weitere<br />

Funktion vorgesehen: „GUIController::callbackKeyboard()“. Das Verhalten dieser<br />

Funktion kann in der abgeleiteten Klasse festgelegt werden. Bei „FKAController“<br />

werden die Buchstaben, die dabei empfangen werden (das ist ja eine Tastatur)<br />

zusammengesetzt und jedes Mal an StateMachine gesendet. In der Konfigurationsdatei<br />

wird es entschieden, wann das aufhören soll, z.B. bei einem erkannten Wort („Köln“).<br />

Mit Callback „updateFromSM“ kann StateMachine diese Information nochmal<br />

anfordern. Danach wird diese in der Funktion „FKAController::updateFromSM()“<br />

endgültig gelöscht. Man kann also diese Funktion auch extra dafür aufrufen.<br />

Zum Schluss noch eine kurze Zusammenfassung des Quellcodes der Bibliothek<br />

„libGui.so“:<br />

Die Klasse „asrlib::Windows“ aus „include/GUI/Windows.h“ ist die Basisklasse für alle<br />

Verwalter von graphischen Oberflächen. Alle GUI- Elemente werden in FLUID in der<br />

Funktion „make_window()“ in der von „asrlib::Windows“ abgeleiteten Klasse erstellt.<br />

Die FLUID- Projektdatei „prototyp.fl“ in „plugins/GuiPrototyp“ ist dafür vorbereitet.<br />

Die Klassen in „include/GUI/Widgets.h“ sind Erweiterungen von FLTK- Klassen für<br />

GUI- Elemente. Bei Bedarf kann man weitere Klassen hinzufügen.<br />

Die Schnittstelle zwischen der graphischen Oberfläche und StateMachine ist ein Objekt<br />

der von „asrlib::GUIController“ abgeleiteten Klasse, das in der „main()“ Funktion<br />

erstellt wird und das sowie StateMachine als auch die graphische Oberfläche startet.<br />

Seite 59

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!