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.
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
[1] Źródło: http://msdn.microsoft.com