RuZman

7061

Illustration

Abstract

Dieser Blogpost beinhaltet Grundlagen zu JavaFX 3D, der Leap Motion API v2 und die praktische Umsetzung einer 3D Hand. Im ersten Teil dieser Reihe werden relevante Punkte einer Hand im Sichtfeld des Leap Motion Controllers aus der API ermittelt und visualisiert.

JavaFX 3D Basics

Zuerst wird wie bei JavaFX üblich eine Scene mit einer Node angelegt. Vom Prinzip her ist das bereits eine 3D-Applikation. Um die einzelnen Objekte aus einer anderen Perspektive zu sehen, passen wir den Blickwinkel mit der Klasse PerspectiveCamera an. Dort übergeben wir den Parameter true. Dieser sorgt dafür, dass die Kamera (1) bei [0, 0, 0] platziert wird und "in" den Bildschirm gerichtet ist. Erzeugen wir nun eine Sphere, dann befindet sich die Kamera in dessen Inneren. Aus diesem Grund verschieben wir die Kamera weiter hinaus (2):

JavaFX 3D Kamera (1) JavaFX 3D Kamera (2)

Die Klasse App.java generiert folgenden Output:

JavaFX 3D Sphere

Ein kleiner Hinweis zum Koordinatensystem (Leap Motion vs. JavaFX 3D):

Leap Motion - Coordinate System JavaFX 3D - Coordinate System

Um die Logik und die Beispielbilder leichter zu verstehen, werden wird das Projekt in der Vogelperspektive umsetzen. Im Nachhinein lässt sich dies durch einen Perspektivenwechsel der Kamera ändern. D.h. es sollte am Ende in etwa so aussehen:

JavaFX 3D - Leap Motion Hand

Dazu drehen wir die Kamera und schieben sie anstatt in Richtung "Person vor dem Bildschirm" etwas in den Himmel:

camera.setTranslateY(10);
camera.setRotationAxis(Rotate.X_AXIS);
camera.setRotate(90);

Leap Moion einbinden

Nach dem Artikel Leap Motion Java SDK - Maven Installation wird ein Maven-Projekt eingerichtet. Zudem nutzen wir die Library aus Leap Motion Java - Hand Tracking (2). So kann man mit zwei Methoden die Leap Motion bequem in das Projekt einbinden:

public static void main(String[] args) {
	LeapApp.init(true);
	LeapApp.setMode(Mode.INTERACTION_BOX);

	launch(args);
}

Mit init wird die passende Systembibliothek zur Laufzeit ermittelt und eingebunden. Zudem steht ein Framework im Hintergrund zur Verfügung, dass mittels Polling den aktuellsten Zustand (frame) der Leap Motion abfrägt. Dadurch ist es möglich die komplette Logik im JavaFX-Thread einzulagern, um Multithreading und den damit verbundenen Synchronisationsaufwand zu vermeiden. Mit setMode legen wir über den Leap Motion Controller eine virtuelle Box mit dem voreingestellten Interaktionsraum. Das Polling wird mithilfe der Timeline geregelt, welche in start() initialisiert wird:

private void synchronizeWithLeapMotion() {
	Timeline timeline = new Timeline();
	timeline.setCycleCount(Timeline.INDEFINITE);
	timeline.getKeyFrames().add(
			new KeyFrame(Duration.seconds(1.0 / 60.0), ea -> LeapApp
					.update()));
	timeline.play();
}

Die Leap Motion ist nun in der Klasse App.java eingebunden, Daten sind aktuell und jetzt fehlt nur noch ein Listener, welcher sich um die Hände kümmert. Das erledigt der PointMotionListener aus dem eingebundenen Framework.

JavaFX 3D mit Leap Motion

Die Methode pointMoved wird mit jedem Leap.update() aufgerufen, sofern eine Hand im Sichtfeld des Leap Motion Controllers befindet oder grade verlassen hat. Immer wenn eine neue Hand entdeckt wird, speichern wir diese anhand ihrer ID in einer Map ab. Als Value hinterlegen wir eine Sphere. Verlässt die Hand das Sichtfeld des Leap Motion Controllers, wird der Eintrag aus der HashMap entfernt und die Sphere aus der 3D-Welt verbannt. Zudem wird die Position der Sphere anhand der absoluten Koordinaten der dazugehörigen Handfläche gesetzt. Vorerst reichen die X und Z Koordinaten, da die Kamera von oben auf die Hand herabschaut.

@Override
public void pointMoved(PointEvent event) {
	int handId = event.getSource().id();
	Sphere hand = hands.get(handId);

	if(event.leftViewPort()) {
		hands.remove(handId);
		group.getChildren().remove(hand);
	} else if(hand == null) {
		hands.put(handId, hand);
		group.getChildren().add(hand);
	}

	hand.setTranslateX(event.getAbsoluteX());
	hand.setTranslateZ(event.getAbsoluteZ());
}

Aktueller Stand: App.java.

Leap Motion – Skeleton Tracking

Eigentlich sollte man erwarten, dass die offizielle Dokumentation kurz und präzise alle Informationen zum Skeleton Tracking zur Verfügung stellt. So wirklich anfreunden kann man sich aber mit den Informationen in Introducing the Skeletal Tracking Model und API Reference nicht. Aufschlussreicher ist die Abbildung aus dem Blogpost Getting Started with the Leap Motion SDK:

Leap Motion Skelton Hand

Um das ganze nochmal aufzuschlüsseln: Es gibt weiterhin die Position der Fingerspitzen / tip (orange) und die Position der Handfläche / palm (grün). Seit der v2 API kann man zudem die Position des Handgelenks /wrist (grün) und einzelner Knochen / bones (blau) bestimmen. Jeder Knochen hat zwei Verbindungsstücke / joints, welche die Knochen mit prevJoint und nextJoint (orange / rot) verbinden. In der Regel sind das vier Knochen, wobei der Daumen einen weniger hat.

JavaFX 3D – 3D Hand

Obwohl in den vorherigen Abschnitten ein Leap Motion Framework eingebunden wurde, ist es erstrebenswert die Abhängigkeiten einer 3D Hand für die Leap Motion möglichst gering zu halten. Aus diesem Grund erstellen wir die Klasse HandFX3D, welche später nur auf die Leap Motion API v2 und Java 8 API zugreifen wird und von Group erbt. Dazu passen wir zusätzlich die Methode pointMoved an, indem wir die Klasse Sphere durch HandFX3D austauschen.

@Override
public void pointMoved(PointEvent event) {
	int handId = event.getSource().id();
	HandFX3D hand = hands.get(handId);

	if(event.leftViewPort()) {
		hands.remove(handId);
		group.getChildren().remove(hand);
	} else if(hand == null) {
		hand = new HandFX3D(handId);
		hands.put(handId, hand);
		group.getChildren().add(hand);
	}

	hand.update(LeapApp.getController().frame().hand(handId));
}

Zunächst erstellen wir eine Hilfsmethode UtilFX.translate(Node node, com.leapmotion.Vector vector). Mal abgesehen davon, dass damit viel Copy & Paste gespart wird, können wir mit der Methode bequem den Leap Motion Vektor in das JavaFX Koordinatensystem transferieren.

public static void transform(Node node, Vector vector) {
	node.setTranslateX(vector.getX());
	node.setTranslateY(-vector.getY());
	node.setTranslateZ(-vector.getZ());
}

In der Klasse HandFX3D vergeben wir für jede Position (palm, wrist und joints) eine Variable vom Typ Sphere und die Methode update(Hand hand) zur Aktualisierung der Positionsangaben. Somit ist HandFX3D folgendermaßen aufgebaut:

HandFX3D UML class diagram

Siehe: HandFX3D.java.

Demonstration des Skeleton Tracking

Wer sich das Projekt genauer anschauen möchte, kann sich den Quellcode auf GitHub auschecken. Der Tag JavaFX 3D – Hand (1) entspricht dem Stand zum Zeitpunkt dieses Blogposts. Interessierte können sich auf gerne die Demonstration des Projekts auf Youtube anschauen: