電卓

各計算ツールはグラフのノードです。新しい P-MAX キャンペーンを 料金計算ツール、電卓の初期化方法、その計算方法、 オプションについて説明します。グラフの各ノードは、 Calculator として実装される。グラフ実行の大部分は 計算ツール計算ツールは、ゼロまたは複数の入力ストリームや ゼロ個以上の出力ストリームやサイドパケットを生成します。

CalculatorBase

計算ツールを作成するには、新しいサブクラスを CalculatorBase クラスの作成、いくつかのメソッドの実装、新しいサブクラスの Mediapipe新しい計算ツールでは、少なくとも以下の 4 つの方法を実装する必要があります。

  • GetContract()
    • 計算ツールでは、想定される入力と出力のタイプを指定できます GetContract() での計算ツールの一例です。グラフを初期化すると、 フレームワークは静的メソッドを呼び出し、パケットの型が 接続された入力と出力の情報は、この例の 仕様。
  • Open()
    • グラフが開始されると、フレームワークは Open() を呼び出します。入力側 パケットを計算できます。Open() ノード構成オペレーションを解釈する(グラフを参照) 計算機のグラフ実行ごとの状態を準備します。この関数は、 計算機の出力にパケットを書き込みますOpen() でエラーが発生すると、 グラフの実行を終了します
  • Process()
    • 入力がある計算ツールの場合、フレームワークは Process() を繰り返し呼び出します。 少なくとも 1 つの入力ストリームに使用可能なパケットがある場合にトリガーされます。フレームワーク デフォルトでは、すべての入力のタイムスタンプが同じであることが保証されます( 同期をご覧ください)。複数 並列実行中に Process() の呼び出しを同時に呼び出すことができる 有効になります。Process() の実行中にエラーが発生した場合、フレームワークは Close() になり、グラフの実行が終了します。
  • Close()
    • Process() へのすべての呼び出しが終了した後、またはすべての入力ストリームが閉じられると、 フレームワークが Close() を呼び出します。この関数は、次の場合に常に呼び出されます。 Open() が呼び出されて成功する(グラフの実行が終了しても) 表示されます。どの入力ストリームでも使用できる入力がありません Close() の間、ただし入力側のパケットと 出力を書き込む可能性があります。Close() が返されると、計算ツール デッドノードとみなすべきですCalculator オブジェクトは破棄されます。 グラフの実行が終了しました

次のコード スニペットは、 CalculatorBase.h

class CalculatorBase {
 public:
  ...

  // The subclasses of CalculatorBase must implement GetContract.
  // ...
  static absl::Status GetContract(CalculatorContract* cc);

  // Open is called before any Process() calls, on a freshly constructed
  // calculator.  Subclasses may override this method to perform necessary
  // setup, and possibly output Packets and/or set output streams' headers.
  // ...
  virtual absl::Status Open(CalculatorContext* cc) {
    return absl::OkStatus();
  }

  // Processes the incoming inputs. May call the methods on cc to access
  // inputs and produce outputs.
  // ...
  virtual absl::Status Process(CalculatorContext* cc) = 0;

  // Is called if Open() was called and succeeded.  Is called either
  // immediately after processing is complete or after a graph run has ended
  // (if an error occurred in the graph).  ...
  virtual absl::Status Close(CalculatorContext* cc) {
    return absl::OkStatus();
  }

  ...
};

電卓とは

MediaPipe グラフの初期化中に、フレームワークは 想定されるパケットの種類を決定する GetContract() 静的メソッド。

フレームワークがグラフを実行するたびに計算ツール全体を構築し、破棄する (動画ごとに 1 回、画像ごとに 1 回など)。高価なオブジェクトや大きなオブジェクトが残っている 渡す必要があります。入力側のパケットとして 後続の実行で繰り返される計算は行われません。

初期化後、グラフを実行するたびに次のシーケンスが発生します。

  • Open()
  • Process()(繰り返し)
  • Close()

フレームワークは Open() を呼び出して計算ツールを初期化します。Open() の正しい行動 オプションを解釈し、計算ツールのグラフ実行ごとの状態を設定します。Open() 入力側のパケットを取得して計算機の出力にパケットを書き込む可能性があります。条件 必要に応じて、SetOffset() を呼び出してパケット バッファリングの発生を抑える 生成します。

Open() または Process() の実行中にエラーが発生した場合 Ok 以外のステータスを返す場合、それ以上の呼び出しなしでグラフの実行を終了します。 計算ツールのメソッドに渡され、料金計算ツールは破棄されます。

入力がある計算ツールでは、少なくとも Process() が呼び出されるたびに ある入力でパケットを利用できます。このフレームワークでは、すべての入力が Process() を呼び出すたびにタイムスタンプが増加します。また、 すべてのパケットが配信されます。そのため、一部の入力では、この制約が Process() が呼び出されたときにすべてのパケットを消費します。パケットが欠落している入力は、 空のパケットを生成する(タイムスタンプなし)。

フレームワークは、Process() へのすべての呼び出しの後に Close() を呼び出します。すべての入力は 使い果たされましたが、Close() は入力側のパケットにアクセスできるため、 出力を書き込むことができます。Close が返品された後、計算ツールは破棄されます。

入力がない計算ツールはソースと呼ばれます。ソース計算ツール Ok ステータスを返す限り、Process() が呼び出されます。 ソース計算ツールは、停止ステータスを返すことにより、枯渇したことを示している (例: mediaPipe::tool::StatusStop())。

入力と出力の特定

計算ツールの公開インターフェースは、一連の入力ストリームと 出力ストリームですCalculatorGraphConfiguration では、いくつかの 電卓は、名前付き入力を使用して他の電卓の入力に接続されています。 使用できます。通常、ストリーム名は小文字ですが、入力タグと出力タグは 通常は大文字にします。以下の例では、タグ名 VIDEO の出力は次のようになります。 という名前のストリームを使用して、タグ名 VIDEO_IN の入力に接続されています video_stream

# Graph describing calculator SomeAudioVideoCalculator
node {
  calculator: "SomeAudioVideoCalculator"
  input_stream: "INPUT:combined_input"
  output_stream: "VIDEO:video_stream"
}
node {
  calculator: "SomeVideoCalculator"
  input_stream: "VIDEO_IN:video_stream"
  output_stream: "VIDEO_OUT:processed_video"
}

入力ストリームと出力ストリームは、インデックス番号、タグ名、 タグ名とインデックス番号の組み合わせです。入力と出力の例をご覧いただき、 出力識別子の例を以下に示します。SomeAudioVideoCalculator の識別情報 タグによる動画出力と、タグとメタデータの組み合わせによる あります。タグ VIDEO の付いた入力が、次の名前のストリームに接続されています video_stream。タグ AUDIO、インデックス 01 を含む出力は次のとおりです。 audio_leftaudio_right という名前のストリームに接続されています。 SomeAudioCalculator は、インデックスのみでオーディオ入力を識別します(タグは必要ありません)。

# Graph describing calculator SomeAudioVideoCalculator
node {
  calculator: "SomeAudioVideoCalculator"
  input_stream: "combined_input"
  output_stream: "VIDEO:video_stream"
  output_stream: "AUDIO:0:audio_left"
  output_stream: "AUDIO:1:audio_right"
}

node {
  calculator: "SomeAudioCalculator"
  input_stream: "audio_left"
  input_stream: "audio_right"
  output_stream: "audio_energy"
}

計算ツールの実装では、入力と出力もタグで識別される 名前とインデックス番号が含まれます。以下の関数では、入力と出力が識別されます。

  • インデックス番号で識別: 結合された入力ストリームはインデックスで識別されます。 0
  • タグ名を使用: 動画出力ストリームはタグ名「VIDEO」で識別されます。
  • タグ名とインデックス番号を使用: 出力オーディオ ストリームはタグ名とインデックス番号によって識別されます。 タグ名 AUDIO とインデックス番号 0 および 1 の組み合わせ。
// c++ Code snippet describing the SomeAudioVideoCalculator GetContract() method
class SomeAudioVideoCalculator : public CalculatorBase {
 public:
  static absl::Status GetContract(CalculatorContract* cc) {
    cc->Inputs().Index(0).SetAny();
    // SetAny() is used to specify that whatever the type of the
    // stream is, it's acceptable.  This does not mean that any
    // packet is acceptable.  Packets in the stream still have a
    // particular type.  SetAny() has the same effect as explicitly
    // setting the type to be the stream's type.
    cc->Outputs().Tag("VIDEO").Set<ImageFrame>();
    cc->Outputs().Get("AUDIO", 0).Set<Matrix>();
    cc->Outputs().Get("AUDIO", 1).Set<Matrix>();
    return absl::OkStatus();
  }

処理中

非ソースノードで呼び出された Process() は、absl::OkStatus() を次の値に戻す必要があります。 すべて正常に完了したことを示すか、エラーを示す

ソース以外の計算ツールで tool::StatusStop() が返された場合、これは次のシグナルです。 グラフが早期にキャンセルされました。この場合 すべてのソース計算ツールとグラフは 閉じられます(残りのパケットはパケットが グラフ)。

グラフ内のソースノードでは、そのソースノードに対して Process() が呼び出され続けます。 absl::OkStatus( を返します。追加できるデータがないことを示す 生成される戻り値は tool::StatusStop() です。その他のステータスは、エラーが 発生しました。

成功した場合は、Close()absl::OkStatus() を返します。その他のステータス 失敗を示します。

基本的な Process() 関数は次のとおりです。Input() メソッドを使用します(このメソッドは、 は、計算ツールの入力が 1 つだけの場合にのみ使用)を使用して、その入力データをリクエストします。これは、 次に、std::unique_ptr を使用して、出力パケットに必要なメモリを割り当てます。 計算を行います。完了すると、リソースへの追加時にポインタが解放され、 作成されます。

absl::Status MyCalculator::Process() {
  const Matrix& input = Input()->Get<Matrix>();
  std::unique_ptr<Matrix> output(new Matrix(input.rows(), input.cols()));
  // do your magic here....
  //    output->row(n) =  ...
  Output()->Add(output.release(), InputTimestamp());
  return absl::OkStatus();
}

電卓オプション

計算機は(1)入力ストリーム パケット(2)を介して処理パラメータを受け入れる (3)計算ツールのオプションについて説明します。計算ツールのオプション(次の場合) node_options フィールドにリテラル値として表示される CalculatorGraphConfiguration.Node 件のメッセージがあります。

  node {
    calculator: "TfLiteInferenceCalculator"
    input_stream: "TENSORS:main_model_input"
    output_stream: "TENSORS:main_model_output"
    node_options: {
      [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
        model_path: "mediapipe/models/detection_model.tflite"
      }
    }
  }

node_options フィールドは proto3 構文を受け入れます。あるいは電卓 オプションは、proto2 構文を使用して options フィールドで指定できます。

  node {
    calculator: "TfLiteInferenceCalculator"
    input_stream: "TENSORS:main_model_input"
    output_stream: "TENSORS:main_model_output"
    node_options: {
      [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
        model_path: "mediapipe/models/detection_model.tflite"
      }
    }
  }

すべての計算ツールが電卓オプションに対応しているわけではありません。オプションを受け入れるには、 計算ツールは通常、新しい protobuf メッセージ型を定義して、 オプション(PacketClonerCalculatorOptions など)。料金計算ツールに CalculatorBase::Open メソッドでその protobuf メッセージを読み取る。場合によっては CalculatorBase::GetContract 関数または CalculatorBase::Process メソッドを使用します。通常、新しい protobuf メッセージ タイプは 「.proto」ファイルを使用して protobuf スキーマとして定義ファイルと mediapipe_proto_library() ビルドルール。

  mediapipe_proto_library(
      name = "packet_cloner_calculator_proto",
      srcs = ["packet_cloner_calculator.proto"],
      visibility = ["//visibility:public"],
      deps = [
          "//mediapipe/framework:calculator_options_proto",
          "//mediapipe/framework:calculator_proto",
      ],
  )

計算ツールの例

このセクションでは、PacketClonerCalculator の実装について説明します。 は比較的シンプルなジョブで、多くの計算ツールのグラフで使用されます。 PacketClonerCalculator は、最新の入力パケットのコピーを生成するだけです。 提供します

PacketClonerCalculator は、到着するデータパケットのタイムスタンプが 配置が完全ではありませんマイク、照明を備えた部屋に センサー、センサー、ビデオカメラです。各センサーは 独立して動作し、データが断続的に収集されます。出力が 各センサーの特徴は次のとおりです。

  • マイク = 室内の音の音量(デシベル単位、整数)
  • 光センサー = 部屋の明るさ(整数)
  • ビデオカメラ = 部屋の RGB 画像フレーム(ImageFrame)

私たちの単純な認識パイプラインは、これら 3 つからの感覚データを処理するように設計されています。 カメラから画像フレーム データを取得したときに 最後に収集したマイクの音量データとライトと同期します センサー輝度データ。MediaPipe でこれを行うために、認識パイプラインは 3 つの 入力ストリーム:

  • Room_mic_signal - この入力ストリームのデータの各パケットは整数データです 室内の音声の音量をタイムスタンプで表します。
  • Room_lightening_sensor - この入力ストリームのデータの各パケットは整数 部屋の明るさを表すタイムスタンプ付きのデータ。
  • Room_video_tick_signal - この入力ストリームのデータ パケットは、 カメラから収集された動画を表す動画データの画像フレーム タイムスタンプ付きのチャットルームです。

PacketClonerCalculator の実装は次のとおりです。詳しくは、 GetContract()Open()Process() の各メソッドとインスタンス 最新の入力パケットを保持する変数 current_

// This takes packets from N+1 streams, A_1, A_2, ..., A_N, B.
// For every packet that appears in B, outputs the most recent packet from each
// of the A_i on a separate stream.

#include <vector>

#include "absl/strings/str_cat.h"
#include "mediapipe/framework/calculator_framework.h"

namespace mediapipe {

// For every packet received on the last stream, output the latest packet
// obtained on all other streams. Therefore, if the last stream outputs at a
// higher rate than the others, this effectively clones the packets from the
// other streams to match the last.
//
// Example config:
// node {
//   calculator: "PacketClonerCalculator"
//   input_stream: "first_base_signal"
//   input_stream: "second_base_signal"
//   input_stream: "tick_signal"
//   output_stream: "cloned_first_base_signal"
//   output_stream: "cloned_second_base_signal"
// }
//
class PacketClonerCalculator : public CalculatorBase {
 public:
  static absl::Status GetContract(CalculatorContract* cc) {
    const int tick_signal_index = cc->Inputs().NumEntries() - 1;
    // cc->Inputs().NumEntries() returns the number of input streams
    // for the PacketClonerCalculator
    for (int i = 0; i < tick_signal_index; ++i) {
      cc->Inputs().Index(i).SetAny();
      // cc->Inputs().Index(i) returns the input stream pointer by index
      cc->Outputs().Index(i).SetSameAs(&cc->Inputs().Index(i));
    }
    cc->Inputs().Index(tick_signal_index).SetAny();
    return absl::OkStatus();
  }

  absl::Status Open(CalculatorContext* cc) final {
    tick_signal_index_ = cc->Inputs().NumEntries() - 1;
    current_.resize(tick_signal_index_);
    // Pass along the header for each stream if present.
    for (int i = 0; i < tick_signal_index_; ++i) {
      if (!cc->Inputs().Index(i).Header().IsEmpty()) {
        cc->Outputs().Index(i).SetHeader(cc->Inputs().Index(i).Header());
        // Sets the output stream of index i header to be the same as
        // the header for the input stream of index i
      }
    }
    return absl::OkStatus();
  }

  absl::Status Process(CalculatorContext* cc) final {
    // Store input signals.
    for (int i = 0; i < tick_signal_index_; ++i) {
      if (!cc->Inputs().Index(i).Value().IsEmpty()) {
        current_[i] = cc->Inputs().Index(i).Value();
      }
    }

    // Output if the tick signal is non-empty.
    if (!cc->Inputs().Index(tick_signal_index_).Value().IsEmpty()) {
      for (int i = 0; i < tick_signal_index_; ++i) {
        if (!current_[i].IsEmpty()) {
          cc->Outputs().Index(i).AddPacket(
              current_[i].At(cc->InputTimestamp()));
          // Add a packet to output stream of index i a packet from inputstream i
          // with timestamp common to all present inputs
        } else {
          cc->Outputs().Index(i).SetNextTimestampBound(
              cc->InputTimestamp().NextAllowedInStream());
          // if current_[i], 1 packet buffer for input stream i is empty, we will set
          // next allowed timestamp for input stream i to be current timestamp + 1
        }
      }
    }
    return absl::OkStatus();
  }

 private:
  std::vector<Packet> current_;
  int tick_signal_index_;
};

REGISTER_CALCULATOR(PacketClonerCalculator);
}  // namespace mediapipe

通常、計算ツールには .cc ファイルのみを使用します。.h は不要です。 Mediapipe は登録を使用して、計算ツールを認識します。新しい P-MAX キャンペーンを 計算クラスを定義したら、マクロ呼び出しで登録します。 REGISTER_CALCULATOR(calculator_class_name).

以下は、3 つの入力ストリームと 1 つのノードがある簡単な MediaPipe グラフです。 (PacketClonerCalculator)と 2 つの出力ストリーム。

input_stream: "room_mic_signal"
input_stream: "room_lighting_sensor"
input_stream: "room_video_tick_signal"

node {
   calculator: "PacketClonerCalculator"
   input_stream: "room_mic_signal"
   input_stream: "room_lighting_sensor"
   input_stream: "room_video_tick_signal"
   output_stream: "cloned_room_mic_signal"
   output_stream: "cloned_lighting_sensor"
 }

以下の図は、PacketClonerCalculator がその出力を定義する方法を示しています。 一連の入力パケットに基づくパケット(下)。

PacketClonerCalculator を使用してグラフを作成する
PacketClonerCalculator は、TICK 入力ストリームでパケットを受信するたびに、各入力ストリームから最新のパケットを出力します。出力パケットのシーケンス(下)は、入力パケットのシーケンス(上)とそのタイムスタンプによって決定されます。タイムスタンプは図の右側に表示されています。