תרשים
אב הטיפוס 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" }
אפשרויות תרשים
אפשר לציין 'אפשרויות תרשים' Protobuf לתרשים MediaPipe
דומה ל-Calculator Options
aprotobuf מוגדר למחשבון 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
מקבל פרוטובוט של אפשרות תרשים
FaceDetectionOptions
. השדה FaceDetectionOptions
משמש להגדרת שדות מסוימים
ערכים באפשרויות המחשבון ImageToTensorCalculatorOptions
ושדות מסוימים
ערכים באפשרויות המשנה InferenceCalculatorOptions
. ערכי השדות
מוגדרות באמצעות התחביר option_value:
.
ב-CalculatorGraphConfig::Node
Protobuf, השדות node_options:
ו-
option_value:
מגדירים יחד את ערכי האפשרויות של מחשבון, כמו
ImageToTensorCalculator
השדה node_options:
מגדיר קבוצה של מילים
קבועים באמצעות התחביר של text protobuf. כל שדה option_value:
מגדיר את הערך של שדה אב-טיפוס אחד באמצעות מידע מהצורך
באופן ספציפי מערכי השדות של אפשרויות התרשים של
גרפי. בדוגמה שלמעלה, השדה 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, מופרדים על ידי '/'. הוא נקרא ProtoPath
תחביר. הודעות מקוננות שיש אליהן הפניה ב-LHS או ב-RHS כבר צריכות להיות
מוגדר ב-protobuf המקיף כך שניתן לחצות אותו באמצעות
option_value:
מחזורים
כברירת מחדל, MediaPipe דורש שהתרשימים של המחשבון יהיו אציקליים ומטפלים במחזורים בתרשים כשגיאות. אם התרשים מיועד לכלול מחזורים, המחזורים צריכים יופיע בתצורה של התרשים. בדף הזה נסביר איך לעשות את זה.
הערה: הגישה הנוכחית היא ניסיונית וכפופה לשינויים. נשמח לראות משוב.
יש להשתמש בבדיקת היחידה של CalculatorGraphTest.Cycle
mediapipe/framework/calculator_graph_test.cc
כקוד לדוגמה. למטה מוצגת
את הגרף המחזורי בבדיקה. הפלט של sum
של כלי הכתיבה הוא הסכום של
של מספרים שלמים שנוצרו על ידי מחשבון המקור של מספרים שלמים.
התרשים הפשוט הזה ממחיש את כל הבעיות שתומכים בתרשימים מחזוריים.
הוספת הערות לקצה האחורי
אנו דורשים שכל קצה של המחזור יסומן כקצה אחורי. כך אפשר המיון הטופולוגי של MediaPipe פועל אחרי הסרת כל הקצוות האחוריים.
בדרך כלל יש כמה דרכים לבחור את הצדדים האחוריים. אילו קצוות מסומנים כי הקצוות האחוריים משפיעים על הצמתים שנחשבים ל-upstream ואילו הצמתים נחשב כמורד הזרם, וזה משפיע על העדיפויות ש-MediaPipe מקצה לצמתים.
לדוגמה, הבדיקה CalculatorGraphTest.Cycle
מסמנת את הקצה old_sum
הקצה האחורי, כך שהצומת Delay נחשב כצומת במורד הזרם (downstream) של מבצע ההוספה
מקבל עדיפות גבוהה יותר. לחלופין, אפשר לסמן את sum
קלט לצומת ההשהיה בתור הקצה האחורי, ובמקרה כזה הצומת
נחשב כצומת ב-upstream של הצומת של ההוספה, ומקבלים עדיפות נמוכה יותר.
מנה ראשונית
כדי שאפשר יהיה להריץ את המחשבון השלם הראשון מתוך המספר השלם
מגיע, אנחנו צריכים חבילה ראשונית, עם ערך 0 ועם אותו
timestamp, ב-old_sum
של הקלט למוסיף. החבילה הראשונית הזו
הפלט אמור להתקבל על ידי מחשבון העיכוב בשיטה 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'
}