Grafik

Grafik

Protokol CalculatorGraphConfig menentukan topologi dan fungsi Grafik MediaPipe. Setiap node dalam grafik mewakili kalkulator tertentu atau subgrafik, dan menentukan konfigurasi yang diperlukan, seperti kalkulator/subgrafik, input, output, dan kolom opsional, seperti opsi khusus node, kebijakan input dan eksekutor, yang dibahas di Sinkronisasi.

CalculatorGraphConfig memiliki beberapa kolom lain untuk mengonfigurasi level grafik global setelan, mis. konfigurasi eksekutor grafik, jumlah thread, dan ukuran antrean maksimum aliran input. Beberapa setelan tingkat grafik berguna untuk menyesuaikan performa grafik pada berbagai platform (mis., desktop vs. seluler). Sebagai misalnya, di perangkat seluler, melampirkan kalkulator inferensi model yang berat ke dapat meningkatkan kinerja aplikasi {i>real-time<i} karena ini mengaktifkan lokalitas thread.

Di bawah ini adalah contoh CalculatorGraphConfig sederhana yang memiliki serangkaian kalkulator {i>passthrough<i}:

# 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 menawarkan representasi C++ alternatif untuk grafik kompleks (misalnya pipeline ML, menangani metadata model, node opsional, dll.). Grafik di atas mungkin terlihat seperti ini:

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();
}

Lihat detail selengkapnya di Membuat Grafik di C++.

Subgrafik

Untuk memodularisasi CalculatorGraphConfig ke dalam sub-modul dan membantu penggunaan kembali dari solusi persepsi, grafik MediaPipe dapat ditentukan sebagai Subgraph. Tujuan antarmuka publik dari subgrafik terdiri dari satu set {i>input<i} dan {i>output stream<i} mirip dengan antarmuka publik kalkulator. Subgrafik tersebut kemudian dapat disertakan dalam CalculatorGraphConfig seolah-olah seperti kalkulator. Ketika grafik MediaPipe dimuat dari CalculatorGraphConfig, setiap node subgrafik diganti dengan grafik kalkulator yang sesuai. Hasilnya, semantik dan performa dari subgrafik tersebut identik dengan grafik kalkulator yang sesuai.

Di bawah ini adalah contoh cara membuat subgrafik bernama TwoPassThroughSubgraph.

  1. Menentukan subgrafik.

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

    Antarmuka publik ke subgrafik terdiri dari:

    • Membuat grafik stream input
    • Membuat grafik stream output
    • Membuat grafik paket sisi input
    • Membuat grafik paket sisi output
  2. Daftarkan subgrafik menggunakan aturan BUILD mediapipe_simple_subgraph. Tujuan parameter register_as menentukan nama komponen untuk subgrafik baru.

    # 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. Gunakan subgrafik dalam grafik utama.

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

Opsi Grafik

Anda dapat menetapkan "opsi grafik" protobuf untuk grafik MediaPipe mirip dengan Calculator Options protobuf yang ditentukan untuk kalkulator MediaPipe. "Opsi grafik" ini dapat menentukan di mana grafik dipanggil, dan digunakan untuk mengisi opsi kalkulator dan dan opsi subgrafik dalam grafik tersebut.

Dalam CalculatorGraphConfig, opsi grafik dapat ditentukan untuk subgrafik persis seperti opsi kalkulator, seperti yang ditunjukkan di bawah ini:

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

Di CalculatorGraphConfig, opsi grafik dapat diterima dan digunakan untuk mengisi opsi kalkulator, seperti yang ditunjukkan di bawah ini:

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

Dalam contoh ini, FaceDetectionSubgraph menerima protobuf opsi grafik FaceDetectionOptions. FaceDetectionOptions digunakan untuk menentukan beberapa kolom nilai pada opsi kalkulator ImageToTensorCalculatorOptions dan beberapa kolom dalam opsi subgrafik InferenceCalculatorOptions. Nilai kolom ditentukan menggunakan sintaksis option_value:.

Di protobuf CalculatorGraphConfig::Node, kolom node_options: dan option_value: bersama-sama menentukan nilai opsi untuk kalkulator seperti ImageToTensorCalculator. Kolom node_options: menentukan kumpulan literal nilai konstan menggunakan sintaks protobuf teks. Setiap kolom option_value: menentukan nilai untuk satu kolom protobuf menggunakan informasi dari grafik, khususnya dari nilai bidang dalam opsi grafik grafik. Pada contoh di atas, option_value: "output_tensor_width:options/tensor_width" menentukan kolom ImageToTensorCalculatorOptions.output_tensor_width menggunakan nilai FaceDetectionOptions.tensor_width.

Sintaksis option_value: mirip dengan sintaksis input_stream:. Tujuan sintaksisnya adalah option_value: "LHS:RHS". LHS mengidentifikasi opsi kalkulator dan RHS mengidentifikasi ruang isian opsi grafik. Lebih tepatnya, LHS dan RHS masing-masing terdiri dari serangkaian nama kolom protobuf yang mengidentifikasi pesan dan kolom protobuf yang dipisahkan oleh '/'. Hal ini dikenal sebagai "ProtoPath" sintaksis. Pesan bersarang yang dirujuk dalam LHS atau RHS harus sudah yang ditentukan dalam protobuf yang melingkupinya untuk dapat dilalui menggunakan option_value:.

Musim

Secara default, MediaPipe memerlukan grafik kalkulator yang asiklik dan memperlakukan siklus dalam grafik sebagai error. Jika grafik dirancang untuk memiliki siklus, siklus itu perlu dianotasikan dalam konfigurasi grafik. Halaman ini menjelaskan cara melakukannya.

CATATAN: Pendekatan saat ini bersifat eksperimental dan dapat berubah sewaktu-waktu. Kami menyambut masukan Anda.

Gunakan pengujian unit CalculatorGraphTest.Cycle di mediapipe/framework/calculator_graph_test.cc sebagai kode contoh. Yang ditampilkan di bawah ini adalah grafik siklus dalam pengujian. Output sum dari adder adalah jumlah dari bilangan bulat yang dihasilkan oleh kalkulator sumber bilangan bulat.

grafik siklik yang menambahkan aliran bilangan bulat

Grafik sederhana ini menggambarkan semua masalah dalam mendukung grafik siklik.

Anotasi Back Edge

Kita mengharuskan edge dalam setiap siklus dianotasi sebagai backend. Hal ini memungkinkan Urutan topologi MediaPipe agar berfungsi, setelah semua tepi belakang dilepas.

Biasanya ada beberapa cara untuk memilih tepi belakang. Tepi mana yang ditandai karena tepi belakang mempengaruhi {i>node<i} mana yang dianggap sebagai hulu dan {i>node<i} mana yang dianggap sebagai downstream, yang pada akhirnya memengaruhi prioritas yang ditetapkan MediaPipe ke beberapa node.

Misalnya, pengujian CalculatorGraphTest.Cycle menandai edge old_sum sebagai tepi belakang, sehingga node Penundaan dianggap sebagai node downstream dari penambah node dan diberi prioritas yang lebih tinggi. Atau, kita dapat menandai sum input ke {i>node<i} penundaan sebagai {i>back edge<i}, dalam hal ini {i>node<i} delay akan dianggap sebagai simpul upstream dari simpul penambah dan diberi prioritas yang lebih rendah.

Paket Awal

Agar kalkulator adder dapat dijalankan saat bilangan bulat pertama dari bilangan bulat {i>source<i} tiba, kita membutuhkan paket awal, dengan nilai 0 dan dengan stempel waktu, pada stream input old_sum ke adder. Paket awal ini harus dihasilkan oleh kalkulator keterlambatan dalam metode Open().

Penundaan dalam Loop

Setiap loop harus mengalami penundaan untuk menyelaraskan output sum sebelumnya dengan yang berikutnya input bilangan bulat. Hal ini juga dilakukan oleh node delay. Jadi {i>node<i} delay harus ketahui hal berikut tentang stempel waktu kalkulator sumber bilangan bulat:

  • Stempel waktu output pertama.

  • Delta stempel waktu di antara output yang berurutan.

Kami berencana untuk menambahkan kebijakan penjadwalan alternatif yang hanya berfokus pada paket memesan dan mengabaikan stempel waktu paket, yang akan menghilangkan ketidaknyamanan ini.

Penghentian Awal Kalkulator Saat Satu Streaming Input Selesai

Secara default, MediaPipe memanggil metode Close() dari kalkulator non-sumber saat semua aliran inputnya selesai. Pada contoh grafik, kita ingin menghentikan {i>adder<i} segera setelah sumber bilangan bulat selesai. Hal ini dicapai dengan mengonfigurasi node adder dengan pengendali aliran input alternatif, EarlyCloseInputStreamHandler.

Kode Sumber yang Relevan

Kalkulator Penundaan

Perhatikan kode di Open() yang menghasilkan paket awal dan kode di Process() yang menambahkan penundaan (unit) untuk memasukkan paket. Seperti disebutkan di atas, node penundaan mengasumsikan bahwa {i>output stream<i}-nya digunakan bersama aliran input stempel waktu paket 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();
  }
};

Konfigurasi Grafik

Perhatikan anotasi back_edge dan input_stream_handler alternatif.

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'
}