رسم بياني للدالة
يحدد النموذج الأوّلي 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" }
تتكون الواجهة العامة للرسم البياني الفرعي مما يلي:
- إنشاء رسم بياني لمصادر الإدخال
- إنشاء رسم بياني لمجموعات الإخراج
- الرسم البياني لحِزم البيانات الجانبية لإدخالها
- إنشاء رسم بياني لحِزم الإخراج الجانبية
سجّل الرسم البياني الفرعي باستخدام قاعدة الإنشاء
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
المحدد لآلة حاسبة 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
، يحدّد الحقلان node_options:
وoption_value:
معًا قيم الخيارات للآلة الحاسبة مثل
ImageToTensorCalculator
. يحدد الحقل node_options:
مجموعة من القيم الثابتة
الحرفية باستخدام بنية النص الأوّلي. يحدد كل حقل 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
من قبل في النموذج الأولي للتضمين حتى يتم اجتيازها باستخدام
option_value:
.
فترات زمنية
تشترط MediaPipe أن تكون الرسوم البيانية للآلة الحاسبة دورية وتتعامل مع الدورات في الرسم البياني على أنها أخطاء. إذا كان الغرض من الرسم البياني أن يحتوي على دورات، يجب إضافة تعليقات توضيحية إلى الدورات في تكوين الرسم البياني. توضّح هذه الصفحة طريقة إجراء ذلك.
ملاحظة: إنّ الأسلوب الحالي تجريبي وخاضع للتغيير. نرحب بملاحظاتك.
يُرجى استخدام اختبار الوحدات CalculatorGraphTest.Cycle
في
mediapipe/framework/calculator_graph_test.cc
كرمز نموذجي. يظهر أدناه الرسم البياني الدوري في الاختبار. ناتج sum
في الإضافة هو مجموع الأعداد الصحيحة التي يتم إنشاؤها بواسطة الآلة الحاسبة لمصدر الأعداد الصحيحة.
يوضح هذا الرسم البياني البسيط جميع المشكلات في دعم الرسوم البيانية الدورية.
التعليق التوضيحي على الحافة الخلفية
نشترط إضافة تعليق توضيحي على حافة في كل دورة كحافة خلفية. يسمح ذلك باستخدام الترتيب الطوبولوجي لـ MediaPipe، بعد إزالة جميع الحواف الخلفية.
هناك عادةً عدة طرق لتحديد الحواف الخلفية. الحواف المميزة على أنها حواف خلفية تؤثر على العُقد التي تعتبر من الأعلى والعُقد التي تعتبر حواف، ما يؤثر بدوره على الأولويات التي يعيّنها MediaPipe للعُقد.
على سبيل المثال، يحدد الاختبار CalculatorGraphTest.Cycle
الحافة old_sum
على أنّها حافة خلفية، وبالتالي تُعتبر عقدة التأخير عقدة متأخّرة من عقدة الوصلة، ويتم منحها أولوية أعلى. بدلاً من ذلك، يمكننا وضع علامة على الإدخال sum
في عقدة التأخير على أنّه حافة خلفية، وفي هذه الحالة ستُعتبر عقدة التأخير عقدة أعلى لعقدة الإضافة وسيتم منحها أولوية أقل.
الحزمة الأولية
لإتاحة إمكانية تشغيل الآلة الحاسبة للإضافة عند وصول أول عدد صحيح من مصدر العدد الصحيح، نحتاج إلى حزمة أولية بالقيمة 0 والطابع الزمني نفسه في تدفق إدخال old_sum
إلى الإضافة. يجب إخراج هذه الحزمة الأولية من خلال حاسبة التأخير في طريقة Open()
.
تأخير في حلقة التكرار
يجب أن تؤدي كل حلقة التكرار إلى تأخير لمحاذاة إخراج sum
السابق مع إدخال العدد الصحيح التالي. يتم ذلك أيضًا بواسطة عقدة التأخير. لذا، تحتاج عقدة التأخير إلى معرفة ما يلي
حول الطوابع الزمنية لحاسبة مصدر الأعداد الصحيحة:
الطابع الزمني للناتج الأول.
يشير ذلك المصطلح إلى دلتا الطابع الزمني بين المخرجات المتتالية.
نحن نخطط لإضافة سياسة جدولة بديلة تهتم فقط بترتيب حزم البيانات وتتجاهل الطوابع الزمنية للحزم، ما سيجنبك هذا الإزعاج.
الإنهاء المبكر للآلة الحاسبة عند اكتمال بث إدخال واحد
يستدعي MediaPipe تلقائيًا طريقة Close()
للآلة الحاسبة غير المصدر عند انتهاء
جميع عمليات بث الإدخال. في مثال الرسم البياني، نريد إيقاف العقدة الإضافية بمجرد الانتهاء من مصدر العدد الصحيح. ويمكن تحقيق ذلك من خلال ضبط عقدة الإضافة باستخدام معالج بث إدخال بديل،
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'
}