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 畫的時候來的完整。

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

8 thoughts on “K4W v2 C++ Part 6:使用 OpenGL 繪製場景”

  1. 你好,我今天看一下你推薦的這個博客,修改一下代碼,然後程序一直提示pCSPoints這個指針為NULL;
    我先說明一下我的思路,看完博客后的理解,我做的工作其實只用pCoordinateMapper->MapColorFrameToCameraSpace這個函數就能解決。uDepthPointNum和pDepthBuffer這兩個參數是獲取深度圖像時得到的,uColorPointNum=1080*1920,pCSPoints是一個空指針。我就按上述的傳參方式寫的,然後程序運行的結果是pCSPoints為NULL,求解釋?

  2. to ytyang@cugb.edu.cn

    pCSPoints 要預先配置好才能用,不是直接傳 NULL 進去。
    建議請參考 GitHub 上的完整程式碼。
    (文內有連結)

  3. 樓主,你好,按照你的指導,我成功得到了點雲,然後我在查看得到的點雲時,發現視場中有少量離散的孤立點,請問這是怎麼回事?

  4. to ytyang@cugb.edu.cn

    不太確定你的狀況,不過應該是深度感應器的雜訊。
    目前深度感應器基本上都一定會有雜訊,視環境狀況而定,有的時候還滿嚴重的。

  5. 楼主,你好。我按照你上面讲解的方法获得的场景的点云分成了一格一格。格子的边线没有点生成。请问是怎么回事?

  6. to tangelbaby

    這個問題文中已經有提過了,雖然不確定,不過個人認為是座標系統轉換演算造成的結果。

  7. 你好,我還有一點疑惑,既然是把深度影像映射到Camera Space,爲什麽你用的函數是MapColorFrameToCameraSpace,而不是MapDepthFrameToCameraSpace呢。這兩者有什麽不同么

  8. ytyang@cugb.edu.cn

    你也可以用 MapDepthFrameToCameraSpace,只不過這樣之後每個點要去查顏色會比較麻煩。

    建議可以自己改寫看看,就知道差別在哪了。

發佈回覆給「tangelbaby」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。