在 OpenNI 2 環境下使用多個 Device


這一篇,稍微寫一下要怎麼在 OpenNI 2 的架構下,使用多個 Device。

首先,OpenNI 這個類別本身有提供一個 enumerateDevices() 的函式,是用來列出系統上所有的裝置的;而透過這個函式取得的結果,會是 DeviceInfo 的陣列。它的使用方法,基本上如下:

Array<DeviceInfo> aDeviceList;
OpenNI::enumerateDevices( &aDeviceList );

DeviceInfo 這個類別,基本上是用來取得裝置的一些基本資訊用的,它提供了五個可以用來取得資訊的函式:

  • const char* getName() const
  • uint16_t    getUsbProductId() const
  • const char* getVendor() const
  • uint16_t    getUsbVendorId() const
  • const cha * getUri() const

其中,getName() 可以取得裝置本身的名稱、getVendor() 則可以取得供應商的名稱,算是可以讓使用者在不同裝置間做判斷的方法;而 getUsbProductId()getUsbVendorId() 這兩個函式的用途,基本上和上面兩個函式類似,只不過他輸出的結果是給電腦看的編號,一般人比較看不出意義。

最後,他還有提供一個 getUri() 的函式,可以取得這個裝置在系統上的連接位置的字串,這個字串可以在呼叫 Deviceopen() 這個函式的時候傳入,指定 Device 要開啟哪個裝置~基本上,也是在 OpenNI 2 的環境下,控制多個裝置最重要的資訊了。

下面就是一個用來輸出 DeviceInfo 陣列的範例:

cout << "There are " << aDeviceList.getSize()
     << " devices on this system." << endl;
for( int i = 0; i < aDeviceList.getSize(); ++ i )
{
  cout << "Device " << i << "\n";
  const DeviceInfo& rDevInfo = aDeviceList[i];
  
  cout << " " << rDevInfo.getName() << " by " << rDevInfo.getVendor() << "\n";
  cout << " PID: " << rDevInfo.getUsbProductId() << "\n";
  cout << " VID: " << rDevInfo.getUsbVendorId() << "\n";
  cout << " URI: " << rDevInfo.getUri() << endl;
}

而這樣的程式碼,在 Heresy 接了一台 ASUS Xtion Pro Live 和一台 Microsoft Kinect 的電腦上,會輸出下面的文字:

There are 2 devices on this system.
Device 0
 Kinect by Microsoft
 PID: 0
 VID: 0
 URI: USB\VID_045E&PID_02C2\5&C0AA250&0&2
Device 1
 PS1080 by PrimeSense
 PID: 1537
 VID: 7463
 URI: \\?\usb#vid_1d27&pid_0601&mi_00#8&23c7366d&0&0000#
{c3b5f022-5a42-1980-1909-ea72095601b1}

可以看到,Kinect 回傳的裝置名稱就是「Kinect」,而 Xtion Pro Live 則是回傳是「PS1080」;另外,也可以發現,Kinect 回傳的 PID 和 VID 都是 0…


上面基本上只是取得裝置的資訊,如果要開啟裝置的話,他的用法就會像下面這樣:

const DeviceInfo& rDevInfo = aDeviceList[i];
Device mDev;
mDev.open( rDevInfo.getUri() );

簡單地講,就是把 getUri() 回傳的字串,丟給 Deviceopen() 就可以了;而接下來,只要透過開啟好的 Device,就可以個別控制不同的裝置了。

下面就是一段使用 OpenCV 來同時顯示多個裝置的深度影像的程式碼:

// STL Header
#include <iostream>
#include <string>
#include <vector>
 
// OpenCV Header
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
 
// OpenNI Header
#include <OpenNI.h>
 
// namespace
using namespace std;
using namespace openni;
 
class CDevice
{
public:
  string        sWindow;
  Device*       pDevice;
  VideoStream*  pDepthStream;
 
