Version [92282]
Dies ist eine alte Version von TutoriumMobileEingebetteteIntelligenzSS18 erstellt von FabianEndres am 2018-11-08 10:55:04.
Tutorium Mobile und Eingebettete Intelligenz
Tutor: Timmy Schieck
Adressaten des Lehrangebotes:
Studenten des Studienganges "Mobile Computing"
Ziel des Tutoriums:
Es wird eine App fuer Android erstellt, die in der Lage ist, in die Luft "gemalte" Gesten mit einer hohen Zuverlässigkeit zu erkennen. z.B. Kreise, Linien, Quadrate, etc.
Die App führt die Erkennung mittels eine trainierten neuronalen Netzes durch, was zuvor auf einem Computer trainiert wurde.
Das Training dieses Netzes benötigt Trainings-und Testdaten, die vorher aufgezeichnet werden.
Es werden dazu drei Anwendungen geschrieben:
1. eine App zum aufzeichen und labeln der gewünschten Muster
2. ein Computerprogramm welches die aufgezeichneten Trainingsdaten so verarbeitet wie das Smartphone, und danach das neuronale Netz trainiert
3. eine App die das trainierte neuronale Netz ausführt und damit die Bewegungen erkennt
Wenn nicht anders angegeben, bin ich der Urheber der Bilder, in dieser Wiki.
1. Datengewinnung/-erhebung:
Die meisten Android-Smartphones verfügen über mindestens drei Sensoren zur Bewegungserkennung:
- den (linearen) Beschleunigungssensor,
- das Gyroskop sowie
- den Schwerkraftsensor.
Es gibt weitere Sensoren, das sind aber die Grundlegenden.
Diese Sensoren werden genutzt um die Bewegungen in Signale umzuwandeln.
Genutztes Koordinatensystem
Das Koordinatensystem ist relativ zum Bildschirm des Telefons in seiner Standardausrichtung definiert.
Die Achsen werden nicht vertauscht, wenn sich die Bildschirmausrichtung des Geräts ändert.
Die X-Achse ist horizontal und zeigt nach rechts, die Y-Achse ist vertikal und zeigt nach oben und die
Z-Achse zeigt zur Außenseite der Vorderseite des Bildschirms. In diesem System haben Koordinaten hinter dem Bildschirm negative Z-Werte.
Abb.: Achsen eines Smartphones:
(Quelle: developer.android.com)
Funktionsweise der App
Abb.: Grafische Oberfläche der Aufzeichnungsanwendung
Auf der linken Seite wird die Geste eingestellt die aufgezeichnet wird. Zum Aufzeichnen wird der Knopf in der Mitte gedrückt und gehalten, wenn die Aufzeichnung beendet ist, den Knopf bitte loslassen. Danach schreibt die App selbständig die Dateien mit den entsprechenden Sensorwerten in den Speicher des Telefons.
Abb.: Verzeichnis mit aufgezeichneten Datein auf Smartphone
In dem Bild lässt sich auch schon erkennen wie das Namesschema der Dateien ist:
- rot - Typ der Geste (quadrat, circle, hline...)
- grün - Zufallszahl
- blau - Sensortyp (lin_acc, magf, grav..., bitte beachten es sind mehr Sensoren, als oben aufgeführt)
- .csv
Die Dateien lassen sich entweder manuell per USB auf den PC übertragen (Ordner: Android/data/com.example.schieck.ga/files/*) oder per Email an eine zuvor eingestellte Adresse versenden. Sie enthalten die Bewegungsmuster die später genutzt werden um das neuronale Netz zu trainieren.
Abb.: Inhalt einer csv Datei
In dem Bild sieht man was der Sensor eigentlich macht, es wird zu einem bestimmten Zeitpunkt ein bestimmter Zustand gemessen. Die App schreibt dann diese Zustände in die Dateien.
Es liegen also timestamp-value-paare vor.
Implementierung
Namensschema:
[Geste].[Zufallszahl].[Sensortyp].csv
Das Namensschema hat die Funktion, das nicht erst die Datei geöffnet werden muss um zu wissen, was sie enthält.
Die Zufallszall verhindert (oder macht es zumindest sehr unwahrscheinlich) das man nacharbeiten muss, und Dateien von Hand umbenennen, falls man Dateien aus mehreren Quellen hat.
Damit die App Sensorwerte vom Betriebssystem empfangen kann, wird ein SensorManager genutzt und ein SensorEventListener (stellt Callback zur Verfügung, das die Sensorwerte vom Betriebssystem entgegenimmt) implementiert.
class MainActivity() : Activity(), SensorEventListener {
var sensor_manager: SensorManager? = null
...und im onCreate() Callback initialisiert.
sensor_manager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
Zusatzlich wird ein Variable deklariert die die Abtastrate der Sensoren festlegt. Sie bieten nur grobe Richtwerte, da sich die in den Smartphones verbauten Sensoren, von Gerät zu Gerät unterscheiden, und sich damit auch die Abtastraten unterscheiden.
- Abtastraten
val delay = SensorManager.SENSOR_DELAY_FASTEST
Die Quellcodes können von mir direkt erhalten werden, bitte schreiben sie mich an. schieck@stud.fh-sm.de
2. Vorverarbeitung der Daten:
Anpassen der Zeitstempel
Um die timestamps besser überschauen zu können, wird von allen timestamps der niedrigste abgezogen, das geschieht mit:
fun adjustTimestamps(sample: Sample): Sample {
var first = sample.timestamp.first()
var retVal = sample
sample.timestamp.forEachIndexed { index, fl ->
retVal.timestamp[index] -= first
}
return retVal
}
Betragsquadrat berechnen
Mittels des Betragsquadrat werden aus den Werten für x, y und z, ein Wert gemacht. Dadurch wird erreicht das die Werte Orientierungsunabhängig sind.
TODO: hier gehört ne formel hin, aber
formel
...formel
funktioniert nicht... fun AbsoluteSquare(sample: Sample): Sample {
val sampleSize = sample.x.size
val retVal = Sample(sample.sensor, sample.gesture)
for (i in 0 until sampleSize) {
val absSquare = Math.abs((sample.x[i] + sample.y[i] + sample.z[i]))
retVal.absoluteSquare.add(absSquare)
}
return retVal
}
Fehlende Werte ermitteln
Die Werte die von Android Sensoren zurückgeliefert werden, sind nicht äquidistant (gleicher zeitlicher Abstand zwischen den Messwerten). Das ist aber nötig um sie später einem maschinellem Lernalgorhythmus zuzuführen. Die Sensoren von Android Geräten haben i.d.R. eine Abtastrate von 20-80Hz. Daher ist, verbunden mit der Unsicherheit WANN bestimmte Sensoren, bestimmte Werte zurückliefern, eine starke Fluktuation zu erkennen.
Abb.: Nicht-Äquidistante Abstände bei den Timestamps
Um dieses Problem zu beheben kann man das Signal (linear) interpolieren. Dabei werden die fehlenden Punkte durch die Mittel der umliegenden Punkte aufgefüllt.
TODO: Formel für lineares interpolieren, die YAML für die wiki nervt echt, sie kommt nicht mal mit simplen formeln klar...
fun interpolate(sample: Sample): Sample {
require(count < 2) { "interpolate: illegal count!" }
val retVal = sample
for (i in 0 until sample.size) {
retVal.timestamp[i] = sample.timestamp.first() + i * (sample.timestamp.last() - sample.timestamp.first()) / sample.size
retVal.x[i] = sample.x.first() + i * (sample.x.last() - sample.x.first()) / sample.size
retVal.y[i] = sample.y.first() + i * (sample.y.last() - sample.y.first()) / sample.size
retVal.z[i] = sample.z.first() + i * (sample.z.last() - sample.z.first()) / sample.size
}
return retVal
}
Trainieren von OLVQ1