Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Information

Die Microsoft Kinect ist eine Kamera, welche für die Spielkonsole Xbox entwickelt wurde. Das Besondere an dieser Kamera ist ihre Möglichkeit, neben einem normalen RGB Bild, ein Tiefenbild ihrer Umgebung zurückzugeben. Damit wird es möglich komplexere Computer Vision Aufgaben zu implementieren. Darüber hinaus bietet das sog. Microsoft Kinect SDK die Möglichkeit das Skelett einer Person zu erkennen. Durch das "Skeleton-Tracking" ist es möglich festzustellen, wo die Gelenke einer Person sind und welche Gesten und Bewegungen sie ausführt. Leider ist dieses SDK nur für Windows erhältlich und wir arbeiten daher vor allem mit dem Titelbild der Kinect Kamera.

Image Removed

Version 1 und Version 2

Mittlerweile gibt es zwei Versionen der Kinect Kamera: Kinect v1 und Kinect v2. Der wesentliche unterschied besteht in der Auflösung und der Geschwindigkeit mit welcher die Kameras arbeiten. Hier ein kurzer Vergleich der Spezifikationen

VersionKinect v1Kinect v2
Auflösung RGB640x4801920x1080
Framerate30fps30fps
Auflösung Tiefenbild320x240512x424
Maximale Distanzca. 450cmca. 450cm
Minimale Distanzca. 40cmca.50 cm
USB InterfaceUSB 2.0USB 3.0

Generell ist also vor allem die Auflösung des RGB Bildes und des Tiefenbilds mit der Version 2 verbessert worden. Ein weiterer kleiner Vorteil der v2 besteht darin, dass sie mit einer Stativschraube befestigt werden kann.

Programmierung

Wir verwenden die Library "OpenKinect" von Daniel Shiffman. Es existiert auch eine Library von Max Rheiner (ehem. Dozent am IAD), leider funktioniert diese "SimpleOpenNI" nicht mehr für die neue Version der Kinect Kamera. Wer jedoch die Möglichkeiten des Selektion-Trackings aufprobieren möchte, dem sei ein Blick in die Beispiele empfohlen.

Zunächst muss die Library, wie alle anderen Bibliotheken in Processing installiert werden. Dies geht am einfachsten über "Sketch > Library importieren ... > Library hinzufügen". In das Suchfeld kann nun "Open Kinect for Processing" eingetragen werden. Ein Klick auf "Install" installiert die Library und die zugehörigen Beispiele.

Um die Library nutzen zu können, muss diese importiert werden:

import org.openkinect.processing.*;

Danach muss eine Referenz auf das Kinect Objekt gesetzt werden:

Kinect kinect;

Im setup() wird das Objekt dann initialisiert:

void setup() {
  kinect = new Kinect(this);
  kinect.initDevice();
}

Wird eine Kinect Kamera v2 verwendet, muss die Referenz und Initialisierung entsprechend angepasst werden:

Kinect2 kinect;

void setup() {
  kinect = new Kinect2(this);
  kinect.initDevice();
}

Sind die obigen Schritte erfolgt kann im Weiteren direkt auf die Informationen der Kinect zugegriffen werden. Die Library macht uns fünf Informationen zugänglich:

  • PImage (RGB) der Kinect Videokamera.
  • PImage (grayscale) der Kinect IR Kamera.
  • PImage (grayscale) ein Tiefenbild, in dem die Helligkeit der Pixel für den Abstand steht (Heller = näher).
  • PImage (RGB) ein Tiefenbild, bei dem die Farbe für die Entfernung steht. 
  • int[] array ein Array, welches die "Rohdaten" der Entfernung beinhaltet (0 bis 2048).

Let’s look at these one at a time. If you want to use the Kinect just like a regular old webcam, you can access the video image as a PImage!

PImage img = kinect.getVideoImage(); 
image(img, 0, 0);

You can simply ask for this image in draw(), however, if you can also use videoEvent() to know when a new image is available.

void videoEvent(Kinect k) {
  // There has been a video event!
}

If you want the IR image:

kinect.enableIR(true);

With kinect v1 cannot get both the video image and the IR image. They are both passed back via getVideoImage() so whichever one was most recently enabled is the one you will get. However, with the Kinect v2, they are both available as separate methods:

PImage video = kinect2.getVideoImage();
PImage ir = kinect2.getIrImage();

Now, if you want the depth image, you can request the grayscale image:

PImage img = kinect.getDepthImage();
image(img, 0, 0);

As well as the raw depth data:

int[] depth = kinect.getRawDepth();

For the kinect v1, the raw depth values range between 0 and 2048, for the kinect v2 the range is between 0 and 4500.

For the color depth image, use kinect.enableColorDepth(true);. And just like with the video image, there's a depth event you can access if necessary.

void depthEvent(Kinect k) {
  // There has been a depth event!
}

Unfortunately, b/c the RGB camera and the IR camera are not physically located in the same spot, there is a stereo vision problem. Pixel XY in one image is not the same XY in an image from a camera an inch to the right. The Kinect v2 offers what's called a "registered" image which aligns all the depth values with the RGB camera ones. This can be accessed as follows:

PImage img = kinect2.getRegisteredImage()

Finally, for kinect v1 (but not v2), you can also adjust the camera angle with the setTilt()method.

float angle = kinect.getTilt();
angle = angle + 1;
kinect.setTilt(angle);

So, there you have it, here are all the useful functions you might need to use the Processing kinect library:

  • initDevice() — start everything (video, depth, IR)
  • activateDevice(int) - activate a specific device when multiple devices are connect
  • initVideo() — start video only
  • enableIR(boolean) — turn on or off the IR camera image (v1 only)
  • initDepth() — start depth only
  • enableColorDepth(boolean) — turn on or off the depth values as color image
  • enableMirror(boolean) — mirror the image and depth data (v1 only)
  • PImage getVideoImage() — grab the RGB (or IR for v1) video image
  • PImage getIrImage() — grab the IR image (v2 only)
  • PImage getDepthImage() — grab the depth map image
  • PImage getRegisteredImage() — grab the registered depth image (v2 only)
  • int[] getRawDepth() — grab the raw depth data
  • float getTilt() — get the current sensor angle (between 0 and 30 degrees) (v1 only)
  • setTilt(float) — adjust the sensor angle (between 0 and 30 degrees) (v1 only)

For everything else, you can also take a look at the javadoc reference.

 Die Microsoft Kinect ist eine Kamera, welche für die Spielkonsole Xbox entwickelt wurde. Das Besondere an dieser Kamera ist ihre Möglichkeit, neben einem normalen RGB Bild, ein Tiefenbild ihrer Umgebung zurückzugeben. Damit wird es möglich komplexere Computer Vision Aufgaben zu implementieren. Darüber hinaus bietet das sog. Microsoft Kinect SDK die Möglichkeit das Skelett einer Person zu erkennen. Durch das "Skeleton-Tracking" ist es möglich festzustellen, wo die Gelenke einer Person sind und welche Gesten und Bewegungen sie ausführt. Leider ist dieses SDK nur für Windows erhältlich und wir arbeiten daher vor allem mit dem Tiefenbild der Kinect Kamera.

Image Added

Version 1 und Version 2

Mittlerweile gibt es zwei Versionen der Kinect Kamera: Kinect v1 und Kinect v2. Der wesentliche Unterschied besteht in der Auflösung und der Geschwindigkeit mit welcher die Kameras arbeiten. Hier ein kurzer Vergleich der Spezifikationen:

VersionKinect v1Kinect v2
Auflösung RGB640x4801920x1080
Framerate30fps30fps
Auflösung Tiefenbild320x240

512x424

Entfernung Auflösung20484500
Maximale Distanzca. 450cmca. 450cm
Minimale Distanzca. 40cmca.50 cm
USB InterfaceUSB 2.0USB 3.0

