GPU

Tổng quan

MediaPipe hỗ trợ các nút tính toán để tính toán và hiển thị 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 bản tóm tắt hiệu suất GPU trên nhiều API. Bạn có thể viết các nút riêng lẻ bằng các API khác nhau, cho phép các nút này tận dụng các tính năng dành riêng cho nền tảng khi cần.

Việc hỗ trợ GPU đóng vai trò thiết yếu để đạt hiệu suất tốt trên các nền tảng di động, đặc biệt là đối với video theo thời gian thực. MediaPipe cho phép nhà phát triển viết công cụ 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ỉ video phân tích

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

  • Công cụ 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 dùng để kết xuất trên màn hình.
  • Quá trình chuyển dữ liệu khung từ một máy tính dựa trên GPU này sang một máy tính dựa trên GPU khác sẽ diễn ra nhanh chóng và không phải chịu các thao tác sao chép tốn kém.
  • Việc chuyển dữ liệu khung giữa CPU và GPU phải hiệu quả như nền tảng 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 cần cho phép linh hoạt trong cách triển khai mọi thứ.
  • Máy tính phải đượ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 ES 3.0 trên iOS. Ngoài ra, MediaPipe cũng hỗ trợ Metal trên iOS.

Cần có OpenGL ES 3.1 trở lên (trên hệ thống Android/Linux) để chạy biểu đồ và máy tính suy luận 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ách này có thể rất hữu ích trong các biểu đồ kết hợp đường dẫn suy luận của GPU chậm hơn (ví dụ: ở 10 khung hình/giây) với đường dẫn kết xuất GPU nhanh hơn (ví dụ: ở 30 FPS): vì một ngữ cảnh GL tương ứng với một hàng đợi lệnh tuần tự, nên việc 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 giải quyết được bằng cách sử dụng nhiều ngữ cảnh là khả năng giao tiếp giữa các bối cảnh đó. Một trường hợp ví dụ là trường hợp có video đầu vào được gửi đến cả đường dẫn kết xuất và đường dẫn suy luận, và quá trình kết xuất cần có quyền truy cập vào kết quả mới nhất từ dự đoán.

Nhiều luồng không thể truy cập bối cảnh OpenGL cùng một lúc. Hơn nữa, 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 tôi là dành riêng một luồng cho mỗi ngữ cảnh. Mỗi luồng đưa ra các lệnh GL, tạo một hàng đợi lệnh nối tiếp trên ngữ cảnh của luồng, sau đó được GPU thực thi 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 Process của máy tính GPU lấy từ lớp cơ sở GlSimpleCalculator. Ví dụ: công cụ tính GPU 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ế nêu trên đã dẫn đến các lựa chọn thiết kế sau đây để 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 và được tối ưu hoá để sử dụng GPU. Nội dung chính xác của loại dữ liệu này là không rõ ràng và dành riêng cho từng nền tảng.
  • API cấp thấp dựa trên thành phần, 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ý bối cảnh OpenGL, thiết lập kết cấu cho dữ liệu đầu vào và đầu ra, v.v.
  • API cấp cao dựa trên phân lớp con, trong đó các máy 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ể, trong khi lớp cấp cao xử lý toàn bộ hệ thống ống nước.
  • Dữ liệu cần được chia sẻ giữa tất cả các công cụ tính dựa trên GPU sẽ đượ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à được quản lý bằng lớp GlCalculatorHelper.
  • Sự kết hợp giữa các trình trợ giúp dành riêng cho máy tính và dịch vụ biểu đồ dùng chung cho phép chúng tôi quản lý tài nguyên GPU một cách linh hoạt tuyệt vời: chúng ta 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ẻ khoá hoặc dữ liệu gốc đồng bộ hoá khác, v.v. -- và tất cả những dữ liệu này được quản lý bởi trình trợ giúp và ẩn khỏi từng máy tính.

Trình chuyển đổi GpuBuffer sang ImageFrame

Chúng tôi cung cấp hai công cụ tính có tên GpuBufferToImageFrameCalculatorImageFrameToGpuBufferCalculator. Các máy tính này chuyển đổi từ ImageFrame đến GpuBuffer, cho phép xây dựng biểu đồ kết hợp máy 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 công cụ tính này sẽ sử dụng chức năng dành riêng cho 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 luồng dữ liệu trong một ứng dụng di động có chức năng quay video bằng máy ảnh, chạy video đó thông qua biểu đồ MediaPipe và kết xuất 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 trong biểu đồ MediaPipe phù hợp. Ứng dụng này chạy một bộ lọc phát hiện cạnh của 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 máy tính GPU tương tác

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