三维重建:QT+OpenNI+Kinect图像校正
后記:
??????? 當時能不放棄這個方向是因為這里面涉及了一種很有效的三位場景存儲方式,可能給出除圖元建模之外的一種三維場景描述方式。這和Flash與位圖的對比一樣,基于圖元的flash始終抵不過基于點描述的位圖格式。
??????? 總結:OpenNI已經有了一個專門的語句對標定進行了封裝,我們不需要再費力去使用自己的代碼了。??????? 原文鏈接:http://www.cnblogs.com/tornadomeet/archive/2012/09/27/2706417.html
??????? 系列文章,作者相當給力!
??????? 不過有一個更為強大的文章:http://blog.csdn.net/chenyusiyuan/article/details/6279762
?????? OpenCV學習筆記(20)Kinect + OpenNI + OpenCV + OpenGL 組合體驗
Kinect+OpenNI學習筆記之(獲取kinect的顏色圖像和深度圖像)
前言
網上有不少使用Qt做界面,OpenNI為庫來開發kinect?;蛟S大家的第一個問題就是詢問該怎樣使用Kinect來獲取顏色信息圖和深度信息圖呢?這一節就是簡單來回答這個問題的。
開發環境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2;原因:Qt與VTK可以完美融合,雖然工作量不大,但是還是沒有時間了。
實驗說明:
在使用OpenNI來驅動讀取kinect數據時,我們需要了解context object這個名詞。查看了下OpenNI UserGuide文檔,簡單翻譯下這個名詞的意思:
Context是openNI中一個主要的object,它掌握了OpenNI使用過程中應用程序的全部狀態,以及這些狀態的prodection chains,一個應用程序有多個context,但是這些context之間不能共享信息。例如一個中間件節點不能使用另一個context的驅動節點。Context在使用前必須被立即初始化,因此此時所有嵌入的模塊被下載和分析。為了釋放context的內存,應用程序需調用shutdown程序。??????
雖然翻譯得不準確,但是它的大概意思就是告訴我們在驅動kinect時,需要用到context這個類,且我們需要安裝一定順序去使用,這與一些常見的庫驅動差不多,比如opengl,這些都需要什么初始化啊,設置屬性啊等。因此我們只需要直接去看懂他人的一個工程實例就ok了。
好了,本文參考Heresy的教程中的源碼寫的。
在新建好工程文件后,需要包含XnCppWrapper頭文件,且需在Qt工程中設置好頭文件目錄和庫文件目錄。
使用OpenNI讀取顏色圖和深度圖的步驟如下(這個是程序的核心部分):
1. 定義一個Context對象,并 調用該對象的Init()方法來進行初始化。
2. 定義一個XnMapOutputMode格式對象,設置好分圖像分辨率和幀率。
3. 定義顏色圖和深度圖的節點對象,并用其Create()方法來創建,參數為Context對象.
4. 設置顏色和深度圖的輸出模式,調用的方法是SetMapOutputMode();參數為步驟2中定義和設置好了的XnMapOutputMode對象。
6. 如果深度圖和顏色圖在一張圖上顯示,則必須對深度圖像進行校正,校正的方法是調用深度圖的如下方法:.GetAlternativeViewPointCap().SetViewPoint();
7. 調用context對象的StartGeneratingAll()來開啟設備讀取數據開關。
8. 調用context對象的更新數據方法,比如WaitAndupdateAll()方法。
9. 定義顏色圖和色彩圖的ImageMetaData對象,并利用對應的節點對象的方法GetMetaData(),將獲取到的數據保存到對應的ImageMetaData對象中。
10. 如果需要將深度圖轉換成灰度圖來顯示,則需要自己將深度值轉換成0~255的單通道或者多通道數據,然后直接用來顯示。
注意如果沒有設置視覺校正,則深度圖的顯示與顏色圖的顯示會出現對應不上的情況,后面的實驗可以看出這2者的區別,另外對于是否需要設置鏡像就要看自己的具體應用場合了。
?
實驗結果:
下面分別分是否設置圖像鏡像,是否對深度圖像進行校正來給出實驗結果.
無鏡像無校正:
?
無鏡像有校正:
?
有鏡像無校正:
?
有鏡像有校正:
?
從有無鏡像可以看出,設置鏡像的效果與字面的理解是一樣的,即有鏡像時就相當于取鏡子中的圖像。有無校正可以看出,沒有校正時,深度圖片和顏色圖片同一個物體都對應不起來,可以看下天花板上的吊燈就可以發現,沒校正,2者不重合,且相差不少。有校正時效果就好多了,只是此時的深度圖像顯示的范圍要稍小些。
?
實驗主要部分代碼及注釋(附錄有工程code下載鏈接):
首先來個最小工程,即去掉那些錯誤處理代碼:
main.cpp:
#include <QtGui> #include <XnCppWrapper.h> //包含OpenNI的頭文件using namespace xn;//使用OpenNI庫中的命名空間//全局的OpenNI object Context g_context; ImageGenerator g_image_generator; DepthGenerator g_depth_generator;//全局的Qt Object QGraphicsPixmapItem *g_image_map; QGraphicsPixmapItem *g_depth_map;//CTimer類的定義 class CTimer : public QObject { public:void start() { g_context.StartGeneratingAll();//開啟設備讀取數據的開關startTimer(33);//使用startTimer()啟動定時器,每當時間到時會自動調用timerEvent()函數} private:void timerEvent(QTimerEvent *) {g_context.WaitAndUpdateAll();//更新數據//顏色數據ImageMetaData image_map;g_image_generator.GetMetaData(image_map);//為g_image_map設置圖片,圖片的數據來源于外部硬件設備g_image_map->setPixmap(QPixmap::fromImage(QImage(image_map.Data(), image_map.XRes(),image_map.YRes(), QImage::Format_RGB888)));//深度數據DepthMetaData depth_map;g_depth_generator.GetMetaData(depth_map);XnDepthPixel max_depth_value = depth_map.ZRes();QImage depth_img(depth_map.XRes(), depth_map.YRes(), QImage::Format_ARGB32);//格式為ARGB32型的for(unsigned int i = 0; i < depth_map.XRes(); i++)for(unsigned int j = 0; j < depth_map.YRes(); j++){XnDepthPixel depth_value_ij = depth_map(i, j);//獲取x,y處的坐標值if(depth_value_ij == 0) {depth_img.setPixel(i, j, qRgba(0, 0, 0, 0));}//如果捕捉不到深度信息,則將其設置為0else {float fscale = 1.0f*depth_value_ij/max_depth_value;//當前深度的比例因子depth_img.setPixel(i, j, qRgba(255*(1-fscale), 0, 255*fscale, 255*(1-fscale)));}}g_depth_map->setPixmap(QPixmap::fromImage(depth_img));} };int main(int argc, char **argv) {QApplication app(argc, argv);g_context.Init();//context初始化g_context.SetGlobalMirror(true);//設置全局鏡像,就像照鏡子一樣,與設置為false時的2張圖片鏡像XnMapOutputMode xmode;//定義圖像的輸出模式xmode.nXRes = 640;//x方向分辨率xmode.nYRes = 480;//y方向分辨率xmode.nFPS = 30;//幀率//設置顏色節點屬性g_image_generator.Create(g_context);g_image_generator.SetMapOutputMode(xmode);//設置深度節點屬性g_depth_generator.Create(g_context);g_depth_generator.SetMapOutputMode(xmode);//視覺校正,否則深度圖和顏色圖感應到的區域不能一一對應g_depth_generator.GetAlternativeViewPointCap().SetViewPoint(g_image_generator);//Qt場景設置QGraphicsScene scene;g_image_map = scene.addPixmap(QPixmap());g_image_map->setZValue(1);//設置為z方向上的第1層g_depth_map = scene.addPixmap(QPixmap());g_depth_map->setZValue(2);//設置為z方向上的第2層//Qt視圖創建QGraphicsView view(&scene);view.resize(660, 500);//設置定時器,每隔一段時間讀取kinect的顏色信息和深度信息CTimer timer;timer.start();view.show();return app.exec(); }
加入錯誤處理部分后的完整main.cpp:
#include <QtGui> #include <XnCppWrapper.h> //包含OpenNI的頭文件using namespace xn;//使用OpenNI庫中的命名空間//全局的OpenNI object XnStatus g_status; Context g_context; ImageGenerator g_image_generator; DepthGenerator g_depth_generator; bool g_has_image_generator = true;//全局的Qt Object QGraphicsPixmapItem *g_image_map; QGraphicsPixmapItem *g_depth_map;//CTimer類的定義 class CTimer : public QObject { public:void start() {g_status = g_context.StartGeneratingAll();//開啟設備讀取數據的開關if(g_status == XN_STATUS_OK) {startTimer(33);//使用startTimer()啟動定時器,每當時間到時會自動調用timerEvent()函數}else {QMessageBox::critical(NULL, "Create Data Error!", xnGetStatusString(g_status));//顯示創建數據失敗,該消息框沒有父窗口}} private:void timerEvent(QTimerEvent *) {g_context.WaitAndUpdateAll();//更新數據//顏色數據if(g_has_image_generator) {ImageMetaData image_map;g_image_generator.GetMetaData(image_map);//為g_image_map設置圖片,圖片的數據來源于外部硬件設備g_image_map->setPixmap(QPixmap::fromImage(QImage(image_map.Data(), image_map.XRes(),image_map.YRes(), QImage::Format_RGB888)));}//深度數據DepthMetaData depth_map;g_depth_generator.GetMetaData(depth_map);XnDepthPixel max_depth_value = depth_map.ZRes();QImage depth_img(depth_map.XRes(), depth_map.YRes(), QImage::Format_ARGB32);//格式為ARGB32型的for(unsigned int i = 0; i < depth_map.XRes(); i++)for(unsigned int j = 0; j < depth_map.YRes(); j++){XnDepthPixel depth_value_ij = depth_map(i, j);//獲取x,y處的坐標值if(depth_value_ij == 0) {depth_img.setPixel(i, j, qRgba(0, 0, 0, 0));}//如果捕捉不到深度信息,則將其設置為0else {float fscale = 1.0f*depth_value_ij/max_depth_value;//當前深度的比例因子depth_img.setPixel(i, j, qRgba(255*(1-fscale), 0, 255*fscale, 255*(1-fscale)));}}g_depth_map->setPixmap(QPixmap::fromImage(depth_img));} };int main(int argc, char **argv) {QApplication app(argc, argv);g_status = g_context.Init();//context初始化if(g_status != XN_STATUS_OK) {QMessageBox::critical(NULL, "Context Initial Error!", xnGetStatusString(g_status));return -1;}// g_context.SetGlobalMirror(true);//設置全局鏡像,就像照鏡子一樣,與設置為false時的2張圖片鏡像XnMapOutputMode xmode;//定義圖像的輸出模式xmode.nXRes = 640;//x方向分辨率xmode.nYRes = 480;//y方向分辨率xmode.nFPS = 30;//幀率//設置顏色節點屬性g_status = g_image_generator.Create(g_context);if(g_status != XN_STATUS_OK) {QMessageBox::critical(NULL, "Image map create failed", xnGetStatusString(g_status));g_has_image_generator = false;}if( g_has_image_generator ) {g_status = g_image_generator.SetMapOutputMode(xmode);if(g_status != XN_STATUS_OK) {QMessageBox::critical(NULL, "Image map output mode error!", xnGetStatusString(g_status));return -1;}}//設置深度節點屬性g_status = g_depth_generator.Create(g_context);if(g_status != XN_STATUS_OK) {QMessageBox::critical(NULL, "Depth map create failed", xnGetStatusString(g_status));return -1;}g_status = g_depth_generator.SetMapOutputMode(xmode);if(g_status != XN_STATUS_OK) {QMessageBox::critical(NULL, "Depth map output mode error!", xnGetStatusString(g_status));return -1;}if(g_has_image_generator)//視覺校正,否則深度圖和顏色圖感應到的區域不能一一對應;//g_depth_generator.GetAlternativeViewPointCap().SetViewPoint(g_image_generator);//Qt場景設置QGraphicsScene scene;g_image_map = scene.addPixmap(QPixmap());g_image_map->setZValue(1);//設置為z方向上的第1層g_depth_map = scene.addPixmap(QPixmap());g_depth_map->setZValue(2);//設置為z方向上的第2層//Qt視圖創建QGraphicsView view(&scene);view.resize(660, 500);//設置定時器,每隔一段時間讀取kinect的顏色信息和深度信息CTimer timer;timer.start();view.show();return app.exec(); } ???? 總結:通過本次實驗,了解了怎樣使用OpenNI來顯示kinect的顏色圖像和深度圖像了。參考資料:http://kheresy.wordpress.com/index_of_openni_and_kinect/comment-page-5/
附錄:實驗工程code下載。
作者:tornadomeet???? 出處:http://www.cnblogs.com/tornadomeet歡迎轉載或分享,但請務必聲明文章出處。 (新浪微博:tornadomeet,歡迎交流!) 總結:源代碼還是挺有用的,不過最后還是放棄了,還是MFC簡單一些,對于只是驗證算法的我們來說,漸漸的失去了方向。
(二):把代碼寫在一個單獨的類里面
前言
在上一篇文章Kinect+OpenNI學習筆記之2(獲取kinect的顏色圖像和深度圖像)?中,已經介紹了怎樣使用OpenNI來獲取Kinect的深度數據和顏色數據,并將獲取到的結果在Qt中顯示,不過那個代碼是寫在同一個cpp文件中,以后用到的時候不能講這些顯示的基本過程單獨拿出來,比較麻煩。所以這節主要是將OpenNI獲取圖像的流程以及Qt顯示這些圖像的結果分開為了2個類來寫,方便以后工程的直接拷貝。
開發環境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2
實驗說明
COpenNI這個類主要是初始化kinect設備,并獲取深度圖像和顏色圖像,參加上一篇博客的初始化過程步驟,如果遇到錯誤,則有相應的錯誤處理過程。CKinectReader類是將COpenNI這個類讀取到的結果顯示在Qt的界面上的。因此一個類是負責與硬件Kinect打交道,一個類是負責與人(界面顯示)打交道的。具體的過程見上篇文章的分析和后面的代碼。
這里發現一個小問題,與kinect有關的工程如果改變了代碼,則在每次編譯前最好clean一下,因為有可能是與硬件設備相關,沒有clean的工程和clean后的工程效果有時會不同。
C/C++知識點總結:
在構造函數中可以使用冒號給類中的數據成員賦值,這樣的好處就是可以給常量和引用變量賦值初始化賦值的效果。
類的私有成員只能是類內部的函數調用,連類的對象都不能去調用私有成員變量。
在類的內部使用qDebug(), cout等函數輸出調試時是不行的。
隱式數據類型轉換,如果是同種類型的數據進行四則運算,則得出的結果也是那種類型,如果其中有常數類型的數據常數參與,則得出的結果會自動轉換成跟常數類型相同的類型。
如果一個類以單獨一個cpp文件出現,在使用到該類的時候,直接include該cpp文件.
?
實驗結果
在程序中設置了鏡像和視覺校正,且將kinect感應不到深度信息的地方全部顯示為不透明的黑色,因此你在圖中看到的黑色部分就是kinect的深度盲區。
效果如下:
?
?
實驗主要部分代碼及注釋(附錄有工程code下載鏈接):
copenni.cpp:
#include <XnCppWrapper.h> #include <QtGui> #include <iostream>using namespace xn; using namespace std;class COpenNI { public:~COpenNI() {context.Release();//釋放空間}bool Initial() {//初始化status = context.Init();if(CheckError("Context initial failed!")) {return false;}context.SetGlobalMirror(true);//設置鏡像//產生圖片nodestatus = image_generator.Create(context);if(CheckError("Create image generator error!")) {return false;}//產生深度nodestatus = depth_generator.Create(context);if(CheckError("Create depth generator error!")) {return false;}//視角校正status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);if(CheckError("Can't set the alternative view point on depth generator")) {return false;}return true;}bool Start() {status = context.StartGeneratingAll();if(CheckError("Start generating error!")) {return false;}return true;}bool UpdateData() {status = context.WaitNoneUpdateAll();if(CheckError("Update date error!")) {return false;}//獲取數據image_generator.GetMetaData(image_metadata);depth_generator.GetMetaData(depth_metadata);return true;}public:DepthMetaData depth_metadata;ImageMetaData image_metadata;private://該函數返回真代表出現了錯誤,返回假代表正確bool CheckError(const char* error) {if(status != XN_STATUS_OK ) {QMessageBox::critical(NULL, error, xnGetStatusString(status));cerr << error << ": " << xnGetStatusString( status ) << endl;return true;}return false;}private:XnStatus status;Context context;DepthGenerator depth_generator;ImageGenerator image_generator; };ckinectreader.cpp:
#include <QtGui> #include <QDebug> #include <XnCppWrapper.h> #include "copenni.cpp" //要包含cpp文件,不能直接包含類 #include <iostream>using namespace std;class CKinectReader: public QObject { public://構造函數,用構造函數中的變量給類的私有成員賦值CKinectReader(COpenNI &openni, QGraphicsScene &scene) : openni(openni), scene(scene) {test = 0.0;}~CKinectReader() {scene.removeItem(image_item);scene.removeItem(depth_item);delete [] p_depth_argb;}bool Start(int interval = 33) {openni.Start();//因為在調用CKinectReader這個類的之前會初始化好的,所以這里直接調用Start了image_item = scene.addPixmap(QPixmap());image_item->setZValue(1);depth_item = scene.addPixmap(QPixmap());depth_item->setZValue(2);openni.UpdateData();p_depth_argb = new uchar[4*openni.depth_metadata.XRes()*openni.depth_metadata.YRes()];startTimer(interval);//這里是繼承QObject類,因此可以調用該函數return true;}float test ; private:COpenNI &openni; //定義引用同時沒有初始化,因為在構造函數的時候用冒號來初始化QGraphicsScene &scene;QGraphicsPixmapItem *image_item;QGraphicsPixmapItem *depth_item;uchar *p_depth_argb;private:void timerEvent(QTimerEvent *) {openni.UpdateData();//這里使用const,是因為右邊的函數返回的值就是const類型的const XnDepthPixel *p_depth_pixpel = openni.depth_metadata.Data();unsigned int size = openni.depth_metadata.XRes()*openni.depth_metadata.YRes();//找深度最大值點XnDepthPixel max_depth = *p_depth_pixpel;for(unsigned int i = 1; i < size; ++i)if(p_depth_pixpel[i] > max_depth )max_depth = p_depth_pixpel[i];test = max_depth;//將深度圖像格式歸一化到0~255int idx = 0;for(unsigned int i = 1; i < size; ++i) {//一定要使用1.0f相乘,轉換成float類型,否則該工程的結果會有錯誤,因為這個要么是0,要么是1,0的概率要大很多float fscale = 1.0f*(*p_depth_pixpel)/max_depth;if((*p_depth_pixpel) != 0) {p_depth_argb[idx++] = 255*(1-fscale); //藍色分量p_depth_argb[idx++] = 0; //綠色分量p_depth_argb[idx++] = 255*fscale; //紅色分量,越遠越紅p_depth_argb[idx++] = 255*(1-fscale); //距離越近,越不透明}else {p_depth_argb[idx++] = 0;p_depth_argb[idx++] = 0;p_depth_argb[idx++] = 0;p_depth_argb[idx++] = 255;}++p_depth_pixpel;//此處的++p_depth_pixpel和p_depth_pixpel++是一樣的}//往item中設置圖像色彩數據image_item->setPixmap(QPixmap::fromImage(QImage(openni.image_metadata.Data(), openni.image_metadata.XRes(), openni.image_metadata.YRes(),QImage::Format_RGB888)));//往item中設置深度數據depth_item->setPixmap(QPixmap::fromImage(QImage(p_depth_argb, openni.depth_metadata.XRes(), openni.depth_metadata.YRes(), QImage::Format_ARGB32)));} };main.cpp:
#include <QtGui/QtGui> #include <QDebug> #include "ckinectreader.cpp"int main(int argc, char **argv) {COpenNI openni;if(!openni.Initial())//初始化返回1表示初始化成功return 1;QApplication app(argc, argv);QGraphicsScene scene;QGraphicsView view;view.setScene(&scene);view.resize(650, 540);view.show();CKinectReader kinect_reader(openni, scene);kinect_reader.Start();//啟動,讀取數據qDebug() << kinect_reader.test;return app.exec(); }總結:這次實驗的目的主要是將相互稍微獨立的代碼用單獨的類來寫,方便以后的代碼重復利用。
參考資料:http://kheresy.wordpress.com/2011/08/18/show_maps_of_openni_via_qt_graphicsview/
??? 附錄: 實驗工程code下載。作者:tornadomeet出處:http://www.cnblogs.com/tornadomeet歡迎轉載或分享,但請務必聲明文章出處。 (新浪微博:tornadomeet,歡迎交流!)
總結
以上是生活随笔為你收集整理的三维重建:QT+OpenNI+Kinect图像校正的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Monkey框架(测试方法篇)
- 下一篇: 每天学点经济学读后感