GPU

Omówienie

MediaPipe obsługuje węzły kalkulatora na potrzeby obliczeń i renderowania za pomocą GPU. Umożliwia łączenie wielu węzłów GPU, a także mieszanie ich z węzłami kalkulatorów opartych na procesorze CPU. Na platformach mobilnych jest kilka interfejsów API GPU (np. OpenGL ES, Metal i Vulkan). MediaPipe nie próbuje oferować abstrakcji GPU pomiędzy interfejsami API. Poszczególne węzły można zapisywać przy użyciu różnych interfejsów API, co pozwala w razie potrzeby korzystać z funkcji platformy.

Obsługa GPU jest niezbędna do zapewnienia dobrej wydajności na platformach mobilnych, zwłaszcza w przypadku filmów w czasie rzeczywistym. MediaPipe umożliwia programistom pisanie kalkulatorów zgodnych z GPU, które obsługują GPU, do:

  • Przetwarzanie w czasie rzeczywistym na urządzeniu, a nie tylko przetwarzanie wsadowe
  • Renderowanie i efekty filmów, a nie tylko analiza

Poniżej znajdziesz zasady projektowania obsługi GPU w MediaPipe

  • Kalkulatory oparte na GPU powinny być możliwe w dowolnym miejscu na wykresie. Nie muszą być używane do renderowania na ekranie.
  • Przenoszenie danych dotyczących klatek z jednego kalkulatora opartego na GPU powinno być szybkie i nie wiąże się z kosztownymi operacjami kopiowania.
  • Przenoszenie danych dotyczących klatek między CPU i GPU powinno być tak efektywne, na ile pozwala platforma.
  • Różne platformy mogą wymagać różnych technik w celu uzyskania optymalnej wydajności, dlatego interfejs API powinien dawać elastyczność w sposobie implementacji za kulisami.
  • Kalkulator powinien zapewniać maksymalną elastyczność korzystania z GPU do całości lub części swojego działania, w razie potrzeby łącząc go z CPU.

Obsługa OpenGL ES

MediaPipe obsługuje standard OpenGL ES do wersji 3.2 w systemach Android/Linux i do ES 3.0 na iOS. Dodatkowo MediaPipe obsługuje również Metal na iOS.

Do uruchamiania wymagany jest interfejs OpenGL ES 3.1 lub nowszy (w systemach Android/Linux) kalkulatorów i wykresów wnioskowania z systemów uczących się.

MediaPipe umożliwia korzystanie z wykresów OpenGL w wielu kontekstach GL. Na przykład to może być bardzo przydatne na wykresach łączących wolniejsze ścieżki wnioskowania GPU (np. przy 10 kl./s) z szybszą ścieżką renderowania GPU (np. 30 kl./s): ponieważ jeden kontekst GL odpowiada jednej sekwencyjnej kolejce poleceń, używając tego samego kontekstu w przypadku obu ograniczy liczbę klatek podczas renderowania.

Jednym z wyzwań, z jakimi radzi sobie MediaPipe, jest możliwość komunikacji między nimi. Przykładem może być film, w którym wysyłane zarówno do ścieżek renderowania, jak i ścieżek zależności, przy czym renderowanie musi mieć do najnowszych danych wyjściowych z wnioskowania.

Dostęp do kontekstu OpenGL przez wiele wątków nie jest możliwy jednocześnie. Poza tym przełączanie aktywnego kontekstu GL w tym samym wątku może działać wolno niektórych urządzeniach z Androidem. Dlatego stosujemy jeden wątek w zależności od kontekstu. Każdy wątek generuje polecenia GL, tworząc szereg poleceń w kontekście, który jest następnie wykonywany asynchronicznie przez GPU.

Jak działa kalkulator GPU

W tej sekcji przedstawiamy podstawową strukturę metody przetwarzania GPU kalkulator uzyskany na podstawie klasy bazowej GlSimpleCalculator. Kalkulator GPU Jako przykład podano LuminanceCalculator. Metoda LuminanceCalculator::GlRender dzwoni z numeru 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();
}

Zgodnie z powyższymi zasadami projektowania możliwości obsługi GPU MediaPipe:

  • Korzystamy z typu danych GPU o nazwie GpuBuffer, który służy do przedstawiania danych obrazu i jest zoptymalizowany pod kątem wykorzystania GPU. Dokładna zawartość tego typu danych jest nieprzejrzysta i różnie się od konkretnej platformy.
  • Niskopoziomowy interfejs API oparty na strukturze, w którym każdy kalkulator, który chce skorzystać z GPU, tworzy instancję klasy GlCalculatorHelper i jest jej właścicielem. Ta klasa udostępnia niezależny od platformy interfejs API do zarządzania kontekstem OpenGL, konfigurowania tekstur dla danych wejściowych i wyjściowych itp.
  • Wysoki poziom interfejsu API oparty na podklasie, w którym proste kalkulatory implementujące podklasę filtrów obrazów z GlSimpleCalculator i muszą zastąpić kilka metod wirtualnych odpowiednim kodem OpenGL, a klasa superklasa zajmuje się całą hydrauliką.
  • Dane, które muszą być współdzielone przez wszystkie kalkulatory oparte na GPU, są dostarczane jako zewnętrzne dane wejściowe implementowane jako usługa grafowa i zarządzane przez klasę GlCalculatorHelper.
  • Połączenie funkcji pomocniczych kalkulatora i wspólnej usługi wykresów zapewnia nam dużą elastyczność zarządzania zasobem GPU: możemy mieć osobny kontekst dla każdego kalkulatora, współużytkować pojedynczy kontekst, blokadę lub inne podstawowe elementy synchronizacji itd. Wszystko to odbywa się za pomocą funkcji pomocniczej, ale jest niewidoczne dla poszczególnych kalkulatorów.

Konwertery GpuBuffer na ImageFrame

Udostępniamy dwa kalkulatory o nazwach GpuBufferToImageFrameCalculator i ImageFrameToGpuBufferCalculator. Te kalkulatory umożliwiają przeliczanie wartości od ImageFrame do GpuBuffer, co umożliwia tworzenie wykresów łączących kalkulatory liczby GPU i CPU. Są obsługiwane zarówno w systemie iOS, jak i Androidzie

W miarę możliwości kalkulatory te korzystają z funkcji platformy, aby udostępniać dane między CPU i GPU bez kopiowania.

Poniższy diagram przedstawia przepływ danych w aplikacji mobilnej, która rejestruje obraz z kamery, przesyła go na wykres MediaPipe i renderuje na ekranie w czasie rzeczywistym. Linia przerywana wskazuje, które części znajdują się na wykresie MediaPipe. Aplikacja uruchamia na CPU filtr wykrywania krawędzi Canny'ego przy użyciu OpenCV i nakłada go na oryginalny film za pomocą GPU.

Jak współdziałają kalkulatory GPU

Klatki wideo z kamery są przesyłane do wykresu w postaci pakietów GpuBuffer. do strumienia wejściowego mają dostęp równolegle dwa kalkulatory. GpuBufferToImageFrameCalculator konwertuje bufor na ImageFrame, który jest przesyłany przez konwerter w skali szarości i filtr Canny’ego (oparte na obu w OpenCV i na CPU), którego dane wyjściowe są następnie konwertowane GpuBuffer. Kalkulator GPU z wieloma danymi wejściowymi, GlOverlayCalculator, przyjmuje wpisz zarówno oryginalny GpuBuffer, jak i ten wychodzący z czujnika krawędzi, i nakłada je za pomocą cieni. Dane wyjściowe są następnie wysyłane z powrotem do za pomocą kalkulatora wywołań zwrotnych, a aplikacja renderuje obraz do ekranu przy użyciu trybu OpenGL.