Grafico
Un protocollo CalculatorGraphConfig
specifica la topologia e la funzionalità di una
Grafico MediaPipe. Ciascun node
nel grafico rappresenta una determinata calcolatrice o
grafico secondario e specifica le configurazioni necessarie, come la registrazione
Tipo di calcolatrice/sottografo, input, output e campi facoltativi, ad esempio
le opzioni specifiche per i nodi, il criterio di input e l'esecutore,
Sincronizzazione.
In CalculatorGraphConfig
sono disponibili diversi altri campi per la configurazione globale a livello di grafico
impostazioni, ad esempio configurazioni degli esecutori grafici, numero di thread e dimensione massima della coda
di flussi di input. Diverse impostazioni a livello di grafico sono utili per
il rendimento del grafico su diverse piattaforme (ad es. computer o dispositivo mobile). Per
Ad esempio, sui dispositivi mobili, il collegamento di un calcolatore pesante di inferenza di modelli a un
un esecutore può migliorare le prestazioni di un'applicazione in tempo reale poiché
abilita la località dei thread.
Di seguito è riportato un banale esempio di CalculatorGraphConfig
in cui sono presenti serie di
calcolatori 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 offre una rappresentazione C++
alternativa per grafici complessi (ad es. pipeline ML, gestione di metadati del modello, nodi facoltativi e così via). Il grafico sopra può avere il seguente aspetto:
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();
}
Per maggiori dettagli, consulta l'articolo Creare grafici in C++.
Grafico secondario
Per modularizzare un CalculatorGraphConfig
in sottomoduli e facilitare il riutilizzo
delle soluzioni di percezione, un grafico MediaPipe può essere definito come Subgraph
. La
l'interfaccia pubblica di un grafico secondario è composta da un insieme di flussi di input e output
in modo simile all'interfaccia pubblica di una calcolatrice. Il grafico secondario può quindi essere incluso
un CalculatorGraphConfig
come se fosse una calcolatrice. Quando un grafico MediaPipe
caricati da un CalculatorGraphConfig
, ogni nodo del sottografo viene sostituito
grafico corrispondente delle calcolatrici. Di conseguenza, la semantica e il rendimento
del grafico secondario è identico al corrispondente grafico delle calcolatrici.
Di seguito è riportato un esempio di come creare un grafico secondario denominato TwoPassThroughSubgraph
.
Definizione del grafico secondario.
# 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'interfaccia pubblica verso il grafico secondario è composta da:
- Tracciare un grafico dei flussi di input
- Tracciare grafici dei flussi di output
- Tracciare il grafico dei pacchetti lato di input
- Tracciare il grafico dei pacchetti lato di output
Registra il grafico secondario utilizzando la regola di creazione
mediapipe_simple_subgraph
. La Il parametroregister_as
definisce il nome del componente del nuovo grafico secondario.# 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", ], )
Utilizza il grafo secondario nel grafico principale.
# 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" }
Opzioni grafico
È possibile specificare "opzioni grafico" protobuf per un grafico MediaPipe
simile a Calculator Options
protobuf specificato per un calcolatore MediaPipe. Queste "opzioni di grafico" può essere
specificato dove viene richiamato un grafico e utilizzato per compilare le opzioni della calcolatrice
le opzioni dei grafi secondari all'interno del grafico.
In un CalculatorGraphConfig
, è possibile specificare le opzioni di grafico per un grafico secondario
esattamente come le opzioni della calcolatrice, come mostrato di seguito:
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 un CalculatorGraphConfig
, le opzioni del grafico possono essere accettate e utilizzate per compilare
calcolatrice, come mostrato di seguito:
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 questo esempio, FaceDetectionSubgraph
accetta l'opzione grafico protobuf
FaceDetectionOptions
. FaceDetectionOptions
viene utilizzato per definire alcuni campi
valori nelle opzioni della calcolatrice ImageToTensorCalculatorOptions
e in alcuni campi
nelle opzioni del grafico secondario InferenceCalculatorOptions
. I valori dei campi
vengono definiti con la sintassi option_value:
.
Nel protobuf CalculatorGraphConfig::Node
, i campi node_options:
e
option_value:
definiscono insieme i valori delle opzioni per una calcolatrice come
ImageToTensorCalculator
. Il campo node_options:
definisce un insieme di valori letterali
di valori costanti usando la sintassi di testo protobuf. Ogni campo option_value:
definisce il valore di un campo protobuf utilizzando le informazioni del
e in particolare dai valori dei campi delle opzioni di grafico del
grafico. Nell'esempio precedente, il valore option_value:
"output_tensor_width:options/tensor_width"
definisce il campo
ImageToTensorCalculatorOptions.output_tensor_width
utilizzando il valore di
FaceDetectionOptions.tensor_width
.
La sintassi di option_value:
è simile a quella di input_stream:
. La
la sintassi è option_value: "LHS:RHS"
. Il riquadro LHS identifica l'opzione per la calcolatrice
e il campo RHS identifica un campo opzione grafico. Più precisamente, la LHS
e RHS ciascuna consiste in una serie di nomi di campi protobuf che identificano
messaggi protobuf e campi separati da "/". Questo è chiamato "ProtoPath"
a riga di comando. I messaggi nidificati a cui si fa riferimento in LHS o RHS devono essere già
definita nel relativo protobuf in modo da essere attraversata utilizzando
option_value:
.
Momenti
Per impostazione predefinita, MediaPipe richiede che i grafici delle calcolatrici siano aciclici e tratta i cicli in un grafico come errori. Se un grafico deve avere dei cicli, questi ultimi devono annotati nella configurazione del grafico. In questa pagina viene descritto come fare.
NOTA: l'approccio attuale è sperimentale e soggetto a modifiche. Diamo il benvenuto il tuo feedback.
Usa il test delle unità CalculatorGraphTest.Cycle
in
mediapipe/framework/calculator_graph_test.cc
come codice di esempio. La figura seguente mostra
il grafo ciclico nel test. L'output sum
del sommatore è la somma dei
numeri interi generati dal calcolatore di origine.
Questo semplice grafico illustra tutti i problemi nei grafi ciclici di supporto.
Annotazione bordo posteriore
Richiediamo che un bordo in ogni ciclo sia annotato come bordo posteriore. Ciò consente L'ordinamento topologico di MediaPipe funzionerà dopo aver rimosso tutti i bordi posteriori.
Di solito sono disponibili diversi modi per selezionare i bordi posteriori. Quali bordi sono contrassegnati poiché i back edge influiscono sui nodi considerati upstream e quali vengono da considerare come downstream, il che a sua volta influisce sulle priorità assegnate da MediaPipe ai nodi.
Ad esempio, il test CalculatorGraphTest.Cycle
contrassegna il bordo old_sum
come
back-edge, quindi il nodo Delay è considerato un nodo downstream del sommatore
nodo, a cui viene assegnata una priorità più alta. In alternativa, potremmo contrassegnare sum
al nodo di ritardo come bordo posteriore, nel qual caso il nodo di ritardo sarebbe
considerato come un nodo a monte del nodo sommatore e a cui viene assegnata una priorità inferiore.
Pacchetto iniziale
Affinché il calcolatore sommatore sia eseguibile quando il primo numero intero
all'origine, abbiamo bisogno di un pacchetto iniziale, con valore 0 e
timestamp, sul flusso di input old_sum
al sommatore. Questo pacchetto iniziale
deve essere generato dalla calcolatrice del ritardo nel metodo Open()
.
Ritardo in un loop
Ogni ciclo dovrebbe subire un ritardo per allineare l'output sum
precedente al successivo
un valore intero. Questo viene fatto anche dal nodo di ritardo. Quindi il nodo di ritardo deve
informazioni sui timestamp del calcolatore dell'origine dei numeri interi:
Timestamp del primo output.
Il delta del timestamp tra output successivi.
Prevediamo di aggiungere un criterio di pianificazione alternativo che interessa solo il pacchetto ed ignora i timestamp dei pacchetti, eliminando così l'inconveniente.
Chiusura anticipata di un calcolatore quando si esegue un unico flusso di input
Per impostazione predefinita, MediaPipe chiama il metodo Close()
di un calcolatore non di origine quando
tutti i suoi flussi di input. Nel grafico di esempio, vogliamo interrompere
adder non appena viene completata l'origine del numero intero. Ciò si ottiene
configurando il nodo adder con un gestore di flussi di input alternativo,
EarlyCloseInputStreamHandler
.
Codice sorgente pertinente
Calcolatore dei ritardi
Prendi nota del codice in Open()
che restituisce il pacchetto iniziale e del codice
Process()
che aggiunge un ritardo (unità) ai pacchetti di input. Come osservato in precedenza,
il nodo di ritardo presuppone che il suo flusso di output venga utilizzato insieme a un flusso di input con
timestamp dei pacchetti 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();
}
};
Configurazione grafico
Osserva l'annotazione back_edge
e l'alternativa 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'
}