GPU

Descripción general

MediaPipe admite nodos de calculadora para el procesamiento y la renderización de GPU, y permite combinar varios nodos de GPU y combinarlos con nodos de calculadora basados en CPU. Existen varias APIs de GPU en plataformas de dispositivos móviles (p. ej., OpenGL ES, Metal y Vulkan). MediaPipe no intenta ofrecer una única abstracción de GPU entre APIs. Se pueden escribir nodos individuales con diferentes APIs, lo que les permite aprovechar las funciones específicas de la plataforma cuando sea necesario.

La compatibilidad con GPU es esencial para lograr un buen rendimiento en plataformas móviles, especialmente para videos en tiempo real. MediaPipe permite a los desarrolladores escribir calculadoras compatibles con GPU que admiten el uso de GPU para lo siguiente:

  • Procesamiento en tiempo real en el dispositivo, no solo procesamiento por lotes
  • Renderización y efectos de video, no solo análisis

A continuación, se presentan los principios de diseño de la compatibilidad con GPU en MediaPipe

  • Las calculadoras basadas en GPU deberían poder aparecer en cualquier lugar del gráfico, y no necesariamente deben usarse para las renderizaciones en pantalla.
  • La transferencia de datos de fotogramas de una calculadora basada en GPU a otra debe ser rápida y no debe incurrir en costosas operaciones de copia.
  • La transferencia de datos de tramas entre la CPU y la GPU debe ser tan eficiente como lo permita la plataforma.
  • Debido a que las diferentes plataformas pueden requerir diferentes técnicas para obtener el mejor rendimiento, la API debe permitir la flexibilidad en la forma en que se implementan los elementos en segundo plano.
  • A la calculadora se le debe permitir la máxima flexibilidad en el uso de la GPU para toda su operación o parte de ella, combinándola con la CPU si es necesario.

Compatibilidad con OpenGL ES

MediaPipe admite OpenGL ES hasta la versión 3.2 en Android/Linux y hasta ES 3.0 en iOS. Además, MediaPipe también es compatible con Metal en iOS.

Se requiere OpenGL ES 3.1 o superior (en sistemas Android/Linux) para ejecutar con calculadoras y gráficos de inferencia de aprendizaje automático.

MediaPipe permite que los gráficos ejecuten OpenGL en varios contextos de GL. Por ejemplo, esta puede ser muy útil en gráficos que combinan una ruta de inferencia de GPU más lenta (p. ej., en 10 FPS) con una ruta de renderización de GPU más rápida (p. ej., a 30 FPS): desde un contexto de GL corresponde a una cola de comandos secuenciales, con el mismo contexto para ambas reduciría la velocidad de procesamiento de los fotogramas.

Uno de los desafíos que resuelve el uso de varios contextos por parte de MediaPipe es la capacidad de comunicarse entre ellas. Un escenario de ejemplo es aquel en el que un video de entrada enviados a las rutas de acceso de inferencias y procesamientos, y la renderización debe tener acceso al último resultado de la inferencia.

Varios subprocesos no pueden acceder al contexto de OpenGL al mismo tiempo. Además, cambiar el contexto de GL activo en el mismo subproceso puede ser lento algunos dispositivos Android. Por lo tanto, nuestro enfoque es contar con un subproceso dedicado según el contexto. Cada subproceso emite comandos de GL, lo que crea una cola de comandos en serie en su contexto, que la GPU ejecuta de forma asíncrona.

Duración de una calculadora de GPU

En esta sección, se presenta la estructura básica del método Process de una GPU. derivada de la clase base GlSimpleCalculator. La calculadora de GPU Se muestra LuminanceCalculator como ejemplo. El método LuminanceCalculator::GlRender se llama desde 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();
}

Los principios de diseño mencionados anteriormente han dado como resultado el siguiente diseño opciones para la compatibilidad con GPU de MediaPipe:

  • Tenemos un tipo de datos de GPU, llamado GpuBuffer, para representar datos de imágenes y optimizado para el uso de GPU. El contenido exacto de este tipo de datos es opaco y específico de la plataforma.
  • Una API de bajo nivel basada en la composición, en la que cualquier calculadora que quiera usar la GPU crea y posee una instancia de la clase GlCalculatorHelper. Esta clase ofrece una API independiente de la plataforma para administrar el contexto de OpenGL, configurar texturas para entradas y salidas, etcétera.
  • Es una API de alto nivel basada en subclases, en la que calculadoras simples que implementan filtros de imagen subclase de GlSimpleCalculator y solo necesitan anular un par de métodos virtuales con su código OpenGL específico, mientras que la superclase se encarga de todo el ensamblaje.
  • Los datos que se deben compartir entre todas las calculadoras basadas en GPU se proporcionan como una entrada externa que se implementa como un servicio de grafos y que administra la clase GlCalculatorHelper.
  • La combinación de asistentes específicos de la calculadora y un servicio de gráficos compartido nos brinda una gran flexibilidad para administrar el recurso de GPU: podemos tener un contexto separado por calculadora, compartir un solo contexto, compartir un bloqueo u otras primitivas de sincronización, etc. Todo esto lo administra el asistente y se oculta de las calculadoras individuales.

Conversores de GPU a ImageFrame

Proporcionamos dos calculadoras llamadas GpuBufferToImageFrameCalculator y ImageFrameToGpuBufferCalculator. Estas calculadoras convierten entre ImageFrame y GpuBuffer, lo que permite crear gráficos que combinan calculadoras de GPU y CPU. Son compatibles con iOS y Android.

Siempre que sea posible, estas calculadoras usan la funcionalidad específica de la plataforma para compartir datos entre la CPU y la GPU sin copiarlos.

En el siguiente diagrama, se muestra el flujo de datos en una aplicación para dispositivos móviles que captura video de la cámara, lo ejecuta a través de un grafo de MediaPipe y renderiza el resultado en la pantalla en tiempo real. La línea punteada indica qué partes se encuentran dentro del gráfico de MediaPipe correctamente. Esta aplicación ejecuta un filtro de detección de bordes Canny en la CPU con OpenCV, y lo superpone al video original usando la GPU.

Cómo interactúan las calculadoras de GPU

Los fotogramas de video de la cámara se ingresan en el gráfico como paquetes de GpuBuffer. El dos calculadoras en paralelo para acceder a la transmisión de entrada. GpuBufferToImageFrameCalculator convierte el búfer en ImageFrame que luego se envía a través de un convertidor de escala de grises y un filtro de Canny (ambos basados en en OpenCV y ejecutándose en la CPU), cuya salida se convierte en GpuBuffer otra vez. Una calculadora de GPU de varias entradas, GlOverlayCalculator, toma lo siguiente: Ingresa el GpuBuffer original y el que sale del detector de aristas, y las superpone con un sombreador. El resultado se devuelve al con una calculadora de devolución de llamada y la aplicación renderiza la imagen a la pantalla con OpenGL.