Gráfico
Um proto CalculatorGraphConfig
especifica a topologia e a funcionalidade de
Gráfico do MediaPipe. Cada node
no gráfico representa uma calculadora ou
e especifica as configurações necessárias, como registros
calculadora/subgráfico, entradas, saídas e campos opcionais, como
opções específicas de nó, política de entrada e executor, discutidas em
Sincronização.
CalculatorGraphConfig
tem vários outros campos para configurar o nível do gráfico global
configurações, por exemplo, configurações do executor do gráfico, número de linhas de execução e tamanho máximo da fila
de fluxos de entrada. Várias configurações no nível do gráfico são úteis para ajustar
desempenho do gráfico em diferentes plataformas (por exemplo, computador x dispositivo móvel). Para
em dispositivos móveis, anexar uma calculadora pesada de inferência de modelo
executor pode melhorar o desempenho de um aplicativo em tempo real,
ativa a localidade da linha de execução.
Confira abaixo um exemplo trivial de CalculatorGraphConfig
, em que temos uma série de
calculadoras de passagem dupla :
# 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"
}
O MediaPipe oferece uma representação alternativa de C++
para gráficos complexos, como pipelines de ML, gerenciamento de metadados de modelo, nós opcionais etc. O gráfico acima pode ter esta aparência:
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();
}
Veja mais detalhes em Como criar gráficos em C++.
Subgráfico
Modularizar um CalculatorGraphConfig
em submódulos e ajudar na reutilização
de soluções de percepção, um gráfico do MediaPipe pode ser definido como um Subgraph
. A
A interface pública de um subgráfico consiste em um conjunto de streams de entrada e saída
semelhante à interface pública de uma calculadora. Assim, o subgráfico pode ser incluído
uma CalculatorGraphConfig
como se fosse uma calculadora. Quando um gráfico do MediaPipe
carregado de um CalculatorGraphConfig
, cada nó do subgráfico é substituído pelo
gráfico correspondente de calculadoras. Como resultado, a semântica e o desempenho
do subgráfico é idêntico ao gráfico de calculadoras correspondente.
Confira abaixo um exemplo de como criar um subgráfico chamado TwoPassThroughSubgraph
.
Definindo o subgráfico.
# 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" }
A interface pública para o subgráfico consiste em:
- Criar gráficos de streams de entrada
- Criar gráficos de streams de saída
- Representar graficamente pacotes laterais de entrada
- Representar graficamente pacotes laterais de saída
Registre o subgráfico usando a regra BUILD
mediapipe_simple_subgraph
. A O parâmetroregister_as
define o nome do componente para o novo subgráfico.# 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", ], )
Use o subgráfico no gráfico 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" }
Opções de gráfico
É possível especificar "opções do gráfico" protobuf para um gráfico do MediaPipe
semelhante ao Calculator Options
protobuf especificado para uma calculadora do MediaPipe. Essas "opções de gráfico" podem ser
especificado onde um gráfico é invocado e usado para preencher opções de calculadora e
as opções do subgráfico dentro do gráfico.
Em um CalculatorGraphConfig
, é possível especificar opções de gráfico para um subgráfico
exatamente como as opções de calculadora, como mostrado abaixo:
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
}
}
}
Em um CalculatorGraphConfig
, as opções de gráfico podem ser aceitas e usadas para preencher
calculadora, como mostrado abaixo:
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"
}
Neste exemplo, o FaceDetectionSubgraph
aceita o protobuf da opção de gráfico.
FaceDetectionOptions
. O FaceDetectionOptions
é usado para definir algum campo
nas opções da calculadora ImageToTensorCalculatorOptions
e em algum campo
nas opções de subgráfico InferenceCalculatorOptions
. Os valores dos campos
são definidas usando a sintaxe option_value:
.
No protobuf CalculatorGraphConfig::Node
, os campos node_options:
e
A função option_value:
define os valores das opções para uma calculadora, como
ImageToTensorCalculator
. O campo node_options:
define um conjunto de valores
valores constantes usando a sintaxe protobuf de texto. Cada campo option_value:
define o valor de um campo protobuf usando as informações do campo
gráfico, especificamente de valores de campo das opções gráficas do objeto
gráfico. No exemplo acima, o option_value:
"output_tensor_width:options/tensor_width"
define o campo
ImageToTensorCalculatorOptions.output_tensor_width
usando o valor de
FaceDetectionOptions.tensor_width
.
A sintaxe de option_value:
é semelhante à sintaxe de input_stream:
. A
a sintaxe é option_value: "LHS:RHS"
. O LHS identifica uma opção de calculadora
e RHS identifica um campo de opção de gráfico. Mais especificamente, o LHS
e RHS consiste em uma série de nomes de campo protobuf que identificam
Mensagens e campos protobuf separados por "/". Isso é conhecido como "ProtoPath"
. As mensagens aninhadas referenciadas no LHS ou RHS já devem ser
definidos no protobuf incluído para ser processado usando
option_value:
:
Ciclos
Por padrão, o MediaPipe exige que os gráficos da calculadora sejam acíclicos e trata ciclos. em um gráfico como erros. Se a intenção é que um gráfico tenha ciclos, eles precisam com anotações na configuração do gráfico. Esta página descreve como fazer isso.
OBSERVAÇÃO: a abordagem atual é experimental e está sujeita a mudanças. Aceitamos seu feedback.
Use o teste de unidade CalculatorGraphTest.Cycle
em
mediapipe/framework/calculator_graph_test.cc
como exemplo de código. O exemplo abaixo
o gráfico cíclico no teste. A saída sum
do somador é a soma:
números inteiros gerados pela calculadora de origem de números inteiros.
Este gráfico simples ilustra todos os problemas no suporte a gráficos cíclicos.
Anotação da borda traseira
É necessário que uma borda em cada ciclo seja anotada como uma borda de fundo. Isso permite Classificação topológica do MediaPipe para funcionar após a remoção de todas as bordas de trás.
Geralmente, há várias maneiras de selecionar as bordas traseiras. Quais bordas estão marcadas já que os back-ends afetam quais nós são considerados como upstream e quais considerados downstream, o que, por sua vez, afeta as prioridades atribuídas pelo MediaPipe aos nós.
Por exemplo, o teste CalculatorGraphTest.Cycle
marca a borda old_sum
como uma
na borda de retorno, ou seja, o nó de atraso é considerado um nó downstream do
nó e recebe uma prioridade mais alta. Como alternativa, podemos marcar o sum
entrada para o nó de atraso como a borda de fundo. Nesse caso, o nó de atraso seria
considerado um nó upstream do nó de somador e recebe uma prioridade mais baixa.
Pacote inicial
Para que a calculadora do somatório possa ser executada quando o primeiro número inteiro do número inteiro
origem chegar, precisamos de um pacote inicial, com valor 0 e com a mesma
carimbo de data/hora, no fluxo de entrada old_sum
para o adicionador. O pacote inicial
será gerado pela calculadora de atraso no método Open()
.
Atraso em um loop
Cada repetição precisa gerar um atraso para alinhar a saída sum
anterior com a próxima
a entrada de um número inteiro. Isso também é feito pelo nó de atraso. Portanto, o nó de atraso precisa
saber o seguinte sobre os carimbos de data/hora da calculadora de fonte de números inteiros:
O carimbo de data/hora da primeira saída.
O delta de carimbo de data/hora entre saídas sucessivas.
Planejamos adicionar uma política alternativa de agendamento que se preocupa apenas com os pacotes e ignora os carimbos de data/hora de pacotes, o que eliminará esse inconveniente.
Encerramento antecipado de uma calculadora quando um stream de entrada é concluído
Por padrão, o MediaPipe chama o método Close()
de uma calculadora que não é de origem quando
todos os streams de entrada estão concluídos. No gráfico de exemplo, queremos parar
do adicionador assim que a origem do número inteiro for concluída. Isso é alcançado ao
a configuração do nó de adicionar com um gerenciador alternativo de fluxo de entrada
EarlyCloseInputStreamHandler
:
Código-fonte relevante
Calculadora de atraso
Observe o código em Open()
que gera o pacote inicial e o código
Process()
que adiciona um atraso (unidade) aos pacotes de entrada. Como mencionado acima,
nó de atraso presume que seu fluxo de saída é usado junto com um fluxo de entrada com
carimbos de data/hora do pacote 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();
}
};
Configuração do gráfico
Observe a anotação back_edge
e o input_stream_handler
alternativo.
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'
}