תרשים
פרוטו CalculatorGraphConfig
מציין את הטופולוגיה והפונקציונליות של תרשים MediaPipe. כל node
בתרשים מייצג מחשבון או תת-תרשים מסוימים, ומצוינים בו ההגדרות הנדרשות, כמו סוג של מחשבון או תת-תרשים רשומים, קלט, פלט ושדות אופציונליים, כמו אפשרויות ספציפיות לצומת, מדיניות קלט וקובץ הרצה, כפי שמפורט בסנכרון.
ב-CalculatorGraphConfig
יש כמה שדות נוספים לקביעת הגדרות גלובליות ברמת התרשים, כמו הגדרות ביצוע התרשים, מספר השרשורים וגודל התור המקסימלי של מקורות הקלט. כמה הגדרות ברמת התרשים שימושיות לכוונון הביצועים של התרשים בפלטפורמות שונות (למשל, מחשבים לעומת ניידים). בנייד, צירוף מחשבון גדול להסקת מודלים, להפעלת מחשבון נפרד להפעלה, יכול לשפר את הביצועים של אפליקציות בזמן אמת, כי כך אפשר לשפר את המוניטיזציה של השרשורים.
בהמשך מוצגת דוגמה טריוויאלית ל-CalculatorGraphConfig
שבה יש לנו סדרה של מחשבון העברה :
# This graph named main_pass_throughcals_nosubgraph.pbtxt contains 4
# passthrough calculators.
input_stream: "in"
output_stream: "out"
node {
calculator: "PassThroughCalculator"
input_stream: "in"
output_stream: "out1"
}
node {
calculator: "PassThroughCalculator"
input_stream: "out1"
output_stream: "out2"
}
node {
calculator: "PassThroughCalculator"
input_stream: "out2"
output_stream: "out3"
}
node {
calculator: "PassThroughCalculator"
input_stream: "out3"
output_stream: "out"
}
MediaPipe מציע ייצוג C++
חלופי לגרפים מורכבים (למשל, צינורות של למידת מכונה, טיפול במטא-נתונים של המודל, צמתים אופציונליים וכו'). התרשים שלמעלה עשוי להיראות כך:
CalculatorGraphConfig BuildGraphConfig() {
Graph graph;
// Graph inputs
Stream<AnyType> in = graph.In(0).SetName("in");
auto pass_through_fn = [](Stream<AnyType> in,
Graph& graph) -> Stream<AnyType> {
auto& node = graph.AddNode("PassThroughCalculator");
in.ConnectTo(node.In(0));
return node.Out(0);
};
Stream<AnyType> out1 = pass_through_fn(in, graph);
Stream<AnyType> out2 = pass_through_fn(out1, graph);
Stream<AnyType> out3 = pass_through_fn(out2, graph);
Stream<AnyType> out4 = pass_through_fn(out3, graph);
// Graph outputs
out4.SetName("out").ConnectTo(graph.Out(0));
return graph.GetConfig();
}
אפשר לקרוא פרטים נוספים במאמר בנושא יצירת תרשימים ב-C++.
תת-תרשים
כדי להתאים CalculatorGraphConfig
למודולים משניים ולסייע בשימוש חוזר בפתרונות תפיסה, אפשר להגדיר תרשים MediaPipe בתור Subgraph
. הממשק הציבורי של תת-תרשים מורכב מקבוצה של זרמי קלט ופלט שדומים לממשק הציבורי של מחשבון. לאחר מכן תוכלו לכלול את המשנה ב-CalculatorGraphConfig
כאילו הוא מחשבון. כשתרשים MediaPipe נטען מ-CalculatorGraphConfig
, כל צומת בתת-תרשים מוחלף בגרף התואם של המחשבונים. כתוצאה מכך, הסמנטיקה והביצועים של התת-תרשים זהים לתרשים המקביל של המחשבונים.
בהמשך מוצגת דוגמה איך ליצור תת-תרשים בשם TwoPassThroughSubgraph
.
הגדרת התת-תרשים.
# This subgraph is defined in two_pass_through_subgraph.pbtxt # and is registered as "TwoPassThroughSubgraph" type: "TwoPassThroughSubgraph" input_stream: "out1" output_stream: "out3" node { calculator: "PassThroughCalculator" input_stream: "out1" output_stream: "out2" } node { calculator: "PassThroughCalculator" input_stream: "out2" output_stream: "out3" }
הממשק הציבורי של תת-התרשים כולל:
- תרשימי קלט בגרף
- הציגו בגרף זרמי פלט
- הציגו בגרף מנות צד
- הציגו בגרף מנות צדדיות של הפלט
צריך לרשום את תת-התרשים באמצעות כלל ה-BUILD
mediapipe_simple_subgraph
. הפרמטרregister_as
מגדיר את שם הרכיב של התת-תרשים החדש.# Small section of BUILD file for registering the "TwoPassThroughSubgraph" # subgraph for use by main graph main_pass_throughcals.pbtxt mediapipe_simple_subgraph( name = "twopassthrough_subgraph", graph = "twopassthrough_subgraph.pbtxt", register_as = "TwoPassThroughSubgraph", deps = [ "//mediapipe/calculators/core:pass_through_calculator", "//mediapipe/framework:calculator_graph", ], )
משתמשים בתת-הגרף שבתרשים הראשי.
# This main graph is defined in main_pass_throughcals.pbtxt # using subgraph called "TwoPassThroughSubgraph" input_stream: "in" node { calculator: "PassThroughCalculator" input_stream: "in" output_stream: "out1" } node { calculator: "TwoPassThroughSubgraph" input_stream: "out1" output_stream: "out3" } node { calculator: "PassThroughCalculator" input_stream: "out3" output_stream: "out4" }
אפשרויות תרשים
אפשר לציין פרוטובוף של "אפשרויות גרף" לתרשים MediaPipe, שדומה ל-Calculator Options
protobuf שצוין למחשבון MediaPipe. ניתן לציין את 'אפשרויות הגרף' האלה כאשר מופעל תרשים, ולהשתמש בהן לאכלוס אפשרויות המחשבון ואפשרויות המשנה בתרשים.
ב-CalculatorGraphConfig
אפשר לציין אפשרויות גרף לתת-תרשים, בדומה לאפשרויות של מחשבון, באופן הבא:
node {
calculator: "FlowLimiterCalculator"
input_stream: "image"
output_stream: "throttled_image"
node_options: {
[type.googleapis.com/mediapipe.FlowLimiterCalculatorOptions] {
max_in_flight: 1
}
}
}
node {
calculator: "FaceDetectionSubgraph"
input_stream: "IMAGE:throttled_image"
node_options: {
[type.googleapis.com/mediapipe.FaceDetectionOptions] {
tensor_width: 192
tensor_height: 192
}
}
}
ב-CalculatorGraphConfig
אפשר לקבל אפשרויות גרף ולהשתמש בהן לאכלוס אפשרויות המחשבון, באופן הבא:
graph_options: {
[type.googleapis.com/mediapipe.FaceDetectionOptions] {}
}
node: {
calculator: "ImageToTensorCalculator"
input_stream: "IMAGE:image"
node_options: {
[type.googleapis.com/mediapipe.ImageToTensorCalculatorOptions] {
keep_aspect_ratio: true
border_mode: BORDER_ZERO
}
}
option_value: "output_tensor_width:options/tensor_width"
option_value: "output_tensor_height:options/tensor_height"
}
node {
calculator: "InferenceCalculator"
node_options: {
[type.googleapis.com/mediapipe.InferenceCalculatorOptions] {}
}
option_value: "delegate:options/delegate"
option_value: "model_path:options/model_path"
}
בדוגמה הזו, FaceDetectionSubgraph
מקבל את אפשרות התרשים protobuf FaceDetectionOptions
. השדה FaceDetectionOptions
משמש להגדרה של ערכי שדות מסוימים באפשרויות המחשבון ImageToTensorCalculatorOptions
, וחלק מערכי השדות באפשרויות InferenceCalculatorOptions
בתרשים המשנה. ערכי השדות מוגדרים באמצעות התחביר option_value:
.
ב-protobuf CalculatorGraphConfig::Node
, השדות node_options:
ו-option_value:
מגדירים ביחד את ערכי האפשרויות למחשבון כמו ImageToTensorCalculator
. השדה node_options:
מגדיר קבוצה של ערכים קבועים ליטרליים באמצעות התחביר של text protobuf. כל שדה option_value:
מגדיר את הערך של שדה Protobuf אחד באמצעות מידע מהתרשים המצורף, במיוחד מערכי השדות של אפשרויות התרשים של התרשים המצורף. בדוגמה שלמעלה, השדה option_value:
"output_tensor_width:options/tensor_width"
מגדיר את השדה ImageToTensorCalculatorOptions.output_tensor_width
באמצעות הערך FaceDetectionOptions.tensor_width
.
התחביר של option_value:
דומה לתחביר של input_stream:
. התחביר הוא option_value: "LHS:RHS"
. ה-LHS מזהה שדה של אפשרות המחשבון, וה-RHS מזהה שדה של אפשרות תרשים. באופן ספציפי יותר, כל אחד מה-LHS וה-RHS מורכב מסדרה של שמות שדות protobuf שמזהים הודעות ושדות מקננים של protobuf המופרדים באמצעות '/'. זה נקרא תחביר "ProtoPath". כדי שאפשר יהיה לעבור דרך option_value:
הודעות מקננות שיש הפניה אליהן ב-LHS או ב-RHS, הן חייבות להיות מוגדרות כבר ב-protobuf המצורף.
מחזורים
כברירת מחדל, MediaPipe מחייב שגרפים של המחשבון יהיו מחזוריים ומתייחסים למחזורים בתרשים כשגיאות. אם התרשים כולל מחזורים, צריך להוסיף להם הערות בהגדרת התרשים. בדף הזה נסביר איך לעשות את זה.
הערה: הגישה הנוכחית היא ניסיונית ועשויה להשתנות. נשמח לקבל משוב.
השתמשו בבדיקת היחידה CalculatorGraphTest.Cycle
ב-mediapipe/framework/calculator_graph_test.cc
כקוד לדוגמה. למטה מוצג התרשים המחזורי בבדיקה. הפלט sum
של הפונקציה הוא סכום המספרים השלמים שנוצר על ידי מחשבון המקור עם המספרים השלמים.
התרשים הפשוט הזה ממחיש את כל הבעיות שנתמכות בתרשימים מחזוריים.
הערה מהשוליים האחוריים
בכל מחזור חייב להיות רישום של קצה כקצה אחורי. כך המיון הטופולוגי של MediaPipe יכול לפעול, אחרי הסרת כל הקצוות האחוריים.
בדרך כלל יש כמה דרכים לבחור את הקצוות האחוריים. אילו קצוות מסומנים כקצוות אחוריים, ומשפיעים על הצמתים שנחשבים כ-upstream, ואילו צמתים נחשבים למורד הזרם (downstream), וזה משפיע על סדר העדיפויות שמוקצה לצמתים על ידי MediaPipe.
לדוגמה, הבדיקה CalculatorGraphTest.Cycle
מסמנת את הקצה של old_sum
כקצה אחורי, כך שצומת העיכוב נחשב כצומת במורד הזרם של צומת להוסיף, ומקבל עדיפות גבוהה יותר. לחלופין, אפשר לסמן את הקלט sum
לצומת העיכוב בתור הקצה האחורי, ובמקרה כזה צומת העיכוב ייחשב כצומת upstream של צומת ה-adder ויקבל עדיפות נמוכה יותר.
מנה ראשונית
כדי שאפשר יהיה להריץ את המחשבון המחשבון כאשר המספר השלם הראשון מגיע מהמקור של המספר השלם, נדרשת חבילה ראשונית עם הערך 0 ועם אותה חותמת זמן, בזרם הקלט של old_sum
. הפלט של החבילה הראשונית הזו צריך להתבצע באמצעות מחשבון ההשהיה ב-method Open()
.
השהיה בלולאה
בכל לולאה צריכה להיות השהיה כדי להתאים את הפלט הקודם של sum
לקלט המספר השלם הבא. הפעולה הזו מתבצעת גם באמצעות צומת העיכוב. לכן, צומת העיכוב צריך לדעת את הפרטים הבאים לגבי חותמות הזמן של מחשבון המקור עם המספרים השלמים:
חותמת הזמן של הפלט הראשון.
הדלתא של חותמת הזמן בין הפלטים הבאים.
אנחנו מתכננים להוסיף מדיניות חלופית לתזמון שמתייחס רק להזמנת חבילות ומתעלמת מחותמות הזמן של החבילות, וכך למנוע אי נוחות רבה.
סיום מוקדם של מחשבון כשמסתיים סטרימינג אחד של קלט
כברירת מחדל, כאשר כל שידורי הקלט שלו הושלמו, MediaPipe קורא לשיטה Close()
למחשבון שאינו מקור. בתרשים לדוגמה, אנחנו רוצים להפסיק את הצומת של התוספת ברגע שמקור המספר השלם מסתיים. כדי לעשות זאת, מגדירים את הצומת של המפיץ באמצעות handler חלופי של מקור קלט, EarlyCloseInputStreamHandler
.
קוד מקור רלוונטי
מחשבון השהיה
שימו לב לקוד ב-Open()
שמפיק את החבילה הראשונית, ולקוד ב-Process()
שיוצר עיכוב (יחידה) במנות הקלט. כפי שצוין למעלה, צומת ההשהיה הזה מניח שזרם הפלט שלו משמש לצד זרם קלט עם חותמות הזמן של החבילות 0, 1, 2, 3, ...
class UnitDelayCalculator : public Calculator {
public:
static absl::Status FillExpectations(
const CalculatorOptions& extendable_options, PacketTypeSet* inputs,
PacketTypeSet* outputs, PacketTypeSet* input_side_packets) {
inputs->Index(0)->Set<int>("An integer.");
outputs->Index(0)->Set<int>("The input delayed by one time unit.");
return absl::OkStatus();
}
absl::Status Open() final {
Output()->Add(new int(0), Timestamp(0));
return absl::OkStatus();
}
absl::Status Process() final {
const Packet& packet = Input()->Value();
Output()->AddPacket(packet.At(packet.Timestamp().NextAllowedInStream()));
return absl::OkStatus();
}
};
תצורת התרשים
חשוב לשים לב להערה back_edge
ולinput_stream_handler
החלופית.
node {
calculator: 'GlobalCountSourceCalculator'
input_side_packet: 'global_counter'
output_stream: 'integers'
}
node {
calculator: 'IntAdderCalculator'
input_stream: 'integers'
input_stream: 'old_sum'
input_stream_info: {
tag_index: ':1' # 'old_sum'
back_edge: true
}
output_stream: 'sum'
input_stream_handler {
input_stream_handler: 'EarlyCloseInputStreamHandler'
}
}
node {
calculator: 'UnitDelayCalculator'
input_stream: 'sum'
output_stream: 'old_sum'
}