Generell ist also vor allem die Auflösung des RGB Bildes und des Tiefenbilds mit der Version 2 verbessert worden. Ein weiterer kleiner Vorteil der v2 besteht darin, dass sie mit einer Stativschraube befestigt werden kann.

Programmierung

Wir verwenden die Library OpenKinect von Daniel Shiffman. Es existiert auch eine Library von Max Rheiner (ehem. Dozent am IAD), leider funktioniert seine SimpleOpenNI Librar nicht mehr mit der neuen Version der Kinect Kamera. Wer jedoch die Möglichkeiten des Selektion-Trackings aufprobieren möchte, dem sei ein Blick in die Beispiele empfohlen.

Zunächst muss die Library, wie alle anderen Bibliotheken in Processing installiert werden. Dies geht am einfachsten über "Sketch > Library importieren ... > Library hinzufügen". In das Suchfeld kann nun "Open Kinect for Processing" eingetragen werden. Ein Klick auf "Install" installiert die Library und die zugehörigen Beispiele.

Um die Library nutzen zu können, muss diese importiert werden:

Code Block
import org.openkinect.processing.*;

Danach muss eine Referenz auf das Kinect Objekt gesetzt werden:

Code Block
Kinect2 kinect;

Im setup() wird das Objekt dann initialisiert:

Code Block
void setup() {
  kinect2 = new Kinect(this);
  kinect2.initDevice();
}

Wird eine Kinect Kamera v1 verwendet, muss die Referenz und Initialisierung entsprechend angepasst werden:

Code Block
Kinect kinect; // ohne "2"
void setup() {
  kinect = new Kinect(this);
  kinect.initDevice();
}

Sind die obigen Schritte erfolgt kann im Weiteren direkt auf die Informationen der Kinect zugegriffen werden.

Video Bild

Zuerst muss der Video-Stream der Kamera in der setup() Funktion aktiviert werden:

Code Block
kinect.initVideo();
kinect.initDevice();

Dann kann das aktuelle Bild gelesen werden: 

Code Block
PImage videoImage = kinect.getVideoImage();

Gezeichnet werden kann das Bild mit:

Code Block
image(videoImage, 0, 0);

Neben dem zeichnen des Bildes in der draw() Funktion, gibt es auch die Möglichkeit, das Bild asynchron zu zeichnen. Das bedeutet, es wird nur dann ein Bild gezeichnet, wenn ein neues vorhanden ist. Die entsprechende Funktion lautet:

Code Block
void videoEvent(Kinect k) {
  // There has been a video event!
}


Code Block
titleBeispiel
collapsetrue
import org.openkinect.processing.*;

Kinect2 kinect;

void setup() {
  size(1920, 1080);

  kinect = new Kinect2(this);
  kinect.initVideo();
  kinect.initDevice();
}

void draw() {
  image(kinect.getVideoImage(), 0, 0);
}

Infrarot Bild

Zuerst muss der Infrarot-Stream der Kamera in der setup() Funktion aktiviert werden:

Code Block
kinect.initIR();
kinect.initDevice();

Dann kann das aktuelle Bild gelesen werden:

Code Block
PImage irImage = kinect.getIrImage();


Code Block
titleBeispiel
collapsetrue
import org.openkinect.processing.*;

Kinect2 kinect;

void setup() {
  size(512, 424);

  kinect = new Kinect2(this);
  kinect.initIR();
  kinect.initDevice();
}

void draw() {
  image(kinect.getIrImage(), 0, 0);
}

Tiefenbild

Zuerst muss der Tiefenbild-Stream der Kamera in der setup() Funktion aktiviert werden:

Code Block
kinect.initDepth();
kinect.initDevice();

Um das Tiefenbild als Graustufen zurückzubekommen wird folgendes gemacht:

Code Block
PImage depthImage = kinect.getDepthImage();


Code Block
titleBeispiel
collapsetrue
import org.openkinect.processing.*;

Kinect2 kinect;

void setup() {
  size(512, 424);

  kinect = new Kinect2(this);
  kinect.initDepth();
  kinect.initDevice();
}

void draw() {
  image(kinect.getDepthImage(), 0, 0);
}

Tiefenarray

Der Nachteil des Graustufen-Tiefenbildes ist es, dass alle Entfernungsinformationen auf Werte zwischen 0 bis 255 (Helligkeit) verteilt werden. Tatsächlich kann die Kinect Kamera aber eine viel genauere Auflösung der Tiefeninformationen liefern. Dazu ist es jedoch nötig, das Tiefenarray aufzurufen und auszulesen. Dies geschieht über:

Code Block
int[] depth = kinect.getRawDepth();


Code Block
titleBeispiel
collapsetrue
import org.openkinect.processing.*;
import peasy.*;

Kinect2 kinect;
PeasyCam cam;

void setup() {
  size(1920, 1080, P3D);
  cam = new PeasyCam(this, 1000);
  cam.setMaximumDistance(5000);
  
  kinect = new Kinect2(this);
  kinect.initDepth();
  kinect.initDevice();
}

void draw() {
  background(0);
  
  int[] depthMap = kinect.getRawDepth();
  int stepSize = 2;
  
  for(int x=0; x<kinect.depthWidth; x+=stepSize) {
    for(int y=0; y<kinect.depthHeight; y+=stepSize) {
      int loc = x+y*kinect.depthWidth;
      
      PVector point = depthToPointCloudPos(x, y, depthMap[loc]);
      
      stroke(255);
      point(point.x, point.y, point.z);
    }
  }
}

PVector depthToPointCloudPos(int x, int y, float depthValue) {
  PVector point = new PVector();
  point.z = (depthValue);
  point.x = (x - CameraParams.cx) * point.z / CameraParams.fx;
  point.y = (y - CameraParams.cy) * point.z / CameraParams.fy;
  return point;
}

static class CameraParams {
  static float cx = 254.878f;
  static float cy = 205.395f;
  static float fx = 365.456f;
  static float fy = 365.456f;
  static float k1 = 0.0905474;
  static float k2 = -0.26819;
  static float k3 = 0.0950862;
  static float p1 = 0.0;
  static float p2 = 0.0;
}

Angleichung

Weil bei der Kinect Kamera der Sensor für das RGB Bild und das Titelbild nicht übereinanderliegen stimmen die Positionen in XY Richtung der einen Kamera, nicht mit denen der anderen überein. Um dies zu korrigieren kann ein RGB Bild angefordert werden, welches durch die Library angepasst wird. Dieses Bild bekommt man über:

Code Block
PImage img = kinect2.getRegisteredImage();


Code Block
titleBeispiel
collapsetrue
import org.openkinect.processing.*;

Kinect2 kinect;

void setup() {
  size(512, 424);

  kinect = new Kinect2(this);
  kinect.initRegistered();
  kinect.initDevice();
}

void draw() {
  image(kinect.getRegisteredImage(), 0, 0);
}

Übersicht der Funktionen

Hier eine Übersicht der Funktionen, welche die Library zur Verfügung stellt:

  • initVideo() — Video Bild initialisieren 
  • initIR()  —Infrarot Bild initialisieren
  • initDepth() — Tiefenbild initialisieren
  • initDevice() — Gerät initialisieren
  • PImage getVideoImage() — RGB Bild
  • PImage getIrImage() — IR Bild
  • PImage getDepthImage() — Tiefenbild
  • PImage getRegisteredImage() — Angeglichenes RGB Bild
  • int[] getRawDepth() — Tiefenarray

Für weitere Informationen: the javadoc reference.

Weitere Beispiele

Code Block
titleKinect Test
collapsetrue
import org.openkinect.processing.*;

Kinect2 kinect;

void setup() {
  size(1024, 848);

  kinect = new Kinect2(this);
  kinect.initVideo();
  kinect.initDepth();
  kinect.initIR();
  kinect.initRegistered();

  kinect.initDevice();
}

void draw() {
  background(0);
  image(kinect.getVideoImage(), 0, 0, kinect.depthWidth, kinect.depthHeight);
  image(kinect.getDepthImage(), kinect.depthWidth, 0);
  image(kinect.getIrImage(), 0, kinect.depthHeight);
  image(kinect.getRegisteredImage(), kinect.depthWidth, kinect.depthHeight);

  fill(255);
  text("Framerate: " + (int)(frameRate), 10, 515);
}


Code Block
titleDepth Threshold
collapsetrue
import org.openkinect.processing.*;

Kinect2 kinect;

int lowerThreshold = 0;
int upperThreshold = 750;

void setup() {
  size(512, 424);

  kinect = new Kinect2(this);
  kinect.initDepth();
  kinect.initDevice();
}

void draw() {
  PImage img = kinect.getDepthImage();
  int[] depthMap = kinect.getRawDepth();

  loadPixels();

  for (int x = 0; x < kinect.depthWidth; x++) {
    for (int y = 0; y < kinect.depthHeight; y++) {
      int loc = x+ y * kinect.depthWidth;
      int rawDepth = depthMap[loc];
      
      if (rawDepth > lowerThreshold && rawDepth < upperThreshold) {
        pixels[loc] = color(150, 50, 50);
      } else {
        pixels[loc] = img.pixels[loc];
      }
    }
  }

  updatePixels();
}


Code Block
titleBackground Removal and mass centre
collapsetrue
import org.openkinect.processing.*;
Kinect2 kinect;
int[] depthMapRef; 
int[] depthMap;
int Threshold = 30;
int depthlength;

// Raw location
PVector loc = new PVector(0, 0);
// Interpolated location
PVector lerpedLoc = new PVector(0, 0);

// Depth data
int[] depth;

void setup() {
  size(640, 520);
  depthlength = 640*480;
  kinect = new Kinect2(this);
  kinect.initDepth();
  kinect.initDevice();
  depthMapRef = new int[depthlength];
}


void draw() {

  float sumX = 0;
  float sumZ = 0;
  float sumY = 0;
  float count = 0;

  fill(255, 255, 255, 1);
  rect(0, 0, width, height);
  PImage img = kinect.getDepthImage();
  depthMap = kinect.getRawDepth();
  
   if (depthMapRef == null) {
      arrayCopy(depthMap, depthMapRef);
  }
  
  img.loadPixels();
  for (int x = 0; x < kinect.depthWidth; x++) {
    for (int y = 0; y < kinect.depthHeight; y++) {
      int loc = x+ y * kinect.depthWidth;
      int difference = abs(depthMap[loc]-depthMapRef[loc]);
      if ( difference  > Threshold) {
        sumX += x;
        sumY += y;
        sumZ += depthMap[loc];
        count++;
        img.pixels[loc] = color(150, 50, 50);
      } else {
        img.pixels[loc] = img.pixels[loc];
      }
    }
  }


  img.updatePixels();
  image(img, 0, 0);
  // find the average location of the "body mass" 
  if (count != 0) {
    loc = new PVector(sumX/count, sumY/count, sumZ/count);
  }
  // Interpolating the location with lerp for a smoother animation
  lerpedLoc.x = PApplet.lerp(lerpedLoc.x, loc.x, 0.3f);
  lerpedLoc.y = PApplet.lerp(lerpedLoc.y, loc.y, 0.3f);
  lerpedLoc.z = PApplet.lerp(lerpedLoc.z, loc.z, 0.3f);
  fill(255);
  text("depth:" +lerpedLoc.z+"", lerpedLoc.x+5, lerpedLoc.y);
  ellipse(lerpedLoc.x, lerpedLoc.y, 5, 5);
}



void mousePressed() {
  background(255);
  arrayCopy(depthMap, depthMapRef);
}