Visual Gesture Builder C++ API


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)裡面讀取出來的姿勢用的,基本上是在初始化階段才需要的東西。

IVisualGestureBuilderFrameSourceIVisualGestureBuilderFrameReaderIVisualGestureBuilderFrame 這三者,則是和其他 K4W SDK v2 的資料一樣的三層式介面架構。

最後,從結果的介面 IVisualGestureBuilderFrame 中,則可以再針對每個姿勢、根據姿勢的類別取出 IDiscreteGestureResultIContinuousGestureResult,作為每個姿勢的偵測結果。

下面則是針對這些介面,來做更詳細的說明。


讀取 VGB 的資料庫

由於在使用 VGB 進行姿勢偵測時,需要先由資料庫檔案(.gba 或 .gbd)裡面,讀取需要姿勢的資料,所以在開始偵測姿勢之前,必須要先把資料庫裡的姿勢讀取出來。

在 VGB 的 C++ API 中,需要透過 CreateVisualGestureBuilderDatabaseInstanceFromFile() 這個函式來讀取檔案、建立資料庫的物件、IVisualGestureBuilderDatabase

這邊程式的寫法大致上會像下面這樣:

wstring sDatabaseFile = L“test.gbd";
IVisualGestureBuilderDatabase* pGestureDatabase = nullptr;
CreateVisualGestureBuilderDatabaseInstanceFromFile(
    sDatabaseFile.c_str(), &pGestureDatabase);

在這個例子裡面,就是讓 VGB 去讀取「test.gbd」這個由 VGB 的工具產生的資料庫檔案、並建立出資料庫的物件、pGestureDatabase

在建立了 pGestureDatabase 後,接下來就要透過他的成員函式,來讀取裡面的姿勢資料。而由於一個資料庫裡面可能會包含多個姿勢,所以在這邊必須要先透過 get_AvailableGesturesCount() 這個函式,來取得可用的姿勢數量,然後再透過 get_AvailableGestures() 這個函式,把每個姿勢都以 IGesture 這個型別抽取出來。

這邊的程式基本可以寫成下面的樣子:

// Get how many gestures in database
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 這個型別的物件並不能直接用來做偵測,而只能讀取他的基本資訊而已。如果要讀取他的基本資訊,則可以寫成下面的形式:

GestureType mType;
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 的物件,然後再透過 IVisualGestureBuilderFrameSourceOpenReader() 這個函式,來取得 IVisualGestureBuilderFrameReader 的物件。

不過比較特別的,是一個 IVisualGestureBuilderFrameSource 物件只能對應一個操作者,所以如果要針對多個操作者坐姿勢偵測的話,就需要產生多的物件才行。

這邊的程式,可以寫成:

IVisualGestureBuilderFrameSource** aGestureSources = new IVisualGestureBuilderFrameSource*[iBodyCount];
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]);
}

在上面的程式碼裡面,iBodyCountIBodyFrameSource 可以追蹤的人體數量,基本上應該是六個。

而這邊就是針對這六個可能存在的人體,都各自建立出 IVisualGestureBuilderFrameSourceIVisualGestureBuilderFrameReader, 以進行之後的姿勢偵測、以及資料讀取。

之後,則是還要針對每個 IVisualGestureBuilderFrameSource,都設定要進行偵測的姿勢;這邊是使用 AddGestures() 這個函式,來把透過 get_AvailableGestures() 取得的 IGesture 陣列(aGestureList)整個餵進去。如果不想每個姿勢都偵測的話,也可以改用 AddGesture() 這個函式、一個一個設定。

當然,其實也不一定要一開始就針對所有可能的使用者、都建立出對應的 VGB 物件,在實作上也是可以在必要時(偵測到人的時候),才去建立這些物件;不過那樣在資源上的管理會相對麻煩,所以這邊就採用這種比較簡單、預先配置的方法了。


主迴圈內的資料讀取

當程式都初始化完成之後,接下來就是要在主迴圈裡面,透過 IVisualGestureBuilderFrameReader 提供的 CalculateAndAcquireLatestFrame() 這個函式,來取得新的資料、也就是 IVisualGestureBuilderFrame  型別的資料。

在主迴圈裏面,首先要先透過 IBodyFrameReader 來取得目前有追蹤到的人體的相關資訊。這邊的程式架構基本上可以寫成下面的樣子:

IBodyFrame* pFrame = nullptr;
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、 讓他去針對這個人體做姿勢的偵測。這部分的程式可以寫成下面的樣子:

UINT64 uTrackingId = 0;
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);
    }
}

在這邊,首先是透過 IBodyget_TrackingID() 來取得這個人體的編號(uTrackingId),然後再去取得對應的 IVisualGestureBuilderFrameSource 物件(aGestureSources[i])目前設定的標號(uGestureId);如果兩者的值不相同的話,代表需要透過 put_TrackingId() 這個函式、來設定編號。

之後呢,則就可以透過 CalculateAndAcquireLatestFrame() 這個函式、來取得新的 IVisualGestureBuilderFrame、也就是下面程式碼中的 pGestureFrame

IVisualGestureBuilderFrame* pGestureFrame = nullptr;
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)、而且一定要得到值。

由於資料型態相差很大,所以兩種類型的結果須要分開處理。這邊的程式碼,可以寫成下面的形式:

// get gesture information
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();
    }
}

在上面的程式碼裡面,主要是先取得姿勢的類型、以及名稱。

然後,如果是離散式的姿勢的話,就是透過 pGestureFrameget_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 的部分就先這樣了~之後再想看看要怎麼利用這個系統吧~


Kinect for Windows v2 C++ 程式開發

廣告

關於 Heresy
https://kheresy.wordpress.com

35 Responses to Visual Gesture Builder C++ API

  1. 故事鹏 says:

    您好,为什么程序在运行的时候会在add gesture哪里停止,程序显示:add gesture failed

    按讚數

    • Heresy says:

      建議請先確認你之前的程式都有正確的執行,並確定 get_AvailableGestures 有正確地取得對應的 gesture。

      按讚數

  2. 故事鹏 says:

    请问为何会出现应用程序无法启动的错误0xc000007b的错误

    按讚數

    • Heresy says:

      建議先換一台電腦試試看能不能正確運作。
      網路上看到這樣的錯誤似乎大多是 DirectX 環境不正確造成的。

      按讚數

  3. 大闻西 says:

    C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V140\Microsoft.CppBuild.targets(1242,59): error MSB4115: 函数“exists”只接受标量值,但其参数“%(Link.ImportLibrary)”的计算结果为“Kinect20.VisualGestureBuilder.dll;AdaBoostTech.dll;RFRProgressTech.dll”,不是标量值

    按讚數

  4. 林炽杰 says:

    我运行那个程序,弹出无法启动此程序,因为计算机中丢失Kinect20.VisualGestureBuilder.dll。

    按讚數

    • 林炽杰 says:

      这是怎么回事了

      按讚數

    • Heresy says:

      請參考上一串、「謝高丰」的問題

      按讚數

  5. 謝高丰 says:

    您好
    如果我的一個gbd裡面各是一個動作
    那麼我今天要做一個動作,去看看裡面有沒有符合的動作
    是要把檔案全部讀完,還是在一個gbd檔案錄製多個動作好呢?

    按讚數

    • Heresy says:

      這兩種方法應該沒有明顯的好壞,而是看你自己覺得哪種方便了。

      按讚數

      • 謝高丰 says:

        您好
        上面的第三個步驟說到

        至於執行時需要的檔案,則是在「$(KINECTSDK20_DIR)\Redist\VGB」這個資料夾下。C++ 程式除了需要「Kinect20.VisualGestureBuilder.dll」這個檔案外,也還需要「vgbtechs」這個資料夾內的檔案;這個資料夾內的兩個 DLL 檔,應該分別是進行連續性、離散式姿勢偵測的演算法實作。

        我不曉得從哪裡操作這個步驟,不曉得可不可以向我說明清楚步驟呢??

        按讚數

        • Heresy says:

          請使用檔案總管將檔案複製到對應的資料夾。

          按讚數

          • 謝高丰 says:

            試問Heresy
            如果以VGB來開發手語的話
            是一個好方法嗎?

            按讚數

        • Heresy says:

          抱歉,Heresy 和手語不熟,無法判斷。

          按讚數

          • 謝高丰 says:

            請問Heresy,我在整合先讀到離散動作,再讀連續動作的時候,"設定成只有在特定的離散式姿勢有被偵測到的情況",我是把我的連續動作寫在
            if (pGestureResult->get_Detected(&bDetected) == S_OK && bDetected)
            {

            ……..
            aGestureList[j]->get_GestureType(&mType);
            if (mType == GestureType_Continuous)
            {
            IContinuousGestureResult* qGestureResult = nullptr;
            if (pGestureFrame->get_ContinuousGestureResult(aGestureList[i], &qGestureResult) == S_OK)
            {
            float fProgress = 0.0f;
            if (qGestureResult->get_Progress(&fProgress) == S_OK)
            {
            if (fProgress > 0.5f)
            {
            // output information
            wcout << L"Detected Gesture " << sName << L" " << fProgress <Release();
            }
            }
            }
            但是變成好像只讀的到離散的動作,但是近不去連續的裡面
            還是有什麼方法可以先讀到離散,再讀到連續的呢

            按讚數

          • Heresy says:

            個人建議你先試著想一下整個流程,模擬一下你的資料在實際上會怎麼運作、並考慮一下你的姿勢到底是在那裡更新的。

            請注意一下,「主迴圈」所處理的流程、以及你的資料(姿勢)進來的順序、以及時間差。

            按讚數

  6. lin says:

    为什么这句代码if (pGestureFrame->get_DiscreteGestureResult(aGestureList[j], &pGestureResult) == S_OK)运行后, 这句if (pGestureResult->get_Detected(&bDetected) == S_OK && bDetected)显示pGestureRresult为空指针然后错误弹出

    按讚數

    • Heresy says:

      抱歉,Heresy 沒碰過這樣的狀況。
      建議請檢查一下,之前的流程是否都正確,並確認是否有其他錯誤訊息。

      按讚數

  7. 遊客 says:

    打擾抱歉。
    剛入門許多東西較不懂
    請問
    取得資料都是透過API
    得到已經做過處理的資料內容
    像是骨架等都是API中去追蹤的

    所以基本上要矯正
    就是需要額外利用深度資料做二次處理嗎?

    按讚數

    • Heresy says:

      不太確定你到底是要做什麼,不過基本上,K4W SDK 能給的結果就是這樣。
      如果對於這結果不滿意,能做的基本上大概有幾種可能:

      1. 自己去從深度影像追蹤骨架
      2. 放棄骨架追蹤、直接用深度影像來分析
      3. 除了使用 K4W SDK 的骨架資料外,另外使用其他方法做輔助

      不管哪個方向,基本上都是要根據你的應用自己去研究方法了。

      按讚數

  8. 遊客 says:

    想請問一個問題
    也就是姿勢學習的時候 是否需要骨架都有被追蹤到
    如果是側面等姿勢,讓骨架偵測跑掉
    這樣是否也會導致姿勢辨識的時候產生誤差
    還是會透過機器學習的演算方法,能夠順利辨識呢?

    按讚數

    • Heresy says:

      你可以試試看。
      但是他的學習基本上是根據骨架狀態來做分析的,當每次誤差都不一樣的情況下,基本上也不要太期待結果了。

      按讚數

      • 遊客 says:

        明白。
        因為在做骨架偵測時發現似乎會有誤差
        因此才在思考做機器學習時是否也是利用骨架位置去做資料的分析
        應該還是需要骨架可信度較高的時候
        去做姿態辨識會得到較好結果吧?
        會再去實驗看看的,感謝您的回答

        按讚數

        • Heresy says:

          只能說,這要碰運氣,看你到底哪些關節會有顯著誤差,會影響學習。
          如果運氣好,必要條件夠充分的話,還是可以學習出來能用的結果的。

          如果深度影像沒問題,但是骨架一直沒辦法很好追蹤的話,其實個人會建議試看看能不能自己從深度影像去做辨識。

          按讚數

          • 遊客 says:

            另外想請問
            kinect放置角度
            對於骨架辨識會有巨大的影響嗎?
            實驗時放置許多角度
            似乎只要全身入鏡就能夠追蹤到

            按讚數

          • Heresy says:

            當然還是有影響,只是影響程度多大而已。
            講極端一點,你如果從正上方拍應該就無法追蹤了。

            按讚數

  9. Gary Kuo says:

    Heresy您好:
    我現在還是無法將利用指令"AddGestures" 把aGestureList中test.gbd的檔案餵進aGestureSources[i]中,建立的六個FrameSource都出現 “Add gestures failed"。
    前面的程式都有確實抓到我的test.gbd檔裡面有兩個gesture,所以不知道是我用Visual studio錄製的影片有錯誤,還是其他的原因…..謝謝

    按讚數

    • Gary Kuo says:

      已經解決了 謝謝Heresy

      按讚數

      • Heresy says:

        可以的話,也分享一下問題到底是什麼、以及解決的方法吧。

        按讚數

        • Gary Kuo says:

          之前是把vgbtechs資料夾中的兩個dll檔直接丟入程式資料夾,但現在直接把vgbtechs資料夾丟入後就神奇的可以跑了

          按讚數

          • Heresy says:

            恩…這應該不是神奇,而是他本來就是設計成這樣啊…

            按讚數

  10. 梁信輝 says:

    你好:
    不好意思想請問一下
    我在操作你的範例程式時會產生連結器錯誤 LINK2019
    CreateVisualGestureBuilderFrameSource
    CreateVisualGestureBuilderDatabaseIntstanceFromFile 無法解析的問題
    這是我建置的時候出錯誤嗎?
    vgbtechs跟DLL都有丟到程式目錄下

    按讚數

    • Heresy says:

      請確認在專案設定中,有正確地設定需要使用 Kinect20.VisualGestureBuilder.lib

      按讚數

      • 梁信輝 says:

        成功了 原來忘記放lib檔進去 謝謝!

        按讚數

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: