Télécharger le code source.
Dans mon précédant billet, nous avons vu comment capturer des images en 640*480 provenant du flux images de la kinect, dans ce billet, nous allons voir comment capturer, et traiter le flux de profondeur et identifier le « player » qui est reconnu par la Kinect.
Pour capturer le flux profondeur, nous allons procéder de la même manière que le flux vidéo.
On déclare deux HANDLE l’un _depthStreamHandle, qui servira de pointeur afin de recevoir le flux de données, l’autre _depthNextFrame qui servira pour signaler la disponibilité de la trame suivante que nous utiliserons en conjonction avec l’API WaitForSingleObject
HANDLE _depthStreamHandle; HANDLE _depthNextFrame;
Le flux de profondeur s’initialise en ouvrant un flux à l’aide l’API NuiImageStreamOpen.
· Tout d’abord, on crée un manual reset event que nous passerons à la méthode NuiImageStreamOpen, afin que le runtime NUI signale la disponibilité d’une nouvelle trame. _depthNextFrame=CreateEvent( NULL, TRUE, FALSE, NULL );
· Le 4ième paramètre correspond au nombre de frame que le runtime NUI doit buffériser. Dans la documentation il est noté que le maximum est 4 (NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM), mais 2 suffit pour la plupart des applications.
· Enfin les 5ièmes et 6ièmes paramètres correspondent à nos deux HANDLE définis plus haut. HRESULT hr = NuiImageStreamOpen( NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_320x240, 0, 2, _depthNextFrame, &_depthStreamHandle );
Pour récupérer la trame suivante, nous utilisons dans une boucle l’API NuiImageStreamGetNextFrame de la manière suivante :
o Nous allouons un tableau de structure RGBQUAD RGBQUAD *rgbWk=(RGBQUAD*)malloc(640*480)
o Ensuite on appelle la méthode NuiImageStreamGetNextFrame en lui passant les paramètres suivants : hr = NuiImageStreamGetNextFrame( _depthStreamHandle, 0, &pImageFrame );
o Le 1er paramètre est _ depthStreamHandle crée par l’API NuiImageStreamOpen
o Le second paramètre est un délai d’attente exprimé en millisecondes avant que la méthode ne retourne sans nouvelle trame.
o Le 3ième est un pointeur sur la structure NUI_IMAGE_FRAME qui recevra les données. C’est cette structure que nous manipulerons pour afficher la trame à l’écran. const NUI_IMAGE_FRAME * pImageFrame = NULL;
o A partir de la trame, nous allons récupérer les informations de profondeurs à l’aide de l’objet NuiImageBuffer (Cet objet est similaire à la texture Direct3D) NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
o A partir de cette texture, nous allons récupérer un pointeur sur la structure KINECT_LOCKED_RECT pTexture->LockRect( 0, &LockedRect, NULL, 0 ) ; Structure qui contiendra un pointeur sur les données de la trame BYTE * pBuffer = (BYTE*) LockedRect.pBits; Ainsi que la longueur de chaque ligne LockedRect.Pitch
o Puis on récupère l’adresse du 1er élément de notre tableau de structure RGBQUAD RGBQUAD * rgbrun=rgbWk;
o On récupère les 16 premiers bits du buffer de données. USHORT * pBufferRun = (USHORT*)pBuffer; Les 13 1er bits correspondant à l’information de profondeur, les 3 bits de poids faible, correspondant aux index des « players » que Kinect a détectée.
o On fournit ces informations, à la méthode KinNuiShortToQuadDepth() (Listing à la fin de ce billet) , qui est en charge de transformer les informations de profondeur en couleur. RGBQUAD quad = KinNuiShortToQuadDepth( *pBufferRun );
o Enfin on dessine notre trame. _pD2D1Helper->D2D1DrawFrame ((BYTE*)rgbWk,LockedRect.Pitch*2,320,240);
Dans la vidéo, l’intensité du player change en fonction de la distance par rapport à la kinect.
Listing complet HRESULT DPEKinectHelper::KinDemarrerDepth () { HRESULT hr=S_OK; //Allocation d'un tableau de structure RGQQUAD RGBQUAD *rgbWk=(RGBQUAD*)malloc(640*480); while(true) { //Si un arrêt est demandé sur la tâche if (Context::IsCurrentTaskCollectionCanceling ()) { break; } const NUI_IMAGE_FRAME * pImageFrame = NULL; WaitForSingleObject (_depthNextFrame,INFINITE); hr = NuiImageStreamGetNextFrame( _depthStreamHandle, 0, &pImageFrame ); if (SUCCEEDED(hr)) { NuiImageBuffer * pTexture = pImageFrame->pFrameTexture; KINECT_LOCKED_RECT LockedRect; pTexture->LockRect( 0, &LockedRect, NULL, 0 ); if( LockedRect.Pitch != 0 ) { BYTE * pBuffer = (BYTE*) LockedRect.pBits; //Récupère le pointeur sur le 1er élèment de notre tableau de RGBQUAD RGBQUAD * rgbrun=nullptr; //si l'allocation n'a pas echouée if (rgbWk) { rgbrun = rgbWk; } else return E_OUTOFMEMORY; //Récupère les 16 premiers bits du buffer de données //13 Bits pour les informations de profondeurs //3 Bits correspondant aux index des "players" que Kinect a detecté
USHORT * pBufferRun = (USHORT*)pBuffer;
//Parcours de l'integralité de la trame for( int y = 0 ; y < 240 ; y++ ) { for( int x = 0 ; x < 320 ; x++ ) { //Passe les 16 1er bits à la méthode KinNuiShortToQuadDepth //afin qu’elle calcul un code de couleur a afficher RGBQUAD quad = KinNuiShortToQuadDepth( *pBufferRun ); //Avance le pointeur USHORT pour récupérer les prochaines informations de profondeurs et de player pBufferRun++; *rgbrun = quad; rgbrun++; } } //Dessiner la trame _pD2D1Helper->D2D1GetContexteRendu ()->BeginDraw (); _pD2D1Helper->D2D1PeindreArrierePlan (); _pD2D1Helper->D2D1DrawFrame ((BYTE*)rgbWk,LockedRect.Pitch*2,320,240); _pD2D1Helper->D2D1GetContexteRendu ()->EndDraw (); hr=pTexture->UnlockRect (0); } else { OutputDebugString( L"Longueur du buffer erronée\r\n" ); } NuiImageStreamReleaseFrame( _depthStreamHandle, pImageFrame ); } } free(rgbWk); return hr; }
Listing de la méthode KinNuiShortToQuadDepth() RGBQUAD DPEKinectHelper::KinNuiShortToQuadDepth( USHORT s ) { //Décale de 3 Bits vers la droite afin de ne récupère que les informations
//concernant la profondeur
USHORT RealDepth = (s & 0xfff8) >> 3; //Masque pour retrouver si un player a été detecté
USHORT Player = s & 7; // Transforme les informations de profondeur sur 13-Bit en une intensité codé sur 8 Bits
// afin d'afficher une couleur en fonction de la profondeur
BYTE l = 255 - (BYTE)(256*RealDepth/0x0fff); RGBQUAD q; q.rgbRed = q.rgbBlue = q.rgbGreen = 0; switch( Player ) { case 0: //Affiche les informations (non player) en niveau de gris q.rgbRed = l / 2; q.rgbBlue = l / 2; q.rgbGreen = l / 2; break; //Si un player est detecté affiche le en couleur //avec une intensité relatif à la profondeur case 1: q.rgbRed = l; break; case 2: q.rgbGreen = l; break; case 3: q.rgbRed = l / 4; q.rgbGreen = l; q.rgbBlue = l; break; case 4: q.rgbRed = l; q.rgbGreen = l; q.rgbBlue = l / 4; break; case 5: q.rgbRed = l; q.rgbGreen = l / 4; q.rgbBlue = l; break; case 6: q.rgbRed = l / 2; q.rgbGreen = l / 2; q.rgbBlue = l; break; case 7: q.rgbRed = 255 - ( l / 2 ); q.rgbGreen = 255 - ( l / 2 ); q.rgbBlue = 255 - ( l / 2 ); } return q; }
Dans mon 3ième billet, nous abordons la manière de traquer et dessiner le squelette détecté par Kinect.
Comment développer avec le SDK Kinect en C++ Part III
Eric Vernié