這是一個 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.dll、Kinect.dll 等檔案放在一起就可以了。 而要在建立一個虛擬裝置的話,則是要在建立 OpenNI Device 的時候,指定特別的 URI: \\OpenNI2\\VirtualDevice\\<<STRING>> 其中,<<STRING>> 的部分可以自行更換,只要名字不同,就可以建立不同的裝置。下面就是一個範例: Device devVirDevice; devVirDevice.open( "\\OpenNI2\\VirtualDevice\\TEST" ); 而在建立出虛擬裝置 devVirDevice 後,接下來則就是按照標準的作法,建立出需要的 VideoStream、用來讀取資料;目前的版本僅支援彩色和深度兩種 Video Stream,不支援紅外線。 在建立出 VideoStream 的物件後,使用前則需要透過 setVideoMode() 來指定要使用的解析度,這樣 VideoStream 才知道之後要產生多大的畫面。 虛擬裝置的 VideoStream 都準備好了之後,如果要給他新的資料的話,基本上是要先透過 VideoStream 的 invoke() 函式、來執行 GET_VIRTUAL_STREAM_IMAGE 這個命令,來要求 VideoStream 配置一個 OniFrame 的物件、讓程式可以填自己的資料進去;而在準備好資料之後,則是再透過 invoke() 執行 SET_VIRTUAL_STREAM_IMAGE 這個命令、把修改好的資料、傳給 VideoStream、告訴他新的畫面已經準備好了。 其中,GET_VIRTUAL_STREAM_IMAGE 和 SET_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,或是其他的執行緒函式庫。
屬性 為了讓這個虛擬裝置可以支援各項屬性,所以在 Device 和 VideoStream 裡,都有設計 property pool 的機制,來儲存各種類型的屬性資料。要使用的話,基本上就是透過 OpenNI 提供的各種介面、或是直接使用 setProperty() 來做設定、並透過 getProperty() 來讀取。 不過,除了 ONI_STREAM_PROPERTY_VIDEO_MODE 和 ONI_STREAM_PROPERTY_CROPPING 這兩個屬性會動到 VideoStream 的設定外,其他的所有屬性都只是單純地被儲存下來、並不會對虛擬裝置造成任何影響。像是雖然可以透過 setMirroringEnabled() 來設定 ONI_STREAM_PROPERTY_MIRRORING,但是實際上影像並不會因此產生鏡像效果。 另外,如果是深度影像、而且會用到座標系統轉換(Depth / World)的話,則還需要指定 ONI_STREAM_PROPERTY_VERTICAL_FOV 和 ONI_STREAM_PROPERTY_HORIZONTAL_FOV 這兩個屬性的值, CoordinateConverter 才有辦法進行座標轉換的計算。
修改現有裝置的影像 如果是需要搭配 OpenNI 現有的感應器、來修改實際感應器讀取到的資料(類似 OpenNI 1 的 mock node)的話,則是可以參考「ExistedDeviceSample」這個範例。 使用方法基本上就是先按照一般的方法,建立出正常的 Device、和需要的 VideoStream;接下來則是建立虛擬的裝置,並透過實際的 VideoStream 的 addNewFrameListener()、來讓真實裝置讀取到新的影像後、就把資料做完處理、丟給虛擬裝置用。 這樣處理後,之後就可以透過虛擬的 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 的 UserTracker 或 HandTracker 物件,在透過 cretae() 指定 Device 來建立的時候,實際上應該是會針對裝置的 URI 作檢查的。不過由於 PrimeSense 並沒有公開他的檢查機制,所以 URI 的部分基本上只能自己測試看看哪種可以通過檢查。 就 Heresy 測試的結果,下面這個 URI 應該是可以通過 NiTE 的檢查的: \OpenNI2\VirtualDevice\Kinect 所以如果建立的虛擬裝置是要給 NiTE2 用的話,就需要使用這個 URI;至於其他還有沒有可以通過 NiTE 檢查的,可能就要做測試才知道了。 支援的解析度 由於 NiTE 是為了 PrimeSense 自家的感應器所開發的,所以他也只支援自家感應器能提供的解析度。基本上,應該就是 QVGA(320x240)和 VGA(640x480)兩種。所以如果是要搭配 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 的話,應該是要可以用才對的。
|