KinectAzureDK編程實戰_OpenGL 顯示 Kinect 數據

  • 2019 年 10 月 8 日
  • 筆記

前幾篇文章,我講了如何用 OpenCV 和 PCL 操作和可視化 Kinect Azure DK 的 RGB + Depth + IR 圖像的 C++ 代碼。

目前,這些代碼已經部分開源於 Github 上。

K4aGrabber https://github.com/forestsen/K4aGrabber

這是關於如何用 PCL 打開 Kinect Azure DK,並可視化彩色點雲。對應的文章是我半個月前寫的《KinectAzureDK編程實戰_PCL》。

另外,還有關於如何用 OpenCV 操作和可視化 Kinect raw data 的代碼,以及用 OpenGL 原生的顯示 raw data 的代碼。過段時間這些都會開源。

這篇文章,我就講如何用 OpenGL 原生的顯示 raw data。

只有開發基於 OpenGL 為顯示系統的 AR 應用時,才會用到 OpenGL 顯示攝像頭視頻。

五年前,我在知乎上曾經回答過一個問題(目前是最高贊)。在這個問題中,我講了如何用 OpenGL 和 OpenCV 實現一個 AR 應用。

想用OpenCV做AR該如何入手? https://www.zhihu.com/question/26983174/answer/35328819

實際上,類似於如何用 PCL 的 Visualizer 來可視化 Kinect 的實時點雲的做法。我們也需要用多線程的方式顯示 Kinect 實時圖像。

主程序用於 OpenGL 可視化,聲明一個線程用於接收實時圖像。但是這兩個線程需要 互斥 的操作共享數據。

我們先看完整代碼。依賴於 freeglut 的 OpenGL。

PS:注意這部分代碼和 Kinect Azure DK 開源的代碼不一樣,Kinect 使用 imgui 作為可視化環境,依賴於 glfw。我這裡用的是 GL 1 版本。後面我也將基於 GL 3 的 Shader 來寫。

我們一點一點的講。

首先看,我們聲明的這些變量。

std::thread capturing_thread;  std::mutex mtx;    GLuint texture_id;  int texture_width;  int texture_height;    int glutwin;  unsigned char key;    GLuint texture_id;  int texture_width;  int texture_height;    int bufferIndex = 0;  k4a::image initTexture;  k4a::image colorImageBuffer;  std::array<k4a::image, 30> colorBuffer;  k4a::image outColorFrame;

關於多線程操作的線程和互斥量。這裡引用的是 C++ 標準庫 STL 的 <thread>, <mutex> 頭文件。

std::thread capturing_thread;  std::mutex mtx;

關於 OpenGL 可視化顯示需要的紋理相關變量。其中 glutwin 變量表示窗口的序號。用戶在鍵盤按下特定鍵 key時,就關閉 glutwin 表示的窗口。

GLuint texture_id;  int texture_width;  int texture_height;    int glutwin;  unsigned char key;

關於從 Kinect 接收的 raw data。

int bufferIndex = 0;  k4a::image initTexture;  k4a::image colorImageBuffer;  std::array<k4a::image, 30> colorBuffer;  k4a::image outColorFrame;

使用一個 buffer 來作為多線程操作的共享數據 buffer。

同一時刻只能有一個線程來操作共享數據 buffer,我把 buffer 設置為一個有 30 個 k4a::image 的靜態數組。這樣盡量保證線程有數據可以提取。

initTexture 表示用於初始化 OpenGL 窗口環境。

colorImageBuffer 用於表示實時獲取的 Kinect 的 raw data。

colorBuffer 用於作為多線程操作的共享數據 buffer。

outColorFrame 用於 OpenGL 每次渲染窗口環境時貼的紋理。

我們再來看 main 函數

const uint32_t deviceCount = k4a::device::get_installed_count();  if (deviceCount == 0)  {      cout << "no azure kinect devices detected!" << endl;  }  config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;  config.camera_fps = K4A_FRAMES_PER_SECOND_30;  config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED;  config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;  config.color_resolution = K4A_COLOR_RESOLUTION_720P;  config.synchronized_images_only = true;    cout << "Started opening K4A device..." << endl;  device = k4a::device::open(K4A_DEVICE_DEFAULT);  device.start_cameras(&config);  cout << "Finished opening K4A device." << endl;    while (1)  {      if (device.get_capture(&capture, std::chrono::milliseconds(0)))      {          initTexture = capture.get_color_image();          break;      }  }    texture_width = initTexture.get_width_pixels();  texture_height = initTexture.get_height_pixels();

上面代碼是 main 函數代碼的上半部分,這部分代碼我們在前幾篇文章中多次講過,這裡就不贅述了。

需要說一下的是上面有一個 while 循環,這是為了獲取當前 Kinect 的第一有效幀 initTexture,用這一幀來初始化 OpenGL 將要顯示於整個窗口環境的紋理。

glutInit(&argc, argv);  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);    glutInitWindowSize(texture_width, texture_height);  glutInitWindowPosition(100, 100);    int glutwin = glutCreateWindow("test");    glutReshapeFunc(reshape);  glutDisplayFunc(display);  glutKeyboardFunc(keyboard);  glutIdleFunc(idle);    capturing_thread = std::thread(frameRetriever);  capturing_thread.detach();    glutMainLoop();  capturing_thread.join();  device.close();

上面代碼是 main 函數代碼的下半部分,關於建立 OpenGL 可視化環境。

基於 glut 的 OpenGL 環境的建立,需要幾個函數。

reshape 函數:用於窗口環境變化時重置窗口。

display 函數:用於如何窗口顯示環境的渲染。

keyboard 函數:用於接收用戶按鍵事件。

idle 函數:用於窗口顯示環境的刷新。

重要的是,我在這裡把接收 Kinect 的 raw data 的 thread 初始化。這個線程所運行的函數就是 frameRetriever

最後在用戶退出窗口後,即 glutMainLoop() 之後,退出 thread,並關閉 Kinect。

下面講這幾個函數如何實現。

createTexture 函數。

上面代碼沒啥好說的。每一個 OpenGL 1 的顯示紋理函數基本都是這樣的流程,glGenTextures 函數生成一個 texture_id,然後 glBindTexture 綁定該 texture_id 到當前的顯示環境中,glTexImage2D 函數顯示紋理數據的 raw data。

frameRetriever 函數。

這個函數主要就是一個 while 循環接收 Kinect 的 raw data,傳給 k4a::image 格式的 colorImageBuffer 。

使用互斥量 mtx 向共享 colorBuffer傳輸數據。

並不斷檢測用戶按鍵,如果按下 」q「 鍵,就退出 OpenGL 顯示環境。

drawBackground 函數。

這是 OpenGL 可視化 Kinect 圖像到窗口環境的代碼。這個函數最重要,我們一部分一部分的講。

首先,根據 bufferIndex 從共享數據 colorBuffer 提取當前幀。需要用互斥量 mtx 來控制,在同一時刻只能由一個線程 存取 共享數據。取出數據後載入 OpenGL 環境中。

if (bufferIndex > 0)  {      mtx.lock();        outColorFrame = colorBuffer[(bufferIndex - 1) % 30];        mtx.unlock();        if (texture_id != 0)      {          glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,                       texture_width, texture_height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE,                       outColorFrame.get_buffer());      }  }

然後,確定 OpenGL 窗口的顯示環境。這裡我們需要確定投影矩陣、模型視圖矩陣。

int w = texture_width;  int h = texture_height;    const GLfloat bgTextureVertices[] = {      0, 0,      (float)w, 0,      0, (float)h,      (float)w, (float)h  };    const GLfloat bgTextureCoords[] = {      1, 0,      1, 1,      0, 0,      0, 1  };    const GLfloat proj[] = {      0, -2.f / w, 0, 0,      -2.f / h, 0, 0, 0,      0, 0, 1, 0,      1, 1, 0, 1  };

bgTextureVertices 數組記錄的是紋理實際坐標,分別表示四個角的實際坐標。

bgTextureCoords 數組記錄的是紋理的 uv 坐標,分別表示四個角的 uv 坐標。

proj 矩陣就是投影矩陣。這個投影矩陣如何計算請參看 《Mastering OpenCV with Practical Computer Vision Projects》 P82。

也可參考我在知乎回答的。

想用OpenCV做AR該如何入手? https://www.zhihu.com/question/26983174/answer/35328819

然後就是把這些數組、矩陣都加載到 OpenGL 環境中。

glMatrixMode(GL_PROJECTION);  glLoadMatrixf(proj);    glMatrixMode(GL_MODELVIEW);  glLoadIdentity();    // Update attribute values.  glEnableClientState(GL_VERTEX_ARRAY);  glEnableClientState(GL_TEXTURE_COORD_ARRAY);    glVertexPointer(2, GL_FLOAT, 0, bgTextureVertices);  glTexCoordPointer(2, GL_FLOAT, 0, bgTextureCoords);    glColor4f(1, 1, 1, 1);  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);    glDisableClientState(GL_VERTEX_ARRAY);  glDisableClientState(GL_TEXTURE_COORD_ARRAY);    glDisable(GL_TEXTURE_2D);

還剩下幾個函數,沒有必要詳細講解了。在上面已經提到過。

void display()  {      drawBackground();      glutSwapBuffers();  }    void reshape(int w, int h)  {      glViewport(0, 0, w, h);      glMatrixMode(GL_PROJECTION);      glLoadIdentity();      glMatrixMode(GL_MODELVIEW);      glLoadIdentity();      createTexture();  }  void keyboard(unsigned char button, int x, int y)  {      key = button;      glutPostRedisplay();  }  void idle()  {      glutPostRedisplay();  }

上面的所有代碼都是關於如何實時顯示 Kinect 的 RGB 圖像。那麼關於 Depth 和 IR 圖像,稍微有點不一樣。在 drawBackground 函數中需要先提取當前幀的 raw buffer,然後經過着色函數 ColorizeDepthImage 來把 Depth 圖像完美的顯示出來。

if (bufferIndex > 0)  {      mtx.lock();        outDepthFrame = depthBuffer[(bufferIndex - 1) % 30];        mtx.unlock();        if (texture_id != 0)      {          std::vector<sen::Pixel> depthTextureBuffer;          ColorizeDepthImage(outDepthFrame,              DepthPixelColorizer::ColorizeBlueToRed,              GetDepthModeRange(config.depth_mode),              &depthTextureBuffer);            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,                       texture_width, texture_height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE,                       outDepthFrame.get_buffer());      }  }

後面我將寫一篇文章,講我最近實現的如何通過 ArUco 這個二維碼標記物,基於 Kinect Azure DK 實現一個 AR 程序。也將去完善五年前在知乎上寫的答案。

本文的所有代碼都將在 Github 和 Coding 上開源。敬請關注我的 Github 和 Coding 主頁。

Github 主頁 https://github.com/forestsen

Coding 主頁 https://coding.net/u/forestsen