17 February 2013

Kinect colour/ IR/ depth image reading

The Kinect SDK is a development platform which includes several APIs for programmer to communicate with Kinect hardware. In this project, we only concern on colour and depth sensor (ignore microphones). The sample programs for the SDK were mainly written in C# while only relatively few resources for C++ (the language I am going to use). Here is a useful tutorial for Kinect C++ SDK programming.
There are simply two steps to get data from Kinect: initialise Kinect and get frame from image stream.
For initialisation, these codes are required:
   bool initKinect() {
    // Get a working kinect sensor
    int numSensors;
    if (NuiGetSensorCount(&numSensors) < 0 || numSensors < 1) return false;
    if (NuiCreateSensorByIndex(0, &sensor) < 0) return false;

    // Initialize sensor
    sensor->NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH | NUI_INITIALIZE_FLAG_USES_COLOR);
    sensor->NuiImageStreamOpen(
        NUI_IMAGE_TYPE_COLOR,            // Depth camera or rgb camera?
        NUI_IMAGE_RESOLUTION_640x480,    // Image resolution
        0,        // Image stream flags, e.g. near mode
        2,        // Number of frames to buffer
        NULL,   // Event handle
        &rgbStream);
    return sensor;
}

HRESULT NuiInitialize(DWORD dwFlags);
dwFlags is a flag used to determine which content you want to capture in the NUI API, include:
Constant
Description
NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD
This flag was deprecated in version 1.5; it is no longer used.
NUI_INITIALIZE_FLAG_USES_AUDIO
Initialize the sensor to provide audio data.
NUI_INITIALIZE_FLAG_USES_COLOR
Initialize the sensor to provide color data.
NUI_INITIALIZE_FLAG_USES_DEPTH
Initialize the sensor to provide depth data.
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX
Initialize the sensor to provide depth data with a player index.
NUI_INITIALIZE_FLAG_USES_SKELETON
Initialize the sensor to provide skeleton data.

These flags can be combined together by | (bitwise-OR).

HRESULT NuiImageStreamOpen(NUI_IMAGE_TYPE eImageType,NUI_IMAGE_RESOLUTION eResolution,DWORD dwImageFrameFlags,DWORD dwFrameLimit,HANDLE hNextFrameEvent,HANDLE *phStreamHandle);
This method create a specified data stream for frame grabbing.
Parameters:
eImageType [in]: Specifying what type of data stream we want, it must correspond to the parameter dwFlags in NuiInitialize.

eResolution [in]: Specifying resolution of the image we will get. For colour image, Kinect supports: 1280x1024 (12fps) and 640x240 (30fps). For depth image, it supports: 640x480, 320x240 and 80x60.

dwImageFrameFlags [in]: Specifies the frame event options (like enable near mode for Kinect for Windows).

dwFrameLimit [in]: The number of frames that the Kinect runtime should buffer. The maximum value is NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM. Most applications should use a frame limit of two.


hNextFrameEvent [in, optional]: A handle to a manual reset event that will be fired when the next frame in the stream is available.

phStreamHandleType [out]: A pointer that contains a handle to the opened stream.

Return value: Type: Returns S_OK if successful; otherwise, returns one of the failure codes.

Getting frame from the stream by following codes:

void getKinectData(GLubyte* dest) {
 NUI_IMAGE_FRAME colorFrame;
 NUI_LOCKED_RECT c_LockedRect;

 if (sensor->NuiImageStreamGetNextFrame(colorStream, 10, &colorFrame) < 0) return;

 INuiFrameTexture* c_texture = colorFrame.pFrameTexture;
 c_texture->LockRect(0, &c_LockedRect, NULL, 0);

    if (c_LockedRect.Pitch != 0) {// check valid data
  BYTE* c_buf = (BYTE*) c_LockedRect.pBits;
  for (int y = 0; y < height; ++y)
  {
   const BYTE* pImage = c_buf;
   for (int x = 0; x < width; ++x)
   {
    // Get depth of pixel in millimeters
    *dest++ = pImage[0]; // B
    *dest++ = pImage[1]; // G 
    *dest++ = pImage[2]; // R
    *dest++ = pImage[3]; // A
    pImage += 4; // Go to next pixel
   }
   c_buf += width*4; // Go to next line
  }
    }
 c_texture->UnlockRect(0);
 sensor->NuiImageStreamReleaseFrame(colorStream, &colorFrame);
}
NuiImageStreamGetNextFrame() retrieve a frame for a given stream. The returned frame is a NUI_IMAGE_FRAME structure which contains information like frame_number, resolution, texture and etc. NUI_LOCKED_RECT contains a pointer to the actual data. An INuiFrameTexture manages the frame data. Then we get an INuiFrameTexture so that we can get the pixel data out of it, using a NUI_LOCKED_RECT. The Kinect data is in BGRA format, so we can copy it to our buffer and use if as an OpenGL texture.
Finally, the frame and sensor must be release for later or other program use.

Results:
Colour frame
Depth frame (mod by 256)

Depth frame(single region)

In order to distinguish the depth easily, a common method is to "compress" the depth in to several intensity region. For example, if the depth values range from 800 to 4000 (mm), we can divide the depth into different regions by mod them by 256 (if the depth is saved in char).
Raw IR frame
When acquiring the raw IR image, the IR emitter need to be covered or turned off by SDK (not valid for Kinect for Xbox) to avoid random dot pattern showing in the image.

Notes:
- For the same codes, the Kinect for Window requires some delay (800ms) after the initialization before acquiring frame from stream while Kinect for Xbox doesn't need the delay. If no delay is applied, the frame returned by the SDK may be empty or incorrect. The reason is not known so far.
- E_NUI_NOTGENUINE error was returned by the Kinect after few seconds normal running. It is caused by inadequate bandwidth of USB controller (multiple devices are connected to the same controller). It can be solved by plugging the Kinect into another USB port.

4 comments:

  1. hi Lin Zhang,
    Regarding the IR stream, how did you retrieve the IR RAW image? Did you use any other libraries?

    ReplyDelete
    Replies
    1. Hi, I used PCL which based on OpenNI for Kinect capture. They do provide raw image from IR camera. You need to cover the IR emitter to avoid any disruption in the IR image.

      Delete
  2. Can you please share C# code for depth data (single region) ? Thanks :)

    ReplyDelete
    Replies
    1. Hi, I used C++ SDK. I believe Microsoft should have provided related tutorial for the C#. Good luck

      Delete