roboforum.ru

Технический форум по робототехнике.

KINECT

Re: KINECT

lorry » 31 мар 2014, 15:17

Код программы я приведу позже, а сейчас расскажу, в общем виде, что нужно сделать для инициализации и запуска сенсора.
Для того чтобы сенсор заработал нужно создать события для каждого из потоков данных, а затем инициализировать сами потоки.
События в C++ - это очень нужная и полезная вещь. Они позволяют сделать нам хорошее многопоточное приложение. Для того, чтобы не тормозить основной поток, мы из другого потока будем получать сведения о том, что кадр уже готов и просто будем забирать этот кадр, вместо того, чтобы ждать его в основном потоке. Для каждого из типов данных нам понадобится одно событие:
Код: Выделить всёРазвернуть
m_hNextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // событие, которое будет сигнализировать, о том что данные глубины доступны (данные с IR камеры)
m_hNextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // событие, которое будет сигнализировать, о том что данные цвета доступны (данные с видео камеры)
m_hNextSkeletonEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // событие, которое будет сигнализировать, о том что данные о скелете доступны

Инициализация потоков происходит довольно просто. При помощи флагов указываем какие данные мы хотим получать от устройства:
Код: Выделить всёРазвернуть
HRESULT result = NuiInitialize(
                  NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | //глубина и индекс фигуры человека
                  NUI_INITIALIZE_FLAG_USES_SKELETON | //скелет
                  NUI_INITIALIZE_FLAG_USES_COLOR); //видео с камеры

Как видно, мы можем получать сразу практически все данные: данные с камеры глубины + индекс игрока, скелет и цветное изображение.
Причем, любой из флагов мы можем опустить (просто не указывать) если какие то данные нам не понадобятся. Порядок указания данных тоже роли не играет. В момент инициализации нужно сразу включать все данные которые вы собираетесь использовать.
Далее, для каждого типа данных нам необходимо открыть свой поток при при помощи метода NuiImageStreamOpen:
Код: Выделить всёРазвернуть
HRESULT NuiImageStreamOpen(
   NUI_IMAGE_TYPE eImageType, // тип изображения, который определяет формат данных
   NUI_IMAGE_RESOLUTION eResolution, // определяет размеры кадра (об этом далее)
   DWORD dwImageFrameFlags_NotUsed, // пока не используется
   DWORD dwFrameLimit, // количество кадров, которые будут буферизироваться. Оно не может быть больше чем NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM (сейчас это 4). Рекомендуют использовать значение 2
   HANDLE hNextFrameEvent, // событие, которое будет сигнализировать нам о том, что новый кадр сформирован
   HANDLE *phStreamHandle // указатель, в который, в случае успешного выполнения, будет записан идентификатор потока
);

Теперь подробнее о разрешении изображения. В документации приведена таблица, которая описывает размеры кадра для каждого из типов данных. Так для NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX разрешение может быть либо 80x60 либо 320x240. Для NUI_IMAGE_TYPE_DEPTH - уже может быть до 640x480. А для NUI_IMAGE_TYPE_COLOR - либо 640x480 либо 1280x1024.
Затем в самой программе мы в цикле будем прокручивать опрос созданных событий и при получении какого либо кадра выводить его, но обо всем этом дальше.
Наверное пока почти ничего не понятно, но дальше, когда мы будем разбирать готовый, рабочий код, надеюсь все встанет на свои места.
Последний раз редактировалось lorry 13 июн 2014, 08:35, всего редактировалось 3 раз(а).

Re: KINECT

BeS » 31 мар 2014, 18:30

А не проще сделать вот так(в убунту например):
sudo add-apt-repository ppa:v-launchpad-jochen-sprickerhof-de/pc и Dl
sudo apt-get update
sudo apt-get install libpcl-all
git clone https://github.com/Itseez/opencv.git
cd opencv
git checkout 2.4
cd ..
mkdir opecncv-build
cd opencv-build
cmake -DWITH_OPENNI=ON ../opencv
make -j8
make install

А потом создавать обычный C++ проект, подключать к нему OpenCV и через cv::VideoCapture вытягивать с Kinect'а и RGB и Depth map?

Kinect SDK который от Microsoft крайне убогая штука, ей лучше не пользоваться...

Re: KINECT

Scorpio » 31 мар 2014, 21:25

BeS писал(а):Kinect SDK который от Microsoft крайне убогая штука, ей лучше не пользоваться...

Я так понимаю, что это единственный API, способный выдавать поток скелетонов.
lori писал(а):Наверное пока почти ничего не понятно...

Это точно. Хотелось бы "для чайников" и более последовательно. Создать проект (тип проекта, форма, если нужна), задекларировать библиотеки, создать объекты...
К сожалению, пока не могу присоединиться к празднику покорения кинекта, т.к. нахожусь в путешествии и кинект с собой не захватил :)

Re: KINECT

BeS » 31 мар 2014, 22:04

Опенсурсный SDK OpenNI(через который OpenCV и взаимодействует с кинектом) уже несколько лет умеет скелеты людей искать, если под "потоком скелетонов" вы их имеете ввиду :-)

Re: KINECT

Scorpio » 31 мар 2014, 23:37

Не знал.

Re: KINECT

lorry » 01 апр 2014, 01:52

Scorpio писал(а):Хотелось бы "для чайников" и более последовательно. Создать проект (тип проекта, форма, если нужна), задекларировать библиотеки, создать объекты...

Вот здесь, forum68/topic13936-45.html#p298863 все расписано "для чайников". Сделайте последовательно все действия и будет вам счастье :). А вот готовый код рассмотрим дальше. Но для понимания методов сенсора эта информация просто необходима, вам, в дальнейшем, будет легче разобраться в программном коде, и вы это увидите. Надеюсь :)

Добавлено спустя 6 минут 36 секунд:
BeS писал(а):А не проще сделать вот так(в убунту например)

Читайте тему сначала.
Так как вы пишите тоже сделаем, всему свое время.
Мы начали с Windows, как закончим с окнами, начнем с Linux разбираться.
Последний раз редактировалось lorry 01 апр 2014, 16:02, всего редактировалось 1 раз.

Re: KINECT

lorry » 01 апр 2014, 07:50

Приступаем к разбору кода.
Как обычно вначале подключаем все нужные библиотеки:
Код: Выделить всёРазвернуть
#include <Windows.h>
#include <stdio.h>

// Подключение библиотек Kinect
#include <Shlobj.h>
#include <NuiApi.h>
//
#include < iostream >
using namespace std;

// Подключение библиотек OpenСV
#include "opencv2\opencv.hpp"
//

Просто копируем этот участок кода в ваш проект.

Дальше объявляем все переменные:
Код: Выделить всёРазвернуть
static const int   cDepthWidth  = 640;
static const int   cDepthHeight = 480;
static const int   cBytesPerPixel = 1;
static const NUI_IMAGE_RESOLUTION cColorResolution = NUI_IMAGE_RESOLUTION_640x480;
LARGE_INTEGER      m_colorTimeStamp; // таймер камеры
LARGE_INTEGER      m_depthTimeStamp; // таймер глубины

BYTE*            m_colorRGBX;
INuiSensor*         m_pNuiSensor = NULL;
HANDLE            m_pDepthStreamHandle = NULL;
HANDLE            m_hNextDepthFrameEvent = INVALID_HANDLE_VALUE; // событие сигнализирующнн о готовности данных глубины
HANDLE            m_pColorStreamHandle;
HANDLE            m_hNextColorFrameEvent; // событие сигнализирующее о готовности видео
USHORT*            m_depthRGBX;
LONG*            m_colorCoordinates;
HANDLE            m_hNextSkeletonEvent = NULL; // событие сигнализирующее о готовности данных скелета
DWORD            m_SkeletonTrackingFlags;

HRESULT CreateFirstConnected(); // функция подключения к Kinect
HRESULT GetColor(); // функция получения видеопотока с камеры
HRESULT GetDepth(); // функция получение потока глубины (с IR камеры)
bool GetSkeleton(); // функция получения скелетона
void Nui_DrawSkeleton(const NUI_SKELETON_DATA & skel, int windowWidth, int windowHeight); // функция отрисовки скелетона
void UpdateTrackedSkeletons( const NUI_SKELETON_FRAME & skel ); // функция определяет какие скелеты отслеживать и отслеживает их

IplImage* ImageVideo = cvCreateImage( cvSize( 640, 480 ), 8, 3 ); // картинка для видео с камеры
IplImage* ImageDepth = cvCreateImage( cvSize( 640, 480 ), 16, 1 ); // картинка для карты глубины
IplImage *ImageSkel = cvCreateImage(cvSize(640, 480), 8, 3); //картинка для скелетона
IplImage* Mask = cvCreateImage( cvSize( 640, 480 ), 8, 1 );

int g_ScreenWidth = 640;
int g_ScreenHeight = 480;
int m_StickySkeletonIds[5];
CvPoint2D32f m_Points[NUI_SKELETON_POSITION_COUNT]; // объявление точки в OpenCV
int humanFigure = 0; // счетчик фигур людей
struct Points {public: int x; int y;}; // создать структуру для хранения координат точек скелетона
Points head; // объявить координаты головы


