圖表

圖表

CalculatorGraphConfig proto 可指定 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 中,就像是計算機一樣。從 CalculatorGraphConfig 載入 MediaPipe 圖表時,每個子圖表節點都會替換為對應的計算機圖形。因此,子圖表的語意和效能與對應的計算器圖形完全相同。

以下範例說明如何建立名為 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 圖表指定「圖表選項」protobuf,類似於為 MediaPipe 計算機指定的 Calculator Options protobuf。這些「圖表選項」可以指定叫用圖表的位置,並用來在圖形中填入計算機選項和子圖表選項。

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 FaceDetectionOptionsFaceDetectionOptions 是用來定義計算機選項 ImageToTensorCalculatorOptions 中的部分欄位值,以及子圖表選項 InferenceCalculatorOptions 中的部分欄位值。欄位值是以 option_value: 語法定義。

CalculatorGraphConfig::Node protobuf 中,node_options:option_value: 欄位會一起定義計算機的選項值,例如 ImageToTensorCalculatornode_options: 欄位會使用文字 protobuf 語法定義一組常值常數值。每個 option_value: 欄位都會使用封閉圖表的資訊 (尤其是外框圖形選項的欄位值) 定義一個 protobuf 欄位的值。在上述範例中,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 分別包含一系列 protobuf 欄位名稱,以識別巢狀 protobuf 訊息,以及以「/」分隔的欄位。這就是所謂的「ProtoPath」語法。LHS 或 RHS 中參照的巢狀訊息必須在封閉的 protobuf 中定義,才能使用 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'
}