Graphique
Un proto CalculatorGraphConfig
spécifie la topologie et les fonctionnalités d'un
Graphique MediaPipe. Chaque node
du graphique représente un calculateur ou
et spécifie les configurations nécessaires, comme les enregistrements
le type de calculatrice/sous-graphique, les entrées, les sorties et les champs facultatifs tels que
les options spécifiques aux nœuds, la stratégie d'entrée et l'exécuteur, abordés dans
Synchronisation.
CalculatorGraphConfig
comporte plusieurs autres champs permettant de configurer le graphique global
paramètres (par exemple, graphiques des configurations d'exécuteur, du nombre de threads et de la taille maximale de la file d'attente
de flux d'entrée. Plusieurs paramètres au niveau du graphique permettent de régler
les performances du graphique sur différentes plates-formes (ordinateur plutôt que mobile, par exemple). Pour
sur un appareil mobile, en connectant un calculateur d'inférence de modèle lourd à
d'exécution peut améliorer les performances d'une application en temps réel,
active la localité des threads.
Vous trouverez ci-dessous un exemple de CalculatorGraphConfig
trivial dans lequel nous avons une série
calculateurs passthrough :
# 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 propose une autre représentation C++
pour les graphiques complexes (par exemple, les pipelines de ML, la gestion des métadonnées du modèle, les nœuds facultatifs, etc.). Le graphique ci-dessus pourrait se présenter comme suit:
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();
}
Pour en savoir plus, consultez Créer des graphiques en C++.
Sous-graphique
Modulariser un CalculatorGraphConfig
en sous-modules et faciliter la réutilisation
des solutions de perception, un graphique MediaPipe peut être défini comme un Subgraph
. La
l'interface publique d'un sous-graphique est constituée d'un ensemble de flux d'entrée et de sortie
similaire à l'interface publique
d'une calculatrice. Le sous-graphique peut ensuite être inclus
un CalculatorGraphConfig
comme s'il s'agissait d'une calculatrice. Lorsqu'un graphique MediaPipe est
chargé à partir d'un élément CalculatorGraphConfig
, chaque nœud de sous-graphique est remplacé par le
graphique de calculatrices correspondant. Par conséquent, la sémantique et les performances
du sous-graphique est identique au
graphique de calculatrices correspondant.
L'exemple ci-dessous montre comment créer un sous-graphique nommé TwoPassThroughSubgraph
.
Définir le sous-graphique
# 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" }
L'interface publique du sous-graphique comprend les éléments suivants:
- Représenter graphiquement des flux d'entrée
- Représenter graphiquement les flux de sortie
- Représenter graphiquement les paquets côté entrée
- Représenter graphiquement les paquets côté sortie
Enregistrez le sous-graphique à l'aide de la règle de compilation
mediapipe_simple_subgraph
. La Le paramètreregister_as
définit le nom du composant pour le nouveau sous-graphique.# 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", ], )
Utilisez le sous-graphique dans le graphique principal.
# 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" }
Options du graphique
Il est possible de spécifier des "options du graphique" Protobuf pour un graphique MediaPipe
semblable à Calculator Options
protobuf spécifié pour un calculateur MediaPipe. Ces "options du graphique" peut être
spécifié où un graphique est appelé, et utilisé pour renseigner les options du calculateur et
dans le graphique.
Dans un CalculatorGraphConfig
, les options du graphique peuvent être spécifiées pour un sous-graphique
exactement comme les options de la calculatrice, comme illustré ci-dessous:
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
}
}
}
Dans une CalculatorGraphConfig
, les options du graphique peuvent être acceptées et utilisées pour remplir
de la calculatrice, comme illustré ci-dessous:
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"
}
Dans cet exemple, FaceDetectionSubgraph
accepte le tampon de protocole de l'option de graphique
FaceDetectionOptions
FaceDetectionOptions
permet de définir un champ
les valeurs des options du calculateur ImageToTensorCalculatorOptions
et un champ
les valeurs des options du sous-graphique InferenceCalculatorOptions
. Les valeurs des champs
sont définies à l'aide de la syntaxe option_value:
.
Dans le tampon de protocole CalculatorGraphConfig::Node
, les champs node_options:
et
option_value:
définissent ensemble les valeurs d'option pour un calculateur tel que
ImageToTensorCalculator
Le champ node_options:
définit un ensemble de valeurs littérales
des valeurs constantes à l'aide de la syntaxe
protobuf du texte. Chaque champ option_value:
définit la valeur d'un champ protobuf à l'aide des informations du
à partir des valeurs des champs des options du graphique
graphique. Dans l'exemple ci-dessus, option_value:
"output_tensor_width:options/tensor_width"
définit le champ
ImageToTensorCalculatorOptions.output_tensor_width
avec la valeur de
FaceDetectionOptions.tensor_width
La syntaxe de option_value:
est semblable à celle de input_stream:
. La
la syntaxe est option_value: "LHS:RHS"
. Le LHS identifie une option de calculateur
et le RHS identifie un champ d'option de graphique. Plus précisément, le LHS
et RHS se compose chacun d'une série de noms de champs protobuf identifiant les
messages et champs protobuf séparés par "/". C'est ce qu'on appelle le "ProtoPath".
syntaxe. Les messages imbriqués référencés dans le texte LHS ou RHS doivent déjà être
définis dans le tampon de protocole englobant afin d'être balayés
option_value:
Cycles
Par défaut, MediaPipe exige que les graphiques du calculateur soient acycliques et traite les cycles dans un graphique comme des erreurs. Si un graphique est censé comporter des cycles, ceux-ci doivent être annotées dans la configuration du graphique. Cette page explique comment procéder.
REMARQUE: L'approche actuelle est expérimentale et susceptible d'être modifiée. Bienvenue vos commentaires.
Veuillez utiliser le test unitaire CalculatorGraphTest.Cycle
dans
mediapipe/framework/calculator_graph_test.cc
comme exemple de code. Ci-dessous,
le graphe cyclique dans le test. La sortie sum
de l'additionur est la somme des
entiers générés par le simulateur de source d'entiers.
Ce graphique simple illustre tous les problèmes liés à la compatibilité des graphiques cycliques.
Annotation du bord arrière
Nous exigeons qu'une arête dans chaque cycle soit annotée en tant qu'arête arrière. Cela permet Le tri topologique de MediaPipe fonctionne après la suppression de tous les bords arrière.
Il existe généralement plusieurs façons de sélectionner les bords arrière. les arêtes marquées car elles déterminent quels nœuds sont considérés comme en amont sont considérées comme en aval, ce qui affecte à leur tour les priorités attribuées par MediaPipe ; aux nœuds.
Par exemple, le test CalculatorGraphTest.Cycle
marque le bord old_sum
comme
bord arrière, de sorte que le nœud Delay est considéré comme un nœud en aval de l'adder
et se voit attribuer une priorité plus élevée. Nous pouvons également marquer la sum
du nœud de retard en tant qu'arête arrière, auquel cas le nœud de retard
est considéré comme un nœud en amont du nœud adder et se voit attribuer une priorité inférieure.
Paquet initial
Pour que le calculateur d'addition soit exécutable lorsque le premier entier de l'entier
source arrive, nous avons besoin d'un paquet initial, de valeur 0 et de même
du code temporel, sur le flux d'entrée old_sum
vers l'additionneur. Ce paquet initial
doit être généré par le calculateur de retard dans la méthode Open()
.
Retard d'une boucle
Chaque boucle doit engendrer un délai pour aligner la sortie sum
précédente avec la suivante
(nombre entier). C'est également le nœud de retard qui se charge de cette opération. Le nœud de retard doit donc
Notez les points suivants concernant les codes temporels du simulateur de source d'entiers:
Code temporel de la première sortie.
Delta du code temporel entre les sorties successives
Nous prévoyons d'ajouter une autre règle de planification qui ne concerne que les paquets le tri et ignore les horodatages des paquets, ce qui élimine ce désagrément.
Arrêt anticipé d'un simulateur à la fin d'un flux d'entrée
Par défaut, MediaPipe appelle la méthode Close()
d'un calculateur non source lorsque
que tous ses flux d'entrée sont terminés. Dans cet exemple, nous voulons arrêter
d'addition dès que la source de l'entier est terminée. Pour ce faire,
configurer le nœud adder avec un autre gestionnaire de flux d'entrée
EarlyCloseInputStreamHandler
Code source pertinent
Calculateur de délai
Notez le code dans Open()
qui génère le paquet initial et le code dans
Process()
qui ajoute un délai (unité) aux paquets d'entrée. Comme indiqué ci-dessus,
un nœud de retard suppose que son flux de sortie est utilisé avec un flux d'entrée
codes temporels des paquets 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();
}
};
Configuration du graphique
Notez l'annotation back_edge
et l'autre 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'
}