GPU

Genel bakış

MediaPipe, GPU hesaplaması ve oluşturma işlemleri için hesap makinesi düğümlerini destekler ve birden fazla GPU düğümünün birleştirilmesinin yanı sıra CPU tabanlı hesap makinesi düğümleriyle karıştırılmasına olanak tanır. Mobil platformlarda (ör. OpenGL ES, Metal ve Vulkan) birkaç GPU API'si vardır. MediaPipe, tek bir API'ler arası GPU soyutlama sunmaya çalışmaz. Bağımsız düğümler farklı API'ler kullanılarak yazılabilir. Böylece gerektiğinde, platforma özgü özelliklerden yararlanabilirler.

GPU desteği, mobil platformlarda, özellikle de gerçek zamanlı videoda iyi bir performans için esastır. MediaPipe, geliştiricilerin aşağıdakiler için GPU kullanımını destekleyen GPU uyumlu hesaplayıcılar yazmasına olanak tanır:

  • Yalnızca toplu işleme değil, cihaz üzerinde gerçek zamanlı işleme
  • Analizle sınırlı kalmayıp video oluşturma ve efektler de

Aşağıda MediaPipe'te GPU desteği için tasarım ilkeleri verilmiştir

  • GPU tabanlı hesaplayıcılar, grafiğin herhangi bir yerinde kullanılabilmeli ve ekranda oluşturma işlemi için kullanılmamalıdır.
  • Çerçeve verilerinin bir GPU tabanlı hesap makinesinden diğerine aktarılması hızlı olmalı ve pahalı kopyalama işlemlerine gerek kalmamalıdır.
  • Çerçeve verilerinin CPU ve GPU arasında aktarımı, platformun izin verdiği kadar verimli olmalıdır.
  • Farklı platformlar en iyi performans için farklı teknikler gerektirebileceğinden API, arka planda işleyiş şekli konusunda esneklik sağlamalıdır.
  • Hesap makinesinde, işlemin tamamı veya bir kısmı için GPU'yu kullanırken maksimum esneklik sağlanmalıdır ve gerektiğinde CPU ile birleştirilmelidir.

OpenGL ES Desteği

MediaPipe, Android/Linux'ta sürüm 3.2'ye ve iOS'ta ES 3.0'a kadar OpenGL ES'yi destekler. Ayrıca MediaPipe, iOS'te Metal'i de destekler.

Makine öğrenimi çıkarım hesaplayıcılarını ve grafiklerini çalıştırmak için OpenGL ES 3.1 veya sonraki sürümleri gerekir (Android/Linux sistemlerinde).

MediaPipe, grafiklerin birden fazla GL bağlamında OpenGL çalıştırılmasına olanak tanır. Örneğin bu, daha yavaş bir GPU çıkarım yolunu (ör. 10 FPS'de) daha hızlı bir GPU oluşturma yolu (ör. 30 FPS'de) ile birleştiren grafiklerde çok faydalı olabilir. Bir GL bağlamı bir sıralı komut kuyruğuna karşılık geldiğinden, her iki görev için de aynı bağlamı kullanmak, oluşturma kare hızını azaltır.

MediaPipe'in birden çok bağlam kullanmasının çözdüğü bir zorluk, bunlar arasında iletişim kurabilmektir. Örneğin, hem oluşturma hem de çıkarım yollarına gönderilen bir giriş videosu vardır ve oluşturma işleminin çıkarımdan elde edilen en son çıkışa erişebilmesi gerekir.

Bir OpenGL bağlamına aynı anda birden çok iş parçacığıyla erişilemez. Ayrıca, bazı Android cihazlarda etkin GL bağlamını aynı iş parçacığında değiştirmek yavaş olabilir. Bu nedenle, yaklaşımımız her bağlam için tek bir özel ileti dizisine sahip olmaktır. Her iş parçacığı GL komutlarını yayınlayarak kendi bağlamında bir seri komut sırası oluşturur. Bu, daha sonra GPU tarafından eşzamansız olarak yürütülür.

GPU Hesaplayıcının Yaşamı

Bu bölümde, temel sınıf GlSimpleCalculator'dan türetilen bir GPU hesap makinesinin Process (İşlem) yönteminin temel yapısı gösterilmektedir. Örnek olarak GPU hesap makinesi LuminanceCalculator gösterilmektedir. LuminanceCalculator::GlRender yöntemi, GlSimpleCalculator::Process içinden çağrılır.

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

Yukarıda bahsedilen tasarım ilkeleri, MediaPipe GPU desteği için şu tasarım seçimlerini gerektirmiştir:

  • Resim verilerini göstermek için GPU kullanımı için optimize edilmiş GpuBuffer adlı bir GPU veri türümüz vardır. Bu veri türünün tam içeriği opak ve platforma özgüdür.
  • GPU'dan yararlanmak isteyen her hesap makinesinin GlCalculatorHelper sınıfının bir örneğini oluşturup bu örneğe sahip olduğu, bileşime dayalı alt düzey bir API'dir. Bu sınıf, OpenGL bağlamını yönetmek, girişler ve çıkışlar için dokular ayarlamak vb. için platformdan bağımsız bir API sunar.
  • Resim filtrelerini uygulayan basit hesaplayıcıların GlSimpleCalculator alt sınıfını uyguladığı ve kendi özel OpenGL kodlarıyla yalnızca birkaç sanal yöntemi geçersiz kılmasının gerektiği, üst sınıf ise tüm tesisat işini üst düzey bir üst sınıfa dayanan üst düzey API.
  • Tüm GPU tabanlı hesap makineleri arasında paylaşılması gereken veriler, grafik hizmeti olarak uygulanan ve GlCalculatorHelper sınıfı tarafından yönetilen harici bir giriş olarak sağlanır.
  • Hesap makinesine özgü yardımcılar ile paylaşılan bir grafik hizmetinin kombinasyonu, GPU kaynağını yönetmede bize büyük esneklik sağlar: Her hesap makinesi için ayrı bir bağlama sahip olabiliriz, tek bir bağlam paylaşabilir, bir kilit veya diğer temel senkronizasyon öğelerini paylaşabiliriz vb. ve tüm bunlar yardımcı tarafından yönetilir ve her bir hesap makinesinden gizlenir.

GpuBuffer - ImageFrame Dönüştürücüler

GpuBufferToImageFrameCalculator ve ImageFrameToGpuBufferCalculator adında iki hesap makinesi sunuyoruz. Bu hesaplayıcılar, ImageFrame ile GpuBuffer arasında dönüşüm sağlayarak GPU ve CPU hesaplayıcılarını birleştiren grafiklerin oluşturulmasına olanak tanır. Hem iOS hem de Android'de desteklenir.

Bu hesaplayıcılar, mümkün olduğunda kopyalama yapmadan CPU ve GPU arasında veri paylaşmak için platforma özgü işlevleri kullanır.

Aşağıdaki şemada, bir mobil uygulamada kameradan video kaydeden, bir MediaPipe grafiğinde çalıştırarak ve çıktıyı ekranda gerçek zamanlı olarak oluşturan veri akışı gösterilmektedir. Kesik çizgi, MediaPipe grafiğinde hangi parçaların doğru olduğunu gösterir. Bu uygulama, OpenCV kullanarak CPU'da Canny kenar algılama filtresi çalıştırır ve bu filtreyi GPU'yu kullanarak orijinal videonun üzerine yerleştirir.

GPU hesaplayıcılarının etkileşimi

Kameradan gelen video kareleri, grafiğe GpuBuffer paketleri olarak aktarılır. Giriş akışına paralel olarak iki hesap makinesi tarafından erişilir. GpuBufferToImageFrameCalculator, tamponu ImageFrame biçimine dönüştürür. Daha sonra, gri tonlamalı dönüştürücü ve canny filtresi (her ikisi de OpenCV'ye dayalı ve CPU'da çalışan) üzerinden gönderilir. Bunların çıktısı, tekrar GpuBuffer değerine dönüştürülür. Çok girişli bir GPU hesaplayıcısı olan GlOverlayCalculator, hem orijinal GpuBuffer hem de kenar algılayıcıdan çıkan değeri girdi olarak alır ve bir gölgelendirici kullanarak bunların üzerine yerleştirir. Sonuç, daha sonra bir geri çağırma hesap makinesi kullanılarak uygulamaya geri gönderilir ve uygulama, OpenGL kullanarak resmi ekranda oluşturur.