Kinect Basics: Skeleton Tracking

Skeletal Tracking

La funcionalidad estrella del sensor Kinect sin duda es el Skeletal tracking. Skeletal tracking significa seguimiento de esqueleto y se basa en un algoritmo que permite que el sensor pueda identificar partes del cuerpo de las personas que están en el campo de visión del sensor. Por medio de este algoritmo podemos obtener puntos que hacen referencia a las partes del cuerpo de la persona o las personas que están frente al sensor y hacer un seguimiento de éstos identificando gestos y/o posturas.

Para crear un nuevo proyecto sigas las instrucciones en el post anterior: Kinect Basics: Fundamentos y Configuración del entorno de desarrollo 

 

Code:

1 // SkeletalTracking.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <NuiApi.h> 7 #include <opencv2\opencv.hpp> 8 9 void drawSkeleton(cv::Mat m , NUI_SKELETON_DATA skeleton); 10 11 12 int _tmain(int argc, _TCHAR* argv[]){ 13 14 15 cv::setUseOptimized( true ); 16 17 18 /* 19 La función NuiCreateSensorByIndex crea una nueva Instancia de la clase INUISensor, 20 el primer parámetro corresponde al índex del sensor con el que vamos a trabajar, 21 en caso de que haya más de un sensor conectado 22 */ 23 INuiSensor* kinect; 24 HRESULT hr = NuiCreateSensorByIndex( 0, &kinect ); 25 if( FAILED( hr ) ){ 26 std::cerr << "Error : NuiCreateSensorByIndex" << std::endl; 27 return EXIT_FAILURE; 28 } 29 30 //inicializamos el sensor usando la función NuiInitialize,especificando 31 //que características queremos habilitar usando las banderas 32 //NUI_INITIALIZE_FLAG_USES_COLOR: color stream 33 //NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX: depth stream 34 //NUI_INITIALIZE_FLAG_USES_SKELETON: Sekeleton Tracking 35 hr = kinect->NuiInitialize( NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON ); 36 if( FAILED( hr ) ){ 37 std::cerr << "Error : NuiInitialize" << std::endl; 38 return -1; 39 } 40 41 // Enable Skeleton Tracking 42 HANDLE hSkeletonEvent = INVALID_HANDLE_VALUE; 43 hSkeletonEvent = CreateEvent( nullptr, true, false, nullptr ); 44 hr = kinect->NuiSkeletonTrackingEnable( hSkeletonEvent, 0);//NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORT ); 45 if( FAILED( hr ) ){ 46 std::cerr << "Error : NuiSkeletonTrackingEnable" << std::endl; 47 return -1; 48 } 49 50 /* 51 NuiImageStreamOpen() esta función es un poco confusa, superficialmente lo que hace es inicializar un 52 HANDLE Object que podemos usar luego para obtener cada frame de video (RGB Stream/DEPTH Stream, de acuerdo a lo que hayamos definido en el primer argumento, 53 NUI_IMAGE_TYPE_COLOR para el RGB color image stream, o NUI_IMAGE_TYPE_DEPTH para el Depth image Stream) a una 54 Resolución de NUI_IMAGE_RESOLUTION_640x480. 55 */ 56 HANDLE hColorEvent = INVALID_HANDLE_VALUE; 57 HANDLE hColorHandle = INVALID_HANDLE_VALUE; 58 hColorEvent = CreateEvent( nullptr, true, false, nullptr ); 59 hr = kinect->NuiImageStreamOpen( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, 0, 2, hColorEvent, &hColorHandle ); 60 if( FAILED( hr ) ){ 61 std::cerr << "Error : NuiImageStreamOpen( COLOR )" << std::endl; 62 return -1; 63 } 64 //crea una ventana, para mostrar el skeleton 65 cv::namedWindow( "Skeleton" ); 66 67 HANDLE hEvents[2] = { hColorEvent, hSkeletonEvent }; 68 69 70 71 while (true) 72 { 73 ResetEvent( hSkeletonEvent ); 74 ResetEvent( hColorEvent ); 75 76 //WaitForSingleObject(hSkeletonEvent, INFINITE); 77 WaitForMultipleObjects( ARRAYSIZE( hEvents ), hEvents, true, INFINITE ); 78 79 // get Color Frame 80 NUI_IMAGE_FRAME pColorImageFrame = { 0 }; 81 hr = kinect->NuiImageStreamGetNextFrame( hColorHandle, 0, &pColorImageFrame ); 82 if( FAILED( hr ) ){ 83 std::cerr << "Error : NuiImageStreamGetNextFrame( COLOR )" << std::endl; 84 return -1; 85 } 86 87 88 // get Skeleton Frame 89 NUI_SKELETON_FRAME pSkeletonFrame = { 0 }; 90 hr = kinect->NuiSkeletonGetNextFrame( 0, &pSkeletonFrame ); 91 if( FAILED( hr ) ){ 92 std::cout << "Error : NuiSkeletonGetNextFrame" << std::endl; 93 return -1; 94 } 95 96 // GetColor Stream 97 INuiFrameTexture* pColorFrameTexture = pColorImageFrame.pFrameTexture; 98 NUI_LOCKED_RECT sColorLockedRect; 99 pColorFrameTexture->LockRect( 0, &sColorLockedRect, nullptr, 0 ); 100 cv::Mat m( 480, 640, CV_8UC4, reinterpret_cast<uchar*>( sColorLockedRect.pBits ) ); 101 102 103 //Get Skeleton Stream 104 cv::Point2f point; 105 for( int count = 0; count < NUI_SKELETON_COUNT; count++ ){ 106 NUI_SKELETON_DATA skeleton = pSkeletonFrame.SkeletonData[count]; 107 if( skeleton.eTrackingState == NUI_SKELETON_TRACKED ){ 108 drawSkeleton(m, skeleton); 109 } 110 } 111 112 cv::imshow( "Skeleton", m );//show Skeleton 113 114 //release color stream 115 pColorFrameTexture->UnlockRect( 0 ); kinect->NuiImageStreamReleaseFrame( hColorHandle, &pColorImageFrame ); 116 117 if( cv::waitKey( 30 ) == VK_ESCAPE ){ break;} 118 } 119 120 // Kinect 121 kinect->NuiShutdown(); kinect->NuiSkeletonTrackingDisable();CloseHandle( hColorEvent );CloseHandle( hSkeletonEvent );cv::destroyAllWindows(); 122 123 return 0; 124 } 125 126 127 //draw Join 128 void drawBone(cv::Mat m , NUI_SKELETON_DATA skeleton, NUI_SKELETON_POSITION_INDEX jointFrom,NUI_SKELETON_POSITION_INDEX jointTo ){ 129 130 NUI_SKELETON_POSITION_TRACKING_STATE jointFromState = skeleton.eSkeletonPositionTrackingState[jointFrom]; 131 132 NUI_SKELETON_POSITION_TRACKING_STATE jointToState = skeleton.eSkeletonPositionTrackingState[jointTo]; 133 134 if (jointFromState == NUI_SKELETON_POSITION_NOT_TRACKED || jointToState == NUI_SKELETON_POSITION_NOT_TRACKED){ 135 return; // nothing to draw, one of the joints is not tracked 136 } 137 138 // Don't draw if both points are inferred 139 if (jointFromState == NUI_SKELETON_POSITION_INFERRED || jointToState == NUI_SKELETON_POSITION_INFERRED){ 140 cv::Point2f pointFrom; 141 NuiTransformSkeletonToDepthImage( skeleton.SkeletonPositions[jointFrom], &pointFrom.x, &pointFrom.y, NUI_IMAGE_RESOLUTION_640x480 ); 142 cv::Point2f pointTo; 143 NuiTransformSkeletonToDepthImage( skeleton.SkeletonPositions[jointTo], &pointTo.x, &pointTo.y, NUI_IMAGE_RESOLUTION_640x480 ); 144 cv::line(m,pointFrom, pointTo, static_cast<cv::Scalar>( cv::Vec3b( 0, 0, 255 ) ),2,CV_AA); 145 cv::circle( m, pointTo, 5, static_cast<cv::Scalar>(cv::Vec3b( 0, 255, 255 )), -1, CV_AA ); 146 } 147 148 // We assume all drawn bones are inferred unless BOTH joints are tracked 149 if (jointFromState == NUI_SKELETON_POSITION_TRACKED && jointToState == NUI_SKELETON_POSITION_TRACKED) 150 { 151 cv::Point2f pointFrom; 152 NuiTransformSkeletonToDepthImage( skeleton.SkeletonPositions[jointFrom], &pointFrom.x, &pointFrom.y, NUI_IMAGE_RESOLUTION_640x480 ); 153 cv::Point2f pointTo; 154 NuiTransformSkeletonToDepthImage( skeleton.SkeletonPositions[jointTo], &pointTo.x, &pointTo.y, NUI_IMAGE_RESOLUTION_640x480 ); 155 //dibujamos una linea que entre los dos punto 156 cv::line(m,pointFrom, pointTo, static_cast<cv::Scalar>( cv::Vec3b( 0, 255, 0 ) ),2,CV_AA); 157 //en donde inicia cada linea dibujamos un circulo 158 cv::circle( m, pointTo, 5, static_cast<cv::Scalar>(cv::Vec3b( 0, 255, 255 )), -1, CV_AA ); 159 } 160 161 } 162 163 //draw the body 164 void drawSkeleton(cv::Mat m , NUI_SKELETON_DATA skeleton){ 165 //Head and Shoulders (cabeza y espalda) 166 drawBone(m , skeleton, NUI_SKELETON_POSITION_HEAD, NUI_SKELETON_POSITION_SHOULDER_CENTER); 167 drawBone(m, skeleton, NUI_SKELETON_POSITION_SHOULDER_CENTER, NUI_SKELETON_POSITION_SHOULDER_LEFT); 168 drawBone(m, skeleton, NUI_SKELETON_POSITION_SHOULDER_CENTER, NUI_SKELETON_POSITION_SHOULDER_RIGHT); 169 170 //hip(cadera) 171 drawBone(m,skeleton, NUI_SKELETON_POSITION_SHOULDER_CENTER, NUI_SKELETON_POSITION_SPINE); 172 drawBone(m,skeleton, NUI_SKELETON_POSITION_SPINE, NUI_SKELETON_POSITION_HIP_CENTER); 173 drawBone(m,skeleton, NUI_SKELETON_POSITION_HIP_CENTER, NUI_SKELETON_POSITION_HIP_LEFT); 174 drawBone(m,skeleton, NUI_SKELETON_POSITION_HIP_CENTER, NUI_SKELETON_POSITION_HIP_RIGHT); 175 176 //Knee (rodillas) 177 drawBone(m,skeleton, NUI_SKELETON_POSITION_HIP_LEFT, NUI_SKELETON_POSITION_KNEE_LEFT); 178 drawBone(m,skeleton, NUI_SKELETON_POSITION_HIP_RIGHT, NUI_SKELETON_POSITION_KNEE_RIGHT); 179 180 //Ankle (rodillas) 181 drawBone(m,skeleton, NUI_SKELETON_POSITION_KNEE_LEFT, NUI_SKELETON_POSITION_ANKLE_LEFT); 182 drawBone(m,skeleton, NUI_SKELETON_POSITION_KNEE_RIGHT, NUI_SKELETON_POSITION_ANKLE_RIGHT); 183 drawBone(m,skeleton, NUI_SKELETON_POSITION_ANKLE_LEFT, NUI_SKELETON_POSITION_FOOT_LEFT); 184 drawBone(m,skeleton, NUI_SKELETON_POSITION_ANKLE_RIGHT, NUI_SKELETON_POSITION_FOOT_RIGHT); 185 186 187 //Left Arm(brazo izquierdo) 188 drawBone(m,skeleton, NUI_SKELETON_POSITION_SHOULDER_LEFT, NUI_SKELETON_POSITION_ELBOW_LEFT); 189 drawBone(m,skeleton, NUI_SKELETON_POSITION_ELBOW_LEFT, NUI_SKELETON_POSITION_WRIST_LEFT); 190 drawBone(m,skeleton, NUI_SKELETON_POSITION_WRIST_LEFT, NUI_SKELETON_POSITION_HAND_LEFT); 191 192 //Right Arm(brazo derecho) 193 drawBone(m,skeleton, NUI_SKELETON_POSITION_SHOULDER_RIGHT, NUI_SKELETON_POSITION_ELBOW_RIGHT); 194 drawBone(m,skeleton, NUI_SKELETON_POSITION_ELBOW_RIGHT, NUI_SKELETON_POSITION_WRIST_RIGHT); 195 drawBone(m,skeleton, NUI_SKELETON_POSITION_WRIST_RIGHT, NUI_SKELETON_POSITION_HAND_RIGHT); 196 197 198 199 200 } 201 202

Resultado:

 

image

image

 

Descargar Código fuente: https://github.com/haruiz/Kinect_Blog

Anuncio publicitario

Kinect Basics: Depth and Player Stream

En el pasado post vimos como usar el sensor RGB del kinect para obtener el Color Stream (Color Stream), en esta ocasión vamos a  mirar como trabajar con el Depth Sensor para obtener el Depth Stream.  el Depth Stream   nos permite identificar que objetos dentro del plano de visión de la cámara están mas cerca a esta. con esta información aplicando técnicas de procesamiento de imágenes podemos tomar(segmentar) solo aquellos objetos que son de nuestro interés e ignorar aquellos que no.

 

Code:

1 // ColorC++.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <NuiApi.h> 7 #include <opencv2\opencv.hpp> 8 /* 9 OpenCV: 10 Cv: contiene las funciones principales de la biblioteca 11 Cvaux: contiene las funciones auxiliares (experimentales) 12 Cxcore: contiene las estructuras de datos y funciones de soporte para algebra lineal 13 Highgui: funciones para el manejo de GUI 14 15 */ 16 using namespace cv; 17 18 int _tmain(int argc, _TCHAR* argv[]) 19 { 20 // Usando la función NuiGetSensorCount podemos obtener la cantidad de sensores conectados 21 int sensorCount; 22 HRESULT hResult = S_OK; 23 hResult = NuiGetSensorCount(&sensorCount); 24 if( SUCCEEDED( hResult ) && sensorCount <= 0){ 25 std::cerr << "Error : No hay ningun sensor conectado" << std::endl; 26 std::system("pause"); 27 return EXIT_FAILURE; 28 } 29 30 /* 31 La función NuiCreateSensorByIndex crea una nueva Instancia de la clase INUISensor, 32 el primer parámetro corresponde al índex del sensor con el que vamos a trabajar, 33 en caso de que haya más de un sensor conectado 34 */ 35 INuiSensor* kinect; 36 hResult = NuiCreateSensorByIndex( 0, &kinect ); 37 if( FAILED( hResult ) ){ 38 std::cerr << "Error : NuiCreateSensorByIndex" << std::endl; 39 std::system("pause"); 40 return EXIT_FAILURE; 41 } 42 43 //inicializamos el sensor usando la función NuiInitialize,especificando 44 //que características queremos habilitar usando las banderas 45 //NUI_INITIALIZE_FLAG_USES_COLOR: color stream 46 //NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX: depth stream 47 //NUI_INITIALIZE_FLAG_USES_SKELETON: Sekeleton Tracking 48 49 hResult = kinect->NuiInitialize( NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX ); 50 if( FAILED( hResult ) ){ 51 std::cerr << "Error : NuiInitialize" << std::endl; 52 std::system("pause"); 53 return -1; 54 } 55 56 /* 57 NuiImageStreamOpen() esta función es un poco confusa, superficialmente lo que hace es inicializar un 58 HANDLE Object que podemos usar luego para obtener cada frame de video (RGB Stream/DEPTH Stream, de acuerdo a lo que hayamos definido en el primer argumento, 59 NUI_IMAGE_TYPE_COLOR para el RGB color image stream, o NUI_IMAGE_TYPE_DEPTH para el Depth image Stream) a una 60 Resolución de NUI_IMAGE_RESOLUTION_640x480. 61 */ 62 63 HANDLE hDepthEvent = INVALID_HANDLE_VALUE; 64 HANDLE hDepthHandle = INVALID_HANDLE_VALUE; 65 hDepthEvent = CreateEvent( nullptr, true, false, nullptr ); 66 hResult = kinect->NuiImageStreamOpen( NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_640x480, 0, 2, hDepthEvent, &hDepthHandle ); 67 if( FAILED( hResult ) ){ 68 std::cerr << "Error : NuiImageStreamOpen( COLOR )" << std::endl; 69 return -1; 70 } 71 //array de colores, para cada player un color diferente (maximo 6) 72 cv::Vec3b color[7]; 73 color[0] = cv::Vec3b( 0, 0, 0 ); 74 color[1] = cv::Vec3b( 255, 0, 0 ); 75 color[2] = cv::Vec3b( 0, 255, 0 ); 76 color[3] = cv::Vec3b( 0, 0, 255 ); 77 color[4] = cv::Vec3b( 255, 255, 0 ); 78 color[5] = cv::Vec3b( 255, 0, 255 ); 79 color[6] = cv::Vec3b( 0, 255, 255 ); 80 //crea una ventana 81 cv::namedWindow("depth", CV_WINDOW_NORMAL); 82 cv::namedWindow( "Player" , CV_WINDOW_NORMAL); 83 84 while (true){ 85 ResetEvent( hDepthEvent );//cambia el estado del objeto pasado como parametro a nonsignaled 86 /*WaitForSingleObject: esta función Detiene el flujo de ejecución de nuestra app (espera) hasta que el estado del objeto hColorEvent cambie a signaled. 87 Para mas info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032%28v=vs.85%29.aspx 88 */ 89 WaitForSingleObject( hDepthEvent, INFINITE ); 90 91 // Obtenemos el frame actual 92 NUI_IMAGE_FRAME pDepthImageFrame = { 0 }; 93 hResult = kinect->NuiImageStreamGetNextFrame( hDepthHandle, 0, &pDepthImageFrame ); 94 if( FAILED( hResult ) ){ 95 std::cerr << "Error : NuiImageStreamGetNextFrame( COLOR )" << std::endl; 96 return EXIT_FAILURE; 97 } 98 99 /* 100 Luego de obtener el frame de video (Depth/Color) actual, existen tres estructuras muy importantes: 101 NUI_IMAGE_FRAME: Contiene la data de alto nivel (metadata) del frame de video capturado ( index,resolution, etc.) 102 NUI_LOCKED_RECT: Contiene la data de bajo nivel del frame (bytes) de video capturado 103 INuiFrameTexture me permite acceder a la data del frame de video capturado. 104 */ 105 106 107 INuiFrameTexture* pDepthPlayerFrameTexture = pDepthImageFrame.pFrameTexture; 108 NUI_LOCKED_RECT sDepthPlayerLockedRect; 109 pDepthPlayerFrameTexture->LockRect( 0, &sDepthPlayerLockedRect, nullptr, 0 ); 110 111 112 LONG registX = 0; 113 LONG registY = 0; 114 //obtenemos los bits de la imagen 115 ushort* pBuffer = reinterpret_cast<ushort*>( sDepthPlayerLockedRect.pBits ); 116 cv::Mat bufferMat = cv::Mat::zeros( 480, 640, CV_16UC1 );//matriz depth pixels (1 channel - Gray) 117 cv::Mat playerMat = cv::Mat::zeros( 480, 640, CV_8UC3 );//matriz player pixels (3 channels - Color) 118 for( int y = 0; y < 480; y++ ){ 119 for( int x = 0; x < 640; x++ ){ 120 kinect->NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution( NUI_IMAGE_RESOLUTION_640x480, NUI_IMAGE_RESOLUTION_640x480, nullptr, x, y, *pBuffer, &registX, &registY ); 121 if( ( registX >= 0 ) && ( registX < 640 ) && ( registY >= 0 ) && ( registY < 480 ) ){ 122 bufferMat.at<ushort>( registY, registX ) = *pBuffer & 0xFFF8; 123 playerMat.at<cv::Vec3b>( registY, registX ) = color[*pBuffer & 0x7]; 124 } 125 pBuffer++; 126 } 127 } 128 cv::Mat depthMat( 480, 640, CV_8UC1 ); 129 bufferMat.convertTo( depthMat, CV_8UC3, -255.0f / NUI_IMAGE_DEPTH_MAXIMUM, 255.0f ); 130 131 /*Existen diferentes formas de adquirir una imagen del mundo real, usando una cámara fotográfica, 132 usando el Kinect, usando un Smartphone etc. sin embargo al final, luego de que estas imágenes son digitalizadas 133 por el sensor, terminan convirtiéndose en matrices de valores, en donde cada valor f(x,y) representa el valor 134 de intensidad de color de un pixel. 135 OpenCV usa dos estructuras de datos para representar estas imágenes 136 la estructura Mat(bajo nivel) y la IPlIMage(alto nivel) 137 */ 138 139 140 //esta función despliega o muestra una imagen sobre una ventana específica 141 cv::imshow("depth",depthMat); 142 cv::imshow( "Player", playerMat ); 143 //Unlocks the buffer. 144 pDepthPlayerFrameTexture->UnlockRect( 0 ); 145 kinect->NuiImageStreamReleaseFrame( hDepthHandle, &pDepthImageFrame ); 146 147 if( cv::waitKey( 30 ) == VK_ESCAPE ){ 148 break; 149 } 150 } 151 152 //Apagamos el sensor, liberamos memoria 153 kinect->NuiShutdown(); 154 CloseHandle( hDepthEvent ); 155 CloseHandle( hDepthHandle ); 156 //Destruimos todas las ventanas creadas 157 cv::destroyAllWindows(); 158 return EXIT_SUCCESS; 159 } 160 161

El resultado:

image

 

Link de descarga del código fuente :  https://github.com/haruiz/Kinect_Blog

Kinect Basics: Color Stream

En el pasado post (Hello World) vimos como configurar nuestro entorno de desarrollo y crear nuestro proyecto hola mundo, miremos ahora como empezar a trabajar con el sdk de kinect desde C++ específicamente para obtener el Stream de color (RGB).

Para poder acceder a las funciones que el API de kinect y openCV provee , Agregue al inicio de su archivo main los include a continuación:

1 #include "stdafx.h" 2 #include <Windows.h> 3 #include <NuiApi.h> 4 #include <opencv2\opencv.hpp> 5 6 using namespace cv;

Antes de continuar, es importante resaltar lo siguiente:  

Qué es OPENCV?:

“OpenCV is released under a BSD (Berkeley Software Distribution) license and hence it’s free for both academic and commercial use. It has C++, C, Python and Java interfaces and supports Windows, Linux, Mac OS, iOS and Android. OpenCV was designed for computational efficiency and with a strong focus on real-time applications. Written in optimized C/C++, the library can take advantage of multi-core processing.

pueden descargar openCV del link http://opencv.org/ .

Código:

1 // ColorC++.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <NuiApi.h> 7 #include <opencv2\opencv.hpp> 8 /* 9 OpenCV: 10 Cv: contiene las funciones principales de la biblioteca 11 Cvaux: contiene las funciones auxiliares (experimentales) 12 Cxcore: contiene las estructuras de datos y funciones de soporte para algebra lineal 13 Highgui: funciones para el manejo de GUI 14 15 */ 16 using namespace cv; 17 18 int _tmain(int argc, _TCHAR* argv[]) 19 { 20 // Usando la función NuiGetSensorCount podemos obtener la cantidad de sensores conectados 21 int sensorCount; 22 HRESULT hResult = S_OK; 23 hResult = NuiGetSensorCount(&sensorCount); 24 if( SUCCEEDED( hResult ) && sensorCount <= 0){ 25 std::cerr << "Error : No hay ningun sensor conectado" << std::endl; 26 return EXIT_FAILURE; 27 } 28 29 /* 30 La función NuiCreateSensorByIndex crea una nueva Instancia de la clase INUISensor, 31 el primer parámetro corresponde al índex del sensor con el que vamos a trabajar, 32 en caso de que haya más de un sensor conectado 33 */ 34 INuiSensor* kinect; 35 hResult = NuiCreateSensorByIndex( 0, &kinect ); 36 if( FAILED( hResult ) ){ 37 std::cerr << "Error : NuiCreateSensorByIndex" << std::endl; 38 return EXIT_FAILURE; 39 } 40 41 //inicializamos el sensor usando la función NuiInitialize,especificando 42 //que características queremos habilitar usando las banderas 43 //NUI_INITIALIZE_FLAG_USES_COLOR: color stream 44 //NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX: depth stream 45 //NUI_INITIALIZE_FLAG_USES_SKELETON: Sekeleton Tracking 46 47 hResult = kinect->NuiInitialize( NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON ); 48 if( FAILED( hResult ) ){ 49 std::cerr << "Error : NuiInitialize" << std::endl; 50 return -1; 51 } 52 53 /* 54 NuiImageStreamOpen() esta función es un poco confusa, superficialmente lo que hace es inicializar un 55 HANDLE Object que podemos usar luego para obtener cada frame de video (RGB Stream/DEPTH Stream, de acuerdo a lo que hayamos definido en el primer argumento, 56 NUI_IMAGE_TYPE_COLOR para el RGB color image stream, o NUI_IMAGE_TYPE_DEPTH para el Depth image Stream) a una 57 Resolución de NUI_IMAGE_RESOLUTION_640x480. 58 */ 59 60 HANDLE hColorEvent = INVALID_HANDLE_VALUE; 61 HANDLE hColorHandle = INVALID_HANDLE_VALUE; 62 hColorEvent = CreateEvent( nullptr, true, false, nullptr ); 63 hResult = kinect->NuiImageStreamOpen( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, 0, 2, hColorEvent, &hColorHandle ); 64 if( FAILED( hResult ) ){ 65 std::cerr << "Error : NuiImageStreamOpen( COLOR )" << std::endl; 66 return -1; 67 } 68 //crea una ventana 69 cv::namedWindow("color", CV_WINDOW_NORMAL); 70 71 while (true){ 72 ResetEvent( hColorEvent );//cambia el estado del objeto pasado como parametro a nonsignaled 73 /*WaitForSingleObject: esta función Detiene el flujo de ejecución de nuestra app (espera) hasta que el estado del objeto hColorEvent cambie a signaled. 74 Para mas info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032%28v=vs.85%29.aspx 75 */ 76 WaitForSingleObject( hColorEvent, INFINITE ); 77 78 // Obtenemos el frame actual 79 NUI_IMAGE_FRAME pColorImageFrame = { 0 }; 80 hResult = kinect->NuiImageStreamGetNextFrame( hColorHandle, 0, &pColorImageFrame ); 81 if( FAILED( hResult ) ){ 82 std::cerr << "Error : NuiImageStreamGetNextFrame( COLOR )" << std::endl; 83 return EXIT_FAILURE; 84 } 85 86 /* 87 Luego de obtener el frame de video (Depth/Color) actual, existen tres estructuras muy importantes: 88 NUI_IMAGE_FRAME: Contiene la data de alto nivel (metadata) del frame de video capturado ( index,resolution, etc.) 89 NUI_LOCKED_RECT: Contiene la data de bajo nivel del frame (bytes) de video capturado 90 INuiFrameTexture me permite acceder a la data del frame de video capturado. 91 */ 92 93 94 INuiFrameTexture* pColorFrameTexture = pColorImageFrame.pFrameTexture; 95 NUI_LOCKED_RECT sColorLockedRect; 96 //pColorFrameTexture->LockRect:Locks el buffer for read and write access. 97 98 pColorFrameTexture->LockRect( 0, &sColorLockedRect, nullptr, 0 ); 99 100 /*Existen diferentes formas de adquirir una imagen del mundo real, usando una cámara fotográfica, 101 usando el Kinect, usando un Smartphone etc. sin embargo al final, luego de que estas imágenes son digitalizadas 102 por el sensor, terminan convirtiéndose en matrices de valores, en donde cada valor f(x,y) representa el valor 103 de intensidad de color de un pixel. 104 OpenCV usa dos estructuras de datos para representar estas imágenes 105 la estructura Mat(bajo nivel) y la IPlIMage(alto nivel) 106 */ 107 108 //En esta línea creamos un Objeto Mat a partir de los bits de cada frame de video capturado por Kinect 109 cv::Mat MatrizDeColor( 480, 640, CV_8UC4, reinterpret_cast<uchar*>( sColorLockedRect.pBits ) ); 110 111 //esta función despliega o muestra una imagen sobre una ventana específica 112 cv::imshow("color",MatrizDeColor); 113 114 //Unlocks the buffer. 115 pColorFrameTexture->UnlockRect( 0 ); 116 kinect->NuiImageStreamReleaseFrame( hColorHandle, &pColorImageFrame ); 117 118 if( cv::waitKey( 30 ) == VK_ESCAPE ){ 119 break; 120 } 121 } 122 123 //Apagamos el sensor, liberamos memoria 124 kinect->NuiShutdown(); 125 kinect->NuiSkeletonTrackingDisable(); 126 CloseHandle( hColorEvent ); 127 CloseHandle( hColorHandle ); 128 //Destruimos todas las ventanas creadas 129 cv::destroyAllWindows(); 130 return EXIT_SUCCESS; 131 } 132 133

 

El resultado:

 image

Link de descarga

 

Espero les sea de utilidad.

Kinect Basics: Fundamentos y Configuración del entorno de desarrollo

Antes de entrar en materia hablemos un poco acerca de kinect, creado por Alex Kipman,desarrollado por Microsoft para la consola xbox-360 este dispositivo lanzado por primera vez en estados unidos el 4 de noviembre del 2010  permite a los usuarios controlar e  interactuar con la consola sin necesidad de tener contacto físico con un controlador de videojuegos tradicional, mediante una interfaz natural de usuario comúnmente conocida como NUI  a través del reconocimiento gestos, comandos de voz, movimientos del cuerpo , tracking de objetos e imágenes. otros dispositivos similares son el Wii MotionPlus para wii o el playstation move. Teniendo en cuenta  las características de este dispositivo y pensando en nosotros lo desarrolladores Microsoft libero el SDK, que permite a los desarrolladores de todo el mundo construir aplicaciones altamente interactivas que respondan a movimientos, gestos y comandos de voz,  poniendo a nuestros pies un abanico de posibilidades, hoy al fin podemos  materializar aquellas ideas que en algún momento pensamos eran imposibles. actualmente el sdk esta en la versión 2.0 y esta versión nos permite desarrollar aplicaciones para la versión  2 del sensor (ver aquí las diferencias), si aún tienes la versión 1 , puedes descargar e instalar la versión 1.8 .

 

