OpenNI + OpenCV


OpenCV 的全名是「Open Source Computer Vision」(官方網站中文網站),是一套採用 BSD 授權的開放原始碼的電腦視覺函式庫,在相關領域來說,算是一套相當之行的函式庫;在 OpenCV 裡面,包含了很多影像處理的功能,同時也包含了基本的圖形介面、以及攝影機的操作等功能。

而對於 OpenNI 這樣、針對深度影像和彩色影像做處理的架構,其實如果不是單純只是想靠 NITE 來追蹤人體骨架的話,OpenCV 是一個相當適合拿來搭配使用的函式庫;實際上,OpenCV 現在也可以直接整合 OpenNI 來讀取影像(請參考《Using Kinect and other OpenNI compatible depth sensors》)。

這篇呢,Heresy 則是以簡單的範例,大概講一下怎麼把 OpenNI 的深度和彩色資料,讀出來轉換成 OpenCV 的格式。下面就直接看原始碼吧~

// OpenNI Header
#include <XnCppWrapper.h>

// link OpenNI library
#pragma comment( lib, "OpenNI.lib" )

// OpenCV Header
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

// Link OpenCV Library
#ifdef _DEBUG
  #pragma comment( lib, "opencv_core242d.lib" )
  #pragma comment( lib, "opencv_highgui242d.lib" )
  #pragma comment( lib, "opencv_imgproc242d.lib" )
#else
  #pragma comment( lib, "opencv_core242.lib" )
  #pragma comment( lib, "opencv_highgui242.lib" )
  #pragma comment( lib, "opencv_imgproc242.lib" )
#endif

// main function
int main( int argc, char** argv )
{
  // 1a. initial OpenNI
  xn::Context xContext;
  xContext.Init();

  // 1b. create depth generator
  xn::DepthGenerator xDepth;
  xDepth.Create( xContext );

  // 1c. create image generator
  xn::ImageGenerator xImage;
  xImage.Create( xContext );

  // 1d. set alternative view point
  xDepth.GetAlternativeViewPointCap().SetViewPoint( xImage );

  // 2. create OpenCV Windows
  cv::namedWindow( "Depth Image", CV_WINDOW_AUTOSIZE );
  cv::namedWindow( "Color Image", CV_WINDOW_AUTOSIZE );
  cv::namedWindow( "Depth Edge", CV_WINDOW_AUTOSIZE );
  cv::namedWindow( "Color Edge", CV_WINDOW_AUTOSIZE );

  // 3. start OpenNI
  xContext.StartGeneratingAll();

  // main loop
  while( true )
  {
    // 4. update data
    xContext.WaitAndUpdateAll();

    // 5. get image data
    {
      xn::ImageMetaData xColorData;
      xImage.GetMetaData( xColorData );

      // 5a. convert to OpenCV form
      cv::Mat cColorImg( xColorData.FullYRes(), xColorData.FullXRes(),
                         CV_8UC3, (void*)xColorData.Data() );

      // 5b. convert from RGB to BGR
      cv::Mat cBGRImg;
      cv::cvtColor( cColorImg, cBGRImg, CV_RGB2BGR );
      cv::imshow( "Color Image", cBGRImg );

      // 5c. convert to signle channel and do edge detection
      cv::Mat cColorEdge;
      cv::cvtColor( cColorImg, cBGRImg, CV_RGB2GRAY );
      cv::Canny( cBGRImg, cColorEdge, 5, 100 );
      cv::imshow( "Color Edge", cColorEdge );
    }

    // 6. get depth data
    {
      xn::DepthMetaData xDepthData;
      xDepth.GetMetaData( xDepthData );

      // 6a. convert to OpenCV form
      cv::Mat cDepthImg( xDepthData.FullYRes(), xDepthData.FullXRes(),
                         CV_16UC1, (void*)xDepthData.Data() );

      // 6b. convert to 8 bit
      cv::Mat c8BitDepth;
      cDepthImg.convertTo( c8BitDepth, CV_8U, 255.0 / 7000 );
      cv::imshow( "Depth Image", c8BitDepth );

      // 6c. convert to 8bit, and do edge detection
      cv::Mat CDepthEdge;
      cv::Canny( c8BitDepth, CDepthEdge, 5, 100 );
      cv::imshow( "Depth Edge", CDepthEdge );
    }

    cv::waitKey( 1 );
  }
}

在這邊的例子裡,Heresy 並沒有去使用整合 OpenNI 的 OpenCV,而是獨立使用 OpenNI 來做資料的讀取,然後再轉換成 OpenCV 的格式。實際上,如果只是要使用 OpenNI 的影像資料的話,使用整合過的 OpenCV 可以直接使用內建的 VideoCapture 來做畫面的讀取,在使用上會比較單純、簡單一點,不過由於這樣會少掉一些 OpenNI 的功能,所以在這邊 Heresy 不使用這樣的方法。

所以,在上面的範例裡面,1a1d 的部分,就是用標準 OpenNI 的流程,來進行初始化的動作;詳細的說明,請參考《透過 OpneNI 讀取 Kinect 深度影像資料》。而接下來 2 的部分,則是使用 OpenCV 的 highgui 這個模組的 namedWindow() 這個函式(官方文件),來建立四個不同名稱的視窗、作為畫面的顯示。

接下來,則是透過一個無窮迴圈,來不停地更新資料了~裡面主要分成兩塊,也就是 5、讀取 Image Generator 的彩色影像、以及 6、讀取 Depth Generator 的深度影像的部分。

其中,在 5a6a 的部分,就是把 OpenNI 讀出來的 map(xn::ImageMetaDataxn::DepthMetaData)轉換成 OpenCV 的影像格式、cv::Mat 的部分(官方文件)。

以彩色影像來說,就是在建立 cv::Mat 物件的時候,把影像的大小、也就是 Y 軸、X 軸的解析度,以及資料的形式、資料的位址,都傳遞給建構子、以建立出一張 OpenCV 的影像、cColorImg。其中,CV_8UC3 是指 3 channel 的 8bit 正整數(unsigned char)的資料(參考)。

不過,由於 OpenCV 所使用的彩色影像的色彩,預設是以 Blue、Green、Red 來做排列,和一般 Red、Green、Blue 排列不同,所以要拿來用的話,還需要先做一個轉換;在這邊(5b)就是透過 cvtColor() 這個 OpenCV 的 imgproc 這個模組裡的函式(官方文件),把本來的 RGB 影像、轉換成 BGR 的影像(cBGRImg)。而在轉換好之後,則就是在透過 imshow() 這個函式,把轉換完成的影像、顯示在對應的視窗(這邊是 Color Image)上了。

而接下來(5c),Heresy 則是試著用 OpenCV 提供的 Canny 這種方法的邊緣偵測(官方文件)。不過由於 OpenCV 所提供的 Canny edge 只有針對 8bit 1 channel 的影像作處理,所以這邊要先再用 cvtColor(),把影像轉成灰階的、然後再來進行;而之後,則是一樣透過 imshow() 這個函式,把偵測完的結果、顯示在對應的視窗上。

深度的部分(6a)也是類似的,不過由於 OpenNI 的深度影像的單一像素的格式是 XnDepthPixel、實際上是單一 channel 的 16bit 的正整數(unsigned short),所以在建立 cv::Mat 的時候的資料型別,則是要設定為 CV_16UC1

不過,雖然 OpenNI 的深度影像是 16bit 的正整數,理論上值的範圍是 0 – 65,535,但是實際上深度的最大值只會到 10,000,所以如果不處理、直接畫的話,會有整個畫面偏暗的問題(基本上,畫面會接近全黑);所以在這邊,Heresy 也先透過 cv::MatconvertTo() 的函式,把這個 16bit 的影像裡的每一個像素都乘上一個 scale(255.0 / 7000)後,轉換成 8bit 的影像(c8BitDepth)。再之後,就是一樣把轉換好的影像,進行 canny edge 偵測了~

而這樣的程式執行的結果,會有下面這樣、四個不同資料的視窗,分別代表彩色影像、基於彩色影像的邊緣偵測結果、深度影像、以及基於深度影像進行邊緣偵測的結果。

這篇就先到這了。基本上,Heresy 是把這篇文章定位成一個極為簡單的 OpenNI 和 OpenCV 的資料整合範例;而由於 OpenCV 還有提供相當多的影像處理的功能,接下來要怎麼做,就是看自己想要做什麼了~


OpenNI / Kinect 相關文章目錄

對「OpenNI + OpenCV」的想法

  1. Heresy大,您好
    想請問您一個問題,就是有關於c8BitDepth的數值是深度值嗎?
    還是這個陣列只是單純作轉換用的?
    不好意思,麻煩您了

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.