之前在《簡單的深度讀取方法》、《使用 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 形式的資料,其實相當簡單,基本上就是先分別透過 IDepthFrameReader 和 IColorFrameReader 讀取到深度和彩色的影像後,再透過 ICoordinateMapper 的 MapColorFrameToCameraSpace() 這個函式(MSDN),讓他計算出對應彩色裡每個像素的座標點就可以了~下面就是一個簡單的程式片段:
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 的陣列裡。
而如果有成功取得新的深度畫面的話,接下來,則是去呼叫 ICoordinateMapper 的 MapColorFrameToCameraSpace() 這個函式,讓 pCoordinateMapper 針對新拿到的深度畫面資料做計算,產生出對應於彩色影像的攝影機空間座標,也就是上面的 pCSPoints。這邊的 pCSPoints 是一個 CameraSpacePoint 的陣列、大小和彩色影像的點的數量相同,都是 uColorPointNum。
由於 Heresy 這邊的範例程式是使用 freeglut(官網)來輔助做 OpenGL 的繪圖的,所以在更新了之後,則是在去呼叫 glutPostRedisplay() 這個函式,讓 freeglut 去做畫面的更新。
在經過這樣的處理之後,要繪製資料的時候,基本上就可以寫成下面的形式:
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 畫的時候來的完整。
不過這邊比較奇怪的是,這個程式有的時候會有奇怪的橫向線條,感覺應該是邊邊角角的深度值有問題造成的…看來,可能還是得想辦法做資料上的過濾,才能讓顯示效果更好了。
你好,我今天看一下你推薦的這個博客,修改一下代碼,然後程序一直提示pCSPoints這個指針為NULL;
我先說明一下我的思路,看完博客后的理解,我做的工作其實只用pCoordinateMapper->MapColorFrameToCameraSpace這個函數就能解決。uDepthPointNum和pDepthBuffer這兩個參數是獲取深度圖像時得到的,uColorPointNum=1080*1920,pCSPoints是一個空指針。我就按上述的傳參方式寫的,然後程序運行的結果是pCSPoints為NULL,求解釋?
to ytyang@cugb.edu.cn
pCSPoints 要預先配置好才能用,不是直接傳 NULL 進去。
建議請參考 GitHub 上的完整程式碼。
(文內有連結)
樓主,你好,按照你的指導,我成功得到了點雲,然後我在查看得到的點雲時,發現視場中有少量離散的孤立點,請問這是怎麼回事?
to ytyang@cugb.edu.cn
不太確定你的狀況,不過應該是深度感應器的雜訊。
目前深度感應器基本上都一定會有雜訊,視環境狀況而定,有的時候還滿嚴重的。
楼主,你好。我按照你上面讲解的方法获得的场景的点云分成了一格一格。格子的边线没有点生成。请问是怎么回事?
to tangelbaby
這個問題文中已經有提過了,雖然不確定,不過個人認為是座標系統轉換演算造成的結果。
你好,我還有一點疑惑,既然是把深度影像映射到Camera Space,爲什麽你用的函數是MapColorFrameToCameraSpace,而不是MapDepthFrameToCameraSpace呢。這兩者有什麽不同么
ytyang@cugb.edu.cn
你也可以用 MapDepthFrameToCameraSpace,只不過這樣之後每個點要去查顏色會比較麻煩。
建議可以自己改寫看看,就知道差別在哪了。