Далее следует основной модуль программы в теле которого находится бесконечный цикл получающий и обрабатывающий данные с сенсора:
Код: Выделить всёРазвернуть
int main()
{
   m_depthRGBX = new USHORT[cDepthWidth*cDepthHeight*cBytesPerPixel];
   m_colorRGBX = new BYTE[cDepthWidth*cDepthHeight*4];
   m_colorCoordinates = new LONG[640*480*2];
   
   // Соединение с KINECT
   CreateFirstConnected();

   int  i,j;

   while (1)
      {
         // если готовы данные с камеры то получить их
         if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextColorFrameEvent, 0) )
             GetColor();

         // если имеется готовые данные о глубине то получить их
         if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextDepthFrameEvent, 0) )
            GetDepth();

         // если готовы данные скелета то получить их
         if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextSkeletonEvent, 0) )
            GetSkeleton();

         //printf ("head = %d X = %d Y = %d\n", humanFigure, head.x, head.y);

         cvShowImage( "глубина", ImageDepth ); // показать карту глубины
         cvShowImage( "видео", ImageVideo ); // показать картинку с камеры
         cvShowImage( "скелет", ImageSkel ); // показать скелетон
         //cvShowImage( "Mask", Mask ); // показать Mask

         cvSet( Mask, cvScalar( 0 ) ); // сбросить Mask

         // формирование кадра с камеры KINECT в openCV
         for( i = 0; i < cDepthHeight; i ++ )
            for( j = 0; j < cDepthWidth; j++ )
               {
                  ImageVideo->imageData[i*ImageVideo->widthStep + j*3] = m_colorRGBX[i*4*cDepthWidth+j*4];
                  ImageVideo->imageData[i*ImageVideo->widthStep + j*3+1] = m_colorRGBX[i*4*cDepthWidth+j*4+1];
                  ImageVideo->imageData[i*ImageVideo->widthStep + j*3+2] = m_colorRGBX[i*4*cDepthWidth+j*4+2];

                  // сформировть фигуру(ы) человека и передать в струкуру Mask
                  USHORT player = NuiDepthPixelToPlayerIndex(m_depthRGBX[i*cDepthWidth+j]);
                  if ( player > 0 )
                     {
                        int j1 =  m_colorCoordinates[(i*cDepthWidth+j)*2];
                        int i1 =  m_colorCoordinates[(i*cDepthWidth+j)*2+1];
                        if ( j1>= 0 && j1 < cDepthWidth && i1 >= 0 && i1 < cDepthHeight )
                           {
                              Mask->imageData[i1*Mask->widthStep + j1] = (char)255;
                           }
                        }
                  }

               memcpy( ImageDepth->imageData,  m_depthRGBX, cDepthWidth*cDepthHeight*2 ); // сформировать карту глубины

               if (cvWaitKey(1) == 27)
                  break; //Если Esc - выходим
      }
   
   // Закрытие
   NuiShutdown();

   if (m_pNuiSensor) {m_pNuiSensor->NuiShutdown();}

   if (m_hNextDepthFrameEvent != INVALID_HANDLE_VALUE) {CloseHandle(m_hNextDepthFrameEvent);}

   if (m_hNextColorFrameEvent != INVALID_HANDLE_VALUE) {CloseHandle(m_hNextColorFrameEvent);}

   if (m_hNextSkeletonEvent != INVALID_HANDLE_VALUE) {CloseHandle(m_hNextSkeletonEvent);}
      
   // удаление данных
   delete[] m_depthRGBX;
   cvReleaseImage( &ImageDepth );
    m_pNuiSensor->Release();

   return 0;
}


Функция получения видео с камеры:
Код: Выделить всёРазвернуть
// Получение видео потока с камеры
HRESULT GetColor()
{
   HRESULT hr = S_OK;
   NUI_IMAGE_FRAME imageFrame;

   // получение кадра видео
   hr = m_pNuiSensor->NuiImageStreamGetNextFrame(m_pColorStreamHandle, 0, &imageFrame);
   if (FAILED(hr)) {return hr;} // возврат в случае не удачи получения кадра

   m_colorTimeStamp = imageFrame.liTimeStamp;

   INuiFrameTexture * pTexture = imageFrame.pFrameTexture;
   NUI_LOCKED_RECT LockedRect;

   // Блокировка кадра Kinect, чтобы они не изменялся во время чтения кадра
   pTexture->LockRect(0, &LockedRect, NULL, 0);

   // Убедиться в том, что мы получили достоверные данные
   if (LockedRect.Pitch != 0) {memcpy(m_colorRGBX, LockedRect.pBits, LockedRect.size);}

   // разблокировать текстуру
   pTexture->UnlockRect(0);

   // Освободить кадр
   m_pNuiSensor->NuiImageStreamReleaseFrame(m_pColorStreamHandle, &imageFrame);

   return hr;
}


Функция получения глубины:
Код: Выделить всёРазвернуть
// Получение потока глубины (с IR камеры)
HRESULT GetDepth()
{
   HRESULT hr = S_OK;
   NUI_IMAGE_FRAME imageFrame;

   // получение кадра глубины
   hr = m_pNuiSensor->NuiImageStreamGetNextFrame(m_pDepthStreamHandle, 0, &imageFrame);
   if (FAILED(hr)) {return hr;} // возврат в случае не удачи получения кадра

   m_depthTimeStamp = imageFrame.liTimeStamp;

   INuiFrameTexture * pTexture = imageFrame.pFrameTexture;
   NUI_LOCKED_RECT LockedRect;

   // Блокировка кадра Kinect, чтобы они не изменялся во время чтения кадра
   pTexture->LockRect(0, &LockedRect, NULL, 0);

   // Убедиться в том, что мы получили достоверные данные
   if (LockedRect.Pitch != 0) {memcpy(m_depthRGBX, LockedRect.pBits, LockedRect.size);}

   // разблокировать текстуру
   pTexture->UnlockRect(0);

   // Освободить кадр
   m_pNuiSensor->NuiImageStreamReleaseFrame(m_pDepthStreamHandle, &imageFrame);

   return hr;
}


Получение скелетона:
Код: Выделить всёРазвернуть
// Получение скелетона
bool GetSkeleton()
{
   NUI_SKELETON_FRAME SkeletonFrame = {0};
   if (humanFigure >= 1)
      {
         cvSet( ImageSkel, cvScalar(0)); // очистить кадр со скелетоном
         humanFigure = 0; // сбросить счетчик фигур
         head.x = 0; head.y = 0; // сбросить координаты головы
      }
   bool foundSkeleton = false;
   
   if ( SUCCEEDED(m_pNuiSensor->NuiSkeletonGetNextFrame( 0, &SkeletonFrame )) )
      {
         for ( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
            {
               NUI_SKELETON_TRACKING_STATE trackingState = SkeletonFrame.SkeletonData[i].eTrackingState;
               if ( trackingState == NUI_SKELETON_TRACKED || trackingState == NUI_SKELETON_POSITION_ONLY )
                  foundSkeleton = true;
            }
      }
   
   if( !foundSkeleton ) return true; // выход если нет скелетонов
   
   // сглаживание данных скелетона
   HRESULT hr = m_pNuiSensor->NuiTransformSmooth(&SkeletonFrame,NULL);

   if ( FAILED(hr) ) return false;

   for ( int i = 0 ; i < NUI_SKELETON_COUNT; i++ )
      {
         NUI_SKELETON_TRACKING_STATE trackingState = SkeletonFrame.SkeletonData[i].eTrackingState;

         if ( trackingState == NUI_SKELETON_TRACKED )
            {
               humanFigure = humanFigure++; // инкремент счетчика фигур людей
               // нарисовать отслеживаемый скелет
               Nui_DrawSkeleton( SkeletonFrame.SkeletonData[i], ImageSkel->width, ImageSkel->height );
            }       
      } 

   UpdateTrackedSkeletons( SkeletonFrame );

   return 1;
}


Обработка скелетонов:
Код: Выделить всёРазвернуть
// Нарисовать кости и суставы
void Nui_DrawBone( const NUI_SKELETON_DATA & skel, NUI_SKELETON_POSITION_INDEX bone0, NUI_SKELETON_POSITION_INDEX bone1 )
{
   NUI_SKELETON_POSITION_TRACKING_STATE bone0State = skel.eSkeletonPositionTrackingState[bone0];
   NUI_SKELETON_POSITION_TRACKING_STATE bone1State = skel.eSkeletonPositionTrackingState[bone1];

   // выход, если суставы не обнаружены
   if ( bone0State == NUI_SKELETON_POSITION_NOT_TRACKED || bone1State == NUI_SKELETON_POSITION_NOT_TRACKED )
      return;
   
   // не отрисовывать если обе точки совпадают
   if ( bone0State == NUI_SKELETON_POSITION_INFERRED && bone1State == NUI_SKELETON_POSITION_INFERRED )
      return;

   // нарисовать зеленым цветом кости суставы которых отслеживаются, а красным суставы кости которых не видно
   if ( bone0State == NUI_SKELETON_POSITION_TRACKED && bone1State == NUI_SKELETON_POSITION_TRACKED ) // если кость видима
      {
         cvLine( ImageSkel, cvPointFrom32f( m_Points[bone0]), cvPointFrom32f( m_Points[bone1]), CV_RGB(0, 128, 0), 2 ); // нарисовать кость
         cvLine( ImageVideo, cvPointFrom32f( m_Points[bone0]), cvPointFrom32f( m_Points[bone1]), CV_RGB(0, 128, 0), 1 ); // нарисовать кость на видео
      }
   else // если кость не видима
      {
         cvLine( ImageSkel, cvPointFrom32f( m_Points[bone0]), cvPointFrom32f( m_Points[bone1]), CV_RGB(128, 0, 0), 2 ); // нарисовать кость
         cvLine( ImageVideo, cvPointFrom32f( m_Points[bone0]), cvPointFrom32f( m_Points[bone1]), CV_RGB(128, 0, 0), 1 ); // нарисовать кость на видео
      }
}

// Точка в OpenCV
CvPoint2D32f SkeletonToScreen( Vector4 skeletonPoint, int width, int height )
{
   LONG x, y;
   USHORT depth;

   // вычислить положение скелета на экране
   // NuiTransformSkeletonToDepthImage возвращает координаты в разрешении NUI_IMAGE_RESOLUTION_320x240
   NuiTransformSkeletonToDepthImage( skeletonPoint, &x, &y, &depth );

   float screenPointX = static_cast<float>(x * width) / g_ScreenWidth * 2;
   float screenPointY = static_cast<float>((y + 10) * height) / g_ScreenHeight * 2;

   return cvPoint2D32f(screenPointX, screenPointY);
}

// Какие скелеты отслеживать и их отслеживание
void UpdateTrackedSkeletons( const NUI_SKELETON_FRAME & skel )
{
   DWORD nearestIDs[2] = { 0, 0 };
   USHORT nearestDepths[2] = { NUI_IMAGE_DEPTH_MAXIMUM, NUI_IMAGE_DEPTH_MAXIMUM };

   // Очистить идентификаторы скелета, если пользователь покинул кадр
   bool stickyID0Found = false;
   bool stickyID1Found = false;
   for ( int i = 0 ; i < NUI_SKELETON_COUNT; i++ )
      {
         NUI_SKELETON_TRACKING_STATE trackingState = skel.SkeletonData[i].eTrackingState;

         if ( trackingState == NUI_SKELETON_TRACKED || trackingState == NUI_SKELETON_POSITION_ONLY )
            {
               if ( skel.SkeletonData[i].dwTrackingID == m_StickySkeletonIds[0] )
                  stickyID0Found = true;
               else if ( skel.SkeletonData[i].dwTrackingID == m_StickySkeletonIds[1] )
                  stickyID1Found = true;
            }
      }

   if ( !stickyID0Found && stickyID1Found )
      {
         m_StickySkeletonIds[0] = m_StickySkeletonIds[1];
         m_StickySkeletonIds[1] = 0;
      }
   else if ( !stickyID0Found )
      {
         m_StickySkeletonIds[0] = 0;
      }
   else if ( !stickyID1Found )
      {
         m_StickySkeletonIds[1] = 0;
      }

   // рассчитать ближайший скелет
   for ( int i = 0 ; i < NUI_SKELETON_COUNT; i++ )
      {
         NUI_SKELETON_TRACKING_STATE trackingState = skel.SkeletonData[i].eTrackingState;

         if ( trackingState == NUI_SKELETON_TRACKED || trackingState == NUI_SKELETON_POSITION_ONLY )
         {
            // сохранить скелетон если нет ранее сохраненных
            if ( 0 == m_StickySkeletonIds[0] && m_StickySkeletonIds[1] != skel.SkeletonData[i].dwTrackingID )
               {
                  m_StickySkeletonIds[0] = skel.SkeletonData[i].dwTrackingID;
               }
            else if ( 0 == m_StickySkeletonIds[1] && m_StickySkeletonIds[0] != skel.SkeletonData[i].dwTrackingID )
               {
                  m_StickySkeletonIds[1] = skel.SkeletonData[i].dwTrackingID;
               }

            LONG x, y;
            USHORT depth;

            // вычислить положение скелета на экране
            NuiTransformSkeletonToDepthImage( skel.SkeletonData[i].Position, &x, &y, &depth );

            if ( depth < nearestDepths[0] )
            {
               nearestDepths[1] = nearestDepths[0];
               nearestIDs[1] = nearestIDs[0];

               nearestDepths[0] = depth;
               nearestIDs[0] = skel.SkeletonData[i].dwTrackingID;
            }
            else if ( depth < nearestDepths[1] )
            {
               nearestDepths[1] = depth;
               nearestIDs[1] = skel.SkeletonData[i].dwTrackingID;
            }
         }
      }   
}


/// Нарисовать скелетон
void Nui_DrawSkeleton( const NUI_SKELETON_DATA & skel, int windowWidth, int windowHeight )
{     
   int i;

   for (i = 0; i < NUI_SKELETON_POSITION_COUNT; i++)
      {
         m_Points[i] = SkeletonToScreen( skel.SkeletonPositions[i], windowWidth, windowHeight );
      }

   // отобразить торс
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_HEAD, NUI_SKELETON_POSITION_SHOULDER_CENTER );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_SHOULDER_CENTER, NUI_SKELETON_POSITION_SHOULDER_LEFT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_SHOULDER_CENTER, NUI_SKELETON_POSITION_SHOULDER_RIGHT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_SHOULDER_CENTER, NUI_SKELETON_POSITION_SPINE );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_SPINE, NUI_SKELETON_POSITION_HIP_CENTER );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_HIP_CENTER, NUI_SKELETON_POSITION_HIP_LEFT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_HIP_CENTER, NUI_SKELETON_POSITION_HIP_RIGHT );

   // левая рука
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_SHOULDER_LEFT, NUI_SKELETON_POSITION_ELBOW_LEFT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_ELBOW_LEFT, NUI_SKELETON_POSITION_WRIST_LEFT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_WRIST_LEFT, NUI_SKELETON_POSITION_HAND_LEFT );

   // правая рука
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_SHOULDER_RIGHT, NUI_SKELETON_POSITION_ELBOW_RIGHT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_ELBOW_RIGHT, NUI_SKELETON_POSITION_WRIST_RIGHT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_WRIST_RIGHT, NUI_SKELETON_POSITION_HAND_RIGHT );

   // левая нога
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_HIP_LEFT, NUI_SKELETON_POSITION_KNEE_LEFT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_KNEE_LEFT, NUI_SKELETON_POSITION_ANKLE_LEFT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_ANKLE_LEFT, NUI_SKELETON_POSITION_FOOT_LEFT );

   // правая нога
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_HIP_RIGHT, NUI_SKELETON_POSITION_KNEE_RIGHT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_KNEE_RIGHT, NUI_SKELETON_POSITION_ANKLE_RIGHT );
   Nui_DrawBone( skel, NUI_SKELETON_POSITION_ANKLE_RIGHT, NUI_SKELETON_POSITION_FOOT_RIGHT );

   head.x = m_Points[NUI_SKELETON_POSITION_HEAD].x; // получить координату X головы
   head.y = m_Points[NUI_SKELETON_POSITION_HEAD].y; // получить координату Y головы
   
   // нарисовать суставы разным цветом
   for ( i = 0; i < NUI_SKELETON_POSITION_COUNT; i++ )
      {
         if ( skel.eSkeletonPositionTrackingState[i] == NUI_SKELETON_POSITION_INFERRED ) // если сустав видим
            {
               cvCircle( ImageSkel, cvPointFrom32f( m_Points[i]), 5, CV_RGB(128, 0, 0), 3 ); // отобразить сустав
               cvCircle( ImageVideo, cvPointFrom32f( m_Points[i]), 5, CV_RGB(128, 0, 0), 1 ); // отобразить сустав на видео
            }
         else if ( skel.eSkeletonPositionTrackingState[i] == NUI_SKELETON_POSITION_TRACKED ) // если сустав не видим
            {
               cvCircle( ImageSkel, cvPointFrom32f( m_Points[i]), 5, CV_RGB(0, 128, 0), 3 ); // отобразить сустав
               cvCircle( ImageVideo, cvPointFrom32f( m_Points[i]), 5, CV_RGB(0, 128, 0), 1 ); // отобразить сустав на видео
            }
      }

   // Отображать надпись при поднятии рук вверх
   CvFont font;
   float aa=1;
   cvInitFont( &font, CV_FONT_HERSHEY_SIMPLEX, aa,aa,0,2, 8 );
   i = 0;
   if ( m_Points[NUI_SKELETON_POSITION_HAND_RIGHT].y < m_Points[NUI_SKELETON_POSITION_HEAD].y )
      {
         cvPutText( ImageSkel, "Right Hand Up", cvPoint(0,30), &font, CV_RGB(255,255,255) );
         i++;
      }
   if ( m_Points[NUI_SKELETON_POSITION_HAND_LEFT].y < m_Points[NUI_SKELETON_POSITION_HEAD].y )
      {
         cvPutText( ImageSkel, "Left Hand Up", cvPoint(0,30+i*30), &font, CV_RGB(255,255,255) );
         i++;
      }
}


