GPU

Tổng quan

MediaPipe hỗ trợ các nút máy tính để tính toán và kết xuất GPU, đồng thời cho phép kết hợp nhiều nút GPU, cũng như kết hợp chúng với các nút tính toán dựa trên CPU. Có một số API GPU trên nền tảng di động (ví dụ: OpenGL ES, Metal và Vulkan). MediaPipe không cố gắng cung cấp một mô hình trừu tượng GPU trên nhiều API. Các nút riêng lẻ có thể được viết bằng nhiều API, cho phép chúng tận dụng các tính năng dành riêng cho nền tảng khi cần.

Khả năng hỗ trợ GPU là cần thiết để có hiệu suất tốt trên nền tảng di động, đặc biệt là video theo thời gian thực. MediaPipe cho phép các nhà phát triển viết các máy tính tương thích với GPU có hỗ trợ việc sử dụng GPU cho:

  • Xử lý theo thời gian thực trên thiết bị, chứ không chỉ xử lý hàng loạt
  • Kết xuất và hiệu ứng video, chứ không chỉ là bản phân tích

Dưới đây là các nguyên tắc thiết kế để hỗ trợ GPU trong MediaPipe

  • Máy tính dựa trên GPU có thể xuất hiện ở bất kỳ vị trí nào trong biểu đồ và không nhất thiết phải được sử dụng để kết xuất trên màn hình.
  • Quá trình chuyển dữ liệu khung hình từ một máy tính dựa trên GPU sang một máy tính khác cần diễn ra nhanh chóng và không phát sinh các thao tác sao chép tốn kém.
  • Quá trình truyền dữ liệu khung giữa CPU và GPU phải hiệu quả trong phạm vi mà nền tảng này cho phép.
  • Vì các nền tảng khác nhau có thể yêu cầu các kỹ thuật khác nhau để có hiệu suất tốt nhất, nên API phải cho phép linh hoạt trong cách triển khai mọi thứ một cách ngầm ẩn.
  • Máy tính nên được phép sử dụng GPU một cách linh hoạt tối đa cho toàn bộ hoặc một phần hoạt động của máy tính, kết hợp GPU đó với CPU nếu cần.

Hỗ trợ OpenGL ES

MediaPipe hỗ trợ OpenGL ES lên đến phiên bản 3.2 trên Android/Linux và tối đa ES 3.0 trên iOS. Ngoài ra, MediaPipe cũng hỗ trợ Metal trên iOS.

Yêu cầu OpenGL ES 3.1 trở lên (trên hệ thống Android/Linux) để chạy đồ thị và máy tính suy luận trong học máy.

MediaPipe cho phép các biểu đồ chạy OpenGL trong nhiều ngữ cảnh GL. Ví dụ: có thể rất hữu ích trong các biểu đồ kết hợp đường dẫn suy luận GPU chậm hơn (ví dụ: FPS) có đường dẫn kết xuất GPU nhanh hơn (ví dụ: ở 30 FPS): do có một ngữ cảnh GL tương ứng với một hàng đợi lệnh tuần tự, sử dụng cùng một ngữ cảnh cho cả hai tác vụ sẽ làm giảm tốc độ khung hình kết xuất.

Một thách thức mà MediaPipe sử dụng để giải quyết nhiều ngữ cảnh là khả năng giao tiếp giữa chúng. Một tình huống ví dụ là khi có video đầu vào được gửi đến cả đường dẫn kết xuất và đường dẫn suy luận, đồng thời kết xuất cần phải có quyền truy cập vào kết quả mới nhất từ suy luận.

Nhiều luồng không thể truy cập vào ngữ cảnh OpenGL cùng một lúc. Ngoài ra, việc chuyển đổi ngữ cảnh GL đang hoạt động trên cùng một luồng có thể bị chậm trên một số thiết bị Android. Do đó, phương pháp của chúng ta là có một luồng chuyên biệt cho mỗi ngữ cảnh. Mỗi luồng đưa ra lệnh GL, tạo ra một hàng đợi lệnh nối tiếp trên ngữ cảnh của nó, sau đó được GPU thực thi một cách không đồng bộ.

Vòng đời của máy tính GPU

Phần này trình bày cấu trúc cơ bản của phương thức Xử lý (Process method) của GPU máy tính bắt nguồn từ lớp cơ sở GlSimpleCalculator. Công cụ tính GPU Đây là một ví dụ về LuminanceCalculator. Phương thức LuminanceCalculator::GlRender được gọi từ GlSimpleCalculator::Process.

// 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();
}

Các nguyên tắc thiết kế được đề cập ở trên đã dẫn đến thiết kế sau lựa chọn hỗ trợ GPU MediaPipe:

  • Chúng ta có một loại dữ liệu GPU tên là GpuBuffer để biểu thị dữ liệu hình ảnh được tối ưu hoá cho việc sử dụng GPU. Nội dung chính xác của loại dữ liệu này là mờ và dành riêng cho từng nền tảng.
  • API cấp thấp dựa trên cấu trúc, trong đó bất kỳ máy tính nào muốn sử dụng GPU sẽ tạo và sở hữu một thực thể của lớp GlCalculatorHelper. Lớp này cung cấp một API không phụ thuộc vào nền tảng để quản lý ngữ cảnh OpenGL, thiết lập kết cấu (texture) cho dữ liệu đầu vào và đầu ra, v.v.
  • Một API cấp cao dựa trên việc phân lớp con, trong đó các hàm tính đơn giản triển khai lớp con bộ lọc hình ảnh từ GlSimpleCalculator và chỉ cần ghi đè một vài phương thức ảo bằng mã OpenGL cụ thể, còn lớp cấp cao sẽ xử lý toàn bộ quy trình sửa ống nước.
  • Dữ liệu cần được chia sẻ giữa tất cả máy tính dựa trên GPU được cung cấp dưới dạng dữ liệu đầu vào bên ngoài được triển khai dưới dạng dịch vụ biểu đồ và do lớp GlCalculatorHelper quản lý.
  • Sự kết hợp giữa trình trợ giúp dành riêng cho máy tính và dịch vụ đồ thị dùng chung cho phép chúng tôi linh hoạt trong việc quản lý tài nguyên GPU: chúng tôi có thể có ngữ cảnh riêng cho mỗi máy tính, chia sẻ ngữ cảnh duy nhất, chia sẻ khóa hoặc dữ liệu đồng bộ hóa khác, v.v. - và tất cả những điều này được trình trợ giúp quản lý và ẩn khỏi các máy tính riêng lẻ.

Bộ chuyển đổi GpuBuffer sang ImageFrame

Chúng tôi cung cấp hai hàm tính tên là GpuBufferToImageFrameCalculatorImageFrameToGpuBufferCalculator. Những hàm tính này chuyển đổi từ ImageFrame đến GpuBuffer, cho phép xây dựng biểu đồ kết hợp giữa công cụ tính GPU và CPU. Các định dạng này được hỗ trợ trên cả iOS và Android

Khi có thể, những hàm tính này sử dụng chức năng dành riêng cho từng nền tảng để chia sẻ dữ liệu giữa CPU và GPU mà không cần sao chép.

Sơ đồ dưới đây cho thấy dòng dữ liệu trong một ứng dụng di động chụp video từ camera, chạy qua đồ thị MediaPipe rồi hiển thị đầu ra trên màn hình theo thời gian thực. Đường nét đứt cho biết phần nào nằm bên trong biểu đồ MediaPipe thích hợp. Ứng dụng này chạy bộ lọc phát hiện cạnh Canny trên CPU bằng OpenCV và phủ bộ lọc này lên trên video gốc bằng GPU.

Cách tương tác của các công cụ tính GPU

Khung video từ máy ảnh được đưa vào biểu đồ dưới dạng gói GpuBuffer. Chiến lược phát hành đĩa đơn luồng đầu vào được truy cập song song bằng hai máy tính. GpuBufferToImageFrameCalculator chuyển đổi vùng đệm thành ImageFrame, sau đó được gửi qua bộ chuyển đổi thang màu xám và bộ lọc canny (cả hai đều dựa trên trên OpenCV và chạy trên CPU), đầu ra mà sau đó được chuyển đổi thành GpuBuffer nữa. Một máy tính GPU nhiều đầu vào, GlOverlayCalculator, sẽ tính như nhập cả GpuBuffer gốc và mã xuất hiện từ trình phát hiện cạnh, rồi phủ chúng bằng chương trình đổ bóng. Sau đó, dữ liệu đầu ra được gửi trở lại ứng dụng bằng cách sử dụng máy tính gọi lại và ứng dụng sẽ kết xuất hình ảnh vào màn hình qua OpenGL.