GPU

ภาพรวม

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 บางรุ่น ดังนั้นแนวทางของเราคือการสร้างชุดข้อความเฉพาะขึ้นมา 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();
}

หลักการออกแบบที่กล่าวถึงข้างต้นได้ส่งผลให้เกิดการออกแบบดังต่อไปนี้ ตัวเลือกสำหรับการรองรับ GPU ของ MediaPipe

  • เรามีประเภทข้อมูล GPU ที่เรียกว่า GpuBuffer สำหรับแสดงข้อมูลรูปภาพ ซึ่งเพิ่มประสิทธิภาพสำหรับการใช้งาน GPU เนื้อหาเฉพาะของประเภทข้อมูลนี้จะไม่ชัดเจนและเจาะจงแพลตฟอร์ม
  • API ระดับต่ำที่อิงตามองค์ประกอบ ซึ่งเครื่องคำนวณที่ต้องการใช้ GPU จะสร้างและเป็นเจ้าของอินสแตนซ์ของคลาส GlCalculatorHelper คลาสนี้มี API ที่เข้าใจได้โดยไม่จำเป็นต้องเข้าใจแพลตฟอร์มสำหรับการจัดการบริบท OpenGL, การตั้งค่าพื้นผิวสำหรับอินพุตและเอาต์พุต ฯลฯ
  • API ระดับสูงที่อิงกับคลาสย่อย ซึ่งเป็นเครื่องคำนวณแบบง่ายที่ใช้คลาสย่อยของตัวกรองรูปภาพจาก GlSimpleCalculator และต้องการลบล้างเมธอดเสมือน 2 รายการด้วยโค้ด OpenGL ที่เจาะจงเท่านั้น ในขณะที่ Superclass มีหน้าที่จัดการท่อประปาทั้งหมด
  • ข้อมูลที่จำเป็นต้องแชร์ระหว่างเครื่องคำนวณที่ใช้ GPU ทั้งหมดจะได้รับเป็นอินพุตภายนอกที่ใช้เป็นบริการกราฟและจัดการโดยคลาส GlCalculatorHelper
  • การใช้ตัวช่วยสำหรับเครื่องคำนวณโดยเฉพาะและบริการกราฟที่ใช้ร่วมกันช่วยให้เราสามารถจัดการทรัพยากร GPU ได้อย่างยืดหยุ่นมากขึ้น เราสามารถแยกบริบทต่อเครื่องคำนวณ แชร์บริบทเดียว แชร์ล็อกหรือการซิงค์พื้นฐานอื่นๆ ฯลฯ ทั้งหมดนี้ได้รับการจัดการโดยตัวช่วยและซ่อนไว้จากเครื่องคำนวณแต่ละเครื่อง

ตัวแปลง GpuBuffer เป็น ImageFrame

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

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

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

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

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