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;
cvtColor( cColorImg, cBGRImg,
CV_RGB2BGR );
cv::imshow(
"Color Image", cBGRImg );

// 5c. convert to signle channel and do edge detection
cv::
Mat cColorEdge;
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 還有提供相當多的影像處理的功能,接下來要怎麼做,就是看自己想要做什麼了~

28 thoughts on “OpenNI + OpenCV”

  1. Heresy您好
    我想請問一下scale的分母7000
    是您經過測試後在視覺上比較順眼的嗎?
    還是說有什麼特別的意義?

  2. to HHC
    那個值是取決於你要把資料做怎樣的對應。
    OpenNI 目前的深度感應器理論上的深度值是 0 – 10,000,所以如果是完全對應的話,就是給 10,000;Heresy 這邊算是去強化 0-7,000 這段深度在視覺上的效果。

  3. 您好!请问用kinect获取的深度信息图像和RGB图像是对齐的么?就是说每个像素点的位置是否是一一对应(匹配)的?或者说两幅图像是从一个视点得到的,只不过一个是深度图像一个是RGB图像,谢谢。:):D

  4. Heresy大大你好,请问OpenNI有没有像OpenCV中cvGetCaptureProperty()这样的函数,如果没有的话该怎样控制openNI下的视频帧?比如我要从第100帧开始读取

  5. to Auu
    不太了解你的問題?你是要使用錄製好的 ONI 檔嗎?
    如果是的話,請參考 OpenNI 的 xn::Player 的說明,他有提供跳到特定 Frame 的功能。

  6. 不好意思,没问清楚,不过我已经解决了。希望Hersey大大能再发点OpenNI OpenCV的blog,让我们学习学习,谢谢了。

  7. 我想問一下
    要如何取得MAT裡面 RGB的值呢?
    或著有方法把mat轉成iplimage去讀取RGB呢?
    謝謝

  8. heresy您好
    我正在寫關於物體追蹤的程式
    我想請問一下
    現在我想要找出連續兩張影像的差異
    但是我卻不知道應該利用openni的哪個function來取得連續的兩張frame
    是利用FrameID()取得前一張跟目前的frame嗎?
    希望可以給我一點指點 麻煩您了

  9. OpenNI 應該沒有這個功能。
    你應該是需要自己把上一個 frame 儲存下來,以供之後使用。

  10. heresy您好
    請問一下我用這組程式碼在VS2008下執行
    但是在header部分出了問題
    我的OpenCV內似乎找不到imgproc 與 core 的hpp檔
    請問該怎麼辦
    麻煩您了

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

  12. heresy您好
    最近做OpenCV和OpenNI結合的程序,OpenNI獲取的Depth圖像Data是16bit unsigned short,如您文中所述,保存到mat CV_16U1中,爲了讀取這些深度值,opencv有直接獲取16bit數據的方法么?一直沒有找到合適的讀取方法。請指教
    自己嘗試這用過CV_MAT_ELEM_PTR,和在IplIMage中保存深度數據,用imageData的方式讀取,都沒有成功獲取到深度值。如果轉化成8bit數據,則識別精度就下降了,期待您好的建議

  13. cv::Mat 有提供 at<>() 這個 template function,應該可以用來讀取。
    ex:

    cv::Mat mImg;
    unsigned short uDepth = mImg.at<unsigned short>( x, y );

  14. 你好!
    如果我儲存深度和彩色圖像, 將來再讀取去繪製點雲的話, 資料會否受損?
    在資料不受損的情況下, 如何儲存或匯出這些資料將來使用? 謝謝

  15. to champo
    這取決於你怎麼做轉換、儲存。
    如果你轉換成彩色的過程是可逆的,就可以確保可以轉換回來。
    但是如果試圖黨的話,也要確定你的儲存格式是無損的,像 JPEG 就會會因為對資料做壓縮,造成損失。

  16. heresy大大,您好,目前按照你的部落格教的开始逐步写点好玩的小程序了,之前做了一个“变脸”的程序,用opencv2做显示,但是始终无法做到png图片的背景透明,大概是opencv自身的问题吧~~:(后来买了本kinect for windows 开发的书,觉得上面用WPF做的界面,感觉很简单,不知道openni可不可以结合WPF来做一些界面之类的东西?

  17. to chao

    Heresy 自己沒有再使用 WPF,所以沒有試過。
    不過理論上,OpenNI 是提供 C 的介面,所以可以和其他 C 的圖形介面函式庫做搭配。

  18. 我想請問一下怎麼讓kinect每隔一段時間存一張圖呢?

  19. to jerry

    這部分應該可以自己用 timer 來做資料存取的控制。

  20. heresy你好,
    我用的是asus xtion pro live,按照您该文的方法得到了bgr和depth的图,但是这个图的长和宽很小,请问有什么办法可以使图的长宽变大些么?
    多谢

  21. to 咸鱼
    感應器解析度最大就是 640×480,這是硬體上的限制。

  22. 您好
    您的文章實在令小弟受益良多
    有個問題想請教您
    請問您知道如何將深度影像的深度值(0~10000)直接讀取出來嗎?
    希望能得到您的指點
    謝謝您 😀

發佈回覆給「0989」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。