Green Screen с помощью Kinect SDK / Kinect / Recog.ru - Распознавание образов для программистов


Green Screen с помощью Kinect SDK

С помощью Kinect SDK легко можно сделать эффект Green Screen – т.е. выделение человека от фона, а вместо фона выводить любую картинку. Я не буду заниматься приведение картинок примеров – в конце-концов если вы используете Kinect, то можете запустить пример Green Screen и посмотреть, что и как. Здесь мы рассмотрим модифицированное приложение для упрощения объяснений, поскольку не каждый разбирается в Win32 API.
Исходный код примера приведен ниже:
#include "opencv2/core/core_c.h"
#include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/highgui/highgui_c.h"
#include "NuiApi.h"
 

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;


HRESULT CreateFirstConnected();
HRESULT GetDepth();
HRESULT GetColor();

int main()
{	
	m_depthRGBX = new USHORT[cDepthWidth*cDepthHeight*cBytesPerPixel];
	m_colorRGBX = new BYTE[cDepthWidth*cDepthHeight*4];
	m_colorCoordinates = new LONG[640*480*2];

	IplImage* Image = cvCreateImage( cvSize( 640, 480 ), 16, 1 );
	IplImage* Image24 = cvCreateImage( cvSize( 640, 480 ), 8, 3 );
	IplImage* ImageResult = cvCreateImage( cvSize( 640, 480 ), 8, 3 );
	IplImage* Mask = cvCreateImage( cvSize( 640, 480 ), 8, 1 );
	IplImage* Fon = cvLoadImage( "fon.jpg", 1 ); // Какое-нибудь изображение 640 на 480

	// Коннектимся к кинекту
    CreateFirstConnected();


	// Получаем глубину
	const int eventCount = 2;
    HANDLE hEvents[eventCount];
	hEvents[0] = m_hNextDepthFrameEvent;
	hEvents[1] = m_hNextColorFrameEvent;
	
	int  i,j;	 
	
	do{
		DWORD dwEvent = MsgWaitForMultipleObjects(eventCount, hEvents, FALSE, INFINITE, QS_ALLINPUT);
		if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextDepthFrameEvent, 0) )
		{
			GetDepth();
		}
		if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextColorFrameEvent, 0) )
			 GetColor();

		cvSet( Mask, cvScalar( 0 ) );
		for( i = 0; i < cDepthHeight; i ++ )
			for( j = 0; j < cDepthWidth; j++ )
			{
				Image24->imageData[i*Image24->widthStep + j*3] = m_colorRGBX[i*4*cDepthWidth+j*4];
				Image24->imageData[i*Image24->widthStep + j*3+1] = m_colorRGBX[i*4*cDepthWidth+j*4+1];
				Image24->imageData[i*Image24->widthStep + j*3+2] = m_colorRGBX[i*4*cDepthWidth+j*4+2];
				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( Image->imageData,  m_depthRGBX, cDepthWidth*cDepthHeight*2 );
		

		cvShowImage( "out", Image );
		cvShowImage( "out2", Image24 );
		cvCopy( Fon, ImageResult );
		cvCopy( Image24, ImageResult, Mask );
		cvShowImage( "out3", ImageResult );
		cvDilate( Mask, Mask );
		cvCopy( Fon, ImageResult );
		cvCopy( Image24, ImageResult, Mask );
		cvShowImage( "out4", ImageResult);
		int c = cvWaitKey( 50 );
		if ( c == 32 ) break;
	}
	while (1);
	// Закрытие
	if (m_pNuiSensor)
    {
        m_pNuiSensor->NuiShutdown();
    }

    if (m_hNextDepthFrameEvent != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hNextDepthFrameEvent);
    } 
	if (m_hNextColorFrameEvent != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hNextColorFrameEvent);
    }
    // done with depth pixel data
    delete[] m_depthRGBX;
	cvReleaseImage( &Image );
    
    m_pNuiSensor->Release();


	return 0;
}


HRESULT CreateFirstConnected()
{
    INuiSensor * pNuiSensor;
    HRESULT hr;

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

    // Look at each Kinect sensor
    for (int i = 0; i < iSensorCount; ++i)
    {
        // Create the sensor so we can check status, if we can't create it, move on to the next
        hr = NuiCreateSensorByIndex(i, &pNuiSensor);
        if (FAILED(hr))
        {
            continue;
        }

        // Get the status of the sensor, and if connected, then we can initialize it
        hr = pNuiSensor->NuiStatus();
        if (S_OK == hr)
        {
            m_pNuiSensor = pNuiSensor;
            break;
        }

        // This sensor wasn't OK, so release it since we're not using it
        pNuiSensor->Release();
    }

    if (NULL != m_pNuiSensor)
    {
        // Initialize the Kinect and specify that we'll be using depth
        hr = m_pNuiSensor->NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX  | NUI_INITIALIZE_FLAG_USES_COLOR); 
        if (SUCCEEDED(hr))
        {
            // Create an event that will be signaled when depth data is available
            m_hNextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

            // Open a depth image stream to receive depth frames
            hr = m_pNuiSensor->NuiImageStreamOpen(
                NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
                NUI_IMAGE_RESOLUTION_640x480,
                0,
                2,
                m_hNextDepthFrameEvent,
                &m_pDepthStreamHandle);
			// Create an event that will be signaled when color data is available
			if (SUCCEEDED(hr)){
				m_hNextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

				// Open a color image stream to receive depth frames
				hr = m_pNuiSensor->NuiImageStreamOpen(
					NUI_IMAGE_TYPE_COLOR,
					cColorResolution,
					0,
					2,
					m_hNextColorFrameEvent,
					&m_pColorStreamHandle);
			}
        }
    }

    if (NULL == m_pNuiSensor || FAILED(hr))
    {        
        return E_FAIL;
    }
	m_pNuiSensor->NuiSkeletonTrackingDisable();


    return hr;
}


HRESULT GetDepth()
{
    HRESULT hr = S_OK;
    NUI_IMAGE_FRAME imageFrame;

    // Attempt to get the depth frame
    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;

    // Lock the frame data so the Kinect knows not to modify it while we're reading it
    pTexture->LockRect(0, &LockedRect, NULL, 0);

    // Make sure we've received valid data
    if (LockedRect.Pitch != 0)
    {
        memcpy(m_depthRGBX, LockedRect.pBits, LockedRect.size);
    }

    // We're done with the texture so unlock it
    pTexture->UnlockRect(0);

    // Release the frame
    m_pNuiSensor->NuiImageStreamReleaseFrame(m_pDepthStreamHandle, &imageFrame);

    // Get of x, y coordinates for color in depth space
    // This will allow us to later compensate for the differences in location, angle, etc between the depth and color cameras
    m_pNuiSensor->NuiImageGetColorPixelCoordinateFrameFromDepthPixelFrameAtResolution(
        cColorResolution,
        cColorResolution,
       640*480,
        m_depthRGBX,
        640*480*2,
        m_colorCoordinates
        );

    return hr;
}



HRESULT GetColor()
{
    HRESULT hr = S_OK;
    NUI_IMAGE_FRAME imageFrame;

    // Attempt to get the depth frame
    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;

    // Lock the frame data so the Kinect knows not to modify it while we're reading it
    pTexture->LockRect(0, &LockedRect, NULL, 0);

    // Make sure we've received valid data
    if (LockedRect.Pitch != 0)
    {
        memcpy(m_colorRGBX, LockedRect.pBits, LockedRect.size);
    }

    // We're done with the texture so unlock it
    pTexture->UnlockRect(0);

    // Release the frame
    m_pNuiSensor->NuiImageStreamReleaseFrame(m_pColorStreamHandle, &imageFrame);

    return hr;
}

В принципе пример первоначально содержал модифицированную версию Green Screen с добавлением нескольких эффектов, но я их удалил, поскольку ничего хорошего из этого не вышло. В данном примере в отличии от предыдущего (http://recog.ru/blog/kinect/93.html) работа программы не ограничивается одним изображением, а осуществляется цикл обработки видеопотока и различий с Kinectа, из которого можно выйти нажав клавишу Пробел.
Первоначально подсоединяемся к Kinect, а затем попадаем в цикл, в котором с использованием функции WaitForSingleObject ожидаем разблокировки потока после приема карты различий и изображения с камеры Kinect. Здесь стоит отметить, что функция CreateFirstConnected отличается от предыдущего примера тем, что используются другие флаги при инициализации: NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_COLOR, т.е. получить карту глубины с указанием обнаруженного игрока и возможность получения цветного изображения. Аналогичным образом модифицируется открытие потоков (теперь их два), и если вы посмотрите пример Green Screen и этот, то увидите, что в том инициализировалась карта глубины на 320x240, а здесь на 640x480 (сделано, чтобы внести хоть какие-то отличия, однако в таком разрешении есть недостаток – который вы увидите если запустите пример, а именно больше пикселей внутри игрока, которые соответствуют фону).
Функция GetDepth также отличается, особенно тем, что в конце вызывается функция NuiImageGetColorPixelCoordinateFrameFromDepthPixelFrameAtResolution, которая выдает соответствие между пикселями графического изображения и картой глубины. Т.е. даже если разрешения не совпадают – это ничего страшного.
Функция GetColor полностью взята из примера.
Далее по циклу. Попадаем в циклы перебора пикселей и глубин. Преобразуем сначала 32 битное изображение в 24 битное OpenCV. С помощью функции NuiDepthPixelToPlayerIndex определяет принадлежит ли глубина игроку или фону. Если игроку, то формируем маску изображения Mask.
После чего выводим картинки на экран, копируя изображения при помощи cvCopy, с указанием маски, что копировать, а что нет. Для устранения просвечивающего фона через отдельные точки игрока вызываем функцию cvDilate. В общем все, можно запускать и тестировать. Я пример изображений не привожу.
  • 0
  • 18 января 2013, 11:19
  • vidikon

Комментарии (0)

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.