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++ 程式開發目錄

廣告

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

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

  1. 引用通告: Heresy写的Kinect for Windows V2 C++开发资料-小明

  2. pengmin says:

    Heresy老师您好:
    想请教一个关于坐标系的问题,比如我通过面部帧接口拿到人脸所有点的三维坐标,那么这个三维坐标是相机空间下的,也就是坐标系原点在Kinect摄像头,如果我想把坐标原点转换到鼻尖,除了z坐标可以直接相减,那么x方向和y方向该怎么处理呢?

    按讚數

    • Heresy says:

      如果已經都轉換到攝影機空間座標系統,那就直接照算,x/y/z 沒什麼不同。
      另,這種東西很好測試,建議自己先玩看看。

      按讚數

  3. 吳百銓 says:

    Heresy 大大您好:
    想請問您關於V2的 intrinsic and extrinsic parameter 的參數存放於那裡?
    我一直想找這些參數做PCL比較,我自己也有K出這兩組參數
    謝謝您

    按讚數

    • Heresy says:

      沒記錯的話,微軟沒有提供

      按讚數

  4. 马桂隆 says:

    请问楼主如何输出某一点的三维空间坐标?

    按讚數

    • Heresy says:

      cout, printf

      如果你希望有有用的回覆,麻煩問題請描述詳細。

      按讚數

      • 希望 says:

        版大您好,他的意思是說,用彩色視頻處理來追蹤一個物體,已經得到物體的彩色2維坐標,怎樣將這個二維的坐標轉換為相機的三維坐標,麻煩您了。

        按讚數

        • Heresy says:

          在 K4W v2 裡面,座標系統的轉換都是靠 ICoordinateMapper 提供的函式。
          而由於計算方法的關係,他並沒有提供單將彩色影像中單點轉換到攝影機空間座標系統的方法;所以基本上就算只是要一個點,還是要透過 MapColorFrameToCameraSpace() 整個做轉換。
          基本上,還是和本文的範例做發相同。

          按讚數

          • 希望 says:

            唔该谢

            按讚數

    • 马桂隆 says:

      就是不用openGL,得到pCSPoints后,怎样获取里面的三维数据,pCSPoints是一个结构体吗?
      我想要得到x,y,z的值。测试了许多次都输不出来。还有MapColorFrameToCameraSpace(
      uDepthPointNum, pDepthBuffer, uColorPointNum, pCSPoints);不需要用到彩色图像吧,只需将uColorPointNum改为1920*1080?

      按讚數

      • Heresy says:

        基本上,你的問題在文章中都有說明了…個人建議認真再看過一遍。

        Q1. pCSPoints是一个结构体吗?
        A1. 「這邊的 pCSPoints 是一個 CameraSpacePoint 的陣列」

        Q2. 要得到x,y,z的值
        A2. 文中就有對應的範例了,只要不要去呼叫 OpenGL 的函式、改成你自己的用途就好了
        const CameraSpacePoint& rPt = pCSPoints[idx];
        glVertex3f(rPt.X, rPt.Y, rPt.Z);

        Q3. MapColorFrameToCameraSpace() 不需要用到彩色图像吧
        A3. 這個計算的確只需要深度影像、而不需要彩色影像。
        文章中已經有說明了,他會根據深度影像,計算出對應彩色裡每個像素的座標點

        按讚數

  5. 颖、 says:

    你好 我在github看到您的code 我试着运行了一下 得到两个错误。Error 1 error LNK2019: unresolved external symbol _GetDefaultKinectSensor@4 referenced in function _main C:\WorkF2015\code\OpenGL1\OpenGL1\Source.obj OpenGL1

    Error 2 error LNK1120: 1 unresolved externals C:\WorkF2015\code\OpenGL1\Debug\OpenGL1.exe 1 1 OpenGL1
    我已经把kinect sdk的iclude 和 lib都放入设置里面了

    按讚數

    • Heresy says:

      Link error 基本上是沒有正確設定 lib 檔造成的。
      建議請確認 link 的相關設定都有正確設定;除了要指定要使用哪個 lib 檔外,也要指定 lib 檔所在目錄。
      另外,是 32 位元還是 64 位元也請區分清楚。

      按讚數

      • says:

        谢谢你大神。我最近对kinect2很有兴趣。但是c++方面是菜鸟。希望您能赐教。

        按讚數

        • Heresy says:

          K4W SDK 也有支援 C# 等 .Net 環境,如果熟系 .Net 的話,建議可以朝該方向看看。

          按讚數

  6. Y.C Syu says:

    大神您好

    小弟不才想要請教一下,把色彩影像轉換成對應到攝影機空間座標系統,
    是指把原先色彩影像座標的(x,y)轉換成3D影像的(X,Y)座標資料嗎?
    然後深度影像它本身應該就是3D影像的Z軸資訊?
    所以最後只要把深度影像對應到在攝影空間的彩色影像
    即可得到一張3D影像(X,Y,Z)?

    PS.(x,y)是在一般二維的影像座標,(X,Y,Z)指的是三維影像座標

    按讚數

    • Heresy says:

      不確定你想問的到底是什麼?
      基本上,在透過 MapColorFrameToCameraSpace() 這類的函式在做轉換的時候,基本上都會去用到深度影像的資訊;而以彩色到攝影機空間來說,它的結果直接就是三度空間座標,而不是 (X, Y) 再加上 Z 的形式。
      而深度影像上的座標,也是需要透過類似的函式來做計算,才會是攝影機空間座標系統的。

      按讚數

      • Y.C Syu says:

        大神您好

        感謝您的回覆,您部落格文章真的讓我受益許多,可是我還是有些疑問,如下:

        Q1:
        所以說攝影機空間的座標系統的座標,每個點其實都是一個三度空間座標(X,Y,Z),單位是公尺?
        另外照大大的說法,也是說如果今天我們想要得到一個三度空間座標,以Kinect V2為例,Color + Depth 影像 = 3D影像??

        Q2:
        因為您在《簡單去背程式》有提到彩色與座標系統,單位是像素,沒有對應到真正的長度單位。所謂的單位像素,是什麼意思?

        Q3:
        如果把整張影像轉成3D畫面的話,勢必運算量會較一般2D的大,可否請問一下,是否能在深度影像裡面,單獨針對深度影像中某個位置(例如頭部位置),然後把這個位置轉換成三度空間座標後,在深度影像觀察這個的位置在三度空間座標Y軸的變化?

        以上問題,再麻煩大大解惑了。 感恩

        按讚數

        • Heresy says:

          1. 不是。單就深度影像本身就可以計算出三度空間座標了,彩色影像只是賦予該點色彩資訊。

          2. 就是圖檔上的一個點。

          3. 計算量不一定比較大,是看你要計算什麼。
          如果你是要取得人體骨架的位置,請參考: https://kheresy.wordpress.com/2015/03/03/k4w-v2-part-7-user-skeleton/

          按讚數

          • Y.C Syu says:

            大神您好

            小弟看完你文章後,整理出一些心得,並自問自答,可否麻煩大神看看,是否有錯誤,感謝

            1.深度定義:表示物件到攝影機的距離,在影像處理深度最主要的貢獻是能夠協助我們找到物件的真實位置。
            2.深度影像:以灰階影像方式呈現深度,因此我們所看到的畫面,它所代表的意思是,的確是物件到攝影機的距離,但是已被攝影機內部轉成0~255數值來表示,而非真實物理單位(mm,在深度影像裡,物件距離攝影機的距離是以mm表示)。
            3.在深度影像我們所得到的資訊,僅能由灰階值的明暗,簡單得知物件距離攝影機的距離,並沒辦法得知物件的真實位置(X,Y,Z)。若要取得物件的真實位置,必須要把深度影像座標系統轉換成相對應的攝影機座標系統才行。
            4.建立 Point Cloud(3D影像):以深度影像來講,就是把該坐標系統轉成相對應的攝影機坐標系統

            以上問題,麻煩您了

            按讚數

          • Heresy says:

            2 是錯誤的。
            基本上,從 Kinect SDK 讀取到的深度影像,本來就是深度資訊。
            轉換成 0-255 是後來自己去做的,而這樣做的意義,只是拿來做顯示。
            https://kheresy.wordpress.com/2015/01/13/k4w-v2-opencv-depth-viewer/

            4. Point Cloud 只是一個通稱,代表空間中大量的「點」資訊。

            按讚數

          • Y.C Syu says:

            大神您好

            感謝您的回覆,讓我逐一把問題釐清,最後針對第2點錯誤,還有一些小疑問,如下

            1.深度指的是真實坐標系統的Z軸資訊?

            2.深度影像,以2D影像方式呈現深度,透過影像處理,把物件所對應到的深度資訊(物件到攝影機距離),轉換成0~255數值來表示。以Kinect深度影像為例,畫面裡的顏色越亮(數值越高)表示距離較遠,顏色越深(數值越低)表示距離較近。若呈現畫面中黑色,則代表該點沒辦法偵測到深度。

            3.深度影像,每個像素雖然代表的是該像素到攝影機距離(以mm表示),但並非該像素到攝影機之間的真實距離,而是我們透過影像處理轉換後,用0~255數值表示相對應的深度,若要取得真實距離,則是要在轉換成攝影機坐標系統才行。

            按讚數

          • Heresy says:

            Kinect 感應器所取得的深度影像,裡面的每個像素值,就是直接是距離感應器所在的平面的距離、也就是 Z 軸的值,單位是 mm。
            建議請直接參考 MSDN 官方文件: https://msdn.microsoft.com/zh-tw/library/dn785530.aspx

            不要再去想深度值 0~255 的問題了!你完全搞錯東西了。
            原始的深度影像不是 8bit,而是 16bit,麻煩請完整看完之前的文章。

            8bit 的深度影像,都是根據需求,才「額外」套用不同的演算法計算出來的。
            之所以會另外轉換成 8bit,只是單純因為目前的電腦顯示色彩的寬度只有 8bit!
            所以這個轉換出來的 8bit 深度影像只是單純為了用來顯示的,實際上也沒有絕對的規則,你高興把 16bit 的深度值轉換成彩色影像(8bit * 3)也可以。

            按讚數

        • Y.C Syu says:

          大神您好

          確實 我把方向搞錯了 一直執著在0~255的問題
          感謝您的解惑^^

          另外,有個疑問想再請教您,為何計算距離、長度、角度資訊,要轉換成攝影機座標系統。這樣是有比在彩色或深度影像裡面計算,佔到優勢??

          謝謝

          按讚數

          • Heresy says:

            不是有什麼優勢,是只有在攝影機座標系統裡面才能計算。
            建議你考慮一下,當你要計算時,如果在其他座標系統,他用的單位會是什麼?
            如果三軸的座標定義都不一樣了,你要怎麼計算?

            按讚數

          • Heresy says:

            補充一下,如果忽略深度值、僅以 X/Y 來計算,也可以在影像上做計算。
            但是相對地,計算出來的距離就不會是一般用的長度單位,而是像素了。

            按讚數

      • Y.C Syu says:

        了解!!

        有一點我還是不太能理解,即使轉成3D。但是今天當物件靠近攝影機時,物件看起來還是會跟2D影像一樣變大,那請問2D-3D的轉換,是差在哪?(因為先前有看到一篇您寫的文章是說,當轉成真實座標,就不會有因距離攝影機遠近而使畫面有變形、或大小改變的問題。)

        因為我不太了解單位像素,跟單位被換成公尺後,他們的差異…

        按讚數

        • Heresy says:

          老實說,這邊算是更基本的電腦視覺的東西了…
          建議你可以先試著去想個情境,然後試著計算看看,看你要怎麼去使用這些資料。
          這樣會比較好理解。

          另外,參考: https://kheresy.wordpress.com/2012/04/05/coordinate-system-in-openni/

          按讚數

  7. says:

    Heresy您好,
    因為我的研究是kinect v2 人體骨架,想要用opengl做3d骨架,
    但kinect+opengl一直配置不起來,程式如果忽略掉您寫的void keyboard(unsigned char key, int x, int y),可以編譯成功,但執行時會出現遺失OPENGL.dll,從網路上下載OPENGL.dll並將它放入方案,又會出現其他dll檔遺失,用了許久,還是配置不起來,想請問您的opengl是如何配置的呢?

    按讚數

    • Heresy says:

      不確定你的環境是什麼,但是理論上,使用 Windows + Visual C++,會用到的檔案,應該是系統的 OpenGL32.dll,而不是 OpenGL.dll。

      另外,keyboard() 函式也不應該會導致無法編譯,裡面比較特別的,應該也只有「VK_ESCAPE」是 Windows 的專用巨集。

      如果你這樣會無法編譯的話,請確認你的開發環境,在這邊的範例都是採用 Visual C++ 做開發環境,如果你要用其他開發工具的話,可能就得自己修改了。

      另外,個人建議你先試著去找更基本的 OpenGL 範例,確定它可以運作。

      按讚數

  8. Porter Chen says:

    Hi Heresy,
    先感謝您的部落格對我幫助很大, 從Kinect1時期開始就有在追, 現在進入了Kinect2,
    一樣還是很有幫助

    但這邊有個地方看不太懂想請教, 您使用了 MapColorFrameToCameraSpace() 函式來轉座標系,
    丟進去的是 pDepthBuffer, 這意思是除了建立深度於彩色的資料對應關係外,
    也一同建立了深度與彩色於實際場景中的位置資訊?

    所以是等同於 MapDepthFrameToColorSpace() -> MapColorFrameToCameraSpace()
    這樣的意思嗎?

    因此出來的 CameraSpacePoint x.y.z 就是實景空間中的深度資料並帶有相對應ColorFrame上Pixels位置的rgba資訊? 可以這樣解讀嗎?

    所以跟Kinect1時期的 ConvertProjectiveToRealWorld(Size, _DepthData, DepthData_)
    是一樣意思?

    這邊搞得我好亂, 只好問問大神了

    按讚數

    • Heresy says:

      K4W SDK v2 的 MapColorFrameToCameraSpace() 這個函式是針對整張彩色影像的每個像素、直接算出該點在攝影機空間中的對應位置。
      但是這個計算基本上還是需要根據深度影像來做,所以才需要把深度影像也傳進去。

      至於他內部怎麼處理,就是只有微軟才知道的事了。
      不過從理論面來看,應該還是兩段轉換跑不掉…

      按讚數

      • 吴君临 says:

        heresy老师,那么有没有直接从DepthFrame直接到CameraSpace的程式?就是不处理彩色图像数据,直接是输入深度图上的一个点,输出一个攝影機空間座標

        按讚數

        • Heresy says:

          個人會建議你在問這類的問題之前,先查一下官方的文件,很多東西都是官方文件可以很簡單查到的。
          至少先確認了 ICoordinateMapper 有提供那些函式可以用來做座標轉換再說
          https://msdn.microsoft.com/en-us/library/microsoft.kinect.kinect.icoordinatemapper.aspx

          按讚數

  9. 引用通告: K4W v2 C++ Part 7a:繪製人體骨架 | Heresy's Space

  10. 引用通告: K4W v2 C++ Part 7:偵測、追蹤人體骨架 | Heresy's Space

發表迴響

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

WordPress.com Logo

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

Twitter picture

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

Facebook照片

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

Google+ photo

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

連結到 %s

%d 位部落客按了讚: