Programowanie Kinecta w Javiea

Kinect to ciekawy wynalazek od Microsoftu, który stworzony został głównie z myślą o rozrywce na konsoli Xbox. Dla tych, którzy pierwszy raz o nim słyszą jest to niewielkich rozmiarów czarne pudełko, którego zadaniem jest rozpoznawanie przede wszystkim ludzkich ruchów w celu umożliwienia interakcji z konsolą lub komputerem.

kinnect

Jakiś czas temu postanowiłem kupić wspomniane urządzenie z myślą, że jest to całkiem fajny gadżet, który może mieć wiele zastosowań. W tym wpisie skupimy się na podstawowej konfiguracji i stworzeniu aplikacji, która pozwoli sterować kursorem myszy na naszym komputerze za pomocą ruchu rąk.

Instalacja i niezbędne biblioteki

Sensor Kinect został stworzony z myślą o współpracy z urządzeniami i systemami Microsoftu, więc domyślne SDK posiada wsparcie dla języków C# i C++. Powstało jednak kilka bibliotek napisanych w Javie, które obudowują natywne możliwości i pozwalają dobrać się do wielu przydatnych informacji. Na start będziemy potrzebowali Kinect SDK. Tutaj uwaga, ponieważ do starszej wersji Kinecta musimy pobrać SDK w wersji 1.8 natomiast do Kinecta 2 analogicznie SDK 2.0 lub nowsze. Nowszy sensor wymaga także m.in. złącza USB 3.0. Druga niezbędna rzecz to biblioteki odpowiednie dla naszego systemu. Zdecydowanie najlepszą biblioteką Javy do obsługi komunikacji z Kinectem jest J4K, którą można pobrać tutaj. Ściągamy plik ufdw.jar oraz biblioteki natywne. W moim przypadku pobrałem biblioteki dla systemu 64-bitowego i starszej wersji Kinecta(j4k-natives-windows-amd64.jar). Kinecta należy podłączyć do komputera, Windows powinien automatycznie rozpoznać urządzenie i zainstalować niezbędne sterowniki.

Tworzymy projekt

W eclipse tworzymy nowy projekt, a w nim najlepiej dodatkowy folder lib, do którego wrzucamy biblioteki(plik ufdw.jar oraz plik dll, który znajduje się w archiwum biblioteki natywnej). Konfigurujemy Build Path wskazując na lokalizację natywnej biblioteki: 

kinnect 2

kinnect project 1

Nasz program będzie na tyle prosty, że całość zmieści się tylko w jednej klasie. Do pobierania danych z Kinecta wykorzystamy podłączoną bibliotekę, natomiast do sterowania kursorem myszy klasę Robot z JDK.  Kinect.java

package pl.javastart.kinect;

import java.awt.AWTException;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Robot;
import java.util.Scanner;

import edu.ufl.digitalworlds.j4k.J4KSDK;
import edu.ufl.digitalworlds.j4k.Skeleton;

public class Kinect extends J4KSDK {

	public static final double MOVE_AREA = 0.2;
	Robot robot;

	public Kinect() {
		super();
		try {
			robot = new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onColorFrameEvent(byte[] arg0) {
	}

	@Override
	public void onDepthFrameEvent(short[] arg0, byte[] arg1, float[] arg2, float[] arg3) {
	}

	@Override
	public void onSkeletonFrameEvent(boolean[] skeletons, float[] positions, float[] orientations, byte[] state) {
		// obiekt reprezentujący osobę
		Skeleton skeleton = null;
		// wyszukujemy pod jakim id biblioteka wykrywa "szkielet" osoby
		for (int i = 0; i < skeletons.length; i++) {
			if (skeletons[i]) {
				skeleton = Skeleton.getSkeleton(i, skeletons, positions, orientations, state, this);
			}
		}
		// jeśli wykryto szkielet, przesuwamy kursor
		if (skeleton != null) {
			moveMousePointer(skeleton);
		}
	}

	private void moveMousePointer(Skeleton skeleton) {
		//odczytujemy położenie kursora myszy
		PointerInfo pointerInfo = MouseInfo.getPointerInfo();
		Point currentPos = pointerInfo.getLocation();
		//odczytujemy położenie prawej dłoni
		float handX = skeleton.get3DJointX(Skeleton.HAND_RIGHT);
		float handY = skeleton.get3DJointY(Skeleton.HAND_RIGHT);
		//przesuwamy w poziomie
		moveVertical(handX, (int)currentPos.getX(), (int)currentPos.getY());
		//wczytujemy aktualne położenie kursora
		pointerInfo = MouseInfo.getPointerInfo();
		currentPos = pointerInfo.getLocation();
		//przesuwamy w pionie
		moveHorizontal(handY, (int)currentPos.getX(), (int)currentPos.getY());
	}

	private void moveVertical(float handX, int pointerX, int pointerY) {
		if (handX > MOVE_AREA) {
			robot.mouseMove(pointerX + 5, pointerY);
		} else if (handX < -MOVE_AREA) {
			robot.mouseMove(pointerX - 5, pointerY);
		}
	}

	private void moveHorizontal(float handY, int pointerX, int pointerY) {
		if (handY > MOVE_AREA) {
			robot.mouseMove(pointerX, pointerY - 5);
		} else if (handY < -MOVE_AREA) {
			robot.mouseMove(pointerX, pointerY + 5);
		}
	}

	public static void main(String[] args) {
		Kinect kinect = new Kinect();
		kinect.start(Kinect.DEPTH | Kinect.COLOR | Kinect.SKELETON | Kinect.XYZ | Kinect.PLAYER_INDEX);
		new Scanner(System.in).nextLine();
	}
}

Najważniejsze w całym kodzie jest to, że klasa J4KSDK, którą rozszerzamy posiada 3 metody abstrakcyjne, które wywoływane są w zależności od typu występujących zdarzeń. My obsługiwali będziemy zdarzenia zmiany stanu wykrytego szkieletu, czyli onSkeletonFrameEvent().  Reszta kodu posiada odpowiednie komentarze, które powinny wszystko wyjaśniać. MOVE_AREA to stała, która definiuje martwy obszar - gdy nasza ręka będzie więc w centrum osi XY, to kursor nie będzie się poruszał. Warto pamiętać, że współrzędna Y na ekranie rośnie w dół, więc odwrotnie do tego do czego jesteśmy przyzwyczajeni. 

xy

Efekt

Poniżej prezentacja zachowania się działającej aplikacji.

Kolejnym razem spróbujemy do tego dodać jeszcze komunikację z Arduino.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzieliś spostrzeżeniami? Zapraszamy dyskusji na naszej grupe na Facebooku.