Heresy 之前在《建立 Kinect 的姿勢辨識資料庫:Visual Gesture Builder 工具(一)》和《建立 Kinect 的姿勢辨識資料庫:Visual Gesture Builder(二)》這兩篇文章,已經把 Visual Gesture Builder 的工具程式的基本操作都做了一定程度的介紹了。
接下來這一篇,則是來講一下要怎麼在自己的 C 程式裡面,透過 Visual Gesture Builder 提供的 C API、以及自己建立出來的姿勢資料庫、來進行姿勢的辨識。
Visual Gesture Builder 的檔案
要在 C 的程式裡面使用 VGB 的 C API 的話,首先需要引入 VGB 的 Header 檔、「Kinect.VisualGestureBuilder.h」。這個檔案所在位置和其他 K4W SDKv2 的 Header 檔所在位置相同,都是在「$(KINECTSDK20_DIR)\inc」下。
而在建置階段所需要的 lib 檔,則是「Kinect20.VisualGestureBuilder.lib」,同樣位於「$(KINECTSDK20_DIR)\Lib」這個資料夾下。(x86 與 x64 放置於不同資料夾內)
至於執行時需要的檔案,則是在「$(KINECTSDK20_DIR)\Redist\VGB」這個資料夾下。C 程式除了需要「Kinect20.VisualGestureBuilder.dll」這個檔案外,也還需要「vgbtechs」這個資料夾內的檔案;這個資料夾內的兩個 DLL 檔,應該分別是進行連續性、離散式姿勢偵測的演算法實作。
VGB 的 C 介面
在使用 VGB 的 C API 的時候,基本上主要會用到它提供的六個介面,包括了:
其中,IVisualGestureBuilderDatabase 是用來儲存由資料庫檔案(.gba 或 .gbd)裡面讀取出來的姿勢用的,基本上是在初始化階段才需要的東西。
而 IVisualGestureBuilderFrameSource、IVisualGestureBuilderFrameReader 和 IVisualGestureBuilderFrame 這三者,則是和其他 K4W SDK v2 的資料一樣的三層式介面架構。
最後,從結果的介面 IVisualGestureBuilderFrame 中,則可以再針對每個姿勢、根據姿勢的類別取出 IDiscreteGestureResult 或 IContinuousGestureResult,作為每個姿勢的偵測結果。
下面則是針對這些介面,來做更詳細的說明。
讀取 VGB 的資料庫
由於在使用 VGB 進行姿勢偵測時,需要先由資料庫檔案(.gba 或 .gbd)裡面,讀取需要姿勢的資料,所以在開始偵測姿勢之前,必須要先把資料庫裡的姿勢讀取出來。
在 VGB 的 C API 中,需要透過 CreateVisualGestureBuilderDatabaseInstanceFromFile() 這個函式來讀取檔案、建立資料庫的物件、IVisualGestureBuilderDatabase。
這邊程式的寫法大致上會像下面這樣:
IVisualGestureBuilderDatabase* pGestureDatabase = nullptr;
CreateVisualGestureBuilderDatabaseInstanceFromFile(
sDatabaseFile.c_str(), &pGestureDatabase);
在這個例子裡面,就是讓 VGB 去讀取「test.gbd」這個由 VGB 的工具產生的資料庫檔案、並建立出資料庫的物件、pGestureDatabase。
在建立了 pGestureDatabase 後,接下來就要透過他的成員函式,來讀取裡面的姿勢資料。而由於一個資料庫裡面可能會包含多個姿勢,所以在這邊必須要先透過 get_AvailableGesturesCount() 這個函式,來取得可用的姿勢數量,然後再透過 get_AvailableGestures() 這個函式,把每個姿勢都以 IGesture 這個型別抽取出來。
這邊的程式基本可以寫成下面的樣子:
UINT iGestureCount = 0;
pGestureDatabase->get_AvailableGesturesCount(&iGestureCount);
// get the list of gestures
IGesture** aGestureList = new IGesture*[iGestureCount];
pGestureDatabase->get_AvailableGestures(iGestureCount, aGestureList);
其中,iGestureCount 就是 pGestureDatabase 這個資料庫內、可用的姿勢的數量;而 aGestureList 則就是取出來後、用來作個別操作的姿勢(IGesture)陣列。
不過,實際上 IGesture 這個型別的物件並不能直接用來做偵測,而只能讀取他的基本資訊而已。如果要讀取他的基本資訊,則可以寫成下面的形式:
wchar_t sName[260];
for (int i = 0; i < iGestureCount; i)
{
if (aGestureList[i]->get_GestureType(&mType) == S_OK)
{
if (mType == GestureType_Discrete)
cout << "\t[D] ";
else if (mType == GestureType_Continuous)
cout << "\t[C] ";
if (aGestureList[i]->get_Name(260, sName) == S_OK)
wcout << sName << endl;
}
}
可以看到,這邊可以透過 get_GestureType() 來取得姿勢的類型(離散或連續),也可以透過 get_Name() 來取得姿勢的名稱。
不過稍微要注意的是,在 Heresy 自己測試的時候,是發現如果用來讀取名稱的字串(sName)長度比 260 小的話,名稱的讀取會失敗。
當然,這邊也要注意一下,VGB C API 的所有函式都和 K4W SDK v2 的 API 一樣,會回傳執行結果;所以安全起見的話,最好是每個函式執行時,都檢查回傳值是否為 S_OK,以確保程式有正常運作。
VGB 資料讀取的初始化
在資料庫讀取好了之後,要使用 VGB 還需要有其他的元件才可以進行姿勢偵測、進行資料的讀取。
而首先,要使用 VGB 來進行姿勢偵測,除了 VGB 的 IVisualGestureBuilderFrameSource 等介面外,還需要 K4W SDK v2 的人體偵測(IBodyFrameSource)的相關程式才行,這部分建議可以先參考之前的《K4W v2 C Part 7:偵測、追蹤人體骨架》。
至於在 VGB 的部分,實際上也和其他 frame source 的使用方法類似,是要先透過 CreateVisualGestureBuilderFrameSource() 這個函式、來建立 IVisualGestureBuilderFrameSource 的物件,然後再透過 IVisualGestureBuilderFrameSource 的 OpenReader() 這個函式,來取得 IVisualGestureBuilderFrameReader 的物件。
不過比較特別的,是一個 IVisualGestureBuilderFrameSource 物件只能對應一個操作者,所以如果要針對多個操作者坐姿勢偵測的話,就需要產生多的物件才行。
這邊的程式,可以寫成:
IVisualGestureBuilderFrameReader** aGestureReaders = new IVisualGestureBuilderFrameReader*[iBodyCount];
for (int i = 0; i < iBodyCount; i)
{
CreateVisualGestureBuilderFrameSource(pSensor, 0, &aGestureSources[i]);
aGestureSources[i]->AddGestures(iGestureCount, aGestureList);
aGestureSources[i]->OpenReader(&aGestureReaders[i]);
}
在上面的程式碼裡面,iBodyCount 是 IBodyFrameSource 可以追蹤的人體數量,基本上應該是六個。
而這邊就是針對這六個可能存在的人體,都各自建立出 IVisualGestureBuilderFrameSource 與 IVisualGestureBuilderFrameReader, 以進行之後的姿勢偵測、以及資料讀取。
之後,則是還要針對每個 IVisualGestureBuilderFrameSource,都設定要進行偵測的姿勢;這邊是使用 AddGestures() 這個函式,來把透過 get_AvailableGestures() 取得的 IGesture 陣列(aGestureList)整個餵進去。如果不想每個姿勢都偵測的話,也可以改用 AddGesture() 這個函式、一個一個設定。
當然,其實也不一定要一開始就針對所有可能的使用者、都建立出對應的 VGB 物件,在實作上也是可以在必要時(偵測到人的時候),才去建立這些物件;不過那樣在資源上的管理會相對麻煩,所以這邊就採用這種比較簡單、預先配置的方法了。
主迴圈內的資料讀取
當程式都初始化完成之後,接下來就是要在主迴圈裡面,透過 IVisualGestureBuilderFrameReader 提供的 CalculateAndAcquireLatestFrame() 這個函式,來取得新的資料、也就是 IVisualGestureBuilderFrame  型別的資料。
在主迴圈裏面,首先要先透過 IBodyFrameReader 來取得目前有追蹤到的人體的相關資訊。這邊的程式架構基本上可以寫成下面的樣子:
if (pFrameReader->AcquireLatestFrame(&pFrame) == S_OK)
{
if (pFrame->GetAndRefreshBodyData(iBodyCount, aBody) == S_OK)
{
for (int i = 0; i < iBodyCount; i)
{
IBody* pBody = aBody[i];
BOOLEAN bTracked = false;
if ((pBody->get_IsTracked(&bTracked) == S_OK) && bTracked)
{
// Process Body Information
}
}
}
pFrame->Release();
}
也就是在讀取到新的資料(pFrame)後,再透過 GetAndRefreshBodyData() 來更新 aBody 裡面的 IBody 資料、然後再針對每個人體(pBody)、去檢查是否有被追蹤、如果有的話、才繼續處理下去(執行「// Process Body Information」的內容)。
而如果要使用 VGB 的話,則是需要在這邊,去取得人體的追蹤編號(Tracking ID),並且告訴 IVisualGestureBuilderFrameSource、 讓他去針對這個人體做姿勢的偵測。這部分的程式可以寫成下面的樣子:
if (pBody->get_TrackingId(&uTrackingId) == S_OK)
{
UINT64 uGestureId = 0;
if (aGestureSources[i]->get_TrackingId(&uGestureId) == S_OK)
{
if (uGestureId != uTrackingId)
aGestureSources[i]->put_TrackingId(uTrackingId);
}
}
在這邊,首先是透過 IBody 的 get_TrackingID() 來取得這個人體的編號(uTrackingId),然後再去取得對應的 IVisualGestureBuilderFrameSource 物件(aGestureSources[i])目前設定的標號(uGestureId);如果兩者的值不相同的話,代表需要透過 put_TrackingId() 這個函式、來設定編號。
之後呢,則就可以透過 CalculateAndAcquireLatestFrame() 這個函式、來取得新的 IVisualGestureBuilderFrame、也就是下面程式碼中的 pGestureFrame。
if (aGestureReaders[i]->CalculateAndAcquireLatestFrame(&pGestureFrame) == S_OK)
{
BOOLEAN bGestureTracked = false;
if (pGestureFrame->get_IsTrackingIdValid(&bGestureTracked) == S_OK && bGestureTracked)
{
for (int j = 0; j < iGestureCount; j)
{
// Process each gesture
}
}
pGestureFrame->Release();
}
在上面的程式碼裡面,是又再確認了一次、目前有姿勢偵測的元件有成功地追蹤到使用者。而如果確定沒問題的話,接下來就是要再針對每個姿勢、去做個別的處理了。
前面有提過,VGB 的姿勢有離散式和連續型的兩種,這兩者的姿勢偵測結果的資料型態是不同的。離散式的姿勢偵測結果(IDiscreteGestureResult)主要會是一個布林變數、代表是否有偵測到,然後再有一個浮點數、代表他的可靠度(confidence),另外也可以知道目前是否是第一個偵測到的畫面;而連續型的姿勢偵測結果(IContinuousGestureResult)就只有一個幅點數、代表目前的進度(progress)、而且一定要得到值。
由於資料型態相差很大,所以兩種類型的結果須要分開處理。這邊的程式碼,可以寫成下面的形式:
GestureType mType;
aGestureList[j]->get_GestureType(&mType);
const UINT uTextLength = 260;
wchar_t sName[uTextLength];
aGestureList[j]->get_Name(uTextLength, sName);
if (mType == GestureType_Discrete)
{
IDiscreteGestureResult* pGestureResult = nullptr;
if (pGestureFrame->get_DiscreteGestureResult(aGestureList[j], &pGestureResult) == S_OK)
{
BOOLEAN bDetected = false;
if (pGestureResult->get_Detected(&bDetected) == S_OK && bDetected)
{
float fConfidence = 0.0f;
pGestureResult->get_Confidence(&fConfidence);
wcout << L"Detected Gesture " << sName << L" @" << fConfidence << endl;
}
pGestureResult->Release();
}
}
else if (mType == GestureType_Continuous)
{
IContinuousGestureResult* pGestureResult = nullptr;
if (pGestureFrame->get_ContinuousGestureResult(aGestureList[j], &pGestureResult) == S_OK)
{
float fProgress = 0.0f;
if (pGestureResult->get_Progress(&fProgress) == S_OK)
{
if (fProgress > 0.5f)
wcout << L"Detected Gesture " << sName << L" " << fProgress << endl;
}
pGestureResult->Release();
}
}
在上面的程式碼裡面,主要是先取得姿勢的類型、以及名稱。
然後,如果是離散式的姿勢的話,就是透過 pGestureFrame 的 get_DiscreteGestureResult() 這個函式、來取得姿勢偵測的結果、pGestureResult。而 IDiscreteGestureResult 本身則提供了 get_Detected()、get_Confidence()、get_FirstFrameDetected() 這三個函式,可用來讀取偵測的結果。
一般來說,首先是要先透過 get_Detected() 來判斷是否有偵測到這個姿勢、在讀取其他的資料、或做其他的處理。在上面的範例中,Heresy 就是寫成當有偵測到這個姿勢的時候,會印出對應的訊息出來。
如果是連續性的姿勢的話,則是要使用 get_ContinuousGestureResult() 這個函式、來取得姿勢偵測的結果。而 IContinuousGestureResult 能提供的資訊基本上是更簡單的,只有 get_Progress() 這個函式而已。
由於連續性的姿勢偵測基本上是一定會給出一個 progress 的值,所以如果要使用連續性的姿勢的話,會比較麻煩一些,有可能需要搭配離散式的姿勢,設定成只有在特定的離散式姿勢有被偵測到的情況下,才處理連續性的姿勢;透過這類型的方法,基本上才能真正地去處理連續性的姿勢、讓他有實用性。
不過由於 Heresy 這邊只是簡單的範例程式,所以就是單純當進度超過 0.5 的時候,就印出對應的訊息而已。
VGB C API 的使用說明基本上就整理到這邊了,完整可以運作的範例程式碼,請參考 Heresy 放在 GitHub 上的檔案。
這個範例程式基本上沒有圖形介面,只有文字介面。在執行之後,會先把姿勢資料庫的姿勢都列出來,然後再開始進行偵測;當有偵測到姿勢的時候,就會輸出對印的資訊。
不過有幾點可能要注意的是:
-
所讀取的姿勢資料庫檔案是寫死在程式碼裡的,檔案名稱是「test.gbd」;所以如果使用自己的資料庫檔案做測試的時候,需要做對應的修改,否則會讀不到資料。
-
程式執行時,除了需要能讀到 Kinect20.VisualGestureBuilder.dll 這個 runtime DLL 外,在資料夾下也需要有「vgbtechs」這個資料夾。所以這邊建議請把這個 DLL 檔、以及「vgbtechs」這個資料夾一併複製到執行檔所在的位置。
總之,VGB 的部分就先這樣了~之後再想看看要怎麼利用這個系統吧~