在上一篇《Kinect for Windows SDK v2 基本介紹》裡,基本上已經簡單地介紹了 Kinect for Windows SDK v2 了~而這一篇,就簡單介紹一下 K4W v2 的 C API 吧!
不過,這邊也先提一下,說實話 Heresy 個人不是很喜歡 K4W v2 SDK API 的設計概念…
其一是他的介面不但大量地使用了指標(pointer)、而且還需要要用他自己的介面風格來做資源的管理,並非使用 C 標準的 new / delete;這點算是讓 Heresy 最討厭的地方,感覺上,一不小心就會有 memory leak…
其二就是他的錯誤處理基本上都還是靠回傳值來做,雖然這是個很簡單的方法,但是結果就是連要取得一個最基本的資料,都必須要透過傳參數的方法來做…某方面來說,也算是相對麻煩。
不過,這算是個人喜好的問題了,下面還是來看一下到底他是怎麼設計的吧。
專案基本設定
在開始之前,這邊還是先提一下,要建立一個使用 K4W SDK v2 的 C 專案,要進行那些設定。
基本上在 C 裡面要使用外部函式庫,大多都是先 include 必要的 header 檔、並連結對應 lib 檔,這樣才能使用外部的函式庫。而對於基本的 K4W v2 程式來說,所需要的 Header 檔只有「Kinect.h」一個,而要 link 的函式庫檔也只有「Kinect20.lib」這個檔案。理論上專案只要針對這兩項做好設定就可以了。(註一)
首先,之前有提過,SDK 預設會被安裝在「C:\Program Files\Microsoft SDKs\Kinect\v2.0_1409」這個目錄下。而「Kinect.h」就是放在他的「inc」目錄,「Kinect20.lib」則是放在「Lib」目錄;不過根據不同的平台,「Lib」目錄下還有一層子目錄要選擇(x86 或 x64)。
而為了方便專案的設定,K4W SDK v2 在安裝的時候,會在電腦裡加入一個巨集「$(KINECTSDK20_DIR)」,代表 SDK 的安裝路徑。所以在 Visual C 裡面要針對 K4W v2 做設定的話,基本上在專案屬性的「組態屬性」裡,要修改的地方會有三個:
-
在「C/C 」的「一般」裡,「其他 Include 目錄」要加上「$(KINECTSDK20_DIR)\inc」
(英文版是「C/C 」、「General」、「Additional Include Directories」) -
在「連結器」的「一般」裡,「其他程式庫目錄」要加上「Kinect20.lib」所在路徑
(英文版是「Linker」、「General」、「Additional Library Directories」)
如果是要建置 32 位元的程式的話是「$(KINECTSDK20_DIR)\Lib\x86」 ,如果是 64 位元的程式則是「$(KINECTSDK20_DIR)\Lib\x64」。 -
在「連結器」的「輸入」裡,「其他相依性」要加上「kinect20.lib」
(英文版是「Linker」、「Input」、「Additional Dependencies」)
而之後,則就是在程式碼裡面,去 include kinect.h,然後就可以使用 K4W v2 的 C API 了~
C API 概述
上面基本上是簡單的說明,接下來,則大概來講一下 Kinect for Windows SDK v2 的 C API 基本概念。
首先,K4W SDK v2 的 C API 沒有使用 namespace,所以所有的函式、類別、結構等等,都是全域(global)的。而在官方的 C Reference(連結)裡,也有針對「介面」(Interface)、「函式」(Function)、「結構」(structure)以及「列舉型別」(Enumeration)各自做條列。
其中,在函式(function)的部分,他所提供的全域函式只有一個,就是用來取得感應器的 GetDefaultKinectSensor()(MSDN);雖然 MSDN 上還有列另一個函式 GetKinectSensorCollection()(連結),但是實際上應該是舊的預覽版才有的,在現行的 SDK 版本裡應該不存在。
而在介面(interface)的部分,則就是提供了 K4W v2 SDK 主要的物件的類別,也就是在開發 Kinect for Windows v2 的程式時,主要要用來操作的物件了~這類的類別在 SDK 裡面,都統一是大寫「I」開頭,完整的列表可以參考 MSDN。
其中,可能要稍微注意一下的是,和上面提到的函式一樣,有部分的 Interface 雖然有列出來,但是實際上並不存在於現行的 SDK 版本裡。
在結構(structure)的部分,則是針對某些特定的資料、定義了專屬的資料型別,來簡化程式的開發。
函式
K4Wv2 SDK 所提供的函式(包含類別的成員函式),介面的一致性相當地高;所有的函式都會回傳一個一個型別定義成「HRESULT」(實際上是 long)的執行結果;如果回傳值是「S_OK」的話,就代表執行成功,否則就是有問題。
由於回傳值已經被用來回傳執行結果了,所以如果是要取得資訊的話,基本上就需要把數值/物件的指標當作參數傳進去、讓函式去修改他的值。
比如說,如果是要使用 GetDefaultKinectSensor() 這個函式來取得預設的感應器、並檢查是否正確執行的話,則是可以寫成:
if (GetDefaultKinectSensor(&pSensor) == S_OK)
{
cout << "Get Sensor OK" << endl;
}
else
{
cerr << "Get Sensor failed" << endl;
}
這段程式的功能,就是透過 GetDefaultKinectSensor() 這個函式、來取得系統預設的感應器;而取得的結果,就是 pSensor。而要知道是否有成功取得,也只要像上面的程式一樣,判斷函式回傳值是否為 S_OK 就可以了。
而如果是要透過 Frame Description 來取得畫面寬度時,則會寫成:
pFrameDescription->get_Width(&iWidth);
透過上面的程式,就可以取得 pFrameDescription(其型別為 IFrameDescription,是用來描述畫面的資訊的)裡面紀錄的畫面寬度資訊、並將之記錄到 iWidth 這個變數了。基本上在 K4W SDK v2 裡面要取得畫面的資訊、大致上都是要這樣做。
在開發 K4W SDK v2 的程式的時候,基本上所有的函式都是這樣使用的。說實話,個人是覺得這樣有點麻煩啦…
介面
由於 K4W SDK v2 在概念上是採取物件導向的設計,所以其他的操作,都是要透過「介面」(Interface)的類別來做操作,像是前面例子裡面、對應到感應器實體的 IKinectSensor 就是一個 interface;而如同前面提過的,這類的類別在 SDK 裡面,都統一是大寫「I」開。
這些 Interface 的類別,在 K4Wv2 SDK 的設計概念下,都是需要透過特定的函式來取得他們的指標、以進行操作;在使用結束後,也不是使用 delete、而是要使用 Release() 函式、來進行資源的釋放。
像是上面例子裡面所使用的 IKinectSensor,就是要透過 GetDefaultKinectSensor() 來取得。
而由於 K4W SDK 基本上就是以 Kinect 感應器為主來設計的,所以他提供的其他 Interface,絕大部分都是屬於 Kinect 感應器的功能;也因此在架構上,這些 Interface 都是透過 IKinectSensor 的成員函式來取得、或是透過所取得的物件一路拿出來的。
以直接可以透過 IKinectSensor 取得的 interface 來說,包括了:
- IKinectSensor (對應到感應器)
- get_DepthFrameSource() -> IDepthFrameSource (深度影像來源)
- get_ColorFrameSource() -> IColorFrameSource (彩色影像來源)
- get_InfraredFrameSource() -> IInfraredFrameSource (紅外線影像來源)
- get_LongExposureInfraredFrameSource() -> ILongExposureInfraredFrameSource (長時間曝光紅外線影像來源)
- get_AudioSource() -> IAudioSource (聲音的來源)
- get_BodyFrameSource() -> IBodyFrameSource (人體骨架資訊的來源)
- get_BodyIndexFrameSource() -> IBodyIndexFrameSource (人體在深度影像所佔位置的資料來源)
- OpenMultiSourceFrameReader() -> IMultiSourceFrameReader (多來源讀取器)
- get_CoordinateMapper() -> ICoordinateMapper (座標系統轉換)
其中,前面七個都是以「Source」作結尾的、代表都是某種資料的來源。而這些資料來源的類別,基本上大多不會直接被用來讀取資料,而是要透過 OpenReader() 這個函式、來取得對應的「Frame Reader」,用來讀取資料。
像是以 IDepthFrameSource 來說,就可以透過 OpenReader() 這個函式來取得 IDepthFrameReader,並透過他來做資料的讀取。而在「Frame Reader」裡,則是有提供 AcquireLatestFrame() 這類的函式,用來讀取最新的資料。
所以以深度影像來說,這樣一路下來,要取得畫面的呼叫紀錄,大致上就會變成是:
- GetDefaultKinectSensor() → IKinectSensor
- get_DepthFrameSource() → IDepthFrameSource
- OpenReader() → IDepthFrameReader
- AcquireLatestFrame() → IDepthFrame
- OpenReader() → IDepthFrameReader
- get_DepthFrameSource() → IDepthFrameSource
這樣的形式。而其他類型的資料雖然在細節上可能有些差異,但是大致上也都是這樣對應的~所以,基本上應該可以靠 interface 的名稱,來判斷出它的用途。(聲音的部分因為本身資料性質的差異,所以比較不一樣。)
另外,IMultiSourceFrameReader 是用來同時讀取多種資料用的,而 ICoordinateMapper 則是用來做座標系統轉換的。其他,像是「Frame Reference」和「Frame Arrived Event Args」這類的介面,則是用在事件導向的程式開發時才會用到的,這部分以後有機會會再提。
基本使用概念
而在資料讀取的架構上,K4W SDK v2 提供了「事件導向」(event)和「輪詢」(polling)兩種操作模式。而基本上,「輪詢」(polling)也算是比較單純的,所以,這邊就先以這個模式來做說明。(註二)
在這個模式下。要開發 K4W SDK v2 的程式,基本上的使用概念,就是:
- 透過 GetDefaultKinectSensor() 取得系統內的感應器、IKinectSensor
- 透過 IKinectSensor 的成員函式,取得所需要資料的資料來源物件(Frame Source、例如 IDepthFrameSource)
- 針對資料來源物件,取得其資料讀取器(Frame Reader、例如 IDepthFrameReader )
- 進入主迴圈,並不停地透過資料讀取器,試著去讀取新的資料(例如 IDepthFrame)
- 檢查資料是否讀取成功,如果讀取成功的話,就針對取得的資料作處理
這篇就先寫到這裡了。接下來下一篇,就是開始寫範例程式、真的來讀取資料了~
附註
-
如果要用到 Kinect Fusion、Face 等其他進階功能,由於並不屬於 K4W SDK v2 核心功能,所以就需要額外的 header 和 lib 檔。
-
說實話,個人是覺得他所提供的事件導向模型很難用…和 Heresy 碰過、其他 C 框架的事件導向架構也不大一樣。他並非採用 callback 或是 signal – slot 的形式,而是採取去等待 event handle 的方式來做,基本上就像是比較麻煩的 polling。