  CDevice( int idx, const char* uri )
  {
    pDevice = new Device();
    pDevice->open( uri );
 
    pDepthStream = new VideoStream();
    pDepthStream->create( *pDevice, SENSOR_DEPTH );
 
    // create OpenCV Window
    stringstream mStrStream;
    mStrStream << "Sensor_" << idx << endl;
    sWindow = mStrStream.str();
    cv::namedWindow( sWindow.c_str(),  CV_WINDOW_AUTOSIZE );
 
    // start
    pDepthStream->start();
  }
};
 
int main( int argc, char **argv )
{
  // Initial OpenNI
  OpenNI::initialize();
 
  // enumerate devices
  Array<DeviceInfo> aDeviceList;
  OpenNI::enumerateDevices( &aDeviceList );
 
  // output information
  vector<CDevice>  vDevices;
  cout << "There are " << aDeviceList.getSize()
       << " devices on this system." << endl;
  for( int i = 0; i < aDeviceList.getSize(); ++ i )
  {
    cout << "Device " << i << "\n";
    const DeviceInfo& rDevInfo = aDeviceList[i];
  
    cout << " " << rDevInfo.getName() << " by " << rDevInfo.getVendor() << "\n";
    cout << " PID: " << rDevInfo.getUsbProductId() << "\n";
    cout << " VID: " << rDevInfo.getUsbVendorId() << "\n";
    cout << " URI: " << rDevInfo.getUri() << endl;
  
    // initialize
    CDevice mDevWin( i, aDeviceList[i].getUri() );
    vDevices.push_back( mDevWin );
  }
  
  while( true )
  {
    for( vector<CDevice>::iterator itDev = vDevices.begin();
         itDev != vDevices.end(); ++ itDev )
    {
      // get depth frame
      VideoFrameRef vfFrame;
      itDev->pDepthStream->readFrame( &vfFrame );
  
      // convert data to OpenCV format
      const cv::Mat mImageDepth( vfFrame.getHeight(), vfFrame.getWidth(),
                                 CV_16UC1,
                                 const_cast<void*>( vfFrame.getData() ) );
 
      // re-map depth data [0,Max] to [0,255]
      cv::Mat mScaledDepth;
      mImageDepth.convertTo( mScaledDepth, CV_8U,
                             255.0 / itDev->pDepthStream->getMaxPixelValue() );
 
      // show image
      cv::imshow( itDev->sWindow.c_str(), mScaledDepth );
 
      vfFrame.release();
    }
 
    // check keyboard
    if( cv::waitKey( 1 ) == 'q' )
      break;
  }
  
  // stop
  for( vector<CDevice>::iterator itDev = vDevices.begin();
       itDev != vDevices.end(); ++ itDev )
  {
    itDev->pDepthStream->stop();
    itDev->pDepthStream->destroy();
    delete itDev->pDepthStream;
     
    itDev->pDevice->close();
    delete itDev->pDevice;
  }
  OpenNI::shutdown();
  
  return 0;
}

這個範例執行後,除了會在命令提示字元,輸出有偵測到的裝置資訊外,也會使用 OpenCV、針對每一個裝置都開啟一個視窗,來顯示深度圖。

當然,這個範例程式在架構上,也還有很大的修改空間,不過反正只是範例而已,所以就先這樣吧~


OpenNI / Kinect 相關文章目錄

