Mausgesten Erkennung

Das folgende Tutorial behandelt eine einfache Möglichkeit der Mausgesten Erkenung ohne Neurale Netze. Das System wird mit Hilfe von Winkeln und Toleranzen aufgebaut.

Der Gesture-Klassen-Header

CGesture
{
private:
vector<float> prv_GestureAngles;
vector<vector3> prv_MousePoints;

public:
int LoadFile(string File);
void AddMousePoint(float X, float Y);
void ClearMousePoints();
void GestureMatch();
void DrawGestureFile();
void DrawMouseGestures();
};

In dem vector prv_GestureAngles werden wir später die Daten des Gesture Files abspeichern. In einem kompletten Spiel oder einer Anwendung würden sämtliche Files auf einmal geladen werden, für unser Beispielprogramm reicht jedoch eine Datei.
prv_MousePoints hält alle Mauskoordinaten die wir für die Tests brauchen.
Auf die Funktionen gehen wir nun im Folgenden ein.

Die Gesture Files

+ 90°
+
+
+
0° + 180°
+ + + + + + + + + + + +
360° +
+
+
+
+ 270°

Unsere Gesture Files bestehen aus Winkelanngaben in jeder Reihe, nach dem oben stehenden Koordinatensystem.
Um zum Beispiel ein L anzugeben würden wir also 270 und 180 in die Datei schreiben falls wir das L von oben nach unten und dann nach rechts malen möchten.
Die Gradzahlen beziehen sich immer auf den letzten Punkt, an dem wieder ein neues Koordinatensystem Angleegt wird.

Funktion: LoadFile

Mit der folgenden Funktion werden wir die Gesture Files laden.

int CGesture::LoadFile(string File)
{
ifstream TempFile;

string TempPath = "gestures/" + File;
TempFile.open(TempPath.c_str());

if(TempFile == NULL)
{
cout << "File not found!" << endl;
return 1;
}

while(!TempFile.eof())
{
float ReadLn;
TempFile >> ReadLn;
prv_GestureAngles.push_back(ReadLn);
cout << ReadLn << endl;
}

TempFile.close();

return 0;
}

Wir werden unsere Gesture Files alle in "./gestures/" suchen. Sollte ein Fehler beim Öffnen auftreten beenden wir sofort die Funktion (und das Programm). Falls die Datei erfolgreich geöffnet wurde, lesen wir diese Zeile für Zeile ein und speichern die Werte in dem dafür vorgesehenen Vektor.

Funktion: AddMousePoint & ClearMousePoints

void CGesture::AddMousePoint(float X, float Y)
{
Vector3 P1 = Vector3(X, Y, 0.0f);
prv_MousePoints.push_back(P1);
}

void CGesture::ClearMousePoints()
{
prv_MousePoints.clear();
cout << "Cleared!" << endl;
}

Mit diesen beiden Funktionen speichern wir die Mauskoordinaten in einem vector als Vector bzw. löschen diesen komplett. Das Löschen dient dazu, damit wir falls wir uns "vermalt" haben, wieder von vorne beginnen können ohne das Programm beenden zu müssen.

Funktion: GestureMatch

void CGesture::GestureMatch()
{
if(prv_MousePoints.size() > prv_GestureAngles.size())
{
int FoundGestures = 0;
int counter = 0;
float LastRes = 65536;
for(int i = 0; i < prv_MousePoints.size()-1; i++)
{
if((prv_MousePoints[i].y == prv_MousePoints[i+1].y) &&
(prv_MousePoints[i].x == prv_MousePoints[i+1].x))
continue;

float Res = atan2f(prv_MousePoints[i].y - prv_MousePoints[i+1].y,
prv_MousePoints[i].x - prv_MousePoints[i+1].x) * 180.0f / PI;
if(Res < 0)
Res = 360 - (Res * -1.0f);

if((Res > LastRes-TOLERANCE) && (Res < LastRes+TOLERANCE))
continue;

LastRes = Res;

if(((Res > prv_GestureAngles[counter]-TOLERANCE) &&
(Res < prv_GestureAngles[counter]+TOLERANCE)) ||
(TOLERANCE-prv_GestureAngles[counter] > (Res-360.0f)*-1)||
(Res-360.0f < TOLERANCE-prv_GestureAngles[counter]))
{
counter++;
FoundGestures++;
} else {
break;
}
}
if(FoundGestures >= prv_GestureAngles.size())
cout << "Gesture Found! " << endl;
} else {
cout << "More Mouse Coordinates needed "
<< prv_MousePoints.size()
<< "/" << prv_GestureAngles.size();
}
}

Bei dieser Funktion handelt es sich um das Herzstück der gesamten Klasse. Hier ist der Ort an dem wir testen ob das Gezeichnete wirklich mit dem Gesture File übereinstimmt.
Als Erstes testen wir ob überhaupt genug Mauskoordinaten vorhanden sind. Falls wir weniger Koordinaten haben als wir benötigen brechen wir den Test sofort ab.
Haben wir genug Koordinaten gehen wir diese einzeln durch, wobei wir identische Koordinaten nicht berücksichtigen.
Für unsere Test rechnen wir alle Polarkoordinaten in Winkel um.
Nun da wir die Winkel haben testen wir den Winkel gegen den Vorgänger. Da es fast unmöglich ist eine perfekte Linie zu zeichnen darf unser Vorgänger eine gewisse Toleranz haben. Sind wir noch innerhalb dieser Toleranz nutzen wir einfach den bestehenden Winkel und brechen den Test ab.

+
+ 0 Durch den Test können wir aus der Reihe (0)
+ X tanzende Punkte (X) ausschließen.
+ 0
+ + + + + + +
+

Der letzte Test ist der Schwierigste und zugleich der Wichtigste. An dieser Stelle haben wir einen Eckpunkt an dem wir testen müssen ob die nächste Linie in dem Winkel liegt den wir im Gesture File vorgegeben haben. Natürlich wird hier auch eine gewisse Toleranz eingabut, weil ein perfekter Winkel so gut wie nie vom Menschen erreicht wird. Gerade die Toleranz bringt aber ein gewisses Problem mit sich. Beispiel: In unserem Gesture File steht 359° und unser Klick hat einen Winkel von 2°. Mit dem normalen Test müsste der Wert zwischen 359 +- Toleranz liegen. Da wir ab 360° wieder bei 0° zählen müssen die beiden Zusatzbedingungen vorhanden sein.
Wenn dann alle Winkel passen können wir davon ausgehen das die Gesture gefunden wurde.

Funktion: DrawGestureFile && DrawMouseGestures

Diese beiden Funktionen zeichnen das Gesture File bzw das was gezeichnet wurde. Da dieser Teil von der Grafik API abhängt, könnt ihr euch im Sourcecode die OpenGL implementation anschauen.

Screenshot und Download


Sourcecode: mouse.tgz 90kb
Entpacken mit WinRAR (Windows) oder "tar -xvzf mouse.tgz" (Linux).
Mit der Linken Maustaste wird ein Punkt gesetzt (bzw beim Festhalten eine Linie gezogen). Die Rechte Maustaste testet ob das Muster erkannt wurde und die mittlere Maustaste löscht alles.