پردازنده گرافیکی

نمای کلی

MediaPipe از گره های ماشین حساب برای محاسبه و رندر GPU پشتیبانی می کند و امکان ترکیب چندین گره GPU و همچنین ترکیب آنها با گره های ماشین حساب مبتنی بر CPU را می دهد. چندین API GPU بر روی پلتفرم های تلفن همراه (مانند OpenGL ES، Metal و Vulkan) وجود دارد. MediaPipe تلاشی برای ارائه یک انتزاع GPU متقابل API ندارد. گره های جداگانه را می توان با استفاده از API های مختلف نوشت و به آنها اجازه می دهد در صورت نیاز از ویژگی های خاص پلت فرم استفاده کنند.

پشتیبانی از GPU برای عملکرد خوب در سیستم عامل های تلفن همراه، به ویژه برای ویدیوهای بلادرنگ ضروری است. MediaPipe توسعه دهندگان را قادر می سازد تا ماشین حساب های سازگار با GPU بنویسند که از GPU برای موارد زیر پشتیبانی می کند:

  • پردازش هم‌زمان روی دستگاه، نه فقط پردازش دسته‌ای
  • رندر و افکت های ویدئو، نه فقط تحلیل

در زیر اصول طراحی برای پشتیبانی از GPU در MediaPipe آمده است

  • ماشین‌حساب‌های مبتنی بر GPU باید بتوانند در هر نقطه از نمودار وجود داشته باشند و لزوماً برای رندر روی صفحه استفاده نشوند.
  • انتقال داده‌های فریم از یک ماشین‌حساب مبتنی بر GPU به ماشین‌حساب دیگر باید سریع باشد و عملیات کپی پرهزینه را متحمل نشود.
  • انتقال داده های فریم بین CPU و GPU باید تا آنجایی که پلتفرم اجازه می دهد کارآمد باشد.
  • از آنجایی که پلتفرم‌های مختلف ممکن است به تکنیک‌های متفاوتی برای بهترین عملکرد نیاز داشته باشند، API باید انعطاف‌پذیری را در نحوه پیاده‌سازی چیزها در پشت صحنه فراهم کند.
  • یک ماشین حساب باید حداکثر انعطاف پذیری را در استفاده از GPU برای تمام یا بخشی از عملکرد خود داشته باشد و در صورت لزوم آن را با CPU ترکیب کند.

پشتیبانی 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 اجرا کنند. به عنوان مثال، این می تواند در نمودارهایی که مسیر استنتاج کندتر GPU (مثلاً در 10 FPS) را با یک مسیر رندر GPU سریعتر (مثلاً در 30 FPS) ترکیب می کنند بسیار مفید باشد: از آنجایی که یک زمینه GL با یک صف دستور متوالی مطابقت دارد، با استفاده از زمینه یکسان برای هر دو کار باعث کاهش نرخ فریم رندر می شود.

یکی از چالش هایی که MediaPipe از چندین زمینه حل می کند، توانایی برقراری ارتباط بین آنهاست. یک سناریو نمونه با یک ویدیوی ورودی است که به هر دو مسیر رندر و استنتاج ارسال می‌شود و رندر باید به آخرین خروجی استنتاج دسترسی داشته باشد.

یک متن OpenGL نمی تواند توسط چندین رشته به طور همزمان قابل دسترسی باشد. علاوه بر این، تغییر زمینه فعال GL در همان رشته می‌تواند در برخی از دستگاه‌های Android کند باشد. بنابراین، رویکرد ما این است که یک رشته اختصاصی در هر زمینه داشته باشیم. هر رشته دستورات GL را صادر می کند و یک صف دستور سریال در زمینه خود ایجاد می کند که سپس توسط GPU به صورت ناهمزمان اجرا می شود.

زندگی یک ماشین حساب GPU

