Gráfico
Un proto CalculatorGraphConfig
especifica la topología y funcionalidad de una
Gráfico de MediaPipe Cada node
del gráfico representa una calculadora o
subgrafo y especifica los parámetros de configuración necesarios, como registros
tipo de calculadora o subgrafo, las entradas, las salidas y los campos opcionales, como
opciones específicas del nodo, política de entrada y ejecutor, que se analizan en
Sincronización.
CalculatorGraphConfig
tiene muchos otros campos para configurar el nivel de gráfico global
configuración, p.ej., configuración del ejecutor de grafos, cantidad de subprocesos y tamaño máximo de cola
de flujos de entrada. Varias configuraciones a nivel de gráfico son útiles para ajustar la
el rendimiento del gráfico en distintas plataformas (p. ej., computadoras de escritorio y dispositivos móviles). Para
en un dispositivo móvil, adjuntar una calculadora de inferencia de modelo pesada a un modelo
el ejecutor puede mejorar el rendimiento de una aplicación en tiempo real, ya que este
habilita la localidad de subprocesos.
A continuación, se muestra un ejemplo trivial de CalculatorGraphConfig
en el que tenemos una serie de
calculadoras de transferencia :
# 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 ofrece una representación alternativa de C++
para grafos complejos (p. ej., canalizaciones de AA, control de metadatos del modelo, nodos opcionales, etcétera). El gráfico anterior puede verse de la siguiente manera:
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();
}
Para obtener más información, consulta Cómo compilar gráficos en C++.
Subgrafo
Para modularizar un CalculatorGraphConfig
en submódulos y ayudar con la reutilización
de soluciones de percepción, un gráfico de MediaPipe se puede definir como un Subgraph
. El
interfaz pública de un subgrafo consiste en un conjunto de flujos de entrada y salida
similar a la interfaz pública de una calculadora. Luego, el subgrafo se puede incluir en
un objeto CalculatorGraphConfig
como si fuera una calculadora. Cuando un gráfico de MediaPipe se
cargado desde un CalculatorGraphConfig
, cada nodo del subgrafo se reemplaza por el
gráfico correspondiente de las calculadoras. Como resultado, la semántica y el rendimiento
del subgrafo es idéntico al gráfico correspondiente de calculadoras.
A continuación, se muestra un ejemplo de cómo crear un subgrafo llamado TwoPassThroughSubgraph
.
Cómo definir el subgrafo
# 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" }
La interfaz pública del subgrafo consta de lo siguiente:
- Representar gráficamente transmisiones de entrada
- Representar gráficamente transmisiones de salida
- Representar gráficamente paquetes laterales de entrada
- Representar gráficamente los paquetes de salida
Registra el subgrafo con la regla de COMPILACIÓN
mediapipe_simple_subgraph
. El El parámetroregister_as
define el nombre del componente para el nuevo subgrafo.# 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", ], )
Usa el subgrafo del 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" }
Opciones gráficas
Es posible especificar "opciones de grafo". protobuf para un gráfico MediaPipe
de manera similar a Calculator Options
protobuf especificado para una calculadora de MediaPipe. Estas “opciones de gráficos” puede ser
especificado cuando se invoca un grafo y se usa para propagar las opciones de la calculadora y
opciones de subgrafo dentro del gráfico.
En un CalculatorGraphConfig
, se pueden especificar opciones de gráfico para un subgrafo.
exactamente como las opciones de la calculadora, como se muestra a continuación:
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
}
}
}
En una CalculatorGraphConfig
, se pueden aceptar opciones de gráfico y usarlas para propagar
opciones de la calculadora, como se muestra a continuación:
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"
}
En este ejemplo, FaceDetectionSubgraph
acepta la opción de gráfico protobuf.
FaceDetectionOptions
FaceDetectionOptions
se usa para definir algún campo
en las opciones de la calculadora ImageToTensorCalculatorOptions
y algún campo
valores en las opciones de subgrafo InferenceCalculatorOptions
. Los valores del campo
se definen con la sintaxis option_value:
.
En el protobuf CalculatorGraphConfig::Node
, los campos node_options:
y
En conjunto, option_value:
definen los valores de opción para una calculadora, como
ImageToTensorCalculator
El campo node_options:
define un conjunto de valores
valores constantes con la sintaxis de protobuf de texto. Cada campo option_value:
define el valor de un campo de protobuf con información de la
gráfico, específicamente a partir de los valores de campo de las opciones de gráfico de la
gráfico. En el ejemplo anterior, option_value:
"output_tensor_width:options/tensor_width"
define el campo.
ImageToTensorCalculatorOptions.output_tensor_width
con el valor de
FaceDetectionOptions.tensor_width
La sintaxis de option_value:
es similar a la de input_stream:
. El
la sintaxis es option_value: "LHS:RHS"
. El LHS identifica una opción de calculadora
y el RHS identifica un campo de opción de gráfico. Más concretamente, el LHS
y el RHS cada uno consiste en una serie de nombres de campo protobuf que identifican
Mensajes y campos protobuf separados por "/". Esto se conoce como la "ProtoPath".
sintaxis. Los mensajes anidados a los que se hace referencia en el LHS o el RHS ya deben estar
definidos en el protobuf adjunto para que se recorran con
option_value:
Ciclos
De forma predeterminada, MediaPipe requiere que los grafos de la calculadora sean acíclicos y trate los ciclos. en un gráfico como errores. Si se pretende que un gráfico tenga ciclos, estos deben en la configuración del grafo. En esta página, se describe cómo hacerlo.
NOTA: El enfoque actual es experimental y está sujeto a cambios. Aceptamos tus comentarios.
Usa la prueba de unidades de CalculatorGraphTest.Cycle
en
mediapipe/framework/calculator_graph_test.cc
como código de muestra A continuación, se muestra
el grafo cíclico en la prueba. El resultado de sum
del sumador es la suma de
números enteros generados por la calculadora de fuente de números enteros.
En este grafo simple, se ilustran todos los problemas de la compatibilidad con grafos cíclicos.
Anotación del borde trasero
Exigimos que una arista en cada ciclo se anote como borde trasero. Esto permite la ordenación topológica de MediaPipe para que funcione después de quitar todos los bordes posteriores.
Por lo general, hay varias formas de seleccionar los bordes posteriores. ¿Qué bordes están marcados? ya que sus extremos afectan los nodos que se consideran ascendentes consideradas como downstream, lo que, a su vez, afecta las prioridades que MediaPipe asigna a los nodos.
Por ejemplo, la prueba CalculatorGraphTest.Cycle
marca el borde old_sum
como una
al backend, por lo que el nodo de retraso se considera como un nodo downstream del sumador.
y se le da una prioridad más alta. De manera alternativa, podríamos marcar el sum
al nodo de retraso como borde posterior, en cuyo caso, el nodo de retraso
se considera un nodo upstream del nodo sumador y se le da una prioridad más baja.
Paquete inicial
Para que la calculadora de sumas se pueda ejecutar cuando el primer número entero del número entero
llega la fuente, necesitamos un paquete inicial con el valor 0 y el mismo
marca de tiempo, en la transmisión de entrada old_sum
al sumador. Este paquete inicial
La calculadora de retrasos debe generar el resultado en el método Open()
.
Demora en bucle
Cada bucle debe generar un retraso para alinear la salida anterior de sum
con la siguiente
de números enteros. Esto también lo hace el nodo de retraso. Así que el nodo de retraso debe
ten en cuenta lo siguiente sobre las marcas de tiempo de la calculadora de fuente de números enteros:
Es la marca de tiempo del primer resultado.
El delta de la marca de tiempo entre salidas sucesivas.
Planeamos agregar una política de programación alternativa que solo se preocupe por los paquetes como el comando e ignora las marcas de tiempo de los paquetes, lo que eliminará este inconveniente.
Rescisión anticipada de una calculadora cuando finaliza un flujo de entrada
De forma predeterminada, MediaPipe llama al método Close()
de una calculadora que no es de origen cuando
estén listas todas sus transmisiones de entrada. En el gráfico de ejemplo, queremos detener la
en el nodo de suma
en cuanto finalice la fuente de número entero. Esto se logra mediante
configurar el nodo de adder con un controlador de flujos de entrada alternativo
EarlyCloseInputStreamHandler
Código fuente relevante
Calculadora de retraso
Observa el código en Open()
que genera el paquete inicial y el código en
Process()
que agrega un retraso (de unidad) a los paquetes de entrada. Como se mencionó anteriormente, esta
retraso del nodo supone que su transmisión de salida se usa junto con una transmisión de entrada con
marcas de tiempo de paquetes 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();
}
};
Configuración del grafo
Observa la anotación back_edge
y el 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'
}