在 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 << "\nDevice " << ++uNum << "\n " << desc.strVendor << " - " << desc.strName;
 
  // output version information
  XnVersion& rVer = desc.Version;
  cout << "\n Version: " << (int)rVer.nMajor << "." << (int)rVer.nMinor;
  cout << "." << rVer.nMaintenance << "." << rVer.nBuild;
 
  // create info
  cout << "\n 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 對應的


OpenNI / Kinect 相關文章目錄

對「在 OpenNI 管理多個裝置」的想法

  1. Heresy你好

    我後來改用桌機加上兩片USB3.0擴充卡, 可以連接3台Kinect

    現在我想用3台Kinect跑同一支程式(例如 NIUserTracker), 抓不同角度的深度及User資訊,

    現在同一支程式開兩次, 抓到的是同一組Kinect

    但希望可以程式中可以指定3個Kinect中的其中一個 (例如用設定檔或#define方式設定)

    不知道Heresy是否知道指定Device或指定USB Port的範例程式該如何撰寫呢

    謝謝

    • OpenNI 本身是有提供 Identification Capability 來做裝置的識別之用,不過如果是搭配 SensorKinect 的話,應該是沒辦法讀出序號、因此無法使用就是了。
      所以你如果要限定使用特定的 Kinect 的話,應該是得用 NodeIndo 的 CreationInfo 來做識別。

      建議試試看搭配《在 OpenNI 環境同時使用多個 Kinect》的內容來實作看看。

  2. Heresy你好

    我嘗試在我的laptop PC上安裝兩台 Kinect for Xbox360
    使用的作業系統是 XP
    所以安裝完 PrimeSense Package Stable Build for Windows x86 (32-bit) v20.4.2.20 Development Edition後
    再安裝 SensorKinect091-Bin-Win32-v5.1.0.25.msi
    如果只使用單一個 Kinect, 在裝置管理員內看到的一組 Kinect Audio, Kinect Camera, Kinect Motor都正常
    但是再接上去另一組Kinect之後, 裝置管理員內看到的第二組Kinect Camera會出現驚嘆號 (驅動程式指定路經重新安裝也是一樣)

    laptop PC上面的 3個USB 接孔都交替試過了, 結果都相同

    即使兩個Kinect 都接好了, 再重新開機, 情形也沒變

    所以您的建議為何呢? 謝謝

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料