Обзор
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.