對「在 OpenNI 2 環境下使用多個 Device」的想法

  1. Heresy大神你好,
    我在学您的这篇文章openni2使用多个devices博客中,尝试运行您的代码,但是一直不成功,能够显示两台kinect v2的信息,不能显示深度影像。(即使只连接一台kienct v2也只能显示信息而不能显示深度影像,但是在您的另一篇文章运用openni2+nite2+opencv能够正常显示)。当连接两台kinect v2时候,我在控制台看到openni2枚举出来的设备数居然是(
    ***** VIDEOINPUT LIBRARY – 0.1995 – TFW07 *****

    [Info] [Freenect2Impl] enumerating devices…
    [Info] [Freenect2Impl] 18 usb devices connected
    [Info] [Freenect2Impl] found valid Kinect v2 @3:23 with serial 002721261547
    [Info] [Freenect2Impl] found 1 devices
    [Info] [Freenect2Impl] enumerating devices…
    [Info] [Freenect2Impl] 18 usb devices connected
    [Info] [Freenect2Impl] found valid Kinect v2 @3:23 with serial 002721261547
    [Info] [Freenect2Impl] found 1 devices
    Device 0
    Kinect by Microsoft
    PID: 51536
    VID: 57456
    URI: freenect2://0
    [Info] [Freenect2DeviceImpl] opening…
    [Info] [Freenect2DeviceImpl] transfer pool sizes rgb: 3*1048
    [Info] [Freenect2DeviceImpl] opened
    [Info] [Freenect2DeviceImpl] starting…
    [Info] [Freenect2DeviceImpl] submitting rgb transfers…
    [Info] [Freenect2DeviceImpl] submitting depth transfers…
    [Info] [Freenect2DeviceImpl] started
    Device 1
    Kinect by Microsoft
    PID: 51536
    VID: 57456
    URI: freenect2://0?depth-size=640×480
    Device 2
    Kinect by Microsoft
    PID: 51536
    VID: 57456
    URI: freenect2://0?depth-size=512×424)

    我的驱动是基于libfreenect2,电脑设备USB能够看到两个USB控制器。
    我的电脑配置:
    电脑型号 戴尔Precision M6800笔记本
    处理器 Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz
    内存容量 32.0GB
    显卡 1、NVIDIA Quadro K3100M
    2、Intel(R) HD Graphics 4600
    硬盘 1、PLEXTOR PX-128M6S+ (128GB)
    2、HGST HTS545050A7E380 (500GB)
    主板 0XD1M5 (A00)
    网卡 1、Intel(R) Ethernet Connection I217-LM
    2、Dell Wireless 1550 802.11ac
    声卡 1、Realtek High Definition Audio
    2、NVIDIA High Definition Audio
    3、蓝牙音频
    显示器 LGD:da02 分辨率:1920×1080
    当前操作系统 Windows 8.1 64位

    我的最终目标是想通过nite2获取人体骨骼点,但是想要这么做必须通过openni2枚举多个设备,再通过设备URI来控制每一台kinect v2启动顺序。希望您能够在百忙之中抽出一点时间。指点一下我。

    • Heresy 並沒有使用 libfreenect2 的經驗,所以不能確定你的問題。
      但是,基本上 Kinect v2 並不能給 NiTE 2 使用。

      • 可是我根据您的代码,NiTE2 是可以利用 kinect v2 来获取到骨骼关节数据的啊。驱动是基于libfreenect2的。不知道多台联合之后怎么样?

        • 請閱讀 NiTE 的授權協議,裡面有明確地說明,NiTE 不能搭配非 PrimeSense 的感應器使用。

          而 libfreenect2 基本上應該是有在內部做加工,才讓他可以的。但是儘管如此,這樣的使用方法,還是違反授權協議的。

          另外,在使用 Kinect for Windows SDK v2 的時候,單一台電腦僅能連結一台感應器,無法同時連接多台裝置。

          • 谢谢Heresy您的回复!我是现在只是课题需要,所以可能会用nite2来开发。

    • 這邊再稍微補充幾點:

      1. 雖然沒用過 libfreenect2,但是以紀錄來看,他應該是把單一感應器模擬成兩個裝置,兩個的深度解析度不一樣;其中「freenect2://0?depth-size=640×480」這個是給 NiTE 用的,之所以要修改大小為 VGA,是因為 NiTE 的限制。

      2. NiTE2 無法在單一台電腦上建立兩個各自獨立的 UserTracker,所以就算接了兩台感應器、而且可以使用,還是沒辦法同時使用兩台感應器來追蹤人體。

    • 我也遇到一样的问题,两个同型号的相机装在不同位置,使用openni启动时如何区分??

  2. Hersey大大,话说取得的这两个stream应该是分开的吧QAQ 怎么来分开这两个stream呢。。。感觉如果能分开这两个stream就能分别录影成功了吧QAQ

    • 兩個不同的 device 的 stream 本來就是分開的,沒有怎麼分開的問題。

      另,麻煩請把同樣的問題放在同一串回應裡,以方便管理。

  3. Hersey大神,窝按照Hersey大大的范例自己修改了一下,想用OpenNI2里面自带的recorder同时录制这两个摄像头的深度图像,但是怎么修改都没有成功,现在是能创建两个.oni的文件,但是都只有2KB,打开也没有画面 心好碎QAQ 有点绝望,想问问大大有可能实现嘛,同时录制一个程序里面接的两个摄像头画面?QAQ

    • 抱歉,沒有測試過這樣使用,所以不確定是什麼問題。
      不過,你有測試過單一個錄影是正常的嗎?
      建議先把你的程式改成只錄單一台試試看。

      • 感谢大大的回复!

        恩恩 之前学习录影功能的时候测试过一个是正常录影的。我现在是把录影的程式放在了CDevice里面pDepthStream->start(); 的下面,用recorder.attach( pDepthStream[0] ); 结果还是录不了,只有一个2KB的文件QAQ

        stringstream filename;
        string wfilename;
        filename<< "Stream_"<< idx << ".oni";
        wfilename = filename.str();
        openni::Recorder recorder;
        recorder.create(wfilename.c_str() );
        recorder.attach( pDepthStream[0] );
        recorder.start();

        • 如果你是把這段程式加在 CDevice::CDevice() 裡的話,那錄不東西應該是正常的。

          請注意你的變數的生命週期,照你的寫法,recorder 的物件在離開 CDevice 的建構子後就已經消失了。

          • 窝也大概感觉到是这个问题了,但是stream的定义又是在CDevice里面的,换成全局变量的话也不对。果然整个架构上要重新改写吗?QAQ

            (抱歉呀大大 窝不怎么上网留言不太明白规则什么的,回复了好几格抱歉呀,窝有办法删除什么的吗?QAQ添麻烦了真抱歉啊。。。)

            额还有窝之前指的分开那个stream是指能用文字表示区分比如stream[0]和stream[1]什么的。QAQ

        • 個人建議,麻煩仔細看一下範例的程式,確認裡面到底在做什麼。

          這麼範例程式這樣的架構,本身就是對應多裝置而設計的,每一個 CDevice 的物件,都對應一個實體感應器;如果在系統偵測到兩個感應器,就會建立出兩個獨立的 CDevice 型別的物件。
          裡面的 pDevice 和 pDepthStream 也都是各自獨立、不會彼此干擾的。

          另外,也請注意,這邊的 pDepthStream 是指標、不是陣列。

          • 是的昨天重新好好把流程图大概画了一下,又把C++的class部分学习了一遍,总之现在是成功了,感谢大神点拨了呀!添麻烦了不好意思呀,以后也会常来支持的!>.<

  4. 天啦噜原来是这么回事,之前查了好久都没有成功啊!!!太谢谢Heresy大神了!!!话说之前都是潜水学的都没给大神回复过感到好内疚啊!QAQ 以后也要每天来支持大神!真是太感谢了!!!

  5. Heresy大神你好,我一直根据你的博客学习OpenNI2的使用方法,都很好的实现了,可是这篇运用多个device的,我却一直运行不成功,能显示两台Xtion的信息,却不能显示深度影像,如果可以的话,希望能够得到大神的些许指点呦QAQ

    • 請確認兩台感應器沒有接在同一個 USB 控制器下。
      因為 Xtion 所需的頻寬不低,如果接在同一個 USB 控制器下,可能會有問題。

      基本上,能做的事就是多換幾個 USB port 試試看。

回覆給Heresy 取消回覆

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