使用 XML 設定檔來初始化 OpenNI

基本上,Heresy 的 OpenNI 程式寫到現在,都是直接在程式碼裡面去做 OpenNI 環境的初始化設定的。也就是很直接地去呼叫 xn::ContextInit() 這個函式,然後再透過各個 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_ScriptCOpenNI 的 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 分別是 ImageDepthUser。由於只是簡單的示範,所以 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 下載。


附註:

  1. 參考 OpenNI 官方論壇《What is ScriptNode good for?》一文。

  2. main() 的第一個參數 argc 是代表執行時所取得的參數的個數。如果在執行時沒有特別制定的話,argc 應該會是 1,而 argv[0] 則會是執行檔本身的名字;有給其他參數的話,會是從 argv[1] 開始。

  3. AddNodeToRecordingcodec 這個參數的值是對應到 XN_CODEC_ID 的四個字元,也就是有 16zP16zTIm8zJPEGNONE 這幾種。

4 thoughts on “使用 XML 設定檔來初始化 OpenNI”

  1. Heresy 可以介绍一下怎么使用多个kinect连接到同一个pc上的使用方法吗

  2. 请问一下pointViewer这个demo,我想跟踪多只手怎么办?要改xml吗?

  3. to snake3784 Heresy 沒真的寫過多手的。不過基本上應該是必須要去修改 nite.ini 這個檔案。請參考官方論壇https://groups.google.com/forum/#!topic/openni-dev/kMzPMGW8ats/discussion

發佈回覆給「snake3784」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *