
Analyse von Mausgesten
mittels eines neuronalen Netzes
|
Name: |
Robert Hahn |
|
Matrikelnummer:
|
8136 |
|
Seminargruppe: |
96/041/02 |
|
Email: |
Die Aufgabe des
Programms Mouse Gesture Recognizer
ist das Erkennen von speziellen Mausbewegungen (sogenannten Gesten) des
Benutzers und deren Zuordnung zu bekannten Mustern.
Diese Technik,
die vorallem mit dem Webbrowser Opera
populär wurde, inzwischen aber auch in einigen anderen Applikationen verwendet
wird, eignet sich für die Entwicklung von intuitiv bedienbaren
Benutzerschnittstellen.
Da es sich bei
Mausbewegungen um relativ ungenaue Eingabedaten handelt bietet sich die
Verwendung eines neuronalen Netzes an zu ihrer Erkennung an.
Die
Funktionalität des neuronalen Netzes sowie der Aufzeichnung von
Bewegungsmustern der Maus wurden in eigene Bibliotheken gekapselt und können
auch in anderen Anwendungen verwendet werden. Zu Demonstrations- und
Testzwecken wird eine Beispielanwendung mitgeliefert die auch als Basis für
eigene Entwicklungen verwendet werden kann.
Der Anwender
zeichnet, meist bei gedrückter rechter Maustaste, Figuren direkt auf die
Programmoberfläche. Diese Muster werden anschliessend analysiert und können in
Aktionen umgesetzt werden. So kann im einfachsten Fall, durch eine Bewegung
nach links oder rechts in einem Browser vorwärts bzw. rückwärts geblättert
werden. Natürlich ist auch die Verwendung komplexerer Muster wie Buchstaben
möglich, um eine Vielzahl verschiedener Programmfunktionen zu steuern.
Zu beachten ist
dabei allerdings, dass die zu lernenden Gesten ausreichende
Unterscheidungsmerkmale haben sollten um gute Ergebnisse zu erzielen, da
Mausbewegungen in der Regel sehr schnell und nicht sonderlich exakt
durchgeführt werden.
Aus diesem Grund
kann in echten Anwendungen oft auch auf eine grafische Darstellung der Gesten
auf dem Bildschirm verzichtet werden. Im vorliegenden Programm ist eine
Visualisierung aber natürlich vorhanden.
Im Gegensatz zu
OCR Texterkennungssystemen, werden die Mausgesten nicht als ein aus Pixeln
bestehendes Bild analysiert. Stattdessen wird eine aufgezeichnete Geste in eine
Menge von Teilstrecken zwischen einer konfigurierbaren Anzahl von Stützpunkten
zerlegt. Die Ausrichtungen der Teilstrecken im Koordinatensystem ergeben das charakteristische
Muster der Geste.
Die Winkel werden
nach der Formel
mit
und
berechnet.
Eine Bewegung
senkrecht nach oben ergibt einen Winkel von 0°, nach rechts 90° und eine
Bewegung nach unten 180°.
Aufgrund dieser
Vorgehensweise ist es wichtig zu beachten, dass zum Beispiel ein im
Uhrzeigersinn gezeichneter Kreis eine völlig andere Geste darstellt als ein
entgegengesetzt gezeichneter Kreis, obwohl beide geometrisch die selbe Figur
darstellen.
Auch der relative
Start- und Endpunkt der Bewegung sind von Bedeutung (z.B. ob man die
Kreisbewegung oben oder unten beginnt). Die absolute Position der Geste
innerhalb des Fensters hat aber keinen Einfluss auf das Ergebnis. Ein großer
Vorteil der Verwendung von Winkeln als Eingabedaten ist auch die Unabhängigkeit
von der Skalierung. Es ist also für die Erkennung der Geste völlig egal ob ein
Kreis mit einem Durchmesser von 5 oder 10 cm gezeichnet wurde.
Beispiel:
Für einen oben
begonnenen, entgegen dem Urzeigersinn gezeichneten Kreis könnten sich bei 15
Stützpunkten folgende Winkel (in Grad) ergeben:
280, 259, 226, 209, 193, 147, 99, 78, 57, 18, 355,
330, 302, 270,
ein Kreis im
Urzeigersinn dagegen könnte folgende Werte ergeben:
98, 120, 154, 193, 228, 259, 273, 319, 340, 5, 35,
47, 75, 90.
Bei der
Aufzeichnung einer Bewegung darf die Maus nicht abgesetzt werden, zwischen
aufeinanderfolgenden Punkten besteht
also immer eine Verbindung. Bei einigen Figuren wie dem Buchstaben X
kann dies zu Problemen führen so dass solche Gesten entweder vermieden oder
durch eine alternative Schreibweise ersetzt werden müssen.
Unter Verwendung
eines neuronalen Netzes sollen die aufgezeichneten Bewegungen zuvor gelernten
Mustern zugeordnet werden. Neuronale Netze eignen sich gut für die Lösung
dieser Problemstellung, da es relativ schwierig ist mit einer Maus Muster exakt
zu reproduzieren und daher der Algorithmus zur Erkennung fehlertolerant und
lernfähig sein muss.
Es wird ein
Backpropagation Netz mit einer Eingabeschicht, einer oder mehreren verdeckten
Schichten und einer Ausgabeschicht verwendet. Um einen extremen Sprung der
Eingabewerte zwischen 0° und 360° zu verhindern wird statt des Winkels in Grad jeweils
seine Sinus und Consinus Komponente verwendet. Die Anzahl der Eingabeneuronen
ergibt sich also aus (Anzahl der Stützpunkte – 1) * 2, wobei die Menge
der Stützpunkte konfigurierbar ist.
Die Anzahl der
Ausgabeneuronen ergibt sich aus der Anzahl der bekannten Gesten. So hätte ein
Netz welches in der Lage ist die Gesten Hoch, Runter, Links, Rechts und einen
Kreis zu erkennen 5 Ausgabeneuronen. Anhand der Ausgabewerte dieser Neuronen
lässt sich die Wahrscheinlichkeit bestimmen mit der ein Muster erkannt wurde.
In diesem
Beispiel wurde eine Bewegung nach links sehr eindeutig erkannt:

Unter neuronalen
Netzen versteht man Algorithmen der Informatik die sich an der biologischen
Funktionsweise des menschlichen Gehirns orientieren. Das Ziel bei der
Verwendung von neuronalen Netzen ist es meist auf eine fehlertolerante Weise aus
ungenauen Eingabedaten die gewünschten Ausgabewerte zu erhalten. Für die zu
lösenden Probleme sind meist keine mathematischen Algorithmen bekannt oder
diese lassen sich aufgrund der ungenauen Eingabedaten nicht anwenden. Beispiele
für den Einsatz von neuronalen Netzen sind die Texterkennung oder biometrische
Verfahren zum Erkennen menschlicher Gesichter oder Fingerabdrücke.
Das Hauptmerkmal
von neuronalen Netzen ist ihre Lernfähigkeit. Ohne explizite Programmierung des
benötigten Algorithmus können neuronale Netze diesen anhand von
Trainingsdatensätzen erlernen.
Das Grundelement
eines neuronalen Netzes ist das künstliche Neuron, ein einfaches
Prozessorelement welches dem biologischen Neuron nachempfunden wurde. Die
Neuronen sind über gerichtete Verbindungen, die den biologischen Synapsen
entsprechen, verbunden und können sich so untereinander aktivieren. Während des
Lernvorgangs werden die Gewichte der Verbindungen angepasst.

Die Aktivierung
eines Neurons ergibt sich indem die Aktivierungsfunktion auf die Netto-Eingabe
(welche sich aus der Summe der Produkte der Ausgabewerte der Vorgängerschicht
mit den jeweiligen Verbindungsgewichten errechnet) angewendet wird.
Während des
Lernvorgangs modifiziert sich ein neuronales Netz selbst. Die Veränderungen
erfolgen anhand einer vorgegebenen Lernregel. Meist werden dabei die Gewichte
der Verbindungen angepasst. Aber auch strukturelle Veränderungen wie das
Hinzufügen oder Entfernen von Neuronen und Verbindungen sind möglich.
Für Mouse Gesture Recognizer wurde ein
ebenenweise verbundenes Feedforward-Netz verwendet. Dabei besteht das Netz aus
mehreren Schichten wobei es nur Verbindungen von Schicht zu Schicht in einer
Richtung gibt. Jedes Neuron ist mit allen Neuronen der darauffolgenden Schicht
verbunden.

Das Netz besteht
aus einer Eingabeschicht (Input), einer oder mehreren verdeckten Schichten
(Hidden) und einer Ausgabeschicht (Output). Aufgrund der Mehrschichtigkeit ist
es möglich auch komplexere Zusammenhänge zwischen Ein- und Ausgängen zu lernen.
In den meisten Fällen sollte die Verwendung von nur einer verdeckten Schicht
ausreichend sein.
Die
Eingabeschicht besitzt keine Eingangsgewichte und reicht die Eingabe an die darauffolgenden
Schichten weiter.
Das verwendete
Netz besitzt ein konstantes Bias Neuron (Wert 1.0) das mit allen Neuronen der
verdeckten Schicht und der Ausgabeschicht verbunden ist. Die Verbindungen
verfügen ebenfalls über veränderliche Gewichte und fungieren als
Aktivierungsschwellwerte der Neuronen in den verbundenen Schichten.
In der Hidden-
und der Outputschicht errechnen sich die Eingabewerte der Neuronen aus der
Ausgabe aller verbundenen Neuronen in der vorherigen Schicht multipliziert mit
den Verbindungsgewichten.

Die Gewichte der
Verbindungen werden beim Erstellen des Netzes mit zufälligen Werten zwischen -1.0
und 1.0 initialisiert und werden während des Lernvorgangs anhand einer
Lernregel modifiziert.
Als Lernregel wird
der Backpropagation Algorithmus
eingesetzt. Dabei wird iterativ die Konfiguration des Netzes mit der minimalen
Fehlersumme über alle Trainingsmuster ermittelt.
Dem Netz werden
während des Lernvorgangs in mehreren Schritten die Trainingseingabemuster
präsentiert. In Abhängigkeit vom auftretenden Fehler (d.h. der Falschheit der errechneten
Netzausgabe im Vergleich zu den erwarteten Werten) werden die Gewichte der
Verbindungen über eine rückwärtsgerichtete Fehlerweitergabe modifiert. Da die
erwarteten Ausgabewerte bekannt sind spricht man von überwachtem Lernen.
Durch die
Anpassung der Gewichte sollte bei erfolgreichem Lernen der Fehler im nächsten
Iterationsschritt geringer ausfallen.
Ziel ist es das
globale Minimum der Fehlerfunktion zu finden, was allerdings nicht immer
möglich ist da die Gefahr besteht dass der Fehlerwert um ein lokales Minimum
oszilliert oder ein Tal der Fehlerfunktion nicht mehr in Richtung des globalen
Minimums verlassen werden kann.
Ablauf des
Lernvorgangs:

![]()
Der Fehler der einzelnen Neuronen berechnet sich abhängig von der
jeweiligen Schicht mittels:
für die
Outputschicht
für Input- und
Hiddenschicht
Die zweite Abbruchbedingung ist meist die maximale Anzahl von
Lernschritten, nach deren Erreichen allerdings nicht immer ein akzeptables
Ergebnis vorliegen muss.
|
|
Lernrate |
|
|
Fehler des
Elements i |
|
|
Output des
Elements j |
|
|
Momentum |
|
|
Gewichtsänderung
des vorherigen Schrittes |
Ein möglichst großer Wert der Lernrate resultiert in einem schnellen
Absinken des Fehlerwertes, wodurch allerdings die Gefahr steigt dass der
Fehlerwert über das Minimum der Fehlerfunktion hinausläuft.
Der Momentumfaktor bestimmt wie stark der Wert der alten Gewichtsänderung
in das neue Ergebnis einfliesst.
Mouse Gesture Recognizer ist auf allen Windows Systemen ab Windows 98 mit
installiertem .NET Framework lauffähig.
Vor der
Installation des eigentlichen Programms muss zunächst die .NET Laufzeitumgebung
auf dem Zielsystem installiert werden falls dies nicht bereits zuvor geschehen
ist. Das .NET Framework enthält die Laufzeitumgebung sowie
Kommandozeilencompiler und wird von Microsoft kostenlos angeboten (z.B. unter http://www.microsoft.com/net).
Zum Ausführen und
Compilieren von Programmen ist das .NET Framework ausreichend. Zum Entwickeln
eigener Applikationen empfiehlt es sich allerdings das ebenfalls kostenlose
.NET SDK (zusätzliche Dokumentationen und Tools) oder das kommerzielle Visual
Studio .NET zu verwenden.
Sollte Visual
Studio .NET nicht verfügbar sein kann auch die kostenlose Entwicklungsumbegung
Sharp Develop (http://www.icsharpcode.net) verwendet werden, die sich allerdings zur
Zeit noch in der Entwicklung befindet.
Die Installation Anwendung
erfolgt menügesteuert durch den Aufruf von Setup.exe. Die Dateien werden in das
ausgewählte Verzeichnis kopiert, ansonsten werden keinerlei Änderungen wie z.B.
Registry-Einträge am System vorgenommen.
Zum Starten der
Anwendung muss nach erfolgreicher Installation die Datei GestureDemo.exe aus
dem Zielverzeichnis gestartet werden.
Alternativ kann
auch der Quelltext selbst kompiliert und das Programm direkt aus Visual Studio
.NET gestartet werden.
Zur Deinstallation
genügt es den erstellten Ordner zu löschen, es ist aber auch möglich den
Software Dialog der Windows Systemsteuerung zu verwenden.
Die Oberfläche
des Programms gliedert sich in eine Menüleiste, ein Property Grid zur
Konfiguration und Datendarstellung, eine Combobox zur Auswahl der aktuellen
Geste mit einem Button zum Hinzufügen neuer Patterns, eine Statuszeile sowie
einer freien Fläche in der die Gesten gezeichnet werden können.

Das File Menü
dient neben dem Beenden des Programms dem Laden und Speichern von
Daten-Dateien, sowie dem Importieren oder Exportieren von Gesten-Dateien.
Die Dateien mit
der Endung .data enthalten neben den
aufgezeichneten Gesten auch das komplette Neuronale Netz inklusive der
aktuellen Gewichte sowie die Konfigurationsoptionen, während die Dateien mit
der Endung .gesture nur die aufgezeichneten
Gesten enthalten. Zum Speichern und Laden wird der Serialisierungsmechanismus
von .NET verwendet.
Dieses Menü
enthält die Kommandos zum Starten und Beenden des Lernvorgangs, zum Erkennen
des aktuellen Musters, sowie zum Zurücksetzen der Gewichte des Netzes auf
Zufallswerte.
Über dieses Menü
können die aufgezeichneten Gesten komplett gelöscht werden, wobei wahlweise nur
die Patterns oder auch die Einträge für den Namen der Geste gelöscht werden.
Dieses Menü
enthält momentan nur einen About Dialog.
Für die
Konfiguration des Programms wird das sehr praktische PropertyGrid Control verwendet,
welches es ermöglicht auch umfangreiche Eigenschaften von Objekten darzustellen
und zu editieren ohne spezielle Dialoge zu entwerfen die bei einer Änderung des
Objekts auch entsprechend angepasst werden müssten.
Folgende
Eigenschaften sind verfügbar:
|
Eigenschaft |
Beschreibung |
|
KnownGestures |
die Liste mit
bekannten Gesten. Ermöglicht das Hinzufügen und Löschen von Gesten und den
Zugriff auf die jeweiligen
Patternlisten |
|
LastPattern |
das zuletzt aufgezeichnete
Mausbewegungsmuster |
|
NeuralNetwork |
die Optionen
des Neuronalen Netzes |
|
AnchorPointNumber |
die Anzahl der Stützpunkte
die eine Geste repräsentieren |
|
MouseButton |
die für das
Aufzeichnen von Gesten verwendete Maustaste |
|
ShowAnchorPoints |
bestimmt ob die
Knotenpunkte der Geste nach dem Aufzeichnen angezeigt werden sollen |
|
AnchorPen |
die Visualisierungsoptionen
der Stützpunkte |
|
DrawingPen |
die Visualisierungsoptionen
für das Zeichnen der Gesten |
Anhand der
folgenden Beispielsession soll die Verwendung der Programms demonstriert
werden.


Für die Entwicklung
von Mouse Gesture Recognizer wurde die Programmiersprache C# (gesprochen
als C Sharp), die im Rahmen von Microsofts .NET Initiative entstanden ist, verwendet.
Bei .NET handelt
es sich um eine umfassende Strategie von Microsoft zur modernen
objektorientierten Softwareentwicklung mit der auf längere Sicht bisherige
Technologien wie COM und ActiveX bei der Entwicklung von Windows Anwendungen
abgelöst werden sollen.
Neben einigen Serverprodukten
und Dienstleistungen versteht man unter der .NET Plattform vorallem das .NET
Framework welches die Laufzeitumgebung CLR (Common Language Runtime) sowie die
umfangreiche Klassenbibliothek FCL (Framework Class Library) umfasst.
Das Konzept ist
vergleichbar mit Java, ist aber im
Gegensatz dazu nicht an eine bestimmte Programmiersprache gebunden. Alle
Compiler erzeugen eine, Intermediate Language (IL oder MSIL) genannte
Zwischensprache, die zur Laufzeit durch einen Just In Time Compiler übersetzt
wird. Microsoft liefert die Sprachen C#, VB.NET und Managed C++ mit, inzwischen
ist aber eine Vielzahl anderer Programmiersprachen für die .NET Platform
umgesetzt worden. Innerhalb eines Projektes können mehrere Sprachen auf
Assembly-Ebene gemischt werden. Unter einer Assembly versteht man eine eigentständige
.NET Komponente die entweder ein ausführbares Programm oder eine Bibliothek
sein kann.
Teile von .NET
sowie die Programmiersprache C# sind inzwischen ein ECMA Standard wodurch
alternative Implementierungen z.B. für Linux durch das Mono Projekt (http://www.go-mono.com) möglich wurden.
Die
wahrscheinlich bedeutendste, und für dieses Projekt vewendete, Programmiersprache
für die .NET Platform ist C#. Die Sprache C# wurde eigens für .NET entwickelt
und erinnert von der Syntax her stark an Java und teilweise an C++ wurde aber
um viele sinnvolle Features wie Properties, Indexer, typsichere Enums und ein
gutes Event-Handling erweitert.
Die Grundlage des
Projekts bildet die Bibliothek NeuralNetworkLibrary, welche die
Funktionalität eines neuronalen Netzes kapselt. Es wurde Wert auf die
Erweiterbarkeit gelegt, so dass es ohne größeren Aufwand möglich sein sollte
andere Netztypen oder Lernalgorithmen neben dem mitgelieferten Backpropagation
Algorithmus zu implementieren.
Aufbauend auf
dieser Funktionalität kümmert sich die Bibliothek GestureRecognition um
das Aufzeichnen von Mausbewegungen sowie das Aufbereiten der Daten für das
neuronale Netz. Durch Einbindung dieser Bibliothek können eigene Anwendungen
mit der Gestenerkennung ausgestattet werden.
Zur Demonstration
der Funktionalität dieser Bibliothken wird das Programm GestureDemo
mitgeliefert welches dem Anwender umfangreiche Konfigurationsmöglichkeiten für
eigene Experimente zur Verfügung stellt.
Die Methode ComputeOutput der Klasse Neuron berechet die Ausgabe eines
Neurons in Abhängigkeit von den eingehenden Verbindungen unter Verwendung von
konfigurierbaren Funktionen für Eingabe, Aktivierung und Ausgabe.
/// <summary>
/// Computes the
output of the neuron.
/// </summary>
public void
ComputeOutput()
{
if (this.inputFunction != null && this.inputConnections.Count
> 0)
{
// call the input function to compute
the net input from
// the incoming connections
this.netInput = this.inputFunction.Compute(this.inputConnections);
}
else
{
// input layer
(no input connections, use external input instead)
this.netInput = this.input;
}
// add bias
if (this.isUsingBias)
{
this.netInput += this.biasWeight;
}
// compute activation
if (this.activationFunction != null)
{
this.activation
= this.activationFunction.Compute(this.netInput);
}
else
{
this.activation = this.netInput;
}
// compute output
if (this.outputFunction != null)
{
this.output = this.outputFunction.Compute(this.activation);
}
else
{
this.output = this.activation;
}
}
Um eine flexible
Wahl der Funktionen zu ermöglichen wurde mit Interfaces gearbeitet, so dass
verschiedene Implementierungen ohne größere Änderungen am Programm verwendet
werden können.
/// <summary>
/// Provides an
interface of an input function.
/// </summary>
public interface
IInputFunction
{
double Compute(ConnectionList input);
}
/// <summary>
/// Provides an
interface for activation functions.
/// </summary>
public interface
IActivationFunction
{
string Description { get; }
double Compute(double input);
double ComputeDerivation(double input);
}
Implementierung der sigmoiden Funktion:
/// <summary>
/// Provides a
sigmoid activation function.
/// </summary>
[Serializable]
public class
SigmoidFunction : IActivationFunction
{
#region Fields
private double gain;
#endregion
#region Properties
/// <summary>
/// Gets the description of the function.
/// </summary>
public string Description
{
get { return "Sigmoid"; }
}
/// <summary>
/// Gets or sets the gain factor of the
sigmoid function.
/// </summary>
public double Gain
{
get { return this.gain; }
set { this.gain = value; }
}
#endregion
#region Methods
public double Compute(double input)
{
return (double) (1.0 /
(1.0 + Math.Exp(-this.gain * input)));
}
public double
ComputeDerivation(double input)
{
double y =
Compute(input);
return (y * (1 -
y));
}
#endregion
}
In diesem Schritt
wird ausgehen von der Ausgabeschicht der Fehler durch das Netz propagiert. Die
aktuelle Gewichtsänderung mittels der Delta Regel errechnet. Die
Gewichtsänderungen des aktuellen Schritts werden jeweils gespeichert da sie in
die Berechnung der nächsten Schritts mit eingehen.
/// <summary>
/// Runs a feeds
backward pass through the network.
/// </summary>
/// <param
name="targetOutput">The target output vector.</param>
public void FeedBackward(double[]
targetOutput)
{
for (int l = layers.Count - 1; l >
0; l--)
{
// output layer
if (l == layers.Count - 1)
{
// calculate errors for output layer
for (int n = 0; n
< this.layers[l].Count;
n++)
{
this.layers[l][n].ComputeError(targetOutput[n]);
}
}
else
{
foreach (Neuron n in this.layers[l])
{
n.ComputeError();
}
}
// adjust weights for connections
to each neuron in the
// current layer
foreach (Neuron n in this.layers[l])
{
double
weightChange;
foreach (Connection
c in
n.InputConnections)
{
// delta rule
weightChange = this.learnRate *
n.OutputError *
c.OtherNeuron.Output + this.momentum *
c.LastWeightChange;
c.Weight +=
weightChange;
c.LastWeightChange =
weightChange; }
if
(n.IsUsingBias)
{
// adjust bias
weightChange = this.learnRate *
n.OutputError +
this.momentum * n.LastBiasChange;
n.BiasWeight
+= weightChange;
n.LastBiasChange
= weightChange;
}
}
}
}
Die Methode Train
startet das Training des neuronalen Netzes mit den übergebenen
Trainingsmustern. Der Lernvorgang wird abgebrochen wenn ein akzeptables
Fehlerminimum oder die maximale Anzahl von Lernschritten erreicht ist. In jedem
Durchlauf werden dem Netz jeweils alle Trainingsmuster präsentiert die das Netz
vorwärts und rückwärts durchlaufen. Anschliessend wird das Fehler berechnet.
Über die Events StepFinished und LearningFinished wird die Aussenwelt über den Verlauf des
Lernvorgang informiert.
/// <summary>
/// Trains the
neural network using the given patterns.
/// </summary>
/// <param
name="patterns">An array of training patterns.</param>
public void
Train(ITrainingPattern[] patterns)
{
this.currentError = 100;
while ((this.currentIteration < this.maximumIterations)
&&
(this.currentError > this.errorLimit))
{
this.currentIteration++;
double error =
0.0;
foreach
(ITrainingPattern p in patterns)
{
FeedForward(p.Input);
FeedBackward(p.Output);
foreach (Neuron n in this.layers[this.layers.Count
- 1])
{
error +=
Math.Pow(n.OutputError, 2);
}
}
this.currentError = error;
// trigger event
if (this.StepFinished
!= null)
{
this.StepFinished(this, new
NeuralNetworkEventArgs(
this.currentIteration, this.currentError));
}
}
this.currentIteration = 0;
if (this.LearningFinished != null)
{
this.LearningFinished(this, null);
}
}
Bei der
Aufzeichnung von Mausbewegungen erhält keine feste Anzahl von Punkten da diese
abhängig von der Geschwindigkeit der Bewegung ist.
Da das neuronale
Netz aber Muster von einheitlicher Größe erwartet müssen die Trainingsdaten von
der Methode UnifyPath der MouseMovement Klasse zuvor aufbereitet werden.
Sind mehr Punkte
als benötigt vorhanden werden solange die beiden am nähesten
beieinanderliegenden Punkte durch einen einzelnen neuen Punkt ersetzt bis die
gewünschte Maximalzahl erreicht wurde.
Sind zuwenig
Punkte vorhanden werden solange neue Punkte eingefügt bis der geforderte
Mindeswert erreicht wurde.
/// <summary>
/// Adds or removes
points from the path until the wanted number of
/// points is
reached, so all paths have the same number of anchor
/// points.
/// </summary>
public void UnifyPath(int
anchorPointNumber)
{
Point
p1 = Point.Empty;
Point
p2 = Point.Empty;
double
d = 0.0; // distance
if (anchorPointNumber < 1) return; // abort
// there are too much points so the
number has to be reduced
while (this.path.Count >
anchorPointNumber)
{
Point p1min
= Point.Empty;
Point p2min
= Point.Empty;
double dmin
= Double.MaxValue;
// search for the 2 points with
the shortest line, calculate
// the mean and replace the 2
points with the new one
// (always keep the first and the
last point).
for (int i = 1; i
< this.path.Count
- 2; i++)
{
p1 = (Point) this.path[i];
p2 = (Point) this.path[i +
1];
d
= Math.Sqrt(Math.Pow(p1.X - p2.X, 2) +
Math.Pow(p1.Y - p2.Y, 2));
if (d <
dmin)
{
dmin = d;
p1min = p1;
p2min = p2;
}
}
p2min.X = (p1min.X + p2min.X) / 2;
p2min.Y = (p1min.Y + p2min.Y) / 2;
//
System.Console.WriteLine("Removing point " + i);
this.path.Remove(p1min);
}
// there are too few points so the
number has to be increased
while (this.path.Count <
anchorPointNumber)
{
Point p1max
= Point.Empty;
Point p2max = Point.Empty;
int insertIndex = 0;
double dmax
= 0.0;
// search for the longest distance
between 2 points and add
// a new point between them
for (int i = 0; i
< this.path.Count
- 1; i++)
{
p1 = (Point) this.path[i];
p2 = (Point) this.path[i +
1];
d = Math.Sqrt(Math.Pow(p1.X
- p2.X, 2) +
Math.Pow(p1.Y - p2.Y,
2));
if (d >
dmax)
{
dmax = d;
p1max = p1;
p2max = p2;
insertIndex = i + 1;
}
}
Point pnew = new Point(
(p1max.X + p2max.X) / 2,
(p1max.Y + p2max.Y) / 2);
this.path.Insert(insertIndex,
pnew);
}
}
Um die
Unabhängigkeit von der Skalierung und Position der Gesten zu erreichen wird
nicht direkt mit den Punkten sondern mit zuvor berechneten Winkeln gearbeitet.
Die Methode CalculateAngels errechnet
die Ausrichtungen der Teilstrecken zwischen 2 Punkten im Koordinatensystem.
Diese werden anschliessend zum Training oder Recall verwendet.
/// <summary>
/// Calculates the
angles between the lines of the path.
/// </summary>
public void
CalculateAngles()
{
this.angles = new double[this.path.Count
- 1];
for (int i = 0; i < this.path.Count -
1; i++)
{
Point p1 = this.path[i];
Point p2 = this.path[i +
1];
int dx = p1.X - p2.X; // y-axis delta
int dy = p1.Y - p2.Y; // x-axis delta
double angle;
if (dx != 0 || dy != 0)
{
double cos = dy / Math.Sqrt(Math.Pow(dx, 2) +
Math.Pow(dy, 2));
if (dx > 0)
{
angle = 360.0 -
(Math.Acos(cos) * 180.0 / Math.PI);
}
else
{
angle =
Math.Acos(cos) * 180.0 / Math.PI;
}
this.angles[i] =
angle;
}
}
}