‏‫الرسوم البيانية

رسم بياني للدالة

يحدد النموذج الأوّلي 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.

  1. تحديد الرسم البياني الفرعي.

    # 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"
    }
    

    تتكون الواجهة العامة للرسم البياني الفرعي من:

    • إنشاء رسم بياني لمصادر الإدخال
    • إنشاء رسم بياني لمصادر الإخراج
    • الرسم البياني لحِزم البيانات الجانبية للإدخال
    • إنشاء رسم بياني لحِزم البيانات الجانبية للمخرجات
  2. سجِّل الرسم البياني الفرعي باستخدام قاعدة 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",
        ],
    )
    
  3. استخدِم الرسم البياني الفرعي في الرسم البياني الرئيسي.

    # 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: تحدد قيمة حقل واحد من حقول 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 من سلسلة من أسماء حقول النموذج الأولي تحدد رسائل النموذج الأوّلي والحقول مفصولة بعلامة "/". وهذا يُعرف باسم "ProtoPath" وبناء الجملة. يجب أن تكون الرسائل المتداخلة المشار إليها في LHS أو RHS في النموذج الأوّلي المُرفَق من أجل اجتيازه باستخدام option_value:

فترات زمنية

يتطلب MediaPipe بشكل افتراضي أن تكون الرسوم البيانية للآلة الحاسبة دائرية وتعالج الدورات. في الرسم البياني على أنها أخطاء. إذا كان من المفترض أن يتضمن الرسم البياني دورات، التعليق التوضيحي في تهيئة الرسم البياني. توضّح هذه الصفحة كيفية إجراء ذلك.

ملاحظة: النهج الحالي تجريبي وخاضع للتغيير. نرحّب بالترحيب ملاحظاتك.

يُرجى استخدام اختبار وحدة CalculatorGraphTest.Cycle في mediapipe/framework/calculator_graph_test.cc كنموذج للرمز. الموضح أدناه هو الرسم البياني الدوري في الاختبار. ناتج sum للإضافة هو مجموع الأعداد الصحيحة التي يتم إنشاؤها بواسطة الآلة الحاسبة لمصدر الأعداد الصحيحة.

رسم بياني دوري يضيف سلسلة من الأعداد الصحيحة

يوضح هذا الرسم البياني البسيط جميع المشكلات التي قد تظهر في دعم الرسومات البيانية الدورية.

التعليق التوضيحي على الحافة الخلفية

نحن نطلب أن تتم إضافة تعليقات توضيحية إلى حافة في كل دورة باعتبارها حافة خلفية. هذا يسمح يعمل الفرز الطبولوجي في MediaPipe، بعد إزالة جميع الحواف الخلفية.

هناك عادةً العديد من الطرق لتحديد الحواف الخلفية. الحواف المميزة حيث تؤثر الحواف الخلفية في العُقد التي تعتبر لأعلى والعُقد باعتباره مرحلة وصول، وهو ما يؤثر بدوره في الأولويات التي تعينها MediaPipe إلى العقد.

على سبيل المثال، يشير اختبار CalculatorGraphTest.Cycle إلى حافة old_sum على أنّها علامة الحافة الخلفية، وبالتالي تعتبر عقدة التأخير كعقدة أسفل الصفحة للإضافة في الجزء العلوي وإعطائها أولوية أعلى. بدلاً من ذلك، يمكننا وضع علامة على sum المدخل إلى عقدة التأخير باعتبارها الحافة الخلفية، وفي هذه الحالة ستكون عقدة التأخير يعتبر بمثابة عقدة الجزء الرئيسي من عقدة adder ومنحها أولوية أقل.

الحزمة الأولية

لكي تكون الآلة الحاسبة للإضافة قابلة للتشغيل عندما يكون أول عدد صحيح من العدد الصحيح المصدر، فإننا نحتاج إلى حزمة أولية ذات قيمة 0 وبنفس الطابع الزمني، على مصدر الإدخال old_sum إلى الإضافة. هذه الحزمة الأولية يجب أن تظهر هذه الدالة من خلال حاسبة التأخير في طريقة Open().

التأخير في التكرار

من المفترض أن تؤدي كل حلقة تكرار إلى تأخير لمحاذاة ناتج sum السابق مع الناتج التالي. إدخال عدد صحيح. ويتم ذلك أيضًا من خلال عقدة التأخير. لذلك يجب أن تكون عقدة التأخير بحاجة إلى عليك الاطّلاع على المعلومات التالية حول الطوابع الزمنية للآلة الحاسبة لمصدر الأعداد الصحيحة:

  • الطابع الزمني للناتج الأول

  • دلتا الطابع الزمني بين المخرجات المتتالية.

نخطط لإضافة سياسة جدولة بديلة تهتم بالحِزمة فقط. ترتيب الطوابع الزمنية للحزم وتجاهُلها، ما سيحدّ من هذا الإزعاج.

الإنهاء المبكر للآلة الحاسبة عند اكتمال بث إدخال واحد

بشكل تلقائي، تستدعي MediaPipe طريقة Close() للآلة الحاسبة غير المصدر في الحالات التالية إتمام جميع عمليات بث المدخلات. في مثال الرسم البياني، نريد إيقاف بمجرد الانتهاء من مصدر العدد الصحيح. يتم تحقيق ذلك من خلال ضبط عقدة adder باستخدام معالج بديل لبث الإدخال، 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'
}