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 不使用這樣的方法。
所以,在上面的範例裡面,1a 到 1d 的部分,就是用標準 OpenNI 的流程,來進行初始化的動作;詳細的說明,請參考《透過 OpneNI 讀取 Kinect 深度影像資料》。而接下來 2 的部分,則是使用 OpenCV 的 highgui 這個模組的 namedWindow() 這個函式(官方文件),來建立四個不同名稱的視窗、作為畫面的顯示。
接下來,則是透過一個無窮迴圈,來不停地更新資料了~裡面主要分成兩塊,也就是 5、讀取 Image Generator 的彩色影像、以及 6、讀取 Depth Generator 的深度影像的部分。
其中,在 5a 和 6a 的部分,就是把 OpenNI 讀出來的 map(xn::ImageMetaData 和 xn::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::Mat 的 convertTo() 的函式,把這個 16bit 的影像裡的每一個像素都乘上一個 scale(255.0 / 7000)後,轉換成 8bit 的影像(c8BitDepth)。再之後,就是一樣把轉換好的影像,進行 canny edge 偵測了~
而這樣的程式執行的結果,會有下面這樣、四個不同資料的視窗,分別代表彩色影像、基於彩色影像的邊緣偵測結果、深度影像、以及基於深度影像進行邊緣偵測的結果。
這篇就先到這了。基本上,Heresy 是把這篇文章定位成一個極為簡單的 OpenNI 和 OpenCV 的資料整合範例;而由於 OpenCV 還有提供相當多的影像處理的功能,接下來要怎麼做,就是看自己想要做什麼了~
[…] 放棄了比較繁瑣、檔案很多的 Qt,而改採用相對簡單的 OpenCV,來做為 2D […]
讚讚
Heresy大,您好
想請問您一個問題,就是有關於c8BitDepth的數值是深度值嗎?
還是這個陣列只是單純作轉換用的?
不好意思,麻煩您了
讚讚
他是從深度影像轉換出來的 8bit 灰階影像。
讚讚
Heresy大,您好
如果我單純想要深度值的話,是直接存xDepthData裡面的數值嗎?!
xDepthData應該就有包含深度距離了吧!
不好意思,麻煩Heresy版大了!
讚讚
如果你要使用 OpenNI 1.x 讀取深度,請參考
https://kheresy.wordpress.com/2011/01/20/read_kinect_depth_data_via_openni/
讚讚
謝謝Heresy大
不好意思麻煩您了!
讚讚