Graphiques

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.

  1. 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
  2. Enregistrez le sous-graphique à l'aide de la règle de compilation mediapipe_simple_subgraph. La Le paramètre register_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",
        ],
    )
    
  3. 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.

un graphe cyclique qui ajoute un flux d&#39;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'
}