ग्राफ़

ग्राफ़

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. बिल्ड नियम 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 ग्राफ़ के लिए एक "ग्राफ़ विकल्प" प्रोटोबफ़ को तय किया जा सकता है. यह MediaPipe Calculator के लिए दिए गए Calculator Options प्रोटोबफ़ के समान है. इन "ग्राफ़ विकल्प" के बारे में यह बताया जा सकता है कि ग्राफ़ को कहां शुरू किया गया है. साथ ही, इनका इस्तेमाल ग्राफ़ में कैलकुलेटर विकल्प और सबग्राफ़ के विकल्पों को पॉप्युलेट करने के लिए किया जा सकता है.

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", FaceDetectionOptions.tensor_width की वैल्यू का इस्तेमाल करके ImageToTensorCalculatorOptions.output_tensor_width फ़ील्ड को तय करता है.

option_value: का सिंटैक्स, input_stream: के सिंटैक्स जैसा ही है. सिटैक्स option_value: "LHS:RHS" है. LHS, एक कैलकुलेटर विकल्प फ़ील्ड की पहचान करता है और RHS एक ग्राफ़ विकल्प फ़ील्ड की पहचान करता है. खास तौर पर, LHS और RHS, दोनों में प्रोटोबफ़ फ़ील्ड के नामों की एक सीरीज़ होती है, जो नेस्ट किए गए प्रोटोबफ़ मैसेज और फ़ील्ड को '/' से अलग करते हैं. इसे "प्रोटोपाथ" सिंटैक्स कहते हैं. option_value: का इस्तेमाल करके ट्रैवर्स किए जाने के लिए, एलएचएस या आरएचएस में रेफ़र किए गए नेस्ट किए गए मैसेज पहले से ही एनक्लोज़िंग प्रोटोबफ़ में तय होने चाहिए.

साइकल

डिफ़ॉल्ट रूप से, MediaPipe को कैलकुलेटर वाले ग्राफ़ को एसाइकलिक होने की ज़रूरत होती है और वह ग्राफ़ में मौजूद साइकल को गड़बड़ी के तौर पर देखता है. अगर किसी ग्राफ़ में साइकल शामिल किए जाने चाहिए, तो ग्राफ़ के कॉन्फ़िगरेशन में उन साइकल की जानकारी दी जानी चाहिए. इस पेज पर बताया गया है कि ऐसा कैसे किया जा सकता है.

ध्यान दें: मौजूदा तरीका एक्सपेरिमेंटल है और इसमें बदलाव हो सकता है. आपका सुझाव, राय या शिकायत का स्वागत है.

कृपया सैंपल कोड के तौर पर, mediapipe/framework/calculator_graph_test.cc में CalculatorGraphTest.Cycle यूनिट टेस्ट का इस्तेमाल करें. नीचे टेस्ट में साइकल ग्राफ़ दिखाया गया है. योजक का sum आउटपुट, पूर्णांक स्रोत कैलकुलेटर से जनरेट किए गए पूर्णांकों का योग होता है.

पूर्णांकों की स्ट्रीम जोड़ने वाला चक्रीय ग्राफ़

इस सरल ग्राफ़ में, चक्रीय ग्राफ़ के साथ काम करने वाली सभी समस्याएं दिखाई गई हैं.

पिछले किनारे की व्याख्या

हमारे लिए यह ज़रूरी है कि हर साइकल के किसी किनारे को बैक एज के तौर पर एनोटेट किया गया हो. ऐसा करने से, MediaPipe की टोपोलॉजिकल सॉर्ट सुविधा, पिछले सभी किनारों को हटा देने के बाद काम करेगी.

आम तौर पर, किनारों को चुनने के कई तरीके होते हैं. किन किनारों को बैक एज के तौर पर मार्क किया जाता है, यह इस बात पर असर डालता है कि किन नोड को अपस्ट्रीम माना जाएगा और किन नोड को डाउनस्ट्रीम माना जाएगा. इससे नोड को MediaPipe असाइन की गई प्राथमिकताओं पर असर पड़ता है.

उदाहरण के लिए, CalculatorGraphTest.Cycle टेस्ट, old_sum किनारे को बैक एज के तौर पर मार्क करता है. इसलिए, देरी वाले नोड को ऐडर नोड का डाउनस्ट्रीम नोड माना जाता है और उसे ऊपर की प्राथमिकता दी जाती है. इसके अलावा, हम देरी वाले नोड में sum इनपुट को बैक एज के तौर पर मार्क कर सकते हैं. ऐसे में, देरी वाले नोड को ऐडर नोड का अपस्ट्रीम नोड माना जाएगा और उसे कम प्राथमिकता दी जाएगी.

शुरुआती पैकेट

पूर्णांक सोर्स से पहला पूर्णांक आने पर, ऐडर कैलकुलेटर को चलाया जा सकता है. इसके लिए, हमें ऐडर के old_sum इनपुट स्ट्रीम पर 0 वैल्यू और उसी टाइमस्टैंप वाले शुरुआती पैकेट की ज़रूरत है. इस शुरुआती पैकेट का आउटपुट, 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'
}