Fundamentos:

1. Como funciona el sensor?

Kinect utiliza una cámara RGB que obtiene imágenes a color y 2 sensores de proximidad(infrarrojos) para medir la distancia a la que se encuentran los elementos que están dentro de su campo de visión. Las imágenes que se obtienen del sensor se codifican en un vector de bytes. Para entender esta codificación hay que saber primero como se estructura una imagen.Una imagen se compone de un conjunto de píxeles. Cada pixel de la imagen tiene 4 componentes que representan los valores de los colores rojo, verde y azul más una componente que corresponde con el valor de transparencia (alfa), en el caso de imágenes RGBA, o un valor vacío, si es de tipo RGB. Cada componente del píxel tiene un valor entre 0 y 255 lo que corresponde a un byte. De esta forma el vector de bytes que obtenemos del sensor, en el caso de la cámara RGB, es una representación de esos píxeles organizados de arriba abajo y de izquierda a derecha donde los 4 primeros elementos del vector serán los valores rojo, verde, azul y alfa del píxel de arriba a la izquierda mientras que los 4 últimos serán del píxel de abajo a la derecha.como se muestra a continuación en la imagen.

 

image image

2. Skeletal Tracking

La funcionalidad estrella del sensor Kinect sin duda es el Skeletal tracking. Skeletal tracking significa seguimiento de esqueleto y se basa en un algoritmo que permite que el sensor pueda identificar partes del cuerpo de las personas que están en el campo de visión del sensor. Por medio de este algoritmo podemos obtener puntos que hacen referencia a las partes del cuerpo de la persona o las personas que están frente al sensor y hacer un seguimiento de éstos identificando gestos y/o posturas.

image

para nuestro entorno de desarrollo necesitamos descargar  e instalar el visual studio 2010  (pueden usar la versión express edition para c++) , el  sdk de kinect la versión 1.8 para el kinect 1 y  Opencv (ya que en el próximo post voy hablar sobre como se puede trabajar procesamiento de imágenes usando esta libreria con kinect). siga los siguiente pasos.

 

3. Instalación y configuración del entorno:

  • requerimientos mínimos de Hardware

  • Sistema operativo Windows 7, Windows 8, Windows 8.1, Windows Embedded Standard 7
  • 32-bit (x86) or 64-bit (x64) processor
  • Dual-core 2.66-GHz or faster processor
  • Dedicated USB 2.0 bus
  • 2 GB RAM
  • A Microsoft Kinect for Windows sensor
  • Instalación de las Herramientas

  1. Descargue e instale Visual Studio 2013, como lo mencione anteriormente con la versión Express 2013 Edition for Windows Desktop o la Community Version pueden trabajar, ambas son free, los links de descarga son:  http://www.visualstudio.com/en-us/products/visual-studio-express-vs.aspx para la Express Edition o http://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx para la Community Edition respectivamente, tenga en cuenta si su equipo es de x86 o x64. el siguiente paso es ejecutar el instalador y seguir el asistente.
  2. Descargue e instale el Microsoft for Kinect SDK, para el ejercicio usaremos la versión 1.8 que puede descargar del link a continuación : http://www.microsoft.com/en-us/download/details.aspx?id=40278, ejecute el instalador y siga el asistente.
  3. Descargue OpenCV del link  http://opencv.org/  y descomprima el contenido de la carpeta en la ubicación de su preferencia.

4. Nuestro primer proyecto

Cree un nuevo proyecto:

 image

 

image

 

  1. Seleccione Visual C++ como lenguaje
  2. «Win 32» como template
  3. «Win 32 Console Application» como tipo de proyecto
  4. defina el titulo del proyecto
  5. especifique en que folder desea guardarlo
  6. haga click sobre el boton ok
  7. siga el asistente

image

image

Visual Studio IDE

image 

1. Property Manager: desde el property Manager Panel, podemos crear diferentes Property Sheet (perfiles de configuración), una property Sheet es un archivo xml de ext .props (Property Sheets) en donde quedan guardados los cambios a nivel de configuración que hagamos sobre nuestro proyecto. por ejemplo las variables de entorno, el path en donde estan la libererias etc.

por nuestro caso en particular por ejemplo debemos especificar en las propiedades del proyecto cual es el path  en donde se encuentra el SDK de kinect para poder referenciar  los archivos de encabezado desde nuestros archivos de código fuente,  necesario para poder ejecutar y compilar el proyecto, para ello podemos crear  un property sheet para todo lo que tiene que ver con kinect, lo mismo con OpenCV . Estos archivos podemos importarlos luego en otros proyectos lo que nos evita tener que nuevamente configurar nuestro IDE, en un mismo proyecto podemos cargar más de una property sheet.  A continuación veremos como crear un property sheet.

2. Work Area: nos permite visualizar y editar nuestros archivos de código fuente.

3. Solution Explorer: nos permite visualizar la estructura de nuestro proyecto.

 

Crear Property Sheet

image

para creer un property sheet

image

Siguiendo  los pasos anteriores creamos dos property sheet , una para OpenCV y otra para Kinect

 

