графический процессор

Обзор

MediaPipe поддерживает узлы калькулятора для вычислений и рендеринга на графическом процессоре и позволяет комбинировать несколько узлов графического процессора, а также смешивать их с узлами калькулятора на базе ЦП. На мобильных платформах существует несколько API-интерфейсов графических процессоров (например, OpenGL ES, Metal и Vulkan). MediaPipe не пытается предложить единую абстракцию графического процессора между API. Отдельные узлы могут быть написаны с использованием разных API, что позволяет им при необходимости использовать преимущества конкретных функций платформы.

Поддержка графического процессора необходима для хорошей производительности на мобильных платформах, особенно для видео в реальном времени. MediaPipe позволяет разработчикам писать калькуляторы, совместимые с графическим процессором, которые поддерживают использование графического процессора для:

  • Обработка в реальном времени на устройстве, а не только пакетная обработка
  • Рендеринг видео и эффекты, а не только анализ

Ниже приведены принципы проектирования поддержки графического процессора в MediaPipe.

  • Калькуляторы на базе графического процессора должны иметь возможность появляться в любом месте графика и не обязательно использоваться для рендеринга на экране.
  • Передача данных кадра из одного калькулятора на базе графического процессора в другой должна быть быстрой и не требовать дорогостоящих операций копирования.
  • Передача данных кадра между ЦП и ГП должна быть настолько эффективной, насколько это позволяет платформа.
  • Поскольку для достижения наилучшей производительности на разных платформах могут потребоваться разные методы, API должен обеспечивать гибкость в способах скрытой реализации.
  • Калькулятору должна быть предоставлена ​​максимальная гибкость в использовании графического процессора для всех или части его операций, при необходимости объединяя его с центральным процессором.

Поддержка OpenGL ES

MediaPipe поддерживает OpenGL ES до версии 3.2 на Android/Linux и до ES 3.0 на iOS. Кроме того, MediaPipe также поддерживает Metal на iOS.

Для запуска калькуляторов и графиков машинного обучения требуется OpenGL ES 3.1 или более поздняя версия (в системах Android/Linux).

MediaPipe позволяет графам запускать OpenGL в нескольких контекстах GL. Например, это может быть очень полезно в графах, которые сочетают более медленный путь вывода графического процессора (например, со скоростью 10 кадров в секунду) с более быстрым путем рендеринга графического процессора (например, со скоростью 30 кадров в секунду): поскольку один контекст GL соответствует одной последовательной очереди команд, используя один и тот же контекст для обеих задач приведет к снижению частоты кадров рендеринга.

Одной из проблем, которую решает использование MediaPipe нескольких контекстов, является возможность взаимодействия между ними. Примером сценария является сценарий, в котором входное видео отправляется как на пути рендеринга, так и на пути вывода, а для рендеринга необходим доступ к последним выходным данным вывода.

Контекст OpenGL не может быть доступен нескольким потокам одновременно. Более того, переключение активного контекста GL в том же потоке может быть медленным на некоторых устройствах Android. Поэтому наш подход заключается в том, чтобы иметь один выделенный поток для каждого контекста. Каждый поток выдает команды GL, создавая последовательную очередь команд в своем контексте, которая затем асинхронно выполняется графическим процессором.

Жизнь калькулятора графического процессора

В этом разделе представлена ​​базовая структура метода Process калькулятора графического процессора, производного от базового класса GlSimpleCalculator. В качестве примера показан калькулятор графического процессора LuminanceCalculator . Метод LuminanceCalculator::GlRender вызывается из 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();
}

Упомянутые выше принципы проектирования привели к следующим вариантам проектирования поддержки графического процессора MediaPipe:

  • У нас есть тип данных графического процессора, называемый GpuBuffer , для представления данных изображения, оптимизированный для использования графического процессора. Точное содержимое этого типа данных непрозрачно и зависит от платформы.
  • Низкоуровневый API, основанный на композиции, где любой калькулятор, желающий использовать графический процессор, создает экземпляр класса GlCalculatorHelper и владеет им. Этот класс предлагает независимый от платформы API для управления контекстом OpenGL, настройки текстур для входных и выходных данных и т. д.
  • Высокоуровневый API, основанный на подклассах, где простые калькуляторы реализуют подкласс фильтров изображений из GlSimpleCalculator , и им нужно всего лишь переопределить пару виртуальных методов с помощью их конкретного кода OpenGL, в то время как суперкласс позаботится обо всей сантехнике.
  • Данные, которые необходимо совместно использовать всем калькуляторам на базе графического процессора, предоставляются в виде внешних входных данных, которые реализованы как графический сервис и управляются классом GlCalculatorHelper .
  • Комбинация вспомогательных функций, специфичных для калькулятора, и службы общих графов обеспечивает большую гибкость в управлении ресурсами графического процессора: мы можем иметь отдельный контекст для каждого калькулятора, совместно использовать один контекст, совместно использовать блокировку или другие примитивы синхронизации и т. д. — и все этим управляет помощник и скрыто от отдельных калькуляторов.

Конвертеры GpuBuffer в ImageFrame

Мы предоставляем два калькулятора: GpuBufferToImageFrameCalculator и ImageFrameToGpuBufferCalculator . Эти калькуляторы конвертируют между ImageFrame и GpuBuffer , позволяя строить графики, объединяющие калькуляторы графического процессора и процессора. Они поддерживаются как на iOS, так и на Android.

Когда это возможно, эти калькуляторы используют специфичные для платформы функции для обмена данными между ЦП и ГП без копирования.

На диаграмме ниже показан поток данных в мобильном приложении, которое захватывает видео с камеры, пропускает его через график MediaPipe и отображает выходные данные на экране в режиме реального времени. Пунктирная линия показывает, какие части находятся внутри самого графа MediaPipe. Это приложение запускает фильтр обнаружения границ Canny на ЦП с помощью OpenCV и накладывает его поверх исходного видео с помощью графического процессора.

Как взаимодействуют калькуляторы графического процессора

Видеокадры с камеры передаются в граф в виде пакетов GpuBuffer . Доступ к входному потоку осуществляется двумя калькуляторами параллельно. GpuBufferToImageFrameCalculator преобразует буфер в ImageFrame , который затем отправляется через преобразователь оттенков серого и фильтр Канни (оба основаны на OpenCV и работают на ЦП), выходные данные которых затем снова преобразуются в GpuBuffer . Калькулятор графического процессора с несколькими входами, GlOverlayCalculator, принимает в качестве входных данных как исходный GpuBuffer , так и тот, который выходит из детектора краев, и накладывает их с помощью шейдера. Затем выходные данные отправляются обратно в приложение с помощью калькулятора обратного вызова, и приложение отображает изображение на экране с помощью OpenGL.