GPU

ภาพรวม

MediaPipe รองรับโหนดเครื่องคำนวณสำหรับการประมวลผลและการแสดงผลของ GPU และอนุญาตให้รวมโหนด GPU หลายโหนด รวมถึงการใช้โหนดเครื่องคำนวณที่อิงตาม CPU ร่วมกัน แพลตฟอร์มอุปกรณ์เคลื่อนที่มี GPU API จำนวนมาก (เช่น OpenGL ES, Metal และ Vulkan) MediaPipe ไม่ได้พยายามเสนอ Abstraction ของ 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 ยังรองรับเพลงเมทัลบน iOS ด้วย

ต้องใช้ OpenGL ES 3.1 ขึ้นไป (ในระบบ Android/Linux) สำหรับการเรียกใช้เครื่องคำนวณและกราฟการอนุมานแมชชีนเลิร์นนิง

MediaPipe อนุญาตให้กราฟเรียกใช้ OpenGL ในบริบท GL หลายรายการ ตัวอย่างเช่น สิ่งนี้จะเป็นประโยชน์มากในกราฟที่รวมเส้นทางการอนุมาน GPU ที่ช้ากว่า (เช่น ที่ 10 FPS) กับเส้นทางการแสดงผล GPU ที่เร็วขึ้น (เช่น ที่ 30 FPS) เนื่องจากบริบท GL รายการหนึ่งสอดคล้องกับคิวคำสั่งตามลำดับหนึ่งรายการ การใช้บริบทเดียวกันสำหรับทั้ง 2 งานจะลดอัตราเฟรมของการแสดงผล

สิ่งหนึ่งที่ท้าทายในการใช้โซลูชันหลายบริบทของ MediaPipe คือความสามารถในการสื่อสารระหว่างบริบทเหล่านั้น ตัวอย่างคือสถานการณ์หนึ่งที่มีวิดีโออินพุตที่ส่งไปยังทั้งเส้นทางการแสดงผลและการอนุมาน การแสดงผลจึงต้องมีสิทธิ์เข้าถึงเอาต์พุตล่าสุดจากการอนุมาน

ไม่สามารถเข้าถึงบริบทของ OpenGL จากเทรดหลายรายการพร้อมกัน นอกจากนี้ การเปลี่ยนบริบท GL ที่ใช้งานอยู่ในเทรดเดียวกันอาจทำได้ช้าในอุปกรณ์ Android บางรุ่น ดังนั้น วิธีการของเราคือใช้ชุดข้อความเฉพาะ 1 ชุดต่อบริบท เทรดแต่ละรายการจะออกคำสั่ง 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 และจำเป็นต้องลบล้างเมธอดเสมือน 2 วิธีด้วยโค้ด OpenGL ที่เฉพาะเจาะจงเท่านั้น ในขณะที่ซูเปอร์คลาสจะดูแลระบบท่อประปาทั้งหมด
  • ข้อมูลที่จำเป็นต้องแชร์ระหว่างเครื่องคำนวณแบบ GPU ทั้งหมดมีการระบุเป็นอินพุตภายนอกที่ใช้งานเป็นบริการกราฟและจัดการโดยคลาส GlCalculatorHelper
  • การผสมผสานระหว่างตัวช่วยเฉพาะเครื่องคิดเลขและบริการกราฟที่ใช้ร่วมกันทำให้เราสามารถจัดการทรัพยากร GPU ได้อย่างยืดหยุ่นมากขึ้น เราสามารถมีบริบทแยกต่างหากต่อเครื่องคำนวณ แชร์บริบทเดียว แชร์การล็อกหรือค่าพื้นฐานการซิงค์อื่นๆ ฯลฯ และทั้งหมดนี้จัดการโดยผู้ช่วยและซ่อนไว้จากเครื่องคำนวณแต่ละเครื่อง

ตัวแปลง GpuBuffer เป็นกรอบรูปภาพ

เรามีเครื่องคำนวณ 2 เครื่องชื่อ GpuBufferToImageFrameCalculator และ ImageFrameToGpuBufferCalculator เครื่องคำนวณเหล่านี้จะแปลงระหว่าง ImageFrame และ GpuBuffer ทำให้สามารถสร้างกราฟที่รวมเครื่องคำนวณ GPU และ CPU เข้าด้วยกันได้ โดยใช้ได้ทั้งบน iOS และ Android

เมื่อเป็นไปได้ เครื่องคำนวณเหล่านี้จะใช้ฟังก์ชันเฉพาะแพลตฟอร์มเพื่อแชร์ข้อมูลระหว่าง CPU และ GPU โดยไม่ต้องคัดลอก

แผนภาพด้านล่างแสดงการรับส่งข้อมูลในแอปพลิเคชันบนอุปกรณ์เคลื่อนที่ที่บันทึกวิดีโอจากกล้อง เรียกใช้กราฟผ่าน MediaPipe และแสดงผลเอาต์พุตบนหน้าจอแบบเรียลไทม์ เส้นประแสดงให้เห็นว่าส่วนใดอยู่ภายในกราฟ MediaPipe ที่เหมาะสม แอปพลิเคชันนี้เรียกใช้ตัวกรองการตรวจจับขอบของ Canny บน CPU โดยใช้ OpenCV และวางซ้อนบนวิดีโอต้นฉบับโดยใช้ GPU

วิธีที่เครื่องคำนวณ GPU ทำงานร่วมกัน

ระบบจะป้อนเฟรมวิดีโอจากกล้องไปยังกราฟเป็นแพ็กเก็ต GpuBuffer เครื่องคิดเลข 2 เครื่องจะเข้าถึงสตรีมอินพุตได้แบบขนานกัน GpuBufferToImageFrameCalculator จะแปลงบัฟเฟอร์เป็น ImageFrame ซึ่งจะส่งผ่านตัวแปลงโทนสีเทาและตัวกรองแบบ Canny (ทั้งขึ้นอยู่กับ OpenCV และทำงานบน CPU) จากนั้นเอาต์พุตจะถูกแปลงเป็น GpuBuffer อีกครั้ง เครื่องคำนวณ GPU แบบมัลติอินพุต GlOverlayCalculator จะใช้อินพุตทั้ง เดิม GpuBuffer เครื่องที่ออกมาจากตัวตรวจจับขอบ และซ้อนทับโดยใช้ตัวปรับแสงเงา จากนั้นเอาต์พุตจะส่งกลับไปยังแอปพลิเคชันโดยใช้เครื่องคำนวณการเรียกกลับ และแอปพลิเคชันจะแสดงผลรูปภาพไปยังหน้าจอโดยใช้ OpenGL