這篇基本上是《OpenNI 2 VideoStream 與 Device 的設定與使用》一文的延伸,主要是提供一個完整的範例,用OpenCV(官網)這套知名的電腦視覺、影像處理的函式庫,來顯示 OpenNI 2 所讀取到的彩色、深度影像。 實際上,在《OpenNI 2 VideoStream 與 Device 的設定與使用》中已經有提到過,該怎麼把 OpenNI VideoStream 讀取到的 VideoFrameRef,轉換成 OpenCV C 的 cv::Mat 的形式了~以彩色影像來說,他的基本做法,就是: const cv::Mat mImageRGB( mColorFrame.getHeight(), mColorFrame.getWidth(), CV_8UC3, (void*)mColorFrame.getData() ); 不過由於 OpenCV 內部彩色影像實際上是使用 BGR、而非 RGB 的形式,所以如果要做後續的處理的話,是需要把 RGB 影像轉換成 BGR 的。而如果是深度影像的話,資料的型別也需要從 CV_8UC3 改成 CV_16UC1 才行。 而如果只是為了要顯示的話,一個完整的程式,會像下面這樣: // STL Header #include <iostream>   // OpenCV Header #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp>   // OpenNI Header #include <OpenNI.h>   // namespace using namespace std; using namespace openni;   int main( int argc, char **argv ) { // 1. Initial OpenNI if( OpenNI::initialize() != STATUS_OK ) { cerr << "OpenNI Initial Error: " << OpenNI::getExtendedError() << endl; return -1; }   // 2. Open Device Device mDevice; if( mDevice.open( ANY_DEVICE ) != STATUS_OK ) { cerr << "Can't Open Device: " << OpenNI::getExtendedError() << endl; return -1; }   // 3. Create depth stream VideoStream mDepthStream; if( mDevice.hasSensor( SENSOR_DEPTH ) ) { if( mDepthStream.create( mDevice, SENSOR_DEPTH ) == STATUS_OK ) { // 3a. set video mode VideoMode mMode; mMode.setResolution( 640, 480 ); mMode.setFps( 30 ); mMode.setPixelFormat( PIXEL_FORMAT_DEPTH_1_MM );   if( mDepthStream.setVideoMode( mMode) != STATUS_OK ) { cout << "Can't apply VideoMode: " << OpenNI::getExtendedError() << endl; } } else { cerr << "Can't create depth stream on device: " << OpenNI::getExtendedError() << endl; return -1; } } else { cerr << "ERROR: This device does not have depth sensor" << endl; return -1; }   // 4. Create color stream VideoStream mColorStream; if( mDevice.hasSensor( SENSOR_COLOR ) ) { if( mColorStream.create( mDevice, SENSOR_COLOR ) == STATUS_OK ) { // 4a. set video mode VideoMode mMode; mMode.setResolution( 640, 480 ); mMode.setFps( 30 ); mMode.setPixelFormat( PIXEL_FORMAT_RGB888 );   if( mColorStream.setVideoMode( mMode) != STATUS_OK ) { cout << "Can't apply VideoMode: " << OpenNI::getExtendedError() << endl; }   // 4b. image registration if( mDevice.isImageRegistrationModeSupported( IMAGE_REGISTRATION_DEPTH_TO_COLOR ) ) { mDevice.setImageRegistrationMode( IMAGE_REGISTRATION_DEPTH_TO_COLOR ); } } else { cerr << "Can't create color stream on device: " << OpenNI::getExtendedError() << endl; return -1; } }   // 5. create OpenCV Window cv::namedWindow( "Depth Image",  CV_WINDOW_AUTOSIZE ); cv::namedWindow( "Color Image",  CV_WINDOW_AUTOSIZE );   // 6. start VideoFrameRef mColorFrame; VideoFrameRef mDepthFrame; mDepthStream.start(); mColorStream.start(); int iMaxDepth = mDepthStream.getMaxPixelValue(); while( true ) { // 7. check is color stream is available if( mColorStream.isValid() ) { // 7a. get color frame if( mColorStream.readFrame( &mColorFrame ) == STATUS_OK ) { // 7b. convert data to OpenCV format const cv::Mat mImageRGB( mColorFrame.getHeight(), mColorFrame.getWidth(), CV_8UC3, (void*)mColorFrame.getData() ); // 7c. convert form RGB to BGR cv::Mat cImageBGR; cv::cvtColor( mImageRGB, cImageBGR, CV_RGB2BGR ); // 7d. show image cv::imshow( "Color Image", cImageBGR ); } }   // 8a. get depth frame if( mDepthStream.readFrame( &mDepthFrame ) == STATUS_OK ) { // 8b. convert data to OpenCV format const cv::Mat mImageDepth( mDepthFrame.getHeight(), mDepthFrame.getWidth(), CV_16UC1, (void*)mDepthFrame.getData() ); // 8c. re-map depth data [0,Max] to [0,255] cv::Mat mScaledDepth; mImageDepth.convertTo( mScaledDepth, CV_8U, 255.0 / iMaxDepth ); // 8d. show image cv::imshow( "Depth Image", mScaledDepth ); }   // 6a. check keyboard if( cv::waitKey( 1 ) == 'q' ) break; }   // 9. stop mDepthStream.destroy(); mColorStream.destroy(); mDevice.close(); OpenNI::shutdown();   return 0; } 這樣的程式執行之後,除了本來的命令提示字元的視窗外,還會由 OpenCV 建立出像下圖這樣的兩個視窗,各別顯示深度影像和彩色影像,並透過無窮迴圈不停地更新畫面: 
而程式會一直進行更新,直到使用者按下鍵盤上的「q」,才會結束程式。 接下來,則是程式原始碼的一些說明。
初始化 在上面的範例裡面,1 到 4 的部分,都是 OpenNI 初始化的部分。依序就是: - OpenNI 環境初始化
- 開啟裝置(Device)
- 建立深度影像的 VideoStream,
並將解析度設定成 640 x 480、30FPS,格式為 PIXEL_FORMAT_DEPTH_1_MM - 建立深度影像的 VideoStream,
並將解析度設定成 640 x 480、30FPS,格式為 PIXEL_FORMAT_RGB888; 同時,也設定裝置的影像校正,將深度影象對到彩色影像的位置。 不過由於 Heresy 有考慮到 ASUS Xtion Pro 沒有彩色攝影影機,所以無法得到彩色影像的資料,因此為了讓程式也可以在 Xtion Pro 上執行,所以在程式的概念上是設計成一定要有深度攝影機,但是可以沒有彩色攝影機的。也因此,可以看到深度和彩色影像的部分,在處理的時候都有些微的不同。 而 5 之後的部分,才開始有用到 OpenCV 的功能。首先,5 的部分,就是透過 cv::namedWindow(),建立出兩個名字分別是「Depth Image」和「Color Image」視窗,分別用來顯示深度影像和彩色影像。 程式主迴圈 6 則是開始 OpenNI VideoStream 的資料讀取,並以一個無窮迴圈、來做資料的更新。而在迴圈裏面,則有搭配一個 6a、使用 cv::waitKey() 這個函式,來讀取鍵盤的輸入,如果使用者按下鍵盤上的「q」的話,就會離開迴圈,進到 9 的部分,開始關閉 OpenNI 的物件、把程式結束程掉。 在迴圈內,則主要是 7 和 8 兩塊,前者是針對彩色影像作處理,後者則是針對深度影像作處理。 彩色影像的處理 以彩色影像來說,在確定讀到資料後,是會先把資料,轉換成一個 cv::Mat 的物件 mImageRGB(7b);不過由於 OpenCV 的彩色影像是採用 BGR 的順序,所以接下來,就是要透過 cv::cvtColor() 來做色彩空間的轉換,把 RGB 排列的 mImageRGB 轉換成 BGR 排列的 mImageBGR(7c)。 處理好了之後,則就是透過 cv::imshow(),把 mImageBGR 的影像用「Color Image」這個視窗顯示出來。 深度影像的處理 而深度影像的部分,則是先轉換成 CV_16UC1 形式的 cv::Mat 物件 mImageDepth(8b)。雖然 OpenCV 有支援 16bit 的影像,但是實際上因為 OpenNI 在目前的感應器上,讀到的深度值範圍最大就是到 10,000(iMaxDepth、透過深度的 VideoStream 的 getMaxPixelValue() 這個函式取得),所以如果直接把 16bit 的影像畫出來的話,應該是會看到接近一片黑的畫面… 所以為了強化顯示的效果,這邊就去使用 cv::Mat 提供的 convertTo() 這個函式,把 16bit(CV_16UC1)的 mImageDepth,轉換成 8bit(CV_8UC1)的影像 mScaledDepth(8c)。而 convertTo() 這個函式,在做轉換的時候,可以透過第三個參數和第四個參數,來設定轉換時的縮放、以及位移。詳細的說明,可以參考 OpenCV 官方的文件(連結),而像 Heresy 這邊就是把深度值乘上「255.0 / iMaxDepth」,讓本來介於 0 ~ 10,000 之間的深度值,變成 0 ~ 255。 最後,則是一樣透過 cv::imshow(),把 mScaledDepth 這個影像用「Depth Image」這個視窗顯示出來。
這邊 Heresy 基本上只是單純把 OpenNI 的影像資料,轉換成 OpenCV 的格式、然後就直接畫出來了。如果有需要的話,也是可以再透過 OpenCV,來做額外的處理的~像在 OpenNI 1.x 的時候,Heresy 就有寫一篇《OpenNI OpenCV》,裡面就有額外把彩色影像和深度影像,都拿來做邊緣偵測~有需要的話,也可以自己試試看。
|