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. Hereys,非常感謝你的文章,我有一個問題想問您,就是採集的彩色圖像和深度圖像的幀數不能一一對應,該怎麼處理啊,比如說,彩色圖像總比深度圖像快,並且相差的幀數不是固定的,而是一個隨機值,有時候相差一幀,有時候相差多幀,能給我點提示嗎,非常感謝,

  2. 您好 我想請問一下
    您有些程式前面是xn::
    有些是openni::
    請問這兩種有什麼差別????

  3. Heresy您好,

    參考本篇文章改寫讀取IR Camera的資料,也順利讀到資料並imshow在視窗,
    但輸出的xIrData.FullYRes()是240,xIrData.FullXRes()是320,
    顯示圖有格子狀,感覺是240×320的pixel放到480×640圖內,每4格裡面有1格放資料,而且有疊影的狀況。

    想請教您要如何透過程式碼更改讓IR camera擷取480×640或更高解析度的資料輸出?

    Thanks in advance,

    Seigfried

    —-

    OpenNI 的版本是1.3.2.1
    硬體: Xtion Pro Live

    以下為程式碼

    int main( int argc, char** argv )
    {
    xn::Context xContext;
    xContext.Init();

    xn::IRGenerator xIrImage;
    xIrImage.Create( xContext );

    cv::namedWindow(“IR Image", 0 );
    xContext.StartGeneratingAll();

    while( true ){
    xContext.WaitAndUpdateAll();
    xn::IRMetaData xIrData;
    xIrImage.GetMetaData( xIrData );
    cv::Mat IrImg( xIrData.FullYRes(), xIrData.FullXRes(), CV_8UC1, (void*)xIrData.Data() );
    cout << "y"<<xIrData.FullYRes();
    cout << "x"<<xIrData.FullXRes()<<endl;
    cv::imshow( "IR Image", IrImg );
    cv::waitKey( 1 );
    }
    }

    • Heresy您好,

      經由閱讀您其他的文章,已經可以更改解析度(640*480/1280*1024),但格子狀的略為有改善但沒有消除,手上剛好有一個別人給的程式用同樣的設備能work,發現這程式抓出來的畫面範圍比直接從IRGenerator的大,而且也沒格子狀(條紋)和疊影,是否哪裡設錯了,所以資料放錯造成畫面被左右交疊?

      • Heresy您好,

        經過確認是資料型別轉換的問題,IR generator產生的是16bits的灰階不能用CV_8UC1去接,屬性不對。在google上查資料,發現要轉對資料再增亮才能較為清楚的看到IR image,應該解決這個的問題了,非常感謝您用心的文章!!

        Many thanks,
        Seigfried

        部分code如下

        IplImage *irImage = cvCreateImage( cvSize(640,480), 16, 1);
        const XnIRPixel* irMap;
        XnDepthPixel tempIR[640*480];

        irMap = xIrImage.GetIRMap();

        unsigned int max = 250;

        for(int i = 0; i widthStep);
        cvShowImage(“IR Image", irImage);

        • 恭喜你的問題解決了。

          附帶一提,< 等字元在網頁上也是特殊語法,所以在一般的留言系統裡,沒有做特殊處理的話都會被濾掉。

          • Hi Heresy 老師您好.

            我也遇到了Seigfried的問題,照您的sample code可以擷取影像,
            並用open CV顯示出來,但是解析度為320X240.

            Seigfried的code似乎不是很完整,請問老師有什麼方法,可以使得
            解析度變成640×480嗎?

            我有試過
            XnMapOutputMode mapMode;
            mapMode.nXRes = 640;
            mapMode.nYRes = 480;
            mapMode.nFPS = 30;
            result = depthGenerator.SetMapOutputMode( mapMode );
            result = imageGenerator.SetMapOutputMode( mapMode );
            可是加上去之後反而完全得不到Xtion pro live的資料.

            謝謝

          • Hi Heresy 老師您好.

            我看到SetMapOutputMode()的回傳值是65575,
            請問哪裡可以查這個數值表示的是什麼呢?

            另外我用同一份code,同一台Xtion pro live,
            在PC上顯示的resolution是320×240,但是在embeded system顯示的resolution是160×120.

            請問您有什麼想法嗎?
            謝謝

  4. 因為想要建立一個系統的關係,所以我想使用OpenCV將這程式的"6a"的cDepthImg(16bit)存下來,而不是將"6b"的c8BitDepth(8bit)存下來,但是卻發生存下來的畫面跟截取到的畫面完全失真,查了一些資料但不知問題出在哪,想請教Heresy。
    我用的OpenCV函數是cvSaveImage,存檔格式為bmp檔。謝謝

  5. 你好,我是初學者,如果我要執行上面這個程式,要做那些環境設定?
    可否請你指導一下??
    因為我看網路上許多對於openCV&openNI環境設定有太多種惹QQ
    一整個混亂~~
    如果要執行上面的城市需要在什麼樣的環境之下呢??
    ((我是用visual studio 2010
    謝謝Heresy大大~~

  6. 補充一下,我是先學習的openNi最新的2版本,然後網上又看到了一些範例,但是好多都是用1的xn寫的,不太看得懂,電腦裡也沒裝1,所以無法編譯運行,想自己給轉換成2的,在進行後面的操作。謝謝

  7. 您好,初學者,請見諒,我想問一下,您提到是用OpenNi先讀影像資料然後再轉換成openCV格式而沒有用videocapture(),這兩個有什麼區別呢,如果是爲了後期的手勢識別,比如需要對捕獲的影響進行手部分析優化之類的,該用哪種方法呢?

    另外,如果要進行openNi 1和 2版本的互轉,該如何做呢,只需修改前面獲取資料的那一部份是吧?ImageMetaData,跟depth generator 相當於2裡的什麽呢? VideoStream mDepthStream 跟 VideoFrameRef 嗎?

    非常感謝!
    同時也該謝您對之前幾次提問的回覆~

    • 透過整合好的 OpenCV 來讀取深度影像,對於熟系 OpenCV 的人來說,會比較簡單,但是相對的,在細部控制上,就沒辦法像直接使用 OpenNI 那樣完整了。
      再加上 OpenCV 整合的應該只有 OpenNI 1,不適用於 OpenNI 2,所以 Heresy 會建議直接使用 OpenNI。

      OpenNI 2 的教學 Heresy 已經有把基礎的部分都寫完了,建議請參考
      https://kheresy.wordpress.com/index_of_openni_and_kinect/

  8. Heresy大你好,
    不好意思我用另一種方法照您步驟打程式碼
    做出基本的深度圖像與彩色圖像
    但是關於Color Edge 與 Depth Edge 不知道如何打
    是否可以稍微提點一下 感謝你
    程式碼如下:
    #include
    #include
    #include
    #include
    #include “opencv/cv.h"
    #include “opencv/highgui.h"

    using namespace std;
    using namespace cv;

    void CheckOpenNIError( XnStatus result, string status )
    {
    if( result != XN_STATUS_OK )
    cerr << status << " Error: " << xnGetStatusString( result ) <imageData,depthMD.Data(),640*480*2);
    cvConvertScale(imgDepth16u,depthShow,255/4096.0,0);
    memcpy(imgRGB8u->imageData,imageMD.Data(),640*480*3);
    cvCvtColor(imgRGB8u,imageShow,CV_RGB2BGR);
    cvShowImage(“depth", depthShow);
    cvShowImage(“image",imageShow);
    key=cvWaitKey(20);
    }

    //destroy
    cvDestroyWindow(“depth");
    cvDestroyWindow(“image");
    cvReleaseImage(&imgDepth16u);
    cvReleaseImage(&imgRGB8u);
    cvReleaseImage(&depthShow);
    cvReleaseImage(&imageShow);
    context.StopGeneratingAll();
    context.Shutdown();
    return 0;
    }

  9. 板大您好
    我照您是面程式碼RUN過
    但是出現ERROR:無法開啟包含檔案: ‘imgproc.hpp’: No such file or directory
    發現那三行Header程式都找不到
    是因為路徑問題嗎?
    但我搜尋一下我的OPENCV內似乎沒有imgproc 跟 core 這兩個的hpp檔

    • 請確認一下你的 OpenCV 版本。
      目前官方網站上下載的 OpenCV 應該都有提供 opencv2,也就是 C++ 版本的 API,裡面應該都有必要的檔案。
      (檔案在 \build\include\opencv2 )

發表迴響

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

WordPress.com 標誌

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

Facebook照片

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

連結到 %s

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