Virtual Device for OpenNI 2

這是一個 OpenNI 2 用的虛擬裝置模組,他的功能基本上是提供 OpenNI 2 環境建立一個虛擬裝置的功能,讓程式開發者可以自行去指定這個虛擬裝置的資料;比如說,如果是有其他形式的深度影像、或是其他深度攝影機的資料,都可以透過這個模組,來讓資料可以在 OpenNI 2 的架構下來使用。

而另一方面,這也可以用來模擬類似 OpenN1 時提供的「Mock Node」、也就是可以用來修改取得道的深度資料、讓之後的 middleware(PrimeSense NiTE)可以使用。(參考《修改 User Generator 用來分析的深度影像》)

這個模組是以 OpenSource 的形式,放在 GitHub 上,他的網址是:https://github.com/VIML/VirtualDeviceForOpenNI2
另外,雖然這個模組也有以 tools 的形式 submit 給 OpenNI 了,但是看來似乎都沒有被處理。


基本使用

由於這個虛擬裝置模組算是 OpenNI 2 的驅動程式模組的一種(類似之前的 WebCam4OpenNI2),所以如果是要使用現成編譯好的檔案,也只需要下載編譯好的 DLL(VirtualDevice.dll),放到工作目錄下的 \OpenNI2\Drivers 裡面,和 PS1080.dllKinect.dll 等檔案放在一起就可以了。

而要在建立一個虛擬裝置的話,則是要在建立 OpenNI Device 的時候,指定特別的 URI:

\\OpenNI2\\VirtualDevice\\<<STRING>>

其中,<<STRING>> 的部分可以自行更換,只要名字不同,就可以建立不同的裝置。下面就是一個範例:

Device  devVirDevice;
devVirDevice.open( "\\OpenNI2\\VirtualDevice\\TEST" );

而在建立出虛擬裝置 devVirDevice 後,接下來則就是按照標準的作法,建立出需要的 VideoStream、用來讀取資料;目前的版本僅支援彩色和深度兩種 Video Stream,不支援紅外線。

在建立出 VideoStream 的物件後,使用前則需要透過 setVideoMode() 來指定要使用的解析度,這樣 VideoStream 才知道之後要產生多大的畫面。

虛擬裝置的 VideoStream 都準備好了之後,如果要給他新的資料的話,基本上是要先透過 VideoStreaminvoke() 函式、來執行 GET_VIRTUAL_STREAM_IMAGE 這個命令,來要求 VideoStream 配置一個 OniFrame 的物件、讓程式可以填自己的資料進去;而在準備好資料之後,則是再透過 invoke() 執行 SET_VIRTUAL_STREAM_IMAGE 這個命令、把修改好的資料、傳給 VideoStream、告訴他新的畫面已經準備好了。

其中,GET_VIRTUAL_STREAM_IMAGESET_VIRTUAL_STREAM_IMAGE 這兩個特殊命令,則是定義在 VirtualDevice.h 這個檔案(連結)裡;如果要使用的話,可以直接把那兩行定義加到自己的程式碼裡面,或是直接 include 這個 header 檔。

下面就是一段範例:

// get a frame form virtual video stream
OniFrame* pFrame = NULL;
if( pVStream->invoke( GET_VIRTUAL_STREAM_IMAGE, pFrame )==openni::STATUS_OK )
{
// type casting
DepthPixel* pVirData = reinterpret_cast<DepthPixel*>( pFrame->data );

// Fill dummy data
for( int y = 0; y < pFrame->height; y )
{
for( int x = 0; x < pFrame->width; x )
{
int idx = x y * pFrame->width;
pVirData[idx] = 100;
}
}

// write data to form virtual video stream
pVStream->invoke( SET_VIRTUAL_STREAM_IMAGE, pFrame );
}

透過上面這樣的程式,就可以讓 pVStream 這個虛擬的深度影像的 VideoStream 產生一張每一個像素都是 100 的深度影像。

而通常這部分程式,應該是需要另外建立一個新的執行緒來執行的。完整的範例,可以參考 GitHub 上的「BasicSample」這個範例。

不過要注意的是,這邊用的 thread 函式庫是使用 OpenNI 原始碼裡面、XnLib 所提供的功能,所以如果要建置的話,是需要 OpenNI 原始碼的;或者,也可以直接用 C 11 的 Thread,或是其他的執行緒函式庫。


屬性

