Grafik
Ein CalculatorGraphConfig
-Proto gibt die Topologie und Funktionalität einer MediaPipe-Grafik an. Jede node
in der Grafik stellt einen bestimmten Rechner oder Teildiagramm dar und gibt erforderliche Konfigurationen wie den registrierten Taschenrechner-/Teilgrafiktyp, Eingaben, Ausgaben und optionale Felder wie knotenspezifische Optionen, Eingaberichtlinie und Executor an, wie unter Synchronisierung erläutert.
CalculatorGraphConfig
enthält einige weitere Felder zum Konfigurieren globaler Einstellungen auf Grafikebene, z.B. Konfigurationen für Executor-Grafiken, Anzahl der Threads und maximale Warteschlangengröße von Eingabestreams. Verschiedene Einstellungen auf Grafikebene sind nützlich, um die Leistung der Grafik auf verschiedenen Plattformen (z. B. Computer und Mobilgeräte) zu optimieren. Auf Mobilgeräten kann beispielsweise die Leistung einer Echtzeitanwendung verbessert werden, wenn ein separater Executor mit einem Rechner für hohe Modellinferenzen verbunden ist, da dies den Thread-Lokalität ermöglicht.
Unten sehen Sie ein einfaches CalculatorGraphConfig
-Beispiel mit einer Reihe von Durchlaufrechnern :
# 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 Diagramm oben 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 Diagramme in C++ erstellen.
Teilgrafik
Um ein CalculatorGraphConfig
in Untermodule zu modularisieren und die Wiederverwendung von Wahrnehmungslösungen zu unterstützen, kann eine MediaPipe-Grafik als Subgraph
definiert werden. Die öffentliche Schnittstelle einer Teilgrafik besteht aus einer Reihe von Eingabe- und Ausgabestreams, ähnlich wie bei der öffentlichen Schnittstelle eines Taschenrechners. Die Teilgrafik kann dann so in eine CalculatorGraphConfig
aufgenommen werden, als wäre es ein Taschenrechner. Wenn eine MediaPipe-Grafik aus einem CalculatorGraphConfig
geladen wird, wird jeder Teilgrafikknoten durch die entsprechende Grafik von Rechnern ersetzt. Daher sind die Semantik und Leistung der Teilgrafik mit der entsprechenden Grafik von Taschenrechnern identisch.
Im Folgenden finden Sie ein Beispiel für die Erstellung einer Teilgrafik mit dem Namen TwoPassThroughSubgraph
.
Teilgrafik definieren
# 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 zum Teildiagramm besteht aus:
- Grafikeingabestreams
- Ausgabestreams grafisch darstellen
- Pakete der Eingabeseite grafisch darstellen
- Pakete der Ausgabeseite grafisch darstellen
Registrieren Sie die Teilgrafik mit der build-Regel
mediapipe_simple_subgraph
. Der Parameterregister_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", ], )
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, einen Protokollpuffer vom Typ „Grafikoptionen“ für eine MediaPipe-Grafik anzugeben, ähnlich wie beim Calculator Options
-Protokollpuffer, der für einen MediaPipe-Rechner angegeben wurde. Diese „Grafikoptionen“ können beim Aufrufen einer Grafik angegeben und zum Ausfüllen von Taschenrechner- und Teilgrafikoptionen in der Grafik verwendet werden.
In einer CalculatorGraphConfig
können Diagrammoptionen für eine Teilgrafik genau wie Taschenrechneroptionen angegeben werden, wie unten dargestellt:
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 Diagrammoptionen akzeptiert und zum Ausfüllen von Taschenrechneroptionen verwendet werden, wie unten dargestellt:
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 der FaceDetectionSubgraph
die Grafikoption protobuf FaceDetectionOptions
. Mit FaceDetectionOptions
werden einige Feldwerte in den Rechneroptionen ImageToTensorCalculatorOptions
und einige Feldwerte in den Teilgrafikoptionen InferenceCalculatorOptions
definiert. Die Feldwerte werden mit der Syntax option_value:
definiert.
Im Protokollzwischenspeicher CalculatorGraphConfig::Node
definieren die Felder node_options:
und option_value:
zusammen die Optionswerte für einen Rechner wie ImageToTensorCalculator
. Das Feld node_options:
definiert mithilfe der Text-protobuf-Syntax eine Reihe von konstanten Literalwerten. Jedes option_value:
-Feld definiert den Wert für ein protobuf-Feld anhand von Informationen aus der einschließenden Grafik, insbesondere anhand der Feldwerte der Grafikoptionen des einschließenden Diagramms. Im Beispiel oben definiert option_value:
"output_tensor_width:options/tensor_width"
das Feld ImageToTensorCalculatorOptions.output_tensor_width
mithilfe des Werts von FaceDetectionOptions.tensor_width
.
Die Syntax von option_value:
ähnelt der Syntax von input_stream:
. Die Syntax lautet option_value: "LHS:RHS"
. Auf der linken Seite steht ein Taschenrechner-Optionsfeld, auf der rechten Seite ein Diagrammoptionsfeld. Genauer gesagt, bestehen die LHS- und die RHS jeweils aus einer Reihe von protobuf-Feldnamen, die verschachtelte Protokollpuffer-Nachrichten und -Felder identifizieren, die durch „/“ getrennt sind. Dies wird als „ProtoPath“-Syntax bezeichnet. Verschachtelte Nachrichten, auf die auf der linken oder rechten Seite verwiesen wird, müssen bereits im einschließenden Protokollzwischenspeicher definiert sein, damit sie mit option_value:
durchlaufen werden können.
Tages- & Jahreszeiten
Standardmäßig erfordert MediaPipe, dass Taschenrechnerdiagramme azyklisch sind, und behandelt Zyklen in einer Grafik als Fehler. Wenn eine Grafik Zyklen enthalten soll, müssen diese in der Grafikkonfiguration annotiert werden. Auf dieser Seite wird beschrieben, wie Sie das tun können.
HINWEIS: Der aktuelle Ansatz ist experimentell und kann sich ändern. Wir freuen uns auf Ihr Feedback.
Verwenden Sie den CalculatorGraphTest.Cycle
-Einheitentest 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 vom Integer-Source-Rechner generierten Ganzzahlen.
Diese einfache Grafik veranschaulicht alle Probleme in unterstützenden zyklischen Graphen.
Anmerkung zum hinteren Rand
Eine Kante in jedem Zyklus muss als hintere Kante annotiert sein. Dadurch funktioniert die topologische Sortierung von MediaPipe, nachdem alle hinteren Ränder entfernt wurden.
Es gibt normalerweise mehrere Möglichkeiten, die hinteren Ränder auszuwählen. Welche Kanten als hintere Kanten markiert sind, wirkt sich darauf aus, welche Knoten als vorgelagert und welche als nachgelagert gelten, was wiederum die Prioritäten beeinflusst, die MediaPipe den Knoten zuweist.
Beispielsweise markiert der CalculatorGraphTest.Cycle
-Test den old_sum
-Kanten als Back-Edge. Daher wird der Verzögerungsknoten als Downstream-Knoten des Adder-Knotens betrachtet und erhält eine höhere Priorität. Alternativ könnten wir die sum
-Eingabe für den Verzögerungsknoten als Back-Edge markieren. In diesem Fall würde der Verzögerungsknoten als vorgelagerter Knoten des Adder-Knotens betrachtet und eine niedrigere Priorität erhalten.
Erstes Paket
Damit der Adder-Rechner ausgeführt werden kann, wenn die erste Ganzzahl aus der Ganzzahlquelle eintrifft, benötigen wir ein Anfangspaket mit dem Wert 0 und demselben Zeitstempel im old_sum
-Eingabestream zum Adder. Dieses erste Paket sollte vom Verzögerungsrechner in der Methode Open()
ausgegeben werden.
Verzögerung in einer Schleife
Für jede Schleife sollte eine Verzögerung auftreten, um die vorherige sum
-Ausgabe auf die nächste Ganzzahleingabe auszurichten. Dies geschieht auch durch den Verzögerungsknoten. Daher muss der Verzögerungsknoten folgende Informationen über die Zeitstempel des Integer-Source-Rechners haben:
Der Zeitstempel der ersten Ausgabe.
Die Zeitstempeldifferenz zwischen aufeinanderfolgenden Ausgaben.
Wir planen, eine alternative Planungsrichtlinie hinzuzufügen, die nur die Paketreihenfolge berücksichtigt und Paketzeitstempel ignoriert, wodurch diese Unannehmlichkeiten beseitigt werden.
Vorzeitiges Beenden eines Taschenrechners, wenn ein Eingabestream abgeschlossen ist
Standardmäßig ruft MediaPipe die Methode Close()
eines Nicht-Quell-Rechners auf, wenn alle Eingabestreams abgeschlossen sind. In der Beispielgrafik soll der Adder-Knoten beendet werden, sobald die Ganzzahlquelle fertig ist. Dazu wird der Adder-Knoten mit dem alternativen Eingabestream-Handler EarlyCloseInputStreamHandler
konfiguriert.
Relevanter Quellcode
Verzögerungsrechner
Beachten Sie den Code in Open()
, der das erste Paket ausgibt, und den Code in Process()
, der den Eingabepaketen eine Verzögerung (Einheit) hinzufügt. Wie oben erwähnt, geht dieser Verzögerungsknoten davon aus, dass sein Ausgabestream neben einem Eingabestream mit den Paketzeitstempeln 0, 1, 2, 3, ... verwendet wird.
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 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'
}