این بخش ساختار اساسی روش فرآیند یک ماشین حساب GPU را ارائه می دهد که از کلاس پایه GlSimpleCalculator مشتق شده است. ماشین حساب GPU 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 GPU شده است:

  • ما یک نوع داده GPU به نام GpuBuffer برای نمایش داده های تصویر داریم که برای استفاده از GPU بهینه شده است. محتویات دقیق این نوع داده مبهم و مختص پلتفرم است.
  • یک API سطح پایین مبتنی بر ترکیب، که در آن هر ماشین حسابی که بخواهد از GPU استفاده کند، نمونه‌ای از کلاس GlCalculatorHelper را ایجاد می‌کند و مالک آن است. این کلاس یک API مبتنی بر پلتفرم برای مدیریت زمینه OpenGL، تنظیم بافت ها برای ورودی ها و خروجی ها و غیره ارائه می دهد.
  • یک API سطح بالا مبتنی بر طبقه‌بندی فرعی، که در آن ماشین‌حساب‌های ساده که فیلترهای تصویر را پیاده‌سازی می‌کنند، از GlSimpleCalculator زیرکلاس می‌شوند و فقط باید چند روش مجازی را با کد OpenGL خاص خود نادیده بگیرند، در حالی که superclass از تمام لوله‌کشی‌ها مراقبت می‌کند.
  • داده‌هایی که باید بین همه ماشین‌حساب‌های مبتنی بر GPU به اشتراک گذاشته شود، به‌عنوان یک ورودی خارجی ارائه می‌شود که به‌عنوان یک سرویس گراف پیاده‌سازی می‌شود و توسط کلاس GlCalculatorHelper مدیریت می‌شود.
  • ترکیبی از کمک‌کننده‌های مخصوص ماشین‌حساب و یک سرویس نمودار مشترک به ما انعطاف‌پذیری زیادی در مدیریت منبع GPU می‌دهد: ما می‌توانیم یک زمینه جداگانه برای هر ماشین‌حساب داشته باشیم، یک زمینه واحد را به اشتراک بگذاریم، یک قفل یا سایر موارد اولیه همگام‌سازی را به اشتراک بگذاریم و غیره. این توسط کمک کننده مدیریت می شود و از ماشین حساب های فردی پنهان می شود.

مبدل های GpuBuffer به ImageFrame

ما دو ماشین حساب به نام‌های GpuBufferToImageFrameCalculator و ImageFrameToGpuBufferCalculator ارائه می‌کنیم. این ماشین‌حساب‌ها بین ImageFrame و GpuBuffer تبدیل می‌شوند و امکان ساخت نمودارهایی را می‌دهند که ماشین‌حساب‌های GPU و CPU را ترکیب می‌کنند. آنها در هر دو iOS و Android پشتیبانی می شوند

در صورت امکان، این ماشین‌حساب‌ها از عملکرد خاص پلتفرم برای به اشتراک گذاشتن داده‌ها بین CPU و GPU بدون کپی استفاده می‌کنند.

نمودار زیر جریان داده را در یک برنامه تلفن همراه نشان می دهد که از دوربین فیلم می گیرد، آن را از طریق نمودار MediaPipe اجرا می کند و خروجی را در زمان واقعی روی صفحه نمایش می دهد. خط چین نشان می دهد که کدام قسمت ها در داخل نمودار MediaPipe مناسب قرار دارند. این برنامه یک فیلتر تشخیص لبه Canny را با استفاده از OpenCV روی CPU اجرا می کند و با استفاده از GPU آن را روی ویدیوی اصلی قرار می دهد.

نحوه تعامل ماشین حساب های GPU

فریم های ویدئویی از دوربین به عنوان بسته های GpuBuffer به نمودار وارد می شوند. جریان ورودی توسط دو ماشین حساب به صورت موازی قابل دسترسی است. GpuBufferToImageFrameCalculator بافر را به ImageFrame تبدیل می‌کند، که سپس از طریق یک مبدل مقیاس خاکستری و یک فیلتر ساده (هر دو بر اساس OpenCV و در حال اجرا بر روی CPU) ارسال می‌شود که خروجی آن دوباره به یک GpuBuffer تبدیل می‌شود. یک ماشین‌حساب GPU چند ورودی، GlOverlayCalculator، هم GpuBuffer اصلی و هم چیزی که از ردیاب لبه بیرون می‌آید را به عنوان ورودی می‌گیرد و با استفاده از یک سایه‌زن آنها را پوشش می‌دهد. سپس خروجی با استفاده از یک ماشین حساب پاسخ به تماس به برنامه ارسال می شود و برنامه با استفاده از OpenGL تصویر را روی صفحه نمایش می دهد.