Соединение с сенсором:
Код: Выделить всёРазвернуть
// Соединение с сенсором
HRESULT CreateFirstConnected()
{
   INuiSensor * pNuiSensor;
   HRESULT hr;

   int iSensorCount = 0;
   hr = NuiGetSensorCount(&iSensorCount);
   if (FAILED(hr))
      return hr;

      // Просматриваем каждый сенсор Kinect
      for (int i = 0; i < iSensorCount; ++i)
         {
            // Создание датчика, для проверки состояния, если датчик не возможно создать, перейти на следующий
            hr = NuiCreateSensorByIndex(i, &pNuiSensor);

            if (FAILED(hr))
               continue;

            // Получить состояние сенсора, и если он подключен, то можно инициализировать его
            hr = pNuiSensor->NuiStatus();
            if (S_OK == hr)
               {
                  m_pNuiSensor = pNuiSensor;
                  break;
               }

            // сенсор не работает, освобождаем его
            pNuiSensor->Release();
         }

      if (NULL != m_pNuiSensor)
         {
            // Инициализировать Kinect и указать, что мы будем использовать глубину, скелет и камеру
            hr = m_pNuiSensor -> NuiInitialize(
                  NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | //глубина и индекс фигуры человека
                  NUI_INITIALIZE_FLAG_USES_SKELETON | //скелет
                  NUI_INITIALIZE_FLAG_USES_COLOR); //видео с камеры

            if (SUCCEEDED(hr))
            {
               // Создать событие, которое будет сигнализировать, о том что данные глубины доступны (данные с IR камеры)
               m_hNextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

               // Открытие потока глубины изображения для получения глубины кадра
               hr = m_pNuiSensor->NuiImageStreamOpen(
                  NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
                  NUI_IMAGE_RESOLUTION_640x480,
                  0,
                  2,
                  m_hNextDepthFrameEvent,
                  &m_pDepthStreamHandle);

               // Создать событие, которое будет сигнализировать, о том что данные цвета доступны (данные с видео камеры)
               if (SUCCEEDED(hr))
                  {
                     m_hNextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

                     // Открытие потока цветного изображения
                     hr = m_pNuiSensor->NuiImageStreamOpen(
                        NUI_IMAGE_TYPE_COLOR,
                        cColorResolution,
                        0,
                        2,
                        m_hNextColorFrameEvent,
                        &m_pColorStreamHandle);
                  }

                  // Создать событие, которое будет сигнализировать, о том что данные о скелете доступны
                  m_hNextSkeletonEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
                  if (HasSkeletalEngine(m_pNuiSensor)) // Поддерживает ли сенсор скелет
                     {
                        // Открытие потока скелета
                        hr = m_pNuiSensor -> NuiSkeletonTrackingEnable(
                           m_hNextSkeletonEvent,
                           m_SkeletonTrackingFlags);
                        if(FAILED(hr))
                           return hr;
                     }
               }
            }

   if (NULL == m_pNuiSensor || FAILED(hr))
      return E_FAIL;

   return hr;
}


Вроде это все. Если есть вопросы спрашивайте. В коде практически к каждой строке имеется комментарий, внимательно проследите ход выполнения программы и тогда все станет более-менее понятно.
От этого кода вы можете оттолкнуться взяв его в свои проекты, развив и модернизировав его.
В нашем проекте forum10/topic13841.html в код добавлен обмен данными через COM порт для управления внешними двигателями и получения информации от датчиков, введены параллельные потоки в программе (в связи с чем пришлось вводить критические секции), добавлено распознавание лиц и др.
Успехов! :)

Re: KINECT

lorry » 03 апр 2014, 15:30

Прежде чем пойти дальше, рассмотрим структуру данных глубины которую возвращает нам сенсор.
Как я уже писал, каждый кадр глубины (в зависимости от выбранного вами разрешения) имеет разрешение 640х480, в свою очередь каждая точка кадра глубины это два байта данных (value). Данные построены следующим образом: первые три бита хранят индекс игрока, следующие 12 бит - значение глубины и самый старший бит не используется. То есть, структура данных каждой точки глубины сцены выглядит так:

Безымянный.jpg
Безымянный.jpg (9.66 КиБ) Просмотров: 4091


Индекс игрока может быть 0 (если игрока нет), или принимать значения от 1 до 6 (точка принадлежит 1-му, 2-му, 3-му,... 6-му игроку). Получить индекс можно с помощью побитового умножения:
playerIndex = value & 0x7;

Если значение value сдвинуть вправо на 3 бита, то получим значение глубины сцены в данной точке в миллиметрах:
Depth = value >> 3;

Теперь у нас имеется значение playerIndex указывающее принадлежит ли данная точка сцены кому либо из игроков или эта точка относится к объекту интерьера и есть значение Depth (от 0 до 4095) показывающее расстояние от сенсора до этой точки.
Чтобы запихнуть его в RGBA текстуру (состоящую из байтов) нужно поделить значение на 16 - получим значения в диапазоне от 0 до 255, после этого сцену можно будет визуализировать.
Т.о. как я и писал ранее мы единовременно получаем не просто байт данных расстояния от датчика до объекта, а целую картину таких байт размером 640х480 пикселей, и еще точно знаем принадлежит ли каждый из пикселей человеку или предмету сцены.
Это дает нам колоссальные возможности в обработке картинки глубины, причем любыми доступными средствами, будь то готовые библиотеки обработки изображений или анализ полученных данных своими методами.
Последний раз редактировалось lorry 23 май 2014, 01:41, всего редактировалось 1 раз.

Re: KINECT

BeS » 03 апр 2014, 23:17

К слову говоря насчет Depth Map получаемого с Кинект: стоит заметить и учитываять при работе с ними, что на самом деле Kinect (тот что первой версии) умеет снимать Depth только разрешении 320x240, а к разрешению 640x480 он приводится при помощи интерполяции и соответственно биекция между RGB и Depth в общем случае не является верной. Но это так, скорей академического интереса для, на практике он все равно шумит настолько сильно, что интерполяция там вносит незначительный вклад в этот процесс :)

Re: KINECT

lorry » 04 апр 2014, 00:35

BeS писал(а):на практике он все равно шумит настолько сильно, что интерполяция там вносит незначительный вклад в этот процесс

Работаем с сенсором уже достаточно долго, никакого сколь бы то ни было серьезного шума, который повлиял бы не детектирование, нет. Вот вам визуализированнвя картинка с сенсора:
1934746.jpg
1934746.jpg (16.63 КиБ) Просмотров: 4054

Где здесь шум? Черные пятна это не шум, это не детектированная глубина сцены, ошибки детектирования происходят не по причине "плохого датчика", а в силу разнородной поверхности объектов на которые попадает излучение. Какие-то поверхности ИК свет отражают, какие-то поглощают, какие-то создают интерференцию, из за этого и возникают некоторые артефакты на изображении, которые, к слову, совсем не влияют на решение поставленных задач. Скажите, какие датчики, по приемлемой цене (да и дороже), дадут лучшую картинку? А главное то, чтобы вы могли работать с данными выдаваемыми этими датчиками.
Не забывайте, что мы рассматриваем сенсор производство которого началось в 2010 году, т.е. этой технологии уже больше 4-х лет. Сами знаете что значит четыре года в IT индустрии. На днях мелко-мягкие анонсировали KINECT ONE for windows, это сенсор второго поколения от приставки XBOX ONE, теперь и он доступен для винды (правда SDK я еще не видел для него), Там возможности с разрешением и прочими вещами на много серьезнее чем в первом кинекте.
Но, как бы там ни было, для решения поставленных нами задач, а в первую очередь это slam и детектирование человека, первого кинекта вполне достаточно. Попробуйте полноценно решить эти задачи при помощи ИК или УЗ датчика :) Которые, к слову сказать, действительно "шумят".

Re: KINECT

Madf » 24 апр 2014, 21:08

Вопрос, есть ли либа готовая для кинекта (в комплекте или ещё как), чтобы сканировать на ходу лицо в небольшом 3Д? Т.е. человек немного двигается перед кинектом, а на выходе какой-то инструмент захвата и определения людей, на подобии движений, но только по лицу.

Re: KINECT

lorry » 25 апр 2014, 00:49

Madf писал(а):человек немного двигается перед кинектом, а на выходе какой-то инструмент захвата и определения людей, на подобии движений, но только по лицу.

Если я вас правильно понял, то вы имеете в виду ситуацию, когда лицо человека находится близко к сенсору? Если так, то вопрос на сколько близко? Главное чтобы сенсор захватил фигуру человека (хотя бы "плечи - голова"), а затем возможно отслеживание человека по 120-ти узловым точкам лица, даже если после захвата приблизиться к сенсору на расстояние меньше 10 см., он уверенно держит лицо. И совсем не обязательно "немного двигаться", вы наверное имеете в виду ставшую популярной 3D съемку с помощью простой камеры методом покачивания камеры или движения объекта? Вообще это интересное и перспективное направление в 3D, но для KINECT нет необходимости двигать камеру или объект чтобы получить объемную картинку, сенсор сам уже возвращает нам 3D. Так что отслеживание лица в 3D возможно.

Re: KINECT

Madf » 25 апр 2014, 11:41

Не, всё проще, меня интересует, может ли кинект различать лица, есть ли готовый инструмент для этого (чтобы велосипед не изобретать) и на каких расстояниях это работает? Т.е., можно получить уникальность лица в небольшой динамике (скажем при смене ног (не быстрое движение)).

Re: KINECT

lorry » 25 апр 2014, 15:32

Вы имеете в виду детектирование лица, т.е. чтобы сенсор узнавал человека стоящего перед ним?

Re: KINECT

Madf » 25 апр 2014, 16:17

Да, чтобы мог узнавать. Причем не нужно это делать при большой удаленности человека или чуть с плечами, скажем с пол метра и до двух.


Rambler\'s Top100 Mail.ru counter