R

Śledzenie ruchu ciała

Jedną z najprostszych a jednocześnie najdokładniejszych metod służących do śledzenia ruchu ciała, jest użycie sensora Kinect

Kinect został początkowo zaprojektowany jako sensor dla konsoli XBOX 360, który pozwala na interaktywną rozrywkę bez potrzeby wykorzystywania dodatkowych kontrolerów. Takie rozwiązanie pozwala użytkownikom grać w gry (i nie tylko) bez potrzeby używania padów i innych powszechnie do tej pory stosowanych urządzeń manipulacyjnych. [1]

Dane, pobierane przez sensor, dostarczają informacji o położeniu 20 części ciała, pozwalają one na określenie ich współrzędnych względem sensora. Punkty te tworzą szkielet postaci. Za pomocą tych punktów jesteśmy w stanie rozpoznać aktualne położenie człowieka względem sensora oraz jego pozę w pozycji „En face”, czyli przodem do sensora.

Cały proces odbywa się z wykorzystaniem kamery głębokości i emitera promieni podczerwonych. Znaczy to mniej więcej tyle, że dla sensora Kinect nie ma znaczenia ilość światła, która dociera do pomieszczenia.

Wykorzystując kamerę głębokości, możemy stworzyć trójwymiarowy model pomieszczenia i obiektów znajdujących się w nim.

Należy jednak uświadomić sobie, że dane, pochodzące ze strumienia pobieranego z kamery odległości, nie niosą ze sobą informacji, dotyczących samego obrazu, ale mają zapisane w sobie odległości pojedynczych pikseli.

Postanowiliśmy wykorzystać powyższe rozwiązanie przy projekcie interaktywnej podłogi na bazie gry „LEDerzy Ewolucji” stworzonej dla Samsung Electronics Polska.

OF

Implementacja

Poniżej przykład implementacji w C++ z użyciem openFrameworks.

Plik testApp.h

#include "ofMain.h"

#include "ofxOpenCv.h"

#include "ofxKinect.h"

#define KINECT_GAME_WIDTH 800

#define KINECT_GAME_HEIGHT 600

class testApp : public ofBaseApp

{

public:

    void setup();

    void update();

    void draw();

    ofxKinect           kinect;

    ofxCvGrayscaleImage grayImage;

    ofxCvContourFinder  contourFinder;

    int                 nearThreshold;

    int                 farThreshold;

};

Plik testApp.cpp

#include "testApp.h"

void testApp::setup()

{

    // enable depth->rgb image calibration

    kinect.setRegistration( true );

    kinect.init();

    kinect.open();

    grayImage.allocate( kinect.width, kinect.height );

    nearThreshold = 255;

    farThreshold = 100;

}

void testApp::update()

{

    kinect.update();

    // there is a new frame and we are connected

    if ( kinect.isFrameNew() )

    {

        // load grayscale depth image from the kinect source

        grayImage.setFromPixels(

            kinect.getDepthPixels(),

            kinect.width, kinect.height

        );

        unsigned char * pixels = grayImage.getPixels();

        int numPixels = grayImage.getWidth() * grayImage.getHeight();

        for ( int i = 0; i < numPixels; i++ )

        {

            if ( pixels[ i ] < nearThreshold

                 && pixels[ i ] > farThreshold )

            {

                pixels[ i ] = 255;

            }

            else

            {

                pixels[ i ] = 0;

            }

        }

        // update the cv image

        grayImage.flagImageChanged ();

        contourFinder.findContours(

            grayImage, 10,

            ( kinect.width * kinect.height ) / 2,

            20, false

        );

    }

}

void testApp::draw()

{

    grayImage.draw( 320, 0, 320, 240 );

    contourFinder.draw( 320, 0, 320, 240 );

    if ( contourFinder.nBlobs > 0 )

    {

        ofxCvBlob blob = contourFinder.blobs [ 0 ];

        ofRectangle rect = blob.boundingRect;

        ofSetColor( 255, 0, 0 );

        ofCircle(

            320 + ( rect.x * 0.5 ) + ( rect.width * 0.25 ),

            0   + ( rect.y * 0.5 ) + ( rect.height * 0.25 ),

            10

        );

        float currentX = ofMap(

            rect.x + ( rect.width  * 0.5 ),

            0, 640,

            0, KINECT_GAME_WIDTH

        );

        float currentY = ofMap(

            rect.y + ( rect.height * 0.5 ),

            0, 480,

            0, KINECT_GAME_HEIGHT

        );

        cout << currentX << " x " << currentY << endl;

    }

}
#

Efekt finalny