GPU

總覽

MediaPipe 支援用於 GPU 運算和轉譯的計算機節點,並可將多個 GPU 節點結合使用,也可混合使用以 CPU 為基礎的計算機節點。行動平台提供多種 GPU API (例如 OpenGL ES、 Metal 和 Vulkan)。MediaPipe 並未嘗試提供單一跨 API GPU 抽象層。您可使用不同 API 編寫個別節點,以便視需要利用平台專屬功能。

GPU 支援能在行動裝置平台上提供良好效能,尤其對於即時影片來說更是如此。MediaPipe 可讓開發人員編寫與 GPU 相容的計算機,以便支援 GPU 的用途:

  • 裝置端即時處理作業,並非僅限於批次處理
  • 影片算繪和效果,而不只是分析

以下是 MediaPipe 中 GPU 支援的設計原則

  • GPU 型計算機應適用於圖表中的任何位置,不一定用於螢幕轉譯。
  • 將影格資料從一個 GPU 型計算器轉移至另一個 GPU 運算的速度應該很快,且不會產生高昂的複製作業。
  • 在 CPU 和 GPU 之間轉移影格資料應盡可能達到平台允許的效率。
  • 由於不同平台可能需要採用不同的技術才能獲得最佳成效,因此這個 API 應能在背景實作各項作業的彈性。
  • 計算機應能提供最大的彈性,以使用 GPU 來處理全部或部分作業;如有需要,請搭配 CPU。

支援 OpenGL ES

MediaPipe 針對 Android/Linux 支援 OpenGL ES 3.2 以上版本;在 iOS 上則支援 ES 3.0 以上的版本。此外,MediaPipe 也支援 iOS 的金屬。

必須使用 OpenGL ES 3.1 以上版本 (在 Android/Linux 系統上),才能執行機器學習推論計算器和圖形。

MediaPipe 可讓圖形在多個 GL 情境中執行 OpenGL。舉例來說,將較慢的 GPU 推論路徑 (例如 10 FPS) 和更快速的 GPU 轉譯路徑 (例如每秒 30 個影格) 結合的圖表,就非常實用,因為兩個 GL 結構定義對應到一個序列指令佇列,而這兩項工作使用相同背景資訊會降低轉譯影格速率。

MediaPipe 使用多種結構定義解決的難題,就是必須相互通訊。例如:具有傳送至轉譯和推論路徑的輸入影片,且轉譯需要存取推論中最新的輸出內容。

多個執行緒無法同時存取 OpenGL 情境,此外,在部分 Android 裝置上,在同一個執行緒上切換使用中的 GL 情境,速度可能相當緩慢。因此,我們的做法是為每個情境設定一個專屬執行緒。每個執行緒都會發出 GL 指令,在結構定義上建構序列指令佇列,然後 GPU 會以非同步方式執行這些指令。

GPU 計算機的壽命

本節說明 GPU 計算機 (源自基本類別 GlSimpleCalculator) 的處理方法基本結構。GPU 計算機 LuminanceCalculator 如範例所示。系統會從 GlSimpleCalculator::Process 呼叫 LuminanceCalculator::GlRender 方法。

// Converts RGB images into luminance images, still stored in RGB format.
// See GlSimpleCalculator for inputs, outputs and input side packets.
class LuminanceCalculator : public GlSimpleCalculator {
 public:
  absl::Status GlSetup() override;
  absl::Status GlRender(const GlTexture& src,
                        const GlTexture& dst) override;
  absl::Status GlTeardown() override;

 private:
  GLuint program_ = 0;
  GLint frame_;
};
REGISTER_CALCULATOR(LuminanceCalculator);

absl::Status LuminanceCalculator::GlRender(const GlTexture& src,
                                           const GlTexture& dst) {
  static const GLfloat square_vertices[] = {
      -1.0f, -1.0f,  // bottom left
      1.0f,  -1.0f,  // bottom right
      -1.0f, 1.0f,   // top left
      1.0f,  1.0f,   // top right
  };
  static const GLfloat texture_vertices[] = {
      0.0f, 0.0f,  // bottom left
      1.0f, 0.0f,  // bottom right
      0.0f, 1.0f,  // top left
      1.0f, 1.0f,  // top right
  };

  // program
  glUseProgram(program_);
  glUniform1i(frame_, 1);

  // vertex storage
  GLuint vbo[2];
  glGenBuffers(2, vbo);
  GLuint vao;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  // vbo 0
  glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
  glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), square_vertices,
               GL_STATIC_DRAW);
  glEnableVertexAttribArray(ATTRIB_VERTEX);
  glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, nullptr);

  // vbo 1
  glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
  glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), texture_vertices,
               GL_STATIC_DRAW);
  glEnableVertexAttribArray(ATTRIB_TEXTURE_POSITION);
  glVertexAttribPointer(ATTRIB_TEXTURE_POSITION, 2, GL_FLOAT, 0, 0, nullptr);

  // draw
  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

  // cleanup
  glDisableVertexAttribArray(ATTRIB_VERTEX);
  glDisableVertexAttribArray(ATTRIB_TEXTURE_POSITION);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
  glDeleteVertexArrays(1, &vao);
  glDeleteBuffers(2, vbo);

  return absl::OkStatus();
}

上述設計原則促成了下列 MediaPipe GPU 支援功能的設計選擇:

  • 我們有一個名為 GpuBuffer 的 GPU 資料類型,用來表示圖片資料,並針對 GPU 用量進行最佳化。此資料類型的確切內容為不透明,且因平台而異。
  • 以組合為基礎的低階 API,任何想使用 GPU 的計算機都會建立及擁有 GlCalculatorHelper 類別的執行個體。這個類別提供各平台通用的 API,可用於管理 OpenGL 環境、設定輸入和輸出的紋理等。
  • 以子類別為基礎的高階 API,其中簡易計算機可從 GlSimpleCalculator 實作圖片篩選器子類別,而且只需用其特定的 OpenGL 程式碼覆寫一些虛擬方法,而父類別會處理所有水電問題。
  • 必須在所有 GPU 式計算機之間共用的資料會以外部輸入形式提供,以圖形服務的形式實作,由 GlCalculatorHelper 類別管理。
  • 結合計算機專用輔助程式和共用的圖形服務,讓我們可以更靈活地管理 GPU 資源:我們可以為每個計算機建立獨立的背景資料、共用單一背景資訊,並共用鎖定或其他同步處理基元等等;這些全都由輔助程式管理,而且隱藏在個別計算機中。

從 GpuBuffer 到 ImageFrame 轉換器

我們提供 GpuBufferToImageFrameCalculatorImageFrameToGpuBufferCalculator 這兩個計算機。這些計算機會在 ImageFrameGpuBuffer 之間轉換,協助建構結合 GPU 和 CPU 計算工具的圖表。iOS 和 Android 皆支援這些格式

這些計算機會盡可能使用平台專屬功能,在 CPU 和 GPU 之間共用資料,而不複製資料。

下圖顯示行動應用程式中的資料流程,可從相機擷取影片,並透過 MediaPipe 圖表執行,並且在螢幕上即時算繪輸出內容。虛線代表 MediaPipe 圖表內部的哪些部分正確。這個應用程式使用 OpenCV 在 CPU 上執行 Canny 邊緣偵測濾鏡,並使用 GPU 將篩選器重疊在原始影片上方。

GPU 計算機如何互動

相機的影片畫面會以 GpuBuffer 封包的形式輸入圖表。輸入串流則由兩個計算器並行存取。GpuBufferToImageFrameCalculator 會將緩衝區轉換為 ImageFrame,接著透過灰階轉換工具與罐頭篩選器 (兩者都以 OpenCV 和在 CPU 上執行) 傳送,接著再將緩衝區再次轉換為 GpuBuffer。多輸入 GPU 計算機 GlOverlayCalculator 會取用原始 GpuBuffer 和來自邊緣偵測工具的輸入內容,然後使用著色器重疊這些輸入值。接著使用回呼計算機將輸出內容傳回應用程式,應用程式則使用 OpenGL 將圖片轉譯至螢幕上。