K4W v2 C++ Part 1:簡單的深度讀取方法


在上一篇《Kinect for Windows SDK v2 C++ API 簡介》裡,基本上已經簡單地介紹 Kinect for Windows SDK v2 的 C++ API 了。而這一篇,就開始來寫第一個 K4W v2 的 C++ 程式吧!

而 Heresy 這邊基本上都是使用 Microsoft Visual Studio 2013 來做開發,如果是沒有這套軟體的話,建議可以考慮去下載免費的 Visual Studio Community 2013 來用。

如果要建立新專案的話,由於 Heresy 是使用標準的 C++ 來做開發,所以是點選 VisualStudio 上方工具列的「檔案」裡的「新增」-「專案」,然後在新增專案的視窗左側,選取「範本」、「Visual C++」下的「Win32」;然後,在右邊應該就會有「Win32 主控台應用程式」可以選取了。

之後在「Win32 應用程式精靈」內,請確定在「應用程式設定」裡的「應用程式類型」是「主控台應用程式」,而如果沒有特殊需求的話,也可以把「其他選項」的「空專案」勾起來。而之後,就可以自行加入原始碼檔(.cpp)、開始撰寫程式了。

而針對 Kinect for Windows SDK 的專案設定的部分,請參考《Kinect for Windows SDK v2 C++ API 簡介》一文內的「專案基本設定」。

簡單的範例

而如果要用 Kinect for Windows SDK v2 來讀取深度的程式該怎麼寫呢?這樣的程式如果要簡化到極致的話,大致上可以寫成下面的樣子:

// Standard Library
#include <iostream>

// Kinect for Windows SDK Header
#include <Kinect.h>

int main(int argc, char** argv)
{
    // 1a. Get default Sensor
    IKinectSensor* pSensor = nullptr;
    GetDefaultKinectSensor(&pSensor);

    // 1b. Open sensor
    pSensor->Open();

    // 2a. Get frame source
    IDepthFrameSource* pFrameSource = nullptr;
    pSensor->get_DepthFrameSource(&pFrameSource);

    // 3a. get frame reader
    IDepthFrameReader* pFrameReader = nullptr;
    pFrameSource->OpenReader(&pFrameReader);

    // Enter main loop
    size_t uFrameCount = 0;
    while (uFrameCount < 100)
    {
        // 4a. Get last frame
        IDepthFrame* pFrame = nullptr;
        if (pFrameReader->AcquireLatestFrame(&pFrame) == S_OK)
        {
            // 4b. Get frame description
            int        iWidth = 0;
            int        iHeight = 0;
            IFrameDescription* pFrameDescription = nullptr;
            pFrame->get_FrameDescription(&pFrameDescription);
            pFrameDescription->get_Width(&iWidth);
            pFrameDescription->get_Height(&iHeight);
            pFrameDescription->Release();
            pFrameDescription = nullptr;

            // 4c. Get image buffer
            UINT    uBufferSize = 0;
            UINT16*    pBuffer = nullptr;
            pFrame->AccessUnderlyingBuffer(&uBufferSize, &pBuffer);

            // 4d. Output depth value
            int x = iWidth / 2,
                y = iHeight / 2;
            size_t idx = x + iWidth * y;
            std::cout << pBuffer[idx] << std::endl;

            // 4e. release frame
            pFrame->Release();
            pFrame = nullptr;

            ++uFrameCount;
        }
    }

    // 3b. release frame reader
    pFrameReader->Release();
    pFrameReader = nullptr;

    // 2b. release Frame source
    pFrameSource->Release();
    pFrameSource = nullptr;

    // 1c. Close Sensor
    pSensor->Close();

    // 1d. Release Sensor
    pSensor->Release();
    pSensor = nullptr;

    return 0;
}

這個程式並沒有圖形介面,如果正確執行的話,在 Console(命令提示字元視窗)上應該會輸出一百個整數,代表了深度感應器中心點所偵測到的深度距離。

不過上面的程式並沒有包含錯誤的偵測與處理,如果需要有加入錯誤偵測的程式碼的話,可以參考 Heresy 放在 GitHub 上的檔案(連結)。


程式說明

在上面的程式裡面,可以看到基本的操作流程大致如下:(這邊的編號和上面程式碼內註解的編號是對應的)

  1. 取得並開啟感應器(IKinectSensor

    這邊首先是透過 GetDefaultKinectSensor()MSDN)來取得預設的感應器物件,然後再呼叫 Open() 來開啟它。
    這邊用來對應實際感應器的物件是 pSensor,其型別為 IKinectSensorMSDN)。

  2. 取得深度影像來源(IDepthFrameSource

    在成功地開啟感應器後,接下來則是透過他提供的 get_DepthFrameSource() 這個函式,來取得深度影像的 Frame Source 物件 pFrameSource,其型別為 IDepthFrameSourceMSDN)。

    這邊要注意的,是 Frame Source 類型的物件是對應感應器提供的單一類型的資料來源,可以提供他的相關資訊,不過基本上不能用來直接讀取資料。

  3. 取得深度影像讀取器(IDepthFrameReader

    而要讀取資料的話,接下來則是要透過 IDepthFrameSourceOpenReader() 函式,取得 Frame Reader 的物件 pFrameReader,其型別為 IDepthFrameReaderMSDN)。

  4. 通常接下來,就可以進入主迴圈、讀取深度影像了。這邊的主迴圈是用簡單的 while 來做。

    • 取得深度影像

      在主迴圈裡面,如果要讀取資料的話,基本上就是去呼叫 IDepthFrameReaderAcquireLatestFrame() 這個函式、來向感應器要新的畫面資料(4a)

      這邊要注意的是,AcquireLatestFrame() 這個函式不一定會成功,所以一定要做回傳狀態的檢查。

    • 處理深度影像

      在這邊讀取到的資料是 pFrame、其型別是 IDepthFrameMSDN),代表的是當下感應器最新的畫面;而透過他的 get_FrameDescription() 函式,可以取得 IFrameDescription 型別(MSDN)的物件。透過 IFrameDescription 提供的函式,可以去讀取裡面包含的畫面的基本資訊(長、寬、像素的大小等等)。 (4b)

      而透過 AccessUnderlyingBuffer() 這個函式的話,則可以取得畫面資料的指標(pBuffer)、以及資料陣列的大小(iBufferSize(4c)
      pBuffer 是一個 UINT16 的指標,他所指到的陣列、就是整張圖的資料了。而接下來這邊做的事,就是讀取 ( x, y ) 這一點的深度值、並透過 cout 輸出。(4d)

而當每一個物件不用的時候,都需要去呼叫個別物件的 Release() 函式、來完成資源的釋放,以避免 memory leak。


一些補充資料

上面基本上是比較簡單的解釋,而實際上還有一些要注意的地方。

關於 AcquireLatestFrame()

第一個,是前面有稍微提到的,當透過 IDepthFrameReaderAcquireLatestFrame() 這個函式來向感應器要求最新的畫面的時候,他不一定會成功(回傳 S_OK)。

實際上,有很大的機會,他回傳的會是 E_PENDING,代表還沒有資料可以讀取。所以,當使用這個函式去要求畫面的時候,一定要檢查他的回傳值!否則接下來會是有問題的~

此外,在上面的範例程式裡的寫法,會造成 CPU 使用率飆高的 busy waiting(維基百科)。如果要避免這個問題的話,可能會需要加入適當的等待時間、或是改採用 timer 或 event 的模式來讀取。不過這邊就先不提了。

讀取深度影像

第二點,在這邊是使用 IDepthFrameAccessUnderlyingBuffer() 這個函式,來取得深度影像資料的指標;這個方法所取得的只是指標,所以速度較快,但是由於真正的資料和 SDK 核心內的資料應該是共用的,所以不能修改、之後他的資料可能也會被改掉。
(附註:K4W SDK v2 內部應該是使用 double buffer 的形式,來操作深度影像的畫面的)

而除了這個方法之外,IDepthFrame 也還有提供 CopyFrameDataToArray() 這個函式(MSDN),可以用來把深度資料複製一份。雖然這個方法會需要額外的記憶體空間,也需要額外的複製時間,但是如果想要針對深度影像作修改、或是保存的話,複製一份會是比較合適的作法。

IFrameDescription

此外,在上面的範例程式裡,針對 metadata、也就是 IFrameDescription 的讀取,是每次取得新的畫面、都去要一次;實際上,這不算是一個有效率的方法。

如果在確定影像的基本資料不會改變的情況下,理論上是在主迴圈外、去讀取一次就可以了。而實際上,IDepthFrameSource 也同樣有提供 get_FrameDescription() 這個函式、可以用來取得 IFrameDescription

所以這邊比較有效率的方法,應該會是把 4b 的部分,抽到主迴圈之前,這樣就可以只讀取一次 frame description 了~


這篇就先寫到這了。下一篇,應該會是講怎麼整合 OpenCV 來做顯示了。


Kinect for Windows v2 C++ 程式開發目錄

廣告

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

22 Responses to K4W v2 C++ Part 1:簡單的深度讀取方法

  1. Gavin Wang says:

    Heresy,你好。请教一下,读取有两种方式分别是polling和event。event的话具体是怎么样的思想?比较起来有什么优势呢?谢谢啦。

    喜歡

  2. KG says:

    版大您好:
    不好意思想請教您,在輸出深度值這邊的計算看不太懂,想和您請教,謝謝
    int x = iWidth / 2,
    y = iHeight / 2;
    size_t idx = x + iWidth * y;

    喜歡

    • Heresy says:

      不知道是哪裡有問題?
      這基本上只是在計算 (x,y) 這一點在一維陣列中的位置而已。
      建議你自己用比較小的狀況模擬一下。

      喜歡

  3. RZ says:

    Heresy您好

    在參考您 SDK 的程式,因需求要錄製深度影像影片,以OpenCV稍作修改。

    雖然是可以錄影,但不曉得原因,無論是彩色或深度影像以 Kinect v2 錄製,

    僅錄製(深度或彩色)一種資訊的情況下,

    得到的frame數卻都只有約 15~20fps ,與規格上說的 30fps 不符。

    15~20fps 是配合 批次 和 cv::imwrite 存下來的影像數,

    目前程式開發環境是 x64 Visual C++ & OpenCV 2411,

    想請問無法達到 Kinect v2 規格上有的效能,是否因配合 OpenCV 開發所造成?

    喜歡

    • Heresy says:

      個人建議,先完全不要做額外的處理、只是單純讀取資料試試看。
      這樣就可以知道到底是不是 OpenCV 處理造成問題的了。
      或者,自己針對處理的程式加上計時功能,看看有沒有花過多的時間。

      喜歡

  4. darotchou says:

    版大您好,我目前使用windows 10 prview+VS 2015RC,在測試您撰寫的程式的時候Sensor, Framesource, Reader的建立都回覆S_OK沒問題,但是pFrameReader->AcquireLatestFrame(&pFrame),卻一直回覆E_PENDING,我試過彩色影像等等也都一樣,感覺sensor都有正常被initial但是卻娶不到影像,請問還有什麼可能的原因呢?感謝

    喜歡

    • Heresy says:

      請參考文中「關於 AcquireLatestFrame() 」的部分。

      喜歡

      • darotchou says:

        感謝版大,我有看到那一段不過我while loop等很久他還是一直E_PENDING,但是我用他的sample code d2d depth c++那個重新編譯發現回個幾次E_Pending以後就會回S_OK,真的搞不清楚有什麼差異,不過其怪的是 sample code理面居然在取像前先release source,不知道是為什麼,不過我加入自己的程式碼也沒辦法讓取像回S_OK

        喜歡

      • darotchou says:

        好像找到原因了 是因為我使用VS 2015RC且開發Windows universal app的關係, 我在win10下改用vs 2012(才能用CLR),就直接沒問題了,不過真不知道為什麼跟CLR有關(Windows universal app用的是CX)

        喜歡

        • Heresy says:

          這就不曉得了。Heresy 基本上都只用 Native C++ 開發。

          喜歡

  5. simon says:

    想請問一下
    size_t idx = x + iWidth * y; 我不太懂這行的意思
    uBufferSize=512*424所以總共會有217088個位子給pBuffer塞進去
    所以pBuffer資料範圍0-217087
    假如我取最右下角的深度pBuffer[217087]是這樣嗎,但套size_t idx = x + iWidth * y
    把座標512,424套進去 資料範圍不就超過了

    Liked by 1 person

    • Heresy says:

      起始值是 0,不是 1,所以最大值是 511/423。

      喜歡

      • simon says:

        謝謝您的答覆,看您的文章真的受益良多

        喜歡

  6. 引用通告: Kinect Fusion Part 1:C++ API 基本使用 | Heresy's Space

  7. 引用通告: K4W v2 C++ Part 6:使用 OpenGL 繪製場景 | Heresy's Space

  8. pluto1027 says:

    Hi Heresy

    1.你好,最近剛接觸OpenNI2,想寫一些簡單讀圖的方法,
    請問有辦法在不接Kinect 的請況下,讀出已存好的oni檔嗎?

    2.另外一個問題是我想要抓NITE做為middleware,可是找不到載點,
    到http://www.openni.ru/ 有看到提供許多middleware,可是一直
    無法註冊成功,請問現在有什麼方法可以抓到NITE呢?

    謝謝

    喜歡

    • Heresy says:

      首先,如果你是要詢問 OpenNI 的相關問題,建議請到 OpenNI 的相關主題做回應。
      這篇是 Kinect for Windows SDK,和 OpenNI 是不一樣的。

      再來,針對你的問題

      1. 請參考:
      https://kheresy.wordpress.com/2013/03/04/record-and-playback-of-openni-2/

      2. 那個網站基本上不是官方的,也不知道是誰架的。所以基本上…要不要去信任他,就見仁見智了。
      個人是認為他只是 OpenNI 官網的靜態網頁部分備份下來,然後再放出來。實際上應該是沒有註冊的功能的。

      喜歡

      • pluto1027 says:

        Hi Heresy.

        謝謝你的回答,我會參考你提供的網誌,對我來說幫助很大.

        那請問你知道目前有什麼方法抓到NITE嗎?

        謝謝

        Liked by 1 person

        • Heresy says:

          PrimeSense 的 NiTE 授權並不允許自行散佈 NiTE 的檔案,所以基本上,在 PrimeSense 官網已經關閉的現在,不會有正式的管道可以下載。
          個人只能建議你試著在網路上找其他人的備份
          http://wp.me/p15GE3-46u

          喜歡

  9. 引用通告: K4W v2 C++ Part 3:讀取彩色影像與紅外線影像 | Heresy's Space

  10. 引用通告: K4W v2 C++ Part 2:使用 OpenCV 顯示深度影像 | Heresy's Space

發表迴響

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

WordPress.com Logo

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

Twitter picture

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

Facebook照片

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

Google+ photo

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

連結到 %s

%d 位部落客按了讚: