סקירה כללית
MediaPipe תומך בצמתים של מחשבון למחשוב ולעיבוד של GPU, ומאפשר לשלב מספר צמתים של GPU ולשלב אותם עם מחשבוןונים המבוססים על מעבד (CPU). קיימים מספר ממשקי API של GPU בפלטפורמות לנייד (למשל, OpenGL ES, Metal ו-Vulkan). ב-MediaPipe לא המערכת מנסה להציע הפשטה אחת של ה-GPU של ה-API בכל ממשקי ה-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 איטי יותר (למשל, 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 הספציפי שלהם, ואילו מחלקת-העל מטפלת בכל האינסטלציה. - נתונים שצריך לשתף בין כל המחשבונים מבוססי ה-GPU מסופקים כקלט חיצוני שמוטמע כשירות תרשים ומנוהלים על ידי המחלקה
GlCalculatorHelper
. - השילוב של עוזרים ספציפיים למחשבון ושירות גרף משותף מאפשר לנו גמישות רבה בניהול משאב ה-GPU: אנחנו יכולים ליצור הקשר נפרד לכל מחשבון, לשתף הקשר יחיד, לשתף מנעול או רכיבי סנכרון אחרים וכו' -- וכל זה מנוהל על ידי המסייע ומוסתר מהמחשבונים הנפרדים.
ממירי GpuBuffer ל-ImageFrame
אנחנו מספקים שני מחשבונים שנקראים GpuBufferToImageFrameCalculator
ו-ImageFrameToGpuBufferCalculator
. המחשבונים האלה ממירים בין ImageFrame
ל-GpuBuffer
, וכך מאפשרים ליצור תרשימים שמשלבים מחשבון GPU ומחשבי CPU. הן נתמכות גם ב-iOS וגם ב-Android
כשהדבר אפשרי, המחשבונים האלה משתמשים בפונקציונליות ספציפית לפלטפורמה כדי לשתף נתונים בין המעבד (CPU) ל-GPU, ללא העתקה.
התרשים הבא מציג את זרימת הנתונים באפליקציה לנייד שמקליטה וידאו מהמצלמה, מעבירה אותו דרך גרף MediaPipe ומעבדת את הפלט במסך בזמן אמת. הקו המקווקו מציין אילו חלקים נמצאים בתוך התרשים של MediaPipe. האפליקציה הזו מפעילה מסנן לזיהוי קצוות של Canny על המעבד באמצעות OpenCV, ומציגה אותו בשכבת-על מעל הסרטון המקורי באמצעות ה-GPU.
פריימים של וידאו מהמצלמה יוזנו בתרשים כמנות GpuBuffer
.
שני מחשבונים יכולים לגשת במקביל לזרם הקלט.
GpuBufferToImageFrameCalculator
ממירה את מאגר הנתונים הזמני ל-ImageFrame
,
ולאחר מכן נשלח דרך ממיר לגווני אפור ומסנן גזיר (שניהם מבוססים על
ב-OpenCV ופועל על המעבד (CPU), שהפלט שלו מומר
GpuBuffer
שוב. מחשבון GPU רב-קלט, GlOverlay Classroom, פועל באופן הבא:
צריך להזין גם את ה-GpuBuffer
המקורי וגם את זה שיוצא ממזהה הקצה,
ושכבות-על עליהן באמצעות תוכנת הצללה. הפלט נשלח בחזרה
באמצעות מחשבון לקריאה חוזרת, והאפליקציה מעבדת את התמונה
למסך באמצעות OpenGL.