Zigfu 跨平台的 Kinect SDK
之前和系上老師借了一個多學期的 Kinect 卻只有做完用 Mac 連接 Kinect 並且搭配 Unity3D 的功課,就一直沒有成果。 暑假也即將結束,緊接而來的就是全力投入在畢業製作,不過在此之前,還是得先把答應老師的功課做完。
雖然時間不足以製作一款遊戲,但是將 Zigfu 這款非常好用的工具使用介紹完整的說明,我想多少也算是能夠完成一部份的任務了!
Zigfu 基本上是設計給 Web 使用的,因此目前支援是 JavaScript 和 Unity3D 兩款(Flash 過了半年依舊開發中⋯⋯) 不過 Zigfu 卻替 Mac 使用者解決了一個問題,就是 OpenNI / OpenNI2 的安裝,沒有驅動就無法使用 Kinect 是 Mac 用戶的痛。
不過很可惜的是,目前最新的 Mac 驅動只能順利與 Kinect 溝通一分鐘左右,之後就是當機。 也因此,這系列的文章都是針對 Windows 所說明的,但是成品對 Mac 的支援是確定的,即使會當掉⋯⋯
至於 Zigfu 大致上做了什麼呢? 將驅動程式包裝起來,協助使用者安裝(Windows 使用者需要自己安裝驅動)並且提供 ZDK (SDK) 讓開發者可以用統一的界面,存取 Kinect(官方)、OpenNI、OpenNI2 的 Middleware。
關於 OpenNI / OpenNI 2 的介紹,可以參考這篇文章。
這篇文章會用 Unity3D 來解釋一些關於 Zigfu 的 ZDK 基本使用。 最基本的就是我們需要能透過 Zigfu 讀取到影像、深度、骨架等資料,才能夠繼續後續的開發與使用。
安裝
首先,我們到官方網站的 Plugin 下載頁面 去下載 Plugin。 (Windows 用戶應該是不需要,至於使用的 Kinect 是 For Windows 還是 For Xbox 要注意驅動是否正確。)
完成之後,再到 Unity3D ZDK 下載頁面下載適合 Unity3D 的 ZDK (是一個 Unitypackage 檔案,並且有含範例。)
之後在 Unity3D 開啟新專案,匯入 Custom Package 之後,就可以使用了。
注意:因為 ZDK 是用 DLL 包裝的,所以你必須使用 Unity3D Pro 才能夠正常使用
了解 Zig 元件
如果有點開範例檔案,會發現每一個範例檔案都有一個叫做 Zigfu
的 GameObject 在場景上,而這個 Zigfu
物件,都附加了一個叫做 Zig
的 Script 在上面。
假設停用了 Zigfu
物件,那麼所有相關 Kinect 的功能都會失效,並且出現 Failed load driver and middleware...
這樣的錯誤。
那麼 Zig
這個 Script 做了些什麼呢?
|
|
上面是節錄自 Zig.cs
這個檔案的內容,我們可以發現裡面對 ZigInput
設定了 InputType
跟 Settings
兩個數值。
在 Unity3D 裡面看到就會是像這樣:
簡單來說 Zig
元件幫我們把「讀取方式」以及讀取的方式設定好了!
在 InputType 裡面可以選擇 Auto / KinectSDK / OpenNI / OpenNI2 幾個選項,在預設的 Auto 狀況下,Zigfu 會自動依照 KinectSDK > OpenNI2 > OpenNI 的順序嘗試呼叫,當成功時就使用該驅動作為讀取 Kinect 資料的驅動。 Settings 裡面則會看到一些關於讀取資料的設定,像是是否要更新 Depth (深度資訊) 等等。
最後,我們需要注意 Awake
方法的最後一行 ZigInput.Instance.AddListener(gameObject)
這一句程式碼。
在程式開發慣例中 Instance 通常是指物件的實體(就 Zigfu 的設計上,應該是屬於單例的設計,簡單說就是只會存在一個。)
gameObject
在 Unity3D 通常是指自己本身,而 AddListener
在這邊指的是「當更新時也一併更新這個物件」的意思。
Listener 基本上設計類似于觀察者這種慣例,在 Unity3D 就類似於
Update
的感覺,在 Zigfu 中選擇了自行實作,跟 Unity3D 分開處理。 某方面也算是比較恰當的做法,畢竟 Kinect 裡面有自己的硬體,跟 Unity3D 分離就可以不受玩家主機的硬體限制。
從 Kinect 讀取影像
首先我們要在深入了解 ZigInput
的作用,我們可以從範例的 ZigImageView.cs
這個檔案了解到一些蛛絲馬跡。
|
|
前面提到的 AddListener
動作中,每當 Kinect 更新畫面並且被 Zigfu 接收時,會做類似 Unity3dD 的 Update
動作,也就是上面這段程式碼所寫的 Zig_Update
方法。
從這段程式碼可以看到,如果我們需要讀取影像,可以從 ZigInput
拿到一個 Image
資料來使用。
除了
Image
之外,我們還能拿到Depth
(深度) 以及Label Map
(標記) 不過 Label Map 在範例中是黑色的畫面,似乎也沒有人了解用途,因此就不多做討論。
接下來,我們先產生新的場景(Scene / Ctrl + N)並且新增一個 Empty GameObject 用來放置 Zig 元件。
產生好物件之後,就馬上把物件命名為 Zigfu 這會是一個好習慣,在中後期專案變大的時候,檔案跟物件沒有好好命名的話,就會碰到非常多問題。而團隊合作的時候更是明顯,因此別忘記修改物件名稱。
在 Zigfu 的 ZDK 匯入到 Unity3D 後,也已經自動對選單增加好所有可用的元件。 我們在 Script 類型的選件中選擇 Zig 就可以對 Zigfu 物件新增這個元件了!
預設的 Zig 元件沒有開啟 Update Image 的選項,因此我們要自己勾選起來。 (上圖還是未勾選狀態)
接著,我們會需要一個 Plane(平面)用來顯示 Kinect 讀取到的影像。
接著調整 Plane(這邊我已經重新命名為 ImageViewer) 跟 Main Camera 讓平面可以順利被攝影機完整照到。
在開始之前,我們先用範例的 ImageViewer 元件來測試效果。 現在啟動遊戲的話,應該可以順利看到 Kinect 的 Camera 照到的影像被更新到 Plane 上。
不過應該是上下顛倒的,不論是 WebCam 或者 Kinect 被照進去的狀況下都是這樣,旋轉一下就可以了! 影像有點暗是因為 3D 物件上面沒有打光,只要在場景上新增光源即可。
自定圖片讀取
我們先將 ZigImageViewer.cs
的內容複製到一個新的檔案 CustomImageViewer.cs
並且以此為基礎修改出我們自己的「圖片讀取功能」
並且把原本的 ImageViewer Panel 的 Script 改為 CustomImageViewer 來套用我們自己的讀取處理。
(這邊最好先執行看看,是否可以順利運作。要注意 class ZigImageViewer
得改為跟檔名一樣的 class CustomImageViewer
才會正常運作。)
修改之前,第一步是要了解範例的 ImageViewer 在做什麼。 下面會直接將解釋標記在程式碼中。
|
|
這邊會解釋兩個東西,一個是 Renderer (渲染器) 另一個是陣列轉為 2D 坐標的方法。
Renderer 基本上會附加在每一個 Unity3D 上「可以被看到」的物件,他用來處理材質球跟材質如何繪製到模型上。 也因此,一旦 Renderer 被關掉,就無法看到物件,這邊用程式的方式設定材質球。
至於陣列轉換為 2D 坐標的方法,其實就是非常簡單的數學邏輯。
假設有一個 10px 乘以 10px 的影像,那麼他就會有 10 * 10 = 100 個像素。 那麼第 11 個像素的坐標在哪裡呢?可以用下面的方式推算出來。
位置 = (y * 寬) + x
所以說 11 要先除以 10 會得到餘數 1 接著用 11 剪掉 1 就得到一個可以被「寬」整除的值,都計算完畢後,就可以知道第 11 個像素位置在 x = 0, y = 1 的位置(註:陣列中是從 0 ~ 99 所以算完會變成 0,1 的坐標)
多想幾次就會理解其中的原理了!
接下來,我們對 UpdateTexture
方法做一些小修改,讓畫面變成黑白的灰階畫面。
|
|
首先,先增加 Color buffer
跟 byte grayscaleByte
方便處理。
grayscale 只在 Color 下可以使用,而 Color32 則沒有這個功能,因此需要先手動將 Color32 轉為 Color
接著 buffer = new Color(rawImageMap[srcIndex].r, rawImageMap[srcIndex].g, rawImageMap[srcIndex].b, rawImageMap[srcIndex].a);
基於拿到的顏色產生一個新的 Color。
因為 Color32 需要用 byte 指定顏色,因此我們用 grayscaleByte = (byte)buffer.grayscale;
將灰階化的數值轉為 byte 方便使用。
最後調整原本複製像素的方式,改為 outputPixels[outputIndex] = new Color32(grayscaleByte, grayscaleByte, grayscaleByte, (byte)rawImageMap[srcIndex].a);
將一個灰階版本的像素複製進去。
現在,執行遊戲的話就可以看到灰階的畫面。
這篇文章就到此告一段落,至於 Depth 跟 Label Map 的使用方式,基本上是一樣的。目前學習的東西用一般的 WebCam 也能做到,下一篇文章會討論關於骨架的使用。
留言