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 不使用這樣的方法。
所以,在上面的範例裡面,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 還有提供相當多的影像處理的功能,接下來要怎麼做,就是看自己想要做什麼了~
Heresy您好
我想請問一下scale的分母7000
是您經過測試後在視覺上比較順眼的嗎?
還是說有什麼特別的意義?
to HHC
那個值是取決於你要把資料做怎樣的對應。
OpenNI 目前的深度感應器理論上的深度值是 0 – 10,000,所以如果是完全對應的話,就是給 10,000;Heresy 這邊算是去強化 0-7,000 這段深度在視覺上的效果。
您好!请问用kinect获取的深度信息图像和RGB图像是对齐的么?就是说每个像素点的位置是否是一一对应(匹配)的?或者说两幅图像是从一个视点得到的,只不过一个是深度图像一个是RGB图像,谢谢。:):D
to HHW
請參考:
http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=216
Heresy大大你好,请问OpenNI有没有像OpenCV中cvGetCaptureProperty()这样的函数,如果没有的话该怎样控制openNI下的视频帧?比如我要从第100帧开始读取
to Auu
不太了解你的問題?你是要使用錄製好的 ONI 檔嗎?
如果是的話,請參考 OpenNI 的 xn::Player 的說明,他有提供跳到特定 Frame 的功能。
不好意思,没问清楚,不过我已经解决了。希望Hersey大大能再发点OpenNI OpenCV的blog,让我们学习学习,谢谢了。
我想問一下
要如何取得MAT裡面 RGB的值呢?
或著有方法把mat轉成iplimage去讀取RGB呢?
謝謝
to haha
請參考 OpenCV 官方文件
http://docs.opencv.org/
http://docs.opencv.org/modules/core/doc/basic_structures.html#mat
heresy您好
我正在寫關於物體追蹤的程式
我想請問一下
現在我想要找出連續兩張影像的差異
但是我卻不知道應該利用openni的哪個function來取得連續的兩張frame
是利用FrameID()取得前一張跟目前的frame嗎?
希望可以給我一點指點 麻煩您了
OpenNI 應該沒有這個功能。
你應該是需要自己把上一個 frame 儲存下來,以供之後使用。
heresy您好
請問一下我用這組程式碼在VS2008下執行
但是在header部分出了問題
我的OpenCV內似乎找不到imgproc 與 core 的hpp檔
請問該怎麼辦
麻煩您了
to 0989
請確認一下你的 OpenCV 版本。
目前官方網站上下載的 OpenCV 應該都有提供 opencv2,也就是 C 版本的 API,裡面應該都有必要的檔案。
(檔案在 \build\include\opencv2 )
heresy您好
最近做OpenCV和OpenNI結合的程序,OpenNI獲取的Depth圖像Data是16bit unsigned short,如您文中所述,保存到mat CV_16U1中,爲了讀取這些深度值,opencv有直接獲取16bit數據的方法么?一直沒有找到合適的讀取方法。請指教
自己嘗試這用過CV_MAT_ELEM_PTR,和在IplIMage中保存深度數據,用imageData的方式讀取,都沒有成功獲取到深度值。如果轉化成8bit數據,則識別精度就下降了,期待您好的建議
cv::Mat 有提供 at<>() 這個 template function,應該可以用來讀取。
ex:
cv::Mat mImg;
unsigned short uDepth = mImg.at<unsigned short>( x, y );
謝謝heresy了,問題解決了~加油
你好!
如果我儲存深度和彩色圖像, 將來再讀取去繪製點雲的話, 資料會否受損?
在資料不受損的情況下, 如何儲存或匯出這些資料將來使用? 謝謝
to champo
這取決於你怎麼做轉換、儲存。
如果你轉換成彩色的過程是可逆的,就可以確保可以轉換回來。
但是如果試圖黨的話,也要確定你的儲存格式是無損的,像 JPEG 就會會因為對資料做壓縮,造成損失。
如何用openc存储openni获取到的kinect深度和颜色数据
to zhuzi
如果你是要用 OpenCV 將資料儲存的話,建議請參考 OpenCV 的文件。
http://docs.opencv.org/doc/tutorials/introduction/load_save_image/load_save_image.html
heresy大大,您好,目前按照你的部落格教的开始逐步写点好玩的小程序了,之前做了一个“变脸”的程序,用opencv2做显示,但是始终无法做到png图片的背景透明,大概是opencv自身的问题吧~~:(后来买了本kinect for windows 开发的书,觉得上面用WPF做的界面,感觉很简单,不知道openni可不可以结合WPF来做一些界面之类的东西?
to chao
Heresy 自己沒有再使用 WPF,所以沒有試過。
不過理論上,OpenNI 是提供 C 的介面,所以可以和其他 C 的圖形介面函式庫做搭配。
我想請問一下怎麼讓kinect每隔一段時間存一張圖呢?
to jerry
這部分應該可以自己用 timer 來做資料存取的控制。
heresy你好,
我用的是asus xtion pro live,按照您该文的方法得到了bgr和depth的图,但是这个图的长和宽很小,请问有什么办法可以使图的长宽变大些么?
多谢
to 咸鱼
感應器解析度最大就是 640×480,這是硬體上的限制。
您好
您的文章實在令小弟受益良多
有個問題想請教您
請問您知道如何將深度影像的深度值(0~10000)直接讀取出來嗎?
希望能得到您的指點
謝謝您 😀
to JEFF
建議可以參考這邊的投影片以及範例程式
http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=5&SUB_ID=8&PAPER_ID=325