基本上,Heresy 的 OpenNI 程式寫到現在,都是直接在程式碼裡面去做 OpenNI 環境的初始化設定的。也就是很直接地去呼叫 xn::Context 的 Init() 這個函式,然後再透過各個 production node 的 Create() 函式,來完成 OpenNI 的值環境的初始化(細節請參考《透過 OpneNI 讀取 Kinect 深度影像資料》)。
相較於此,其實 OpenNI 還有提供另一種,透過外部的 XML 設定檔,來完成環境初始化的。這種方法是把一些對於 OpenNI 的設定參數(主要是包含了 production node 的使用條件、參數),以固定的形式寫在一個 XML 檔後,在執行程式時再把這個檔案讀進來,並根據檔案內的內容,來建立 OpenNI 的執行環境;而 OpenNI 的官方範例,基本上都是使用這樣的形式來寫的。
實際上,Heresy 這種不需要去讀取 XML 的方法在一般來說,是不會有什麼問題的,所以 Heresy 也就一直沒有想去看看要怎樣用 XML 來做初始化的參數設定。不過前一陣子,由於玩到了 kinect-mssdk-openni-bridge 這個可以讓 OpenNI 可以透過微軟 Kinect SDK 讀取 Kinect 的資料的東西(介紹)後,才發現其實使用 XML 來做初始化的條件設定,是有它的優勢在的!所以,這邊就來大概看一下,要怎麼樣寫,才可以讓 OpenNI 透過 XML 初始化整個環境吧∼
其實在程式的部分,要把原來的程式,改成使用 XML 來做初始化很簡單,主要只有兩個對應的地方要做修改而已。
- 用 xn::Context::InitFromXmlFile() 取代 xn::Context::Init()
- 用 xn::Context::FindExistingNode() 取代 xn::GENERATOR::Create()
然後,如果本來有針對 Node 的建立做搜尋(query)條件的限制、或是有做額外的參數設定的,都拿掉,讓他改由 XML 來讀取,這樣就可以了。
實際上,OpenNI 在使用 XML 來做初始化、執行 InitFromXmlFile() 的時候,是先把 XML 檔裡面所指定的所有 node 都先建立好、並完成相關的設定,記錄在 context 中;所以等到 InitFromXmlFile() 執行完之後,所有的 node 都是已經建立好的了!也因此接下來,才是改用 FindExistingNode()、來做 node 的搜尋,而非透過 Create() 建立新的 node。這部分算是在操作概念上比較不一樣的地方。
而如果以《使用 Qt 顯示 OpenNI 的人體骨架》一文中的範例來改寫的話,Heresy 的做法,就是再加入一個新的 COpenNI::Initial() 的函式,讓他可以用來處理根據 XML 設定檔做初始化的動作。下面就是新加入的這個函式:
/* Initial OpenNI context and create nodes with given XML */
bool Initial( const char* sFilename )
{
// Initial OpenNI Context
m_eResult = m_Context.InitFromXmlFile( sFilename, m_Script );
if( CheckError( "Context Initial failed" ) )
return false;
// create image node
m_eResult = m_Context.FindExistingNode( XN_NODE_TYPE_IMAGE, m_Image );
if( CheckError( "Create Image Generator Error" ) )
return false;
// create depth node
m_eResult = m_Context.FindExistingNode( XN_NODE_TYPE_DEPTH, m_Depth );
if( CheckError( "Create Depth Generator Error" ) )
return false;
// create user node
m_eResult = m_Context.FindExistingNode( XN_NODE_TYPE_USER, m_User );
if( CheckError( "Create User Generator Error" ) )
return false;
// set nodes
...
}
上面的程式碼中,黃底的部分,就是有做修改的地方;可以比較之前的程式碼,應該可以發現其實差異不大。
其中,sFilename 是這個函式所需要的參數,代表 XML 檔所在路徑;而 m_Script 是 COpenNI 的 member data,他的型別在 OpenNI 1.3 所加入的新的型別:xn::ScriptNode,基本上應該只是用 reference counter 來管理物件的生命週期用的,並沒有其他的用處(註一)。
接下來,則是在主程式 main() 的部分,也做對應的修改:
/* Main function */
int main( int argc, char** argv )
{
// initial OpenNI
COpenNI mOpenNI;
if( argc > 1 )
{
// initial OpenNI with XML Script
if( !mOpenNI.Initial( argv[1] ) )
return 1;
}
else
{
// initial OpenNI without XML Script
if( !mOpenNI.Initial() )
return 1;
}
// Qt Application
...
}
在這邊,Heresy 是透過 argc 來判斷程式執行時,所取得的參數數目,如果大於 1 的話,就把第二個參數、也就是 argv[1] 視為 XML 檔的路徑、然後呼叫新寫的 Initial() 函式、來透過 XML 檔案進行初始化;否則的話,還是呼叫之前所寫的、不需要 XML 的 Initial() 來進行初始化。(註 2)
如此一來,所有必要的程式碼上的修改就都結束了。而接下來,只要在執行這個程式的時候,再加上 XML 檔的路徑當作參數,就可以透過指定的 XML 檔來做 OpenNI 的初始化動作了!例如,Heresy 這邊編譯出來的執行黨名稱是「QTKinect.exe」、XML 設定檔是「Sample.xml」,那只要執行
QTKinect.exe Sample.xml
就可以讓這個程式,去讀取 Sample.xml 的設定資料,進行 OpenNI 的初始化了∼而已這個例子來說,由於需要用到 Image Generator、Depth Generator 和 User Generator,所以在 XML 設定檔裡,至少要針對這三者做基本的設定;而最簡單的一個 XML 的例子,大致上就會像下面一樣:
<OpenNI>
<ProductionNodes>
<Node type="Image">
<Configuration>
<Mirror on="true"/>
</Configuration>
</Node>
<Node type="Depth" />
<Node type="User" />
</ProductionNodes>
</OpenNI>
在這個最簡單的 XML 檔裡,就只定義了 ProductionNodes 的相關資訊,而裡面也就只有三個 Node,他們的 type 分別是 Image、Depth 和 User。由於只是簡單的示範,所以 Heresy 在這邊,只有示範性地把 Image 的 node 加上了簡單的設定、讓他的鏡射效果(mirror)開啟。
如此一來,如果根據這個 XML 來進行 OpenNI 的初始化設定的話,在程式執行後、所抓到的彩色影像和深度影像,就會像右圖一樣左右顛倒、有鏡射的效果了∼
其他如果說是像是要使用 kinect-mssdk-openni-bridge 提供的 user generator 這樣的狀況的話,也就可以不必修改程式、直接透過修改 XML 檔裡面對於 user node 的設定,來指定要使用哪個 user generator 了!
而實際上,在 OpenNI 的 XML 設定檔內,還可以設定 Licenses、Log、Global Mirror、Recording 等等其他的參數,對於各種 Node 也都有不少細部的設定可以使用;如果有需求的話,建議可以參考 OpenNI 官方 User Guide 裡的《Configuration Using XML file》這個章節的說明,來做額外的設定。
比如下面的 XML 設定,就可以不需要修改程式碼(參考)、而強制把 image map 和 depth map 錄下來、記錄在 Test.oni 這個檔案裡(註 3)。
<OpenNI>
<ProductionNodes>
<Node type="Depth" name="de1">
<Configuration>
<Mirror on="true"/>
</Configuration>
</Node>
<Node type="Image" name="im1">
</Node>
<Node type="User" />
<Node type="Recorder" name="recorder1">
<Configuration>
<RecorderDestination medium="File" name="Test.oni" />
<AddNodeToRecording name="im1" codec="JPEG" />
<AddNodeToRecording name="de1" codec="16zP" />
</Configuration>
</Node>
</ProductionNodes>
</OpenNI>
這篇就寫到這了。完整的程式碼,以及範例的 XML 檔,請到 Heresy 的 SkyDrive 下載。
附註:
-
參考 OpenNI 官方論壇《What is ScriptNode good for?》一文。
-
main() 的第一個參數 argc 是代表執行時所取得的參數的個數。如果在執行時沒有特別制定的話,argc 應該會是 1,而 argv[0] 則會是執行檔本身的名字;有給其他參數的話,會是從 argv[1] 開始。
-
AddNodeToRecording 的 codec 這個參數的值是對應到 XN_CODEC_ID 的四個字元,也就是有 16zP、16zT、Im8z、JPEG、NONE 這幾種。
Heresy 可以介绍一下怎么使用多个kinect连接到同一个pc上的使用方法吗
@cqqhzxgh 抱歉,Heresy 這邊沒有兩個 Kinect 可以測試
请问一下pointViewer这个demo,我想跟踪多只手怎么办?要改xml吗?
to snake3784 Heresy 沒真的寫過多手的。不過基本上應該是必須要去修改 nite.ini 這個檔案。請參考官方論壇https://groups.google.com/forum/#!topic/openni-dev/kMzPMGW8ats/discussion