為了讓這個虛擬裝置可以支援各項屬性,所以在 DeviceVideoStream 裡,都有設計 property pool 的機制,來儲存各種類型的屬性資料。要使用的話,基本上就是透過 OpenNI 提供的各種介面、或是直接使用 setProperty() 來做設定、並透過 getProperty() 來讀取。

不過,除了 ONI_STREAM_PROPERTY_VIDEO_MODEONI_STREAM_PROPERTY_CROPPING 這兩個屬性會動到 VideoStream 的設定外,其他的所有屬性都只是單純地被儲存下來、並不會對虛擬裝置造成任何影響。像是雖然可以透過 setMirroringEnabled() 來設定 ONI_STREAM_PROPERTY_MIRRORING,但是實際上影像並不會因此產生鏡像效果。

另外,如果是深度影像、而且會用到座標系統轉換(Depth / World)的話,則還需要指定 ONI_STREAM_PROPERTY_VERTICAL_FOVONI_STREAM_PROPERTY_HORIZONTAL_FOV 這兩個屬性的值, CoordinateConverter 才有辦法進行座標轉換的計算。


修改現有裝置的影像

如果是需要搭配 OpenNI 現有的感應器、來修改實際感應器讀取到的資料(類似 OpenNI 1 的 mock node)的話,則是可以參考「ExistedDeviceSample」這個範例。

使用方法基本上就是先按照一般的方法,建立出正常的 Device、和需要的 VideoStream;接下來則是建立虛擬的裝置,並透過實際的 VideoStreamaddNewFrameListener()、來讓真實裝置讀取到新的影像後、就把資料做完處理、丟給虛擬裝置用。

這樣處理後,之後就可以透過虛擬的 VideoStream 來讀取修改過的資料了。


給 PrimeSense NiTE 使用

首先要注意的是,雖然 PrimeSense 所提供的 NiTE2 這個 middleware 是可以免費使用的,但是實際上在使用上,他是有一些限制的。其中一個最重要的,就是在它的使用授權裡面、就有明寫、他只能用在「經過授權的硬體」上,而經過授權的硬體,基本上就是採用 PrimeSense 自家感應器的產品(MS Kinect、ASUS Xtion…)了~

c. You shall not (and shall not permit third parties to):
(a) integrate the NITE with any products other than the Authorized Hardware;
(b) distribute the NITE in any manner;
(c) use or make available the NITE pursuant to an open source license;
(d) modify the NITE; or
(e) change, obscure or delete any proprietary notices or legends which appear in the NITE.
You understand and agree that under no circumstances will You (or will You allow distributors, resellers or agents to) distribute stand-alone copies or versions of the NITE.

完整的文件,可以參考:http://www.openni.org/nite-licensing-and-distribution-terms/

所以即使是使用虛擬裝置、也不能在沒有 PrimeSense 感應器的情況下,使用 NiTE。


而如果要是要把 VirtualDevice 拿來當 Mock Node 的形式來使用、也就是用來修改現有感應器的深度資料、給 NiTE 來分析的話,在使用上,也還有幾個要注意的地方。

裝置的 URI

首先,NiTE2 的 UserTrackerHandTracker 物件,在透過 cretae() 指定 Device 來建立的時候,實際上應該是會針對裝置的 URI 作檢查的。不過由於 PrimeSense 並沒有公開他的檢查機制,所以 URI 的部分基本上只能自己測試看看哪種可以通過檢查。

就 Heresy 測試的結果,下面這個 URI 應該是可以通過 NiTE 的檢查的:

\OpenNI2\VirtualDevice\Kinect

所以如果建立的虛擬裝置是要給 NiTE2 用的話,就需要使用這個 URI;至於其他還有沒有可以通過 NiTE 檢查的,可能就要做測試才知道了。

支援的解析度

由於 NiTE 是為了 PrimeSense 自家的感應器所開發的,所以他也只支援自家感應器能提供的解析度。基本上,應該就是 QVGA(320×240)和 VGA(640×480)兩種。所以如果是要搭配 NiTE 來使用的話,也需要使用這兩種解析度才行,其他的解析度是有可能會導致 NiTE 無法成功建立所需要的 tracker 的。

額外的屬性

另外,NiTE 在分析深度影像時,還會再像深度影像的 VideoStream 要求一些特別的屬性資料,包括了:

XN_STREAM_PROPERTY_CONST_SHIFT 
XN_STREAM_PROPERTY_PARAM_COEFF
XN_STREAM_PROPERTY_SHIFT_SCALE
XN_STREAM_PROPERTY_MAX_SHIFT
XN_STREAM_PROPERTY_S2D_TABLE
XN_STREAM_PROPERTY_D2S_TABLE
XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE
XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE
XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE

這些屬性都定義在 PS1080.h 裡面(連結)。

所以如果要給 NiTE 使用的話,在建立出深度的 VideoStream 後,還需要把上面這些屬性、從實際的裝置裡面讀取出來、在設定給虛擬的裝置。

而針對這個需求的處理,Heresy 的做法是這樣:

std::map<int,int>  mapProperties;
mapProperties[XN_STREAM_PROPERTY_CONST_SHIFT] = 8;
mapProperties[XN_STREAM_PROPERTY_PARAM_COEFF] = 8;
mapProperties[XN_STREAM_PROPERTY_SHIFT_SCALE] = 8;
mapProperties[XN_STREAM_PROPERTY_MAX_SHIFT] = 8;
mapProperties[XN_STREAM_PROPERTY_S2D_TABLE] = 4096;
mapProperties[XN_STREAM_PROPERTY_D2S_TABLE] = 20002;
mapProperties[XN_STREAM_PROPERTY_ZERO_PLANE_DISTANCE] = 8;
mapProperties[XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE] = 8;
mapProperties[XN_STREAM_PROPERTY_EMITTER_DCMOS_DISTANCE] = 8;

for( auto itProp = mapProperties.begin();
itProp != mapProperties.end(); itProp )
{
int iSize = itProp->second;
char* pData = new char[itProp->second];
rStream.getProperty( itProp->first, pData, &iSize );
pStream->setProperty( itProp->first, pData, iSize );
delete [] pData;
}

其中,rStream 是真正的感應器的 VideoStream、而 pStream 則是虛擬感應器的 VideoStream。這樣的做法,就是先把需要的屬性、以及該屬性的資料大小,都先定義在 mapProperties 裡面,然後再透過迴圈、一個一個從 rStream 裡讀出來、並儲存到 pStream 裡。`

完整的範例,可以參考「NiTESample」這個範例程式。在這個範例程式裡面,Heresy 是建立了一個名為 VirtualDeviceHelper.h 的檔案,裡面提供了一個 CreateVirtualStream() 的函式,可以用來快速地根據現有的 VideoStream、建立出一個 NiTE 可以使用的虛擬 VideoStream。他的介面如下:

openni::VideoStream* CreateVirtualStream( openni::Device& rVDevice,
openni::VideoStream& rStream,
CFrameModifer::TCallback func )

其中,第一個參數的 vDevice 是虛擬的 Device,而第二個 rStream 則是用來做參考的實際 VideoStream;最後的 func 則是一個 function object,用來指定當 rStream 取得到新畫面後、要做那些事;他的介面是:

std::function<void(const OniFrame&,OniFrame&)>

第一個參數是讀取到的新畫面、第二個參數則是要送給虛擬 VideoStream 的畫面。

完整的使用範例,就請參考範例程式了。


這篇介紹就寫到這了。基本上,這東西算是一個比較進階的東西,他主要的功能,就是產生一個虛擬的 OpenNI Device,接下來要怎麼應用,就看自己了~

而目前 Heresy 自己是測試過,可以把它和 PrimeSense NiTE 2 合併使用,不過其他的 middleware library 的話,Heresy 就沒有試過了。理論上,如果可以指定要用哪個 Device、或是指定要用哪個 VideoStream 的話,應該是要可以用才對的。

24 thoughts on “Virtual Device for OpenNI 2”

  1. 您好,非常感谢您给我推荐的这篇文章,对此方面的内容很感兴趣,我已经下载了相关代码,有几个地方不太懂:
    1VirtualDevice.dll在哪能下载到,一定要下载这个dll才能用吗?
    2放到vs里,需要在include和lib里链接哪些文件,其中有个XnLib.h在哪里可以下载到?您说可将其换成c 11中的Thread,应该怎么换?还有”Driver/OniDriverAPI.h”在哪里可以下载到?
    3类似oni开头的函数名、类名,它的类定义在哪里?
    本人用WIN7系统,Kinect装置,想用openni2和nite2的相关函数做处理,openni1.0的函数代xn的不太熟悉。
    以上问题可能有些很小白,但是我是初学者,还请博主包涵,尽量全面的解答我的问题,不胜感激,麻烦博主了,非常感谢!!!

  2. to Avril

    1. 你可以選擇自己下載原始檔來建置。
    如果不想自己建置的話,則可以到 GitHub 上下載編譯好的 dll 檔。
    https://github.com/VIML/VirtualDeviceForOpenNI2/releases

    2. 請自行參考 std::thread 的使用教學
    http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=342

    3. XnLib.h、OniDriverAPI.h,以及 oni 開頭的東西,都是 OpenNI SDK 原始碼的一部分。
    你需要有完整的 OpenNI SDK 原始碼才能使用。
    如果只是要使用 Virtual Device 的話,並不需要這些東西。

  3. 问题补充:
    您好,环境配置我已经基本完成,就还剩上面提到的XnLib.h和VirtualDevice.dll问题了,这两个地方还是不太懂

  4. 您好,我运行了下NITESample程序,编译成功,但是debug时出现错误:应用程序无法正常启动(0xc000007b),是什么原因?
    运行ExistedDeviceSample时出现:Can’t create virtual device :Device open:Couldn’t open device’\openNI2\virtual Decice\Test,是什么原因,我将test换成kinect也不行

  5. to Avril

    請確認你是否有將 VirtualDevice.dll 放到執行檔所在路徑 OpenNI2Drivers 目錄內,裡面應該會有其他裝置的 dll(ex: kinect.dll)。
    另外也請確認你的 dll 版本和 OpenNI 是一致的(32/64 位元)

  6. 您好,我编译运行NITESample这个例程时,系统出现bug。
    单步调试时,运行到if( mUserTracker.create( &virDevice ) != STATUS_OK ),出现错误:Unhandled exception at 0x000007feeea9db3d in NiTESample.exe: 0xC0000094: Integer division by zero.虚拟的装置建立tracker吗,还是nite2.dll有问题,我的nite2.dll是直接粘贴复制过来的,放到debug文件夹中。

  7. 您好,我是用debug单步调试的,并且我看程序的思路也能理解,之前的初始化也用了错误检测,并没有出错,可是就是到建立虚拟user tracker时运行出错,错误显示被除数不能为0,不知道是什么原因,很困扰,麻烦heresy帮忙看下,非常感谢!!!

  8. 您好,我用debug单步调试NITESample时,create不能建立UserTrackerhandle,好像是URI不能通过检查的原因,我的URI:virDevice.open( “\OpenNI2\VirtualDevice\Kinect” );前面还需再加别的东西吗?麻烦您了

  9. to Avril

    請直接使用範例程式內的 URI,如果修改過 URI 可能會有問題。

    可以麻煩先試試看其他的範例是否可以正常運作嗎?

  10. 您好,我的URI没有改过,其他范例「ExistedDeviceSample」可以运行,depthTOworld可以运行,效果不好

  11. to Avril
    抱歉,那就不知道問題在哪了。
    這邊用 OpenNI 2.2.0.30 NiTE 2.2.0.10 並沒有使用上的問題。

  12. 您好,如果知道彩色图像某点的横纵坐标(i,j),如何将该点坐标转化为实际空间坐标(x,y),kinect采集的彩色图像点(i,j)坐标是否和openi采集的彩色图像点(i,j)的坐标一一对应?

  13. 没接真实设备,使用虚拟设备,参考CreateVirtualStream 骗过nite2 但是提示 couldn’t get depth2Depth tabale.请教怎么骗过去,卡在这里了

  14. 我用了作者您的Virtual Device创建的虚拟设备来将一系列的图片保存为oni格式,数据可以无缝的转换,但是就是在转成oni格式的时候,视频流太快,感觉fps只有1,我在我的代码中设置成30,还是不可以,然后我把您源码中设置fps为1的也都设成了30还是不行,我想问我应该怎么解决

  15. to 武大西门

    感謝您的回報。
    不過由於 OpenNI 目前已經算是停止開發了,所以這邊短時間內應該也不會去修改 Virtual Device 的程式。

    針對你的問題,個人會建議試著在透過 invoke() 寫入修改後的 frame 之前,也順便修改每個 frame 的 timestamp 試試看。

  16. 果然,时间戳改一下就可以了,谢谢博主哈!!!

  17. 您好,依照文章的方法,使用nite2的UserTracker::Create()函数时(其中参数为虚拟设备\\OpenNI2\\VirtualDevice\\Kinect),出现错误Couldn’t get shift2Depth table. Segmentation fault (core dumped),应该怎么解决啊?

  18. to smith

    請確認你的 rStream 是對應到真正的感應器,這樣才能存他那邊去讀取需要的資料。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。