Grafiken

Grafik

Ein CalculatorGraphConfig-Proto gibt die Topologie und Funktionalität eines MediaPipe-Diagramm Jeder node im Diagramm steht für einen bestimmten Rechner oder Teilgraph auf und gibt notwendige Konfigurationen an, z. B. registrierte Rechner-/Untergrafiktyp, Eingaben, Ausgaben und optionale Felder wie Knotenspezifische Optionen, Eingaberichtlinie und Executor, die in Synchronisierung.

CalculatorGraphConfig hat mehrere weitere Felder zum Konfigurieren der globalen Grafikebene Einstellungen, z.B. Graph Executor-Konfigurationen, Anzahl der Threads und maximale Warteschlangengröße von Eingabestreams. Mehrere Einstellungen auf Diagrammebene sind hilfreich, um den Leistung des Diagramms auf verschiedenen Plattformen (z. B. Computer und Mobilgeräte) Für können Sie auf Mobilgeräten einen umfangreichen Rechner für die Modellinferenz an einen separaten Executor kann die Leistung einer Echtzeitanwendung verbessern, da dies aktiviert die Thread-Lokalität.

Unten sehen Sie ein einfaches CalculatorGraphConfig-Beispiel, in dem eine Reihe von Passthrough-Rechner :

# 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 bietet eine alternative C++-Darstellung für komplexe Grafiken (z.B. ML-Pipelines, Verarbeitung von Modellmetadaten, optionale Knoten usw.). Das obige Diagramm könnte so aussehen:

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

Weitere Informationen finden Sie unter Grafiken in C++ erstellen.

Teildiagramm

Um ein CalculatorGraphConfig in untergeordnete Module zu modularisieren und bei der Wiederverwendung zu unterstützen Wahrnehmungslösungen kann ein MediaPipe-Diagramm als Subgraph definiert werden. Die Die öffentliche Schnittstelle eines Subgraphs besteht aus einer Reihe von Eingabe- und Ausgabestreams. ähnlich wie die öffentliche Benutzeroberfläche eines Rechners. Der Teilgraph kann dann in CalculatorGraphConfig wie ein Rechner. Wenn ein MediaPipe-Diagramm aus einem CalculatorGraphConfig geladen, wird jeder Teilgraphknoten durch den entsprechenden Rechner. Daraus folgt, dass die Semantik und Leistung des Teilgraphen identisch mit dem entsprechenden Rechnerdiagramm.

Im Folgenden finden Sie ein Beispiel für das Erstellen eines Teildiagramms mit dem Namen TwoPassThroughSubgraph.

  1. Definieren des Teilgraphs.

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

    Die öffentliche Schnittstelle für den Subgraph besteht aus:

    • Grafikeingabestreams
    • Grafikausgabestreams
    • Pakete auf der Eingabeseite grafisch darstellen
    • Ausgabeseitenpakete grafisch darstellen
  2. Registrieren Sie den Teilgraph mit der Build-Regel mediapipe_simple_subgraph. Die Der Parameter register_as definiert den Komponentennamen für die neue Teilgrafik.

    # 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. Verwenden Sie die Teilgrafik in der Hauptgrafik.

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

Grafikoptionen

Es ist möglich, Grafikoptionen protobuf für einen MediaPipe-Graphen ähnlich wie Calculator Options protobuf, der für einen MediaPipe-Rechner angegeben wurde. Diese "Grafikoptionen" kann sein wird angegeben, wo eine Grafik aufgerufen wird, und wird zum Ausfüllen von Rechneroptionen und Teilgrafikoptionen innerhalb der Grafik.

In einem CalculatorGraphConfig können Grafikoptionen für einen Teildiagramm angegeben werden genau wie bei den Rechneroptionen:

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

In einem CalculatorGraphConfig können Grafikoptionen akzeptiert und zum Ausfüllen verwendet werden Rechneroptionen wie unten gezeigt:

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

In diesem Beispiel akzeptiert FaceDetectionSubgraph die Graph-Option „protobuf“ FaceDetectionOptions. Mit FaceDetectionOptions wird ein Feld definiert Werte in den Rechneroptionen ImageToTensorCalculatorOptions und einigen Feldern Werte in den Teilgrafikoptionen InferenceCalculatorOptions Die Feldwerte werden mit der Syntax option_value: definiert.

Im Protokollzwischenspeicher CalculatorGraphConfig::Node werden die Felder node_options: und option_value: definieren gemeinsam die Optionswerte für einen Rechner: ImageToTensorCalculator. Das Feld node_options: definiert eine Reihe von Literalen mit der protobuf-Textsyntax konstante Werte festlegen. Jedes option_value:-Feld definiert den Wert für ein protobuf-Feld mithilfe von Informationen aus dem einschließenden insbesondere aus Feldwerten der Grafikoptionen der einschließenden Diagramm. Im obigen Beispiel hat die option_value: "output_tensor_width:options/tensor_width" definiert das Feld ImageToTensorCalculatorOptions.output_tensor_width mit dem Wert von FaceDetectionOptions.tensor_width.

Die Syntax von option_value: ähnelt der Syntax von input_stream:. Die Die Syntax lautet option_value: "LHS:RHS". Auf der linken Seite ist eine Taschenrechneroption zu sehen. und auf der rechten Seite das Feld mit den Grafikoptionen. Genauer gesagt, auf der linken Seite und RHS besteht jeweils aus einer Reihe von protobuf-Feldnamen, die verschachtelte protobuf-Nachrichten und -Felder, die durch '/' getrennt sind. Dies wird als „ProtoPath“ Syntax. Verschachtelte Nachrichten, auf die links oder rechts verwiesen wird, müssen bereits im einschließenden Protokollzwischenspeicher definiert, option_value:

Tages- & Jahreszeiten

Standardmäßig muss MediaPipe Rechnerdiagramme azyklisch sein und behandelt Zyklen in einer Grafik als Fehler an. Wenn ein Diagramm Zyklen enthalten soll, müssen die Zyklen in der Grafikkonfiguration annotiert werden. Auf dieser Seite wird beschrieben, wie Sie dazu vorgehen.

HINWEIS: Der aktuelle Ansatz ist experimentell und kann sich ändern. Gern begrüßen wir Feedback geben.

Verwenden Sie den Einheitentest CalculatorGraphTest.Cycle in mediapipe/framework/calculator_graph_test.cc als Beispielcode. Unten sehen Sie den zyklischen Graphen im Test. Die sum-Ausgabe des Addierers ist die Summe der Ganzzahlen, die vom Integer-Quellenrechner generiert wurden.

Ein zyklischer Graph, der einen Strom von Ganzzahlen addiert

Dieses einfache Diagramm veranschaulicht alle Probleme bei der Unterstützung zyklischer Graphen.

Back-Edge-Anmerkung

Eine Kante in jedem Zyklus muss als Hinterkante gekennzeichnet sein. Dadurch können Sie Die topologische Sortierung von MediaPipe funktioniert nach dem Entfernen aller Hinterkanten.

Normalerweise gibt es mehrere Möglichkeiten, die hinteren Kanten auszuwählen. Welche Kanten sind markiert? da Hinterkanten sich darauf auswirken, welche Knoten als Upstream angesehen werden und welche Knoten werden als nachgelagert betrachtet, was sich wiederum auf die Prioritäten auswirkt, die MediaPipe zuweist. zu den Knoten.

Zum Beispiel markiert der CalculatorGraphTest.Cycle-Test die Kante old_sum als Back Edge, damit der Delay-Knoten als nachgelagerter Knoten des Addierers betrachtet wird Knoten und erhält eine höhere Priorität. Alternativ können wir das sum-Objekt Eingabe für den Verzögerungsknoten als Back Edge. In diesem Fall wäre der Verzögerungsknoten wird als vorgelagerter Knoten des Adder-Knotens betrachtet und erhält eine niedrigere Priorität.

Anfangspaket

Damit der Additionsrechner ausgeführt werden kann, wenn die erste Ganzzahl der Ganzzahl und Quelle angekommen ist, brauchen wir ein erstes Paket mit dem Wert 0 Zeitstempel im old_sum-Eingabestream für die Addierer. Dieses erste Paket sollte vom Verzögerungsrechner in der Methode Open() ausgegeben werden.

Verzögerung in einer Schleife

In jeder Schleife sollte eine Verzögerung auftreten, um die vorherige sum-Ausgabe an die nächste anzupassen Ganzzahleingabe. Dies geschieht ebenfalls durch den Verzögerungsknoten. Der Verzögerungsknoten muss also über die Zeitstempel des Integer-Quellenrechners wissen:

  • Der Zeitstempel der ersten Ausgabe.

  • Zeitstempel-Delta zwischen aufeinanderfolgenden Ausgaben.

Wir planen die Einführung einer alternativen Planungsrichtlinie, die sich nur auf das Paket bezieht und ignoriert Paketzeitstempel, wodurch diese Unannehmlichkeiten vermieden werden.

Vorzeitige Beendigung eines Rechners nach Fertigstellung eines Eingabestreams

Standardmäßig ruft MediaPipe die Close()-Methode eines externen Rechners auf, wenn alle Eingabestreams abgeschlossen sind. In der Beispielgrafik wollen wir die Adder-Knoten hinzu, sobald die Ganzzahlquelle abgeschlossen ist. Dies wird erreicht, indem Konfigurieren des Adder-Knotens mit einem alternativen Eingabestream-Handler EarlyCloseInputStreamHandler

Relevanter Quellcode

Verzögerungsrechner

Achten Sie auf den Code in Open(), der das ursprüngliche Paket und den Code in Process(), die den Eingabepaketen eine (Einheits-)Verzögerung hinzufügt. Wie bereits erwähnt, Verzögerungsknoten geht davon aus, dass sein Ausgabestream zusammen mit einem Eingabestream mit Paketzeitstempel 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();
  }
};

Grafikkonfiguration

Beachten Sie die Annotation back_edge und die alternative Annotation 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'
}