K4W v2 C++ Part 6:使用 OpenGL 繪製場景


之前在《簡單的深度讀取方法》、《使用 OpenCV 顯示深度影像》、《讀取彩色影像與紅外線影像》和《讀取人體位置(Body Index)》這幾篇文章,已經大概解釋過了怎麼去讀取 Kinect for Windows v2 的影像資料了。

而在《簡單的去背程式》一文,則是有大概介紹過 ICoordinateMapper 這個介面,說明怎麼透過他提供的函式,來做不同座標系統的轉換了。不過當時 Heresy 只有說明「彩色空間座標系統」(Color Space)和「深度空間座標系統」(Depth Space)做說明而已,針對「攝影機空間座標系統」(Camera Space) ,基本上是草草帶過。

接下來這篇,就來比較認真一點地介紹攝影機空間座標系統、並把 Kinect 的資料、透過 OpenGL 用 3D 的形式畫出來吧~


攝影機空間座標系統(Camera Space)

在開始之前,這邊先針對「攝影機空間座標系統」(Camera Space),作一些簡單的介紹。

和另外兩個座標系統不同,攝影機空間座標系統是一個三度空間的座標系統,它可以算是用來把感應器看到的東西、對應回現實空間的座標系統;如果要做 3D 重建,或是要計算距離、長度、角度等資訊的話,基本上都需要轉換到這個座標系統。

這個座標系統是以深度感應器本身為原點、面向感應器的右側為 +X、上方為 +Y、出攝影機的方向為 +Z 的座標系統;畫出來的話,應該會是像右圖的樣子。
(原點的位置不是很精確)

在 K4W SDK v2 內部,是用 CameraSpacePoint 這個結構(MSDN)來記錄這個座標系統的點,使用的型別是 float、單位是公尺,所以一般來說,X、Y、Z 的值都不會太大,這點是可能要注意的。
(在深度空間座標系統的時候,深度值得單位是 mm,兩者單位不同)


建立 Point Cloud(點雲)

要透過 K4W SDK v2 建立 Point Cloud 形式的資料,其實相當簡單,基本上就是先分別透過 IDepthFrameReaderIColorFrameReader 讀取到深度和彩色的影像後,再透過 ICoordinateMapperMapColorFrameToCameraSpace() 這個函式(MSDN),讓他計算出對應彩色裡每個像素的座標點就可以了~下面就是一個簡單的程式片段:

// Read color data
IColorFrame* pCFrame = nullptr;
if (pColorFrameReader->AcquireLatestFrame(&pCFrame) == S_OK)
{
    pCFrame->CopyConvertedFrameDataToArray(
        uColorBufferSize, pColorBuffer, ColorImageFormat_Rgba);

    pCFrame->Release();
    pCFrame = nullptr;
}

// Read depth data
IDepthFrame* pDFrame = nullptr;
if (pDepthFrameReader->AcquireLatestFrame(&pDFrame) == S_OK)
{
    pDFrame->CopyFrameDataToArray(uDepthPointNum, pDepthBuffer);

    pDFrame->Release();
    pDFrame = nullptr;

    // map to camera space
    pCoordinateMapper->MapColorFrameToCameraSpace(
        uDepthPointNum, pDepthBuffer, uColorPointNum, pCSPoints);
    glutPostRedisplay();
}

在上面的程式裡,首先是透過 pColorFrameReader 這個 IColorFrameReader 的物件,來讀取最新的彩色畫面、並將資料轉換為 RGBA 後、複製到 pColorBuffer 這個 BYTE 的陣列裡。

之後,則是透過 pDepthFrameReader 這個 IDepthFrameReader 的物件,來讀取最新的深度畫面、並將資料複製到 pDepthBuffer 這個 UINT16 的陣列裡。

而如果有成功取得新的深度畫面的話,接下來,則是去呼叫 ICoordinateMapperMapColorFrameToCameraSpace() 這個函式,讓 pCoordinateMapper 針對新拿到的深度畫面資料做計算,產生出對應於彩色影像的攝影機空間座標,也就是上面的 pCSPoints。這邊的 pCSPoints 是一個 CameraSpacePoint 的陣列、大小和彩色影像的點的數量相同,都是 uColorPointNum

由於 Heresy 這邊的範例程式是使用 freeglut(官網)來輔助做 OpenGL 的繪圖的,所以在更新了之後,則是在去呼叫 glutPostRedisplay() 這個函式,讓 freeglut 去做畫面的更新。

在經過這樣的處理之後,要繪製資料的時候,基本上就可以寫成下面的形式:

glBegin(GL_POINTS);

for (int y = 0; y < iColorHeight; ++y)
{
    for (int x = 0; x < iColorWidth; ++x)
    {
        int idx = x + y * iColorWidth;
        const CameraSpacePoint& rPt = pCSPoints[idx];
        if (rPt.Z > 0)
        {
            glColor4ubv((const GLubyte*)(&pColorBuffer[4 * idx]));
            glVertex3f(rPt.X, rPt.Y, rPt.Z);
        }
    }
}
glEnd();

在上面的程式裡面,是使用舊版的 OpenGL 的形式,來畫出一個一個有顏色的點。

而點的資料的存取方法,基本上就是使用兩層迴圈,來掃過整張彩色影像裡的每一個像素( x, y ),並確認對應於這一點的攝影機空間座標、rPt;由於深度影像的範圍基本上比彩色影像來的小、再加上有的時候會有讀不到資料的狀況,所以這邊要去做個簡單的檢查,如果 rPt 的 Z 值大於 0,該點才是有意義的、才需要畫出來,否則就應該跳過。

如果透過這樣的形式,來把 Kinect 看的資料畫出來的話,基本上就會像下面的畫面:

其中畫面下方的三原色線條,是用來代表座標軸用的。至於畫面中可以看到有類似格線的黑線,基本上是沒有可用資料的點;這問題感覺上應該是 ICoordinateMapper 在做轉換時造成的?不過這點 Heresy 還不確定就是了。

完整的程式的部分,可以參考 Heresy 放在 GitHub 上的檔案(連結);不過由於這邊的重點不是 OpenGL 和 freeglut,所以就不詳加解釋了。

而這個程式有實作簡單的鍵盤操作(檔案),所以在程式執行起來後,可以透過鍵盤的 ASDWZX 這六個鍵來移動、並用方向鍵來旋轉視角,按 Esc 則可以結束程式。


建立三角形繪製

雖然用 Point Cloud 的形式,已經可以把整個三度空間的場景畫出來了;但是實際上,由於每個點的大小都是固定的,所以在攝影機走近一點、或是畫面放大的時候,就會發現點太小、直接看到背景的黑色的問題。這基本上是直接畫 Point Cloud 的先天限制。

而如果不希望這樣的話,則是可以考慮依序把相連的四個點((x,y)、(x+1,y)、(x,y+1)、(x+1,y+1))、建立出兩個三角形,改採用多邊形的形式來做繪製,這樣就不會出現畫面拉近就整個穿幫的問題了~這部分的程式可以參考這份範例程式,他執行起來的樣子會是像下圖:

這個程式的操作方法,則是和前面的範例相同。可以看到,雖然有的地方還是有破碎的狀況,但是整體來說會比用 Point cloud 畫的時候來的完整。

不過這邊比較奇怪的是,這個程式有的時候會有奇怪的橫向線條,感覺應該是邊邊角角的深度值有問題造成的…看來,可能還是得想辦法做資料上的過濾,才能讓顯示效果更好了。


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

廣告

對「K4W v2 C++ Part 6:使用 OpenGL 繪製場景」的想法

  1. 大大,我主要想知道如何。利用Kinect获取对应点的{x,y,z}坐标。还请大大明示

    • 建議請先看完這篇文章,這篇就是對應的教學。
      或者也可以參考 MSDN 中針對 ICoordinateMapper 的說明。

      • 老师,我将您的文章和代码认真阅读了。但是在是能力有限,想向您请教,如何输出一个定点的三维坐标的代码。您能指点一下吗

        • 如果你堅持要針對單點做轉換,請參考 MSDN 上 ICoordinateMapper 的函式說明。
          https://docs.microsoft.com/en-us/previous-versions/windows/kinect/dn772972(v%3dieb.10)
          MapDepthPointToCameraSpace() 就是用來把單一深度點轉換成世界座標的。
          而你如果是要從彩色座標系統轉換,那就沒有單點的方法,只能整個畫面轉換。

          實際上,你提供的資訊也很不充分。
          你既有的資訊是什麼?你的點的來源資訊有哪些?你是要從深度影像轉換還是從彩色影像轉換?
          這些都會讓整個流程有所不同,建議你自己先釐清清楚。

  2. Hersy大大你好。我想咨询一下如果我想通过Kinectv2来测量物体的尺寸信息,让Kinectv2输出对应三维坐标应该做哪些工作

發表迴響

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

WordPress.com 標誌

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

Google photo

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

Twitter picture

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

Facebook照片

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

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.