在 OpenNI 管理多個裝置

一般人應該都是只有使用單一個 OpenNI 的相容硬體(例如 Kinect)吧?不過實際上,有的應用或許會需要用到兩個、或是更多個感應器,來同時使用。而其實 Heresy 想寫這個主題很久了,不過由於之前一直沒有第二個裝置可以拿來玩,所以一直沒辦法測試…

而前一陣子,Heresy 這邊終於拿到第二個 Kinect、可以進行這方面的測試以及開發了!所以,這篇就來寫一下,要怎麼在 OpenNI 環境下,使用多台裝置吧∼不過,這篇會先把主題放在管理的部分,下一篇才會實際寫一個同時使用多個裝置的程式了∼

這方面的資料,建議可以參考官方 User Guide 的《Enumerating Possible Production Chains》一節,裡面有提到基本的方法;而官方的 NiViewer 這個範例,也有實作裝置選擇的方法可供參考(註 1)

列出所有的裝置

首先,OpenNI 的 xn::Contex 有提供一個函式,叫做 EnumerateProductionTrees()。這個函式的功能,就是用來根據指定的條件,列出 OpenNI 環境中,所有符合條件的 Node;而基本上在一台電腦上有連接多個 OpenNI 裝置的話,就是要先靠這個函式、來做查詢。這個函式的形式是:

XnStatus EnumerateProductionTrees( XnProductionNodeType Type,
const Query* pQuery,
NodeInfoList& TreesList,
EnumerationErrors* pErrors = NULL
)

第一個參數 Type 是只要要查詢的 Node 的類型(OpenNI 定義的列舉型別),第二個參數 pQuery 則是查詢的附加條件、如果給 NULL 的話就是代表不額外限制,第三個參數 TreesList 則是列舉出來的結果。最後的 pErrors,則是用來儲存列舉時的錯誤資訊用的,如果不想紀錄也可以給 NULL

整個使用的方法,大致如下:

xn::NodeInfoList liChains;
mContext.EnumerateProductionTrees( XN_NODE_TYPE_DEVICE, NULL, liChains, NULL );

unsigned int uNum = 0;
for( xn::NodeInfoList::Iterator itNode = liChains.Begin();
itNode != liChains.End(); itNode )
{
// Get Node
xn::NodeInfo mNodeInf = *itNode;

// get node description
XnProductionNodeDescription desc = mNodeInf.GetDescription();

// output basic name
cout << "Device " << uNum << " " << desc.strVendor << " - " << desc.strName;

// output version information
XnVersion& rVer = desc.Version;
cout << " Version: " << (int)rVer.nMajor << "." << (int)rVer.nMinor;
cout << "." << rVer.nMaintenance << "." << rVer.nBuild;

// create info
cout << " Create Info: " << mNodeInf.GetCreationInfo() << endl;
}

在上面的程式碼裡,Heresy 是去列舉出所有的 device node(XN_NODE_TYPE_DEVICE),並把得到的結果(liChains)依序透過 output stream 輸出到 standard output。

而這邊用來儲存列舉的結果的 liChains 的型別是 xn::NodeInfoList,算是一個 xn::NodeInfo 的陣列,不過在使用形式的設計上算是類似 STL container 的介面,所以必須要使用 iterator 的方法來做操作(不過感覺他實作的不完整 :p)。基本的概念,就是把整個 NodeInfoList 掃一遍(從 Begin()End())、依序去存取每個 NodeInfo 的資料了∼

而由於這邊只是要輸出簡單的資訊,所以就是透過 NodeInfoGetDescription() 這個函式,來取得針對這個 Node 的描述;這邊得到的資料的類型是 XnProductionNodeDescription,裡面包含了製作者的名稱(strVendor)、Node 本身的名稱(strName),以及版本資訊(Version)。(註 2)

另外,NodeInfo 也還有一個函式 GetCreationInfo() 可以取得 Node 建立的相關資料,Heresy 也就順便印出來了∼而這樣的程式執行後輸出的結果,大概會類似下面的形式:

Device 1
PrimeSense - SensorKinect
Version: 5.0.5.1
Create Info: \?usb#vid_045e&pid_02ae#a00364914166107a#{c3b5f022-5a42-1980-1909-ea72095601b1}

Device 2
PrimeSense - SensorKinect
Version: 5.0.5.1
Create Info: \?usb#vid_045e&pid_02ae#a00364904743049a#{c3b5f022-5a42-1980-1909-ea72095601b1}

上面的結果可以看到,這邊抓到了兩個 device,vendor 都是 PrimeSense、device 名稱都是 SensorKinect,而版本也都是 5.0.5.1。而 creation Information 的部分,似乎也是只有 device node 才會有的,得到的資料感覺上應該是指這個 device 在電腦上的硬體連線狀況,一般人應該是不大可能直接看,應該是 OpenNI 拿來做多個裝置的識別之用的。(註 3)

而實際上 Context 的 EnumerateProductionTrees() 這個函式除了可以用來列出 device node 外,也可以用來查詢 OpenNI 裡的各種 production node,有興趣的話可以自己更改 XnProductionNodeType 試試看。

透過 xn::NodeInfo 來建立 Production Node

上面的範例,只是單純地把 node 列出來、並且輸出資料而已。接下來呢,就是要來看怎麼使用這些列出來的 NodeInfo 了∼這邊主要是根據官方的範例來看。

首先,NodeInfo 本身有提供 GetInstance() 這個函式,可以用來取得對應於 NodeInfo 的 production node;它的使用方法,基本上就是:

xn::Device devNode;
mNodeInf.GetInstance( devNode );

如此一來,就可以取得對應於 mNodeInfxn::Device 了∼

但是由於這個動作並不是去建立一個 node,所以所取得的 devNode 可能是還沒有被建立起來的,所以會無法使用;所以在取得後最好是檢查一下,取得的 node 是否可以使用,如果不行的話,就必須要自己建立。官方範例所使用的寫法,大致上如下:

if( !devNode.IsValid() )
mContext.CreateProductionTree( mNodeInf, devNode );

而這邊使用的函式是 xn::ContextCreateProductionTree(),他可以根據指定的 NodeInfo 來建立出對應的 Node。(註 4)

不過實際上,如果確定 Node 還沒被建立過的話,在使用的時候應該是可以不必試著透過 GetInstance() 來取得 Node,而可以直接使用 CreateProductionTree() 來建立;也就是,可以直接寫成:

xn::Device devNode;
mContext.CreateProductionTree( mNodeInf, devNode );

另外,在 NodeInfo 裡,有所謂的「Instance name」的資訊,是用來在 OpenNI 環境裡,區分同一類 node、被建立出來的不同 node 實體的;這東西在要使用 xn::Query 來做裝置的搜尋條件時,是相當重要的。這個資訊在 node 實際被建立出來前是空的,在建立 node 後,則可以透過 NodeInfoGetInstanceName() 來取得。

而根據測試的結果,這個名稱應該會是 node 類型的名稱、再加上編號的形式,例如在有兩個 device 的情況下,先建立的 device 的 instance name 就是「Device1」、後建立的則就是「Device2」;而除了 device node 外,其他類型的 node 也會有同樣的 instance name。

最後,如果一個已經建立好的 node 要查詢他的 instance name 的話,可以先透過 GetInfo() 這個函式,取得 NodeInfo 後,就可以透過 NodeInfo::GetInstanceName() 來取得 instance name 了∼下面就是簡單的範例:

xn::DepthGenerator xDepth;
xDepth.Create( mContext );
cout << xDepth.GetInfo().GetInstanceName() << endl;

這篇大概就先寫到這了,比較完整的測試程式,請到 Heresy 的 SkyDrive 下載(連結)。下一篇,應該就會是提供一個同時操作兩個 OpenNI 裝置的範例了∼


附註:

  1. 相關程式主要是在 Device.cpp 這個檔案裡的 openDeviceFromXmlWithChoice() 這個函式裡。

  2. XnVersion 的版本資訊包含了四個數字、分別代表不同層次的版本編號,一般使用時應該都會是 nMajor.nMinor.nMaintenance.nBuild 這樣的形式。不過使用時要注意的是,由於 nMajornMinor 的型別是 XnUInt8、實際上是定義為 unsigned char,所以在使用 C 的 ostream 輸出時,必須要先強制轉型為數值型態在座輸出,不然 ostream 會把它當字元做輸出。

  3. 其實 xn::Device 也有提供所謂的 Identification Capability,裡面有一個 GetSerialNumber() 的函式,可以取得裝置的序號。不過由於 Heresy 拿 Microsoft Kinect 來做測試時,讀出來的訊號都是 0,所以看來是不能用…

  4. Heresy 在測試的時候,是發現使用之前使用的 devNode.Create( mContext ) 似乎也是可以,但是 Heresy 不確定這樣是否可以保證建立出來的 device node 是和 NodeInfo 對應的

2 thoughts on “在 OpenNI 管理多個裝置”

  1. 您好,请问您是否将两台kinect设备插入了两个分别的USB controller,还是在同一个USB controller下,您是使用的笔记本电脑吗?

發佈留言

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