這篇基本上是《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》,裡面就有額外把彩色影像和深度影像,都拿來做邊緣偵測~有需要的話,也可以自己試試看。
Heresy老師您好:
這個程式寫得讓人很容易懂,一拿到就可以在自己的kinect上用真高興!
有一個問題想請教老師,這邊出現的深度是以圖像的方式表現的,
我知道擷取深度值是用openni::SENSOR_DEPTH,
Q:"我現在如果想要將我目前擷取到的深度用數字的方式以640*480的方法呈現在顯示幕上
請問因該要怎麼做呢?
我有看過老師上一篇寫到有關擷取深度資訊的程式碼
https://kheresy.wordpress.com/2012/12/24/openni2-basic-example/
我有試著改寫看看,但是不太行。
讚讚
抱歉,麻煩請詳述你遇到的問題,否則實在無法回答。
讚讚
H老師您好~
想請問一下,
如果我要在這行程式裡面開始寫我要的東西
// 8a. get depth frame
if( mDepthStream.readFrame( &mDepthFrame ) == STATUS_OK )
{
然後 這邊開始———————
const openni::DepthPixel* pDepth = (const openni::DepthPixel*)mDepthFrame.getData();
這邊我寫一個迴圈,要跑這320*240的影像的每一點Pixel,然後去判斷條件做改變,
可是我印出我改變的pixel數值好像都沒被改變到。 不知道問題出在哪?(我有先跑pDepth[index]裡面的值跟做完判斷的值,好像都一樣,至少沒出現2480或1000)
———————-
if( (2480 – pDepth[index] > 1000) ){
pDepth[index] == 2480;
std::cout << pDepth[index] << std::endl;
}
else{
pDepth[index] == 1000;
std::cout << pDepth[index] << std::endl;
}
———————————————————————————————
然後這邊還想再問一下,因為之後就要做轉換成opencv格式的動作,
所以上面那些應該寫在8a裡面,還是做完轉換後的後面呢?
不好意思問題有點長。
讚讚
請確認你的程式內容,以你給的程式碼來說,裡面並沒有做任何修改深度影像的動作。
1. 「==」是用來做比較的,而非指定值
2. pDepth 是 const,請不要直接修改他
讚讚
Heresy您好:
我现在使用OPENNI的函数convertDepthToColor来实现深度到彩色的对应,然后生成带有色彩的3D point cloud,但是我发现在这种映射关系并不十分准确,好像会有一定的偏移误差,您知道是怎么回事吗?
讚讚
基本上由於感應器本來就是獨立的,不管是視野還是鏡頭參數,都是不同的,所以這個位置對應一定會有誤差。
但是在一般裝況下,應該都是堪用的。
讚讚
您好,我现在是想把颜色数据尽可能准确的注册到深度数据上,OpenNI提供了setImageRegistrationMode( openni::IMAGE_REGISTRATION_DEPTH_TO_COLOR );的方法来实现,但这个方法是把深度向色彩注册,虽然深度和色彩对应上了,但是深度数据被改变,与实际拍摄的3D物体大小不太一致,您知道有没有方法能把色彩像深度对应转换?多谢!
讚讚
OpenNI 只有提供單一方向的轉換,如果要反過來對應,就得自己想辦法計算了。
讚讚
Heresy您好:
請問如何使用讀取OpenNI Recoding File (oni) 影片深度及彩色資訊
讚讚
請參考: https://kheresy.wordpress.com/2013/03/04/record-and-playback-of-openni-2/
讚讚
Heresy您好:
我已參考上方連結文章讀取oni檔並結合此篇文章的方法操作來讀取影像,目前遇到的問題是使用PlaybackControl::getNumberOfFrames函數取得的張數和實際while迴圈取得的張數不符,迴圈跑至一部分便會卡住不動。請問有可能是何種問題造成?謝謝
讚讚
用迴圈讀取的時候,有可能會因為處理超過時間、而忽略掉某些畫面,所以有可能比較少。
至於卡住不動就不確定你的程式是卡在哪了,建議用偵錯模式確認看看。
讚讚
請問此句 “mDepthStream.readFrame( &mDepthFrame ) == STATUS_OK" 中的STATUS_OK是什麼意思?能取得影像?另外設定setRepeatEnabled()為false該放在何處?謝謝
讚讚
1. 請參考 https://kheresy.wordpress.com/2012/12/26/openni-error-handle/
2. 你可以在建立出 PlaybackControl 的物件後就做設定,這是看你自己的需求的。
讚讚
由於while迴圈讀取的畫面數每次執行後的數量都不同,故用偵錯模式無法看出端倪。請問是否有處理超過時間的相關函式判斷? 謝謝
讚讚
OpenNI 沒有提供對應的 API,應該是需要自行針對 Frame 的資料來判斷。
讚讚
官方文件在Status openni::VideoStream::readFrame(VideoFrameRef * pFrame)中有說
If no new frame is available, the call will block until one is available.
請問意思是有取得下一張影像就會回傳STATUS_OK?
另外如何使用VideoStream::Listener函式來搭配避免block
讚讚
Listener 模式請參考:
https://kheresy.wordpress.com/2013/09/16/openni-listener-mode/
讚讚
不懂深度 PIXEL_FORMAT_DEPTH_1_MM 的格式是如何?
我知道彩色 PIXEL_FORMAT_RGB888 格式這樣是RGB各8bits
謝謝!
讚讚
請參考: https://kheresy.wordpress.com/2012/12/24/openni2-basic-example/
他實際上是 unsigned short,代表距離感應器的距離,單位是 mm。
讚讚
我看Heresy之前也有使用過Xtion PRO LIVE,Xtion PRO LIVE提供的資療顯示最大的深度值可以偵測到10000mm,但是他深度的OutputFormat 為12bit depth values,一般來說12bit可以顯示的值是0~4095,所以我對這很困惑。
不知Heresy有無這方面的資料能提供,或是他深度顯示的轉換,謝謝!
讚讚
透過 OpenNI 的 API 取得的深度影像取得的值實際上是 16bit 的 unsigned short 的資料,並非是 12bit 的資料
讚讚
可是他們提供的資料深度輸出是12bit
還是有再經過轉換嗎?
謝謝!
讚讚
基本上,這是 OpenNI 的驅動程式模組內部作掉的事情,所以一般開發者拿到的,都是已經換算成以 mm 為單位的深度了。
讚讚
請問OpenNI 的驅動程式模組是?
是說深度攝影機所傳的資料是專門給OpenNI 的格式嗎?其他開發者無法直接去使用原始資料嗎?
麻煩了謝謝!
讚讚
OpenNI 是一個開放框架,他是透過個別的驅動模組,來作感應器資料的讀取。
而如果你是希望得到處理前的原始深度資料的話,可能得自己研究他的原始碼,看看能不能找出他在驅動程式模組裡面是怎麼做的了。
讚讚
請問一下,如何能將連續影像中不斷跳動的黑色殘影去除(深度部分)?是利用影像處理的哪些方法能夠解決?因爲我想建立背景,這些黑色殘影影響我切割背景的效果,麻煩大大指點~謝謝!
讚讚
要做這類的事,方法很多種,比較簡單的就是直接套用影像處理現有的去雜訊、或是其他方法。
網路上應該可以找到不少相關資料。
讚讚
Heresy老師~
想請問您為什麼我編譯的時候都會出現這個訊息
於 0x77bde4b4 的 test0924.exe 中發生未處理的例外狀況: 0xC0000005: 寫入位置 0x0000000000000024 時發生存取違規
我的硬體是用kinect , 這樣會有影響嗎=口=?
讚讚
你的錯誤應該不是編譯錯誤,而是執行時的錯誤,這兩者是不一樣的。
而以這個範例來說,比較有可能是因為環境的問題,電腦沒辦法成功地建立 VideoStream 造成的。個人會建議你先使用偵錯模式、或是加上錯誤偵測的功能,來確定是什麼問題。
讚讚
老師您好 ~ 程式每次都執行到mColorStream.start();這行就出錯了 ,
接著就會進到openni的函式裡,
if (!isValid())
{
return STATUS_ERROR;
}
bool isValid() const
{
return m_stream != NULL;
}
Status start()
{
if (!isValid())
{
return STATUS_ERROR;
}
return (Status)oniStreamStart(m_stream);
}
最後就是http://imgur.com/l3dsEJF <<網址內圖片顯示的 , 找不到openni.cpp
剛摸索這個,有點不知所以然,想請問Heresy老師,是跟硬體使用kinect有關嗎?
kinect的colorStream的寫法有不同嗎@@? 跪求老師解答Q_Q
讚讚
Kinect 的程式寫法是一樣的,因為 OpenNI 本來就是為了統一硬體的使用而設計的。
找不到 OpenNI .cpp 只是在偵錯的時候,他想試著去找程式執行到哪裡,但是找不到的關係,這點對於執行並不影響。
而如果是死在 Color VideoStream 的開始的話,比較有可能的原因,應該還是因為彩色的 VideoStream 建立錯誤。
有試過執行 NiViewer,確認看看 OpenNI 環境是否可以運作嗎?
另外,程式執行時,命令提示字元有什麼錯誤訊息輸出嗎?
讚讚
[…] 之前已經有寫過 OpenNI 2 的基本教學,如果只是要透過 OpenNI 2 的介面,來讀取深度、彩色影像,只要照著寫,應該不會有什麼問題;而如果是希望有畫面可以呈現的話,Heresy 也有提供整合 OpenCV 的範例,可以拿來做畫面的呈現。 […]
讚讚
您好
請問一下要如何讀取Mat每一個像素點
我用cImageBGR.at(x,y)
會出現錯誤
還有別的方式來讀取嗎?
讚讚
OpenCV Mat 的 at() 這個函式是 template 的,你需要指定型別。
另外, at() 裡面的參數,是先給 y 再給 x
讚讚
在請教一下
要如何把深度和彩色影像重疊一起顯示
讚讚
這部分純粹看你自己要怎麼透過 OpenCV 處理,根據需求的不同,方法也有很多種。
這邊算是一個簡單的範例: http://sdrv.ms/14Rhuia
讚讚