Kinectplayer

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche

Kinectplayer

Steuerung mit Kinect

Um mit einer Kinect eine GUI bedienen zu können, müssen verschiedene Schritte durchgeführt werden.

Um die Kinect leicht und effektiv ansteuern und die gelieferten Daten auslesen zu können, wurde openframeworks mit dem beiliegenden Treiber in der IDE Codeblocks 12.11 verwendet. Ziel war es, auf der Fläche eines ordinären Tisches die Hände des jeweiligen Benutzers zu erkennen, als Blobs zu identifizieren und bei gefordertem Abstand der Hände zur Tischfläche die Koordinaten des zugeordneten Blobs auszuwerten. Als größte Schwierigkeiten stellten sich hierbei die Binarisierung der Hand nach Hautfarbe im Bild der Farbkamera der Kinect und die große Schwankung der Werte des gelieferten Tiefenbildes zur Identifizierung der Höhe.

Code

Header

#pragma once
 
#include "ofMain.h"
#include "ofxOpenCv.h"
#include "ofxKinect.h"
 
class testApp : public ofBaseApp{
 
   public:
        void setup();
       	void update();
	void draw();
	void keyPressed(int key);
	void keyReleased(int key);
	void mouseMoved(int x, int y );
	void mouseDragged(int x, int y, int button);
	void mousePressed(int x, int y, int button);
	void mouseReleased(int x, int y, int button);
	void windowResized(int w, int h);
	void dragEvent(ofDragInfo dragInfo);
	void gotMessage(ofMessage msg);
 
 
    private:
        ofxKinect kinect;
 
        ofxCvGrayscaleImage depth;
        ofxCvGrayscaleImage depthbg;
 
        ofxCvGrayscaleImage depthdiff;
        ofxCvColorImage color;
 
        ofxCvGrayscaleImage hue;
        ofxCvGrayscaleImage saturation;
        ofxCvGrayscaleImage value;
 
        bool setBg;
        bool blob_activated[6];
        unsigned int blob_distance[6];
 
        ofxCvContourFinder finder;
};

Source

#include "testApp.h"
 
//--------------------------------------------------------------
//Die setup-Funktion wird zu Beginn (beim Starten des Programms) einmalig aufgerufen und initialisiert die benötigten Programmteile
//--------------------------------------------------------------
 
void testApp::setup(){
 
    //Kinect initialisieren
    kinect.init(); 
    kinect.open();
 
    //Speicher für die Bilder alloziieren
    depth.allocate(kinect.width, kinect.height);
    depthbg.allocate(kinect.width, kinect.height);
    depthdiff.allocate(kinect.width, kinect.height);
    color.allocate(kinect.width, kinect.height);
    hue.allocate(kinect.width, kinect.height);
    saturation.allocate(kinect.width, kinect.height);
    value.allocate(kinect.width, kinect.height);
    bin.allocate(kinect.width, kinect.height);
 
    setBg = true;
}
 
//--------------------------------------------------------------
//Die update-Funktion wird zyklisch aufgerufen, in ihr muss immer das kinect.update() erfolgen und es wird nur gearbeitet falls ein neues Bild vorhanden ist
//--------------------------------------------------------------
void testApp::update(){
 
    kinect.update();
 
    if(kinect.isFrameNew())
    {
        //Tiefen- und Farbbild "abholen"
        depth.setFromPixels(kinect.getDepthPixels(),kinect.width, kinect.height);
        color.setFromPixels(kinect.getPixels(),kinect.width, kinect.height);
 
        //Falls benötigt, neues Tiefenhintergrundbild setzen
        if(setBg)
        {
            setBg = false;
            depthbg = depth;
        }
        //Hintergrundsubtraktion beim Tiefenbild
        depthdiff.absDiff(depth, depthbg);
 
        //Konvertierung von RGB nach HSV
        color.convertRgbToHsv();
        color.convertToGrayscalePlanarImages(hue, saturation, value);
 
        //Zeiger auf die Daten zur Manipulation
        unsigned char * huepixels = hue.getPixels();
        unsigned char * satpixels = saturation.getPixels();
        unsigned char * binpixels = value.getPixels();
 
        //Iteration durch alle Pixel 
        for(int x = 0; x < kinect.height * kinect.width; x++)
        {
            //Binarisierung 
            //hue < 15 oder zwischen 170 und 180 (Werte für Finger) und sat zwischen 30 und 120
            if(((huepixels[x] < 15) || ((huepixels[x] > 170) && (huepixels[x] < 180))) &&
               ((satpixels[x] > 30) && (satpixels[x] < 120)))
                binpixels[x] = 255;
            else
                binpixels[x] = 0;
        }
 
        //Glättung des binarisierten Bildes
        value.erode_3x3();
 
        //Blobs finden
        finder.findContours(value, 150, kinect.width * kinect.height / 4, 2, false, true);
 
        //Tiefeninformation in der Mitte der Blobs auswerten (Hand auf richtiger Höhe)
        for(int i = 0; i < finder.nBlobs; i ++)
        {
            blob_distance[i] = kinect.getDistanceAt(finder.blobs[i].centroid);
            if(blob_distance[i] < 50)
                blob_activated[i] = true;
            else
                blob_activated[i] = false;
        }
 
    }
}
 
//--------------------------------------------------------------
//draw-Funktion zeichnet die Bilder in das Testfenster
//--------------------------------------------------------------
void testApp::draw(){
    ofSetHexColor(0xFFFFFF);
 
    ofSetHexColor(0x000000);
    ofDrawBitmapString("If hue between",10, kinect.height + 20);
    ofDrawBitmapString(ofToString((int)frshold1_hue),10, kinect.height + 30);
    ofDrawBitmapString("and",10, kinect.height + 40);
    ofDrawBitmapString(ofToString((int)frshold2_hue),10, kinect.height + 50);
 
 
    ofDrawBitmapString("If saturation is between",10, kinect.height + 100);
    ofDrawBitmapString(ofToString((int)frshold1_sat),10, kinect.height + 110);
    ofDrawBitmapString("and",10, kinect.height + 120);
    ofDrawBitmapString(ofToString((int)frshold2_sat),10, kinect.height + 130);
 
    ofDrawBitmapString("Hue value:", 10, kinect.height + 150);
    ofDrawBitmapString(ofToString((unsigned int)hue_value), 100, kinect.height + 150);
 
    ofDrawBitmapString("Sat value:", 10, kinect.height + 170);
    ofDrawBitmapString(ofToString((unsigned int)sat_value), 100, kinect.height + 170);
 
    ofDrawBitmapString("Blob 1 distance:", 10, kinect.height + 190);
    ofDrawBitmapString(ofToString(blob_distance[1]), 200, kinect.height + 190);
    ofDrawBitmapString("Blob 2 distance:", 10, kinect.height + 200);
    ofDrawBitmapString(ofToString(blob_distance[2]), 200, kinect.height + 200);
 
    ofSetHexColor(0xFFFFFF);
 
    value.draw(kinect.width, 0);
    depthdiff.draw(kinect.width, kinect.height);
    ofSetHexColor(0xFF0000);
 
    //Malen der Blobs in anderer Farbe
    for(int i = 0; i < finder.nBlobs; i ++)
    {
        if(blob_activated[i])
            ofSetHexColor(0x00FF00);
        else
            ofSetHexColor(0xFF0000);
        finder.blobs[i].draw();
        ofDrawSphere(finder.blobs[i].centroid, 3.0f);
    }
}

Momentaner Stand

Screenshot kinect.png

Theoretisches weiteres Vorgehen

Um die bereitgestellte GUI bedienen zu können, müssen aus den gewonnenen Daten der Kinect Events generiert werden, die von der GUI ausgewertet werden können.

GUI

Ziel war es, mit der GUI die intuitive Steuerung von Wiedergabe-Parameter wie Tempo und Panorama zu ermöglichen. Die vom Kinect-Modul erkannten Steuergesten sollten per OSC an das GUI-Modul übermittelt und dort ausgewertet werden. Das GUI-Modul enthält einen einfachen Audio-Player, der für die in Echtzeit parametrisierte Audio-Wiedergabe zuständig ist. Weiterhin sollten Erweiterbarkeit und Flexibilität des Moduls weitgehend gewährleistet sein. Deswegen wurde die Oberfläche so angelegt, dass die Bedienung einer beliebige Zahl an parallel abgespielten Samples möglich ist.

Code

Main App Header

#pragma once
 
#include "ofMain.h"
#include "ofxGui.h"
#include "trackControl.h"
 
class testApp : public ofBaseApp
{
 
public:
   void setup();
   void update();
   void draw();
   void exit();
 
   void keyPressed(int key);
   void keyReleased(int key);
   void mouseMoved(int x, int y );
   void mouseDragged(int x, int y, int button);
   void mousePressed(int x, int y, int button);
   void mouseReleased(int x, int y, int button);
   void windowResized(int w, int h);
   void dragEvent(ofDragInfo dragInfo);
   void gotMessage(ofMessage msg);
 
   void volumeChanged(int &volume);
   void positionChanged(float &position);
 
   trackControl trackControl1;
   trackControl trackControl2;
};

Main App Source

#include "testApp.h"
 
#define NUM_PLAYERS 2
 
//--------------------------------------------------------------
void testApp::setup()
{
   ofSetVerticalSync(true);
 
   trackControl1.setup(0, 0, "sounds/synth.wav");     // Static track choice for now.
   trackControl2.setup(1, 640, "sounds/beat.wav");
}
 
//--------------------------------------------------------------
void testApp::exit()
{
   trackControl1.exit();
   trackControl2.exit();
}
 
//--------------------------------------------------------------
void testApp::update(){
 
}
 
//--------------------------------------------------------------
void testApp::draw()
{
   float widthDiv = ofGetWidth() / NUM_PLAYERS;
 
   for (int i = 0; i < NUM_PLAYERS; i++)              // Draw set number of audio players.
   {
      (i % 2) ? ofSetHexColor(0xeeeeee) : ofSetHexColor(0xffffff);
      ofRect(widthDiv*i, 0, widthDiv, ofGetHeight());
   }
 
   trackControl1.draw();
   trackControl2.draw();
}
 
//--------------------------------------------------------------
void testApp::keyPressed(int key){
 
}
 
//--------------------------------------------------------------
void testApp::keyReleased(int key){
 
}
 
//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){
 
}
 
//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button)
{
   trackControl1.mouseDragged(x, y, button);             // Let the players handle the events.
   trackControl2.mouseDragged(x, y, button);
}
 
//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button)
{
   trackControl1.mousePressed(x, y, button);
   trackControl2.mousePressed(x, y, button);
}
 
//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button)
{
   trackControl1.mouseReleased(x, y, button);
   trackControl2.mouseReleased(x, y, button);
}
 
//--------------------------------------------------------------
void testApp::windowResized(int w, int h){
 
}
 
//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){
 
}
 
//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){ 
 
}

Audio-Player Header

#include "ofMain.h"
#include "ofxGui.h"
 
class trackControl : public ofBaseApp
{
 
public:
   void setup(int playerID, int offset, string song_path);
   void update();
   void draw();
   void exit();
 
   void keyPressed(int key);
   void keyReleased(int key);
   void mouseMoved(int x, int y );
   void mouseDragged(int x, int y, int button);
   void mousePressed(int x, int y, int button);
   void mouseReleased(int x, int y, int button);
   void windowResized(int w, int h);
   void dragEvent(ofDragInfo dragInfo);
   void gotMessage(ofMessage msg);
 
   void volumeChanged(int &volume);
   void positionChanged(float &position);
   void playPauseChanged(bool &isPlay);
   void dragChanged(bool &isDrag);
 
   void applySoundEffects(int x, int y);
 
   ofxToggle      playPauseToggle;
   ofxToggle      dragToggle;
   ofxIntSlider    volumeIntSlider;
   ofxFloatSlider scrubbarFloatSlider;
 
   ofSoundPlayer  soundPlayer;
 
   string         song_title;
   int            offset;
   int            playerID;
   bool           isDrag;
   bool           isUserClick;
   ofTrueTypeFont	font;
};

Audio-Player Source

#include "trackControl.h"
 
//--------------------------------------------------------------
void trackControl::setup(int playerID, int offset, string song_path)
{
   song_title     = song_path;
   this->playerID = playerID;
   this->offset   = offset;
 
   /* Add event listeners. */
   volumeIntSlider.addListener(this, &trackControl::volumeChanged);
   scrubbarFloatSlider.addListener(this, &trackControl::positionChanged);
   playPauseToggle.addListener(this, &trackControl::playPauseChanged);
   dragToggle.addListener(this, &trackControl::dragChanged);
 
   /* Customize Looks. */
   playPauseToggle.setBackgroundColor(ofColor::lightGrey);
   playPauseToggle.setup("play", false);
   playPauseToggle.setShape(offset+10, 700, 100, 30);
 
   dragToggle.setBackgroundColor(ofColor::lightGrey);
   dragToggle.setup("drag", false);
   dragToggle.setShape(offset+10, 660, 100, 30);
 
   scrubbarFloatSlider.setBackgroundColor(ofColor::lightGrey);
   scrubbarFloatSlider.setup("scrub", 0, 0, 100);
   scrubbarFloatSlider.setShape(offset+120, 700, 300, 30);
 
   volumeIntSlider.setBackgroundColor(ofColor::lightGrey);
   volumeIntSlider.setup("volume", 100, 0, 100);
   volumeIntSlider.setShape(offset+430, 700, 200, 30);
 
   soundPlayer.loadSound(song_path);
 
   font.loadFont("Sudbury_Basin_3D.ttf", 16);
 
}
 
//--------------------------------------------------------------
void trackControl::exit()
{
   volumeIntSlider.removeListener(this,&trackControl::volumeChanged);
   scrubbarFloatSlider.removeListener(this, &trackControl::positionChanged);
   playPauseToggle.removeListener(this, &trackControl::playPauseChanged);
}
 
//--------------------------------------------------------------
void trackControl::volumeChanged(int &volume)
{
   soundPlayer.setVolume(volume / 100.f);
}
 
//--------------------------------------------------------------
void trackControl::positionChanged(float &position)
{
   soundPlayer.setPosition(scrubbarFloatSlider / 100.f);
}
 
//--------------------------------------------------------------
void trackControl::playPauseChanged(bool &isPlay)
{
   /* Toggle button appearance. */
   if (playPauseToggle)
   {
      playPauseToggle.setName("play");
   }
   else
   {
      playPauseToggle.setName("pause");
   }
 
   (isPlay) ? soundPlayer.play(): soundPlayer.stop();
}
 
//--------------------------------------------------------------
void trackControl::dragChanged(bool &isDrag)
{
   this->isDrag = isDrag;
 
   /* Toggle button appearance. */
   if (dragToggle)
   {
      dragToggle.setName("drag / loop");
   }
   else
   {
      dragToggle.setName("click");
   }
 
   if (isDrag)
   {
      soundPlayer.setMultiPlay(false);
      soundPlayer.setLoop(true);
   }
   else
   {
      soundPlayer.setMultiPlay(true);
      soundPlayer.setLoop(false);
   }
}
 
 
//--------------------------------------------------------------
void trackControl::applySoundEffects(int x, int y)
{
   /* Continuously control the speed and panning of the sample via drag,
    * when in this players region:
    */
   float x_start = offset;
   float x_end   = offset + ofGetWidth() / 2;  // hacky!
 
   if ((x >= x_start) && (x < x_end))
   {
      if (!isDrag)
      {
         soundPlayer.play();
      }
 
      /* Set speed according to y. */
      soundPlayer.setSpeed(0.5f + ((float) (ofGetHeight() - y) / (float) ofGetHeight()) * 1.0f);
 
      /* Map x within the last third of window to -1 to 1 ( for left / right panning ) */
      soundPlayer.setPan(ofMap(x, x_start, x_end, -1, 1, true));
   }
}
 
 
//--------------------------------------------------------------
void trackControl::update()
{
   /* Update the sound playing system: */
   ofSoundUpdate();
}
 
//--------------------------------------------------------------
void trackControl::draw()
{
   /* Display song title. */
   ofSetHexColor(0x000000);
   font.drawString(song_title, offset + 50, 100);
 
   ofSetHexColor(0x000000);
   string tempStr = "position: " + ofToString(soundPlayer.getPosition())+"\nspeed: " + 
      ofToString(soundPlayer.getSpeed()) + "\npan: " + ofToString(soundPlayer.getPan());
   ofDrawBitmapString(tempStr, offset + 50, ofGetHeight() / 2);
 
   scrubbarFloatSlider = soundPlayer.getPosition() * 100;
 
   playPauseToggle.draw();
   dragToggle.draw();
   volumeIntSlider.draw();
   scrubbarFloatSlider.draw();
}
 
//--------------------------------------------------------------
void trackControl::keyPressed(int key){
 
}
 
//--------------------------------------------------------------
void trackControl::keyReleased(int key){
 
}
 
//--------------------------------------------------------------
void trackControl::mouseMoved(int x, int y ){
 
}
 
//--------------------------------------------------------------
void trackControl::mouseDragged(int x, int y, int button)
{
   applySoundEffects(x, y);
}
 
//--------------------------------------------------------------
void trackControl::mousePressed(int x, int y, int button)
{
   applySoundEffects(x, y);
}
 
//--------------------------------------------------------------
void trackControl::mouseReleased(int x, int y, int button){
 
}
 
//--------------------------------------------------------------
void trackControl::windowResized(int w, int h){
 
}
 
//--------------------------------------------------------------
void trackControl::gotMessage(ofMessage msg){
 
}
 
//--------------------------------------------------------------
void trackControl::dragEvent(ofDragInfo dragInfo){
 
}

Momentaner Stand

Screenshot gui.png

Theoretisches weiteres Vorgehen

Während die beiden Module unabhängig voneinander bereits voll funktionstüchtig sind, ist die Kommunikation über OSC noch nicht implementiert worden. Folglich wäre die Einbindung eines OSC-Writers und Parsers der letzte Schritt zu einem vollständigen System.


Link zu den Source-Dateien

source code