image

para editar un property Sheet solo selecciónela y haga doble click, empecemos por la de kinect

Configuración Kinect SDK (Kinect Property Sheet):

 

Common Properties > VC ++ Directories > Include Directories >  C:\Program Files\Microsoft SDKs\Kinect\v1.8\inc

Common Properties > VC ++ Directories >  Library Directories >  C:\Program Files\Microsoft SDKs\Kinect\v1.8\lib\x86

Common Properties > Linker > Input > Additional Dependencies > Kinect10.lib

 

image

image

 

image

 

Configuración OpenCV (OpenCV Property Sheet):

 

Common Properties > VC ++ Directories > Include Directories > 

  • C:\opencv\build\include\
  • C:\opencv\build\include\opencv2
  • C:\opencv\build\include\opencv

Common Properties > VC ++ Directories >  Library Directories > 

  • C:\opencv\build\x86\vc11\lib

Common Properties > Linker > Input > Additional Dependencies >

opencv_calib3d249d.lib
opencv_contrib249d.lib
opencv_core249d.lib
opencv_features2d249d.lib
opencv_flann249d.lib
opencv_gpu249d.lib
opencv_highgui249d.lib
opencv_imgproc249d.lib
opencv_legacy249d.lib
opencv_ml249d.lib
opencv_nonfree249d.lib
opencv_photo249d.lib
opencv_stitching249d.lib
opencv_superres249d.lib
opencv_ts249d.lib
opencv_video249d.lib
opencv_videostab249d.lib
opencv_objdetect249d.lib

image

image

image

Finalmente ya nuestro proyecto  esta configurado, solo resta correr el código a continuación y validar de que todo funciona perfectamente.

1 #include "stdafx.h" 2 #include <iostream> 3 #include <Windows.h> 4 #include <NuiApi.h> 5 #include <opencv2/opencv.hpp> 6 #include <highgui/highgui.hpp> 7 8 using namespace cv; 9 using namespace std; 10 11 int _tmain(int argc, _TCHAR* argv[]) 12 { 13 INuiSensor* kinect; 14 int n; 15 HRESULT hr = NuiGetSensorCount(&n); 16 if(FAILED(hr) && n <= 0) 17 return EXIT_FAILURE; 18 19 hr = NuiCreateSensorByIndex(0, &kinect); 20 if(FAILED(hr)) 21 return EXIT_FAILURE; 22 23 hr = kinect->NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON); 24 if(FAILED(hr)) 25 return EXIT_FAILURE; 26 27 HANDLE hColorEvent = INVALID_HANDLE_VALUE; 28 HANDLE hColorHandle = INVALID_HANDLE_VALUE; 29 30 31 hColorEvent =CreateEvent(nullptr, true, false, nullptr); 32 hr = kinect->NuiImageStreamOpen( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, 0, 2, hColorEvent, &hColorHandle ); 33 if( FAILED( hr ) ){ 34 return EXIT_FAILURE; 35 } 36 37 unsigned long refWidth = 0; 38 unsigned long refHeight = 0; 39 40 NuiImageResolutionToSize( NUI_IMAGE_RESOLUTION_640x480, refWidth, refHeight ); 41 int width = static_cast<int>( refWidth ); 42 int height = static_cast<int>( refHeight ); 43 44 while(true){ 45 IplImage* frame = cvCreateImage( cvSize( width, height ), IPL_DEPTH_8U, 4 );; 46 ResetEvent( hColorEvent ); 47 WaitForSingleObject( hColorEvent, INFINITE ); 48 NUI_IMAGE_FRAME colorImageFrame = { 0 }; 49 hr = kinect->NuiImageStreamGetNextFrame( hColorHandle, 0, &colorImageFrame ); 50 if( FAILED( hr ) ){ 51 return EXIT_FAILURE; 52 } 53 54 INuiFrameTexture* pColorFrameTexture = colorImageFrame.pFrameTexture; 55 NUI_LOCKED_RECT colorLockedRect; 56 pColorFrameTexture->LockRect( 0, &colorLockedRect, nullptr, 0 ); 57 memcpy( frame->imageData, (BYTE*)colorLockedRect.pBits, frame->widthStep * frame->height ); 58 59 Mat m = Mat(frame); 60 Mat gray, hsv; 61 cvtColor(m, gray, CV_BGR2GRAY); 62 cvtColor(m, hsv, CV_BGR2HSV); 63 64 imshow("gray", gray); 65 imshow("hsv", hsv); 66 imshow("src", m); 67 68 69 cvShowImage("src", frame); 70 71 pColorFrameTexture->UnlockRect( 0 ); 72 kinect->NuiImageStreamReleaseFrame( hColorHandle, &colorImageFrame ); 73 cvReleaseImage(&frame); 74 if( cv::waitKey( 30 ) == VK_ESCAPE ){ 75 break; 76 } 77 78 } 79 80 81 system("pause"); 82 return EXIT_SUCCESS; 83 } 84 85

 

El resultado

image

 

 

En el próximo post explicare en detalle como interactuar con el dispositivo usando C++ y les ensenaré algunas técnicas de procesamiento de imágenes con kinect.

 

espero que les sea de utilidad.