Programowanie Kinecta w Javie
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.
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:
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.
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 grupie na Facebooku.