GPU

Übersicht

MediaPipe unterstützt Rechnerknoten für GPU-Computing und -Rendering und ermöglicht die Kombination mehrerer GPU-Knoten sowie deren Kombination mit CPU-basierten Rechnerknoten. Es gibt mehrere GPU APIs auf mobilen Plattformen (z. B. OpenGL ES, Metal und Vulkan). MediaPipe versucht nicht, eine einzelne API-übergreifende GPU-Abstraktion anzubieten. Einzelne Knoten können mit verschiedenen APIs geschrieben werden, sodass sie bei Bedarf plattformspezifische Funktionen nutzen können.

Die GPU-Unterstützung ist für eine gute Leistung auf mobilen Plattformen unerlässlich, insbesondere für Echtzeitvideos. MediaPipe ermöglicht Entwicklern, GPU-kompatible Rechner zu schreiben, die die Verwendung von GPU für Folgendes unterstützen:

  • Nicht nur die Batchverarbeitung, sondern die Verarbeitung in Echtzeit auf dem Gerät
  • Video-Rendering und -Effekte, nicht nur Analyse

Im Folgenden finden Sie die Designprinzipien für die GPU-Unterstützung in MediaPipe

  • GPU-basierte Rechner sollten überall im Diagramm angezeigt werden können und nicht unbedingt für das Rendering auf dem Bildschirm verwendet werden.
  • Die Übertragung von Framedaten von einem GPU-basierten Rechner in einen anderen sollte schnell sein und keine teuren Kopiervorgänge erfordern.
  • Die Übertragung von Framedaten zwischen CPU und GPU sollte so effizient sein, wie die Plattform dies zulässt.
  • Da verschiedene Plattformen unterschiedliche Techniken benötigen, um die beste Leistung zu erzielen, sollte die API Flexibilität bei der Implementierung im Hintergrund ermöglichen.
  • Einem Rechner sollte die größtmögliche Flexibilität bei der Nutzung der GPU für den gesamten oder einen Teil des Betriebs geboten werden, wobei die GPU gegebenenfalls mit der CPU kombiniert werden kann.

Unterstützung für OpenGL ES

MediaPipe unterstützt OpenGL ES bis Version 3.2 unter Android/Linux und bis ES 3.0. auf iOS-Geräten. Darüber hinaus unterstützt MediaPipe auch Metal auf iOS.

Zum Ausführen ist OpenGL ES 3.1 oder höher (auf Android-/Linux-Systemen) erforderlich. Inferenzrechner und Grafiken für Inferenz von maschinellem Lernen.

MediaPipe ermöglicht es Diagrammen, OpenGL in mehreren GL-Kontexten auszuführen. Zum Beispiel kann in Grafiken sehr nützlich sein, die einen langsameren GPU-Inferenzpfad kombinieren (z. B. bei 10 fps) mit einem schnelleren GPU-Rendering-Pfad (z. B. bei 30 fps): Da ein GL-Kontext entspricht einer sequenziellen Befehlswarteschlange, wobei für beide der gleiche Kontext verwendet wird die Framerate für das Rendering verringern würden.

Eine Herausforderung, die MediaPipe durch die Verwendung mehrerer Kontexte gelöst hat, ist die Fähigkeit, zwischen ihnen zu kommunizieren. Ein Beispielszenario ist ein Eingabevideo, sowohl an den Rendering- als auch an den Inferenzpfad gesendet werden, Zugriff auf die neueste Ausgabe von Inferenz.

Auf einen OpenGL-Kontext kann nicht gleichzeitig von mehreren Threads zugegriffen werden. Darüber hinaus kann das Wechseln des aktiven GL-Kontexts im selben Thread auf auf einigen Android-Geräten. Daher besteht unser Ansatz darin, einen eigenen Thread zu haben, pro Kontext. Jeder Thread gibt GL-Befehle aus und baut damit eine serielle Befehlswarteschlange auf. für seinen Kontext, der dann von der GPU asynchron ausgeführt wird.

Lebensdauer eines GPU-Rechners

In diesem Abschnitt wird die Grundstruktur der Prozessmethode einer GPU dargestellt Von der Basisklasse GlSimpleCalculator abgeleiteter Rechner. GPU-Rechner LuminanceCalculator wird als Beispiel gezeigt. Die Methode LuminanceCalculator::GlRender wird von GlSimpleCalculator::Process aufgerufen.

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

Die oben erwähnten Designprinzipien haben zu folgendem Design geführt: Auswahlmöglichkeiten für die MediaPipe-GPU-Unterstützung:

  • Wir haben einen GPU-Datentyp namens GpuBuffer zur Darstellung von Bilddaten, der für die GPU-Nutzung optimiert ist. Der genaue Inhalt dieses Datentyps ist undurchsichtig und plattformspezifisch.
  • Eine Low-Level-API auf Basis der Zusammensetzung, bei der jeder Rechner, der die GPU verwenden möchte, eine Instanz der GlCalculatorHelper-Klasse erstellt und besitzt. Diese Klasse bietet eine plattformunabhängige API zum Verwalten des OpenGL-Kontexts, zum Einrichten von Texturen für Ein- und Ausgaben usw.
  • Eine API auf übergeordneter Ebene, die auf Unterklassen basiert. Einfache Rechner, die die abgeleitete Bildfilterklasse von GlSimpleCalculator implementieren, müssen nur ein paar virtuelle Methoden mit ihrem spezifischen OpenGL-Code überschreiben, während die übergeordnete Klasse sich um alles kümmert.
  • Daten, die von allen GPU-basierten Rechnern geteilt werden müssen, werden als externe Eingabe bereitgestellt, die als Grafikdienst implementiert und von der Klasse GlCalculatorHelper verwaltet wird.
  • Die Kombination aus rechnerspezifischen Hilfsfunktionen und einem gemeinsam genutzten Grafikdienst bietet uns eine große Flexibilität bei der Verwaltung der GPU-Ressource: Wir können einen separaten Kontext pro Rechner haben, einen einzelnen Kontext gemeinsam nutzen, eine Sperre oder andere Synchronisierungsprimitive teilen usw. – all dies wird vom Hilfsprogramm verwaltet und vor den einzelnen Rechnern verborgen.

GpuBuffer zu ImageFrame-Konverter

Wir stellen die beiden Rechner GpuBufferToImageFrameCalculator und ImageFrameToGpuBufferCalculator zur Verfügung. Diese Rechner rechnen zwischen ImageFrame und GpuBuffer um, sodass Sie Grafiken erstellen können, in denen GPU- und CPU-Rechner kombiniert werden. Sie werden sowohl auf iOS- als auch auf Android-Geräten unterstützt.

Wenn möglich, verwenden diese Rechner plattformspezifische Funktionen, um Daten zwischen CPU und GPU ohne Kopieren auszutauschen.

Das folgende Diagramm zeigt den Datenfluss in einer mobilen App, die Videos von der Kamera aufzeichnet, durch ein MediaPipe-Diagramm führt und die Ausgabe in Echtzeit auf dem Bildschirm wiedergibt. Die gestrichelte Linie gibt an, welche Teile sich innerhalb des MediaPipe-Diagramms befinden. Diese Anwendung führt einen Canny-Edge-Erkennungsfilter auf der CPU mithilfe von OpenCV aus und überlagert ihn mithilfe der GPU über das Originalvideo.

So interagieren GPU-Rechner

Videoframes von der Kamera werden als GpuBuffer-Pakete in das Diagramm eingespeist. Die Der Eingabestream wird von zwei Rechnern parallel aufgerufen. GpuBufferToImageFrameCalculator konvertiert den Zwischenspeicher in ein ImageFrame-Element, Dieser wird dann über einen Graustufen-Konverter und einen Canny-Filter auf OpenCV und auf der CPU ausgeführt), dessen Ausgabe dann in ein GpuBuffer noch einmal. Der GPU-Rechner mit mehreren Eingaben, GlOverlayCalculator, Geben Sie sowohl den ursprünglichen GpuBuffer als auch den vom Edge-Detektor ausgegebenen ein. und sie mithilfe eines Shaders überlagern. Die Ausgabe wird dann an den -Anwendung einen Callback-Rechner verwendet und die Anwendung das Bild rendert, mit OpenGL auf den Bildschirm übertragen.