Comment développer avec le SDK Kinect en C++ : Part II

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 );

  • Nous décidons de capturer à la fois les informations de profondeurs, mais également l’index du ‘player’ reconnu par la Kinect (NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX), dans une résolution de 320*240 (NUI_IMAGE_RESOLUTION_320x240)
  • Le 3ième paramètre contrôle le pré-processing de l’image. Nous le mettons ici est à 0, car ce drapeau n’est pas utilisé dans la version actuelle du SDK Kinect.

· 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;
}

Télécharger le code source.

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é