Wykres
Protokół CalculatorGraphConfig
określa topologię i funkcjonalność
Wykres MediaPipe. Każda node
na wykresie odpowiada konkretnemu kalkulatorowi lub
i określa niezbędne konfiguracje, takie jak zarejestrowane
typ kalkulatora/podgrafu, dane wejściowe, dane wyjściowe i pola opcjonalne, takie jak
opcji dla konkretnego węzła, zasady wejściowej oraz wykonawcy, omówione w
Synchronizacja.
Funkcja CalculatorGraphConfig
ma kilka innych pól do konfigurowania globalnego wykresu
ustawienia, np. konfiguracje wykonawcy grafu, liczba wątków oraz maksymalny rozmiar kolejki
strumieni wejściowych. Kilka ustawień na poziomie wykresu pomaga dostosować
skuteczności wykresu na różnych platformach (np. komputer i telefon komórkowy). Dla:
przez umieszczenie ciężkiego kalkulatora wnioskowania dotyczącego modelu w osobnym
może zwiększyć wydajność aplikacji w czasie rzeczywistym, ponieważ
włącza lokalność wątku.
Poniżej znajduje się trywialny przykład reguły CalculatorGraphConfig
, w której mamy serię
kalkulatory przejścia :
# 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 oferuje alternatywną reprezentację C++
dla złożonych wykresów (np. potoki ML, obsługa metadanych modelu, węzły opcjonalne itp.). Powyższy wykres może wyglądać tak:
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();
}
Więcej informacji znajdziesz w artykule Tworzenie wykresów w C++.
Podtytuł
Możliwość modularyzacji modułu CalculatorGraphConfig
w moduły podrzędne i pomoc w ich ponownym używaniu.
rozwiązań percepcyjnych, wykres MediaPipe można zdefiniować jako Subgraph
.
publiczny interfejs podgrafu składa się ze zbioru strumieni wejściowych i wyjściowych
podobny do publicznego interfejsu kalkulatora. Podgraf można następnie umieścić w
CalculatorGraphConfig
, jakby to był kalkulator. Gdy wykres MediaPipe jest
wczytany z interfejsu CalculatorGraphConfig
, każdy węzeł podgrafu jest zastępowany przez
odpowiedni wykres kalkulatorów. W rezultacie semantyka i skuteczność
podgrafu jest taki sam jak odpowiedni wykres kalkulatorów.
Poniżej znajdziesz przykład tworzenia podgrafu o nazwie TwoPassThroughSubgraph
.
Definiowanie podgrafu
# 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" }
Publiczny interfejs podgrafu składa się z:
- Strumienie danych wejściowych wykresu
- Wykres strumieni danych wyjściowych
- Wykres po stronie pakietów wejściowych
- Narysuj wykres pakietów wyjściowych po stronie
Zarejestruj podgraf przy użyciu reguły BUILD
mediapipe_simple_subgraph
.register_as
określa nazwę komponentu nowego podgrafu.# 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", ], )
Skorzystaj z podgrafu na wykresie głównym.
# 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" }
Opcje wykresu
Można określić „opcje wykresu”. protobuf dla grafu MediaPipe
podobnie jak Calculator Options
protobuf określony dla kalkulatora MediaPipe. Te „opcje wykresu” może być
określone miejsce wywoływania grafu i używane do wypełniania opcji kalkulatora oraz
opcje podgrafu.
W CalculatorGraphConfig
opcje wykresu można określić dla podgrafu
dokładnie tak jak w przypadku opcji kalkulatora, jak poniżej:
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
}
}
}
W elemencie CalculatorGraphConfig
opcje wykresu mogą być akceptowane i używane do wypełniania
kalkulatora, jak pokazano poniżej:
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"
}
W tym przykładzie FaceDetectionSubgraph
akceptuje protobuf opcji grafu
FaceDetectionOptions
Pole FaceDetectionOptions
jest używane do zdefiniowania pola
wartości w opcjach kalkulatora ImageToTensorCalculatorOptions
i w niektórych polach
w opcjach podgrafu InferenceCalculatorOptions
. Wartości pól
są zdefiniowane za pomocą składni option_value:
.
W protobufie CalculatorGraphConfig::Node
pola node_options:
i
option_value:
razem definiują wartości opcji kalkulatora, takie jak
ImageToTensorCalculator
Pole node_options:
definiuje zbiór literału
stałych wartości za pomocą składni protokołu tekstowego. W każdym polu option_value:
określa wartość jednego pola protobuf przy użyciu informacji z zamykającego
zwłaszcza z wartości pól opcji wykresu
wykres. W przykładzie powyżej option_value:
"output_tensor_width:options/tensor_width"
definiuje pole
ImageToTensorCalculatorOptions.output_tensor_width
z użyciem wartości
FaceDetectionOptions.tensor_width
Składnia instrukcji option_value:
jest podobna do składni input_stream:
.
składnia to option_value: "LHS:RHS"
. LHS określa opcję kalkulatora
, a RHS określa pole opcji wykresu. Mówiąc bardziej szczegółowo,
i RHS, każda z nich składa się z serii nazw pól protobuf identyfikujących zagnieżdżone
wiadomości i pola buforów protokołu rozdzielone znakiem „/”. Jest to tzw. „ProtoPath”.
składni. Zagnieżdżone komunikaty, do których odwołuje się LHS lub RHS, muszą już być
zdefiniowane w protobufie otaczającym, aby przejść do przemierzania za pomocą
option_value:
Cykle
Domyślnie MediaPipe wymaga, aby wykresy kalkulatora były acykliczne i traktuje cykle na wykresie jako błędy. Jeśli wykres ma zawierać cykle, muszą one zostaną oznaczone adnotacjami w konfiguracji grafu. Na tej stronie dowiesz się, jak to zrobić.
UWAGA: obecne podejście ma charakter eksperymentalny i może ulec zmianie. Zapraszamy z Twoją opinią.
Użyj testu jednostkowego CalculatorGraphTest.Cycle
w:
mediapipe/framework/calculator_graph_test.cc
jako przykładowy kod. Widoczne poniżej to
wykresu cyklicznego w teście. Dane wyjściowe funkcji sum
sumy
liczb całkowitych wygenerowanych przez kalkulator źródłowy liczby całkowitej.
Ten prosty wykres ilustruje wszystkie problemy związane z grafami cyklicznymi.
Adnotacja na tylnej krawędzi
Wymagamy, aby krawędź w każdym cyklu była oznaczona jako tylna. Dzięki temu Sortowanie topologiczne w MediaPipe działa po usunięciu wszystkich tylnych krawędzi.
Tylne krawędzie można zwykle wybrać na kilka sposobów. Które krawędzie są oznaczone ponieważ tylne krawędzie mają wpływ na to, które węzły są uznawane za nadrzędne, a które jest uznawana za dalszą, co z kolei wpływa na priorytety przypisane przez MediaPipe do węzłów.
Na przykład test CalculatorGraphTest.Cycle
oznacza krawędź old_sum
jako
tylną krawędzią, więc węzeł opóźnienia jest uważany za węzeł nadrzędny sumy
i ma wyższy priorytet. Możemy też oznaczyć sum
do węzła opóźnienia jako krawędzi tylnej. W takim przypadku węzeł opóźnienia będzie miał postać
uważany za węzeł nadrzędny węzła dodatkowego i ma niższy priorytet.
Początkowy pakiet
Aby kalkulator sumy mógł zostać uruchomiony, gdy zostanie użyta pierwsza liczba całkowita z liczby całkowitej
dociera do źródła, potrzebujemy pakietu początkowego o wartości 0 i o takiej samej wartości
sygnatura czasowa w strumieniu wejściowym old_sum
dodanym do dodatku. Ten początkowy pakiet
powinien być zwracany przez kalkulator opóźnienia w metodzie Open()
.
Opóźnienie w pętli
Każda pętla powinna powodować opóźnienie wynikające z wyrównania poprzedniego wyjścia sum
z następnym
jest liczbą całkowitą. To samo robi węzeł opóźnienia. Węzeł opóźnienia musi więc
zapoznaj się z poniższymi informacjami o sygnaturach czasowych kalkulatora źródła liczb całkowitych:
Sygnatura czasowa pierwszego wyniku.
Różnica sygnatury czasowej między kolejnymi danymi wyjściowymi.
Planujemy dodać alternatywne zasady planowania, które będą uwzględniać tylko pakiety kolejności i ignoruje sygnatury czasowe pakietów, co wyeliminuje ten problem.
Wczesne zakończenie kalkulatora po zakończeniu jednego strumienia wejściowego
Domyślnie MediaPipe wywołuje metodę Close()
kalkulatora niebędącego źródłem, gdy:
jego wszystkie strumienie wejściowe są gotowe. Na przykładowym wykresie chcemy zatrzymać
po zakończeniu pracy źródła w postaci liczby całkowitej. Jest to możliwe dzięki
konfigurując węzeł dodatkowy z alternatywnym modułem obsługi strumienia wejściowego,
EarlyCloseInputStreamHandler
Odpowiedni kod źródłowy
Kalkulator opóźnienia
Zwróć uwagę na kod w kodzie Open()
, który zwraca pakiet początkowy, oraz kod w
Process()
, który dodaje opóźnienie (jednostkowe) do pakietów wejściowych. Jak wspomnieliśmy powyżej,
opóźniony węzeł zakłada, że jego strumień wyjściowy jest używany razem ze strumieniem wejściowym z
sygnatury czasowe pakietów 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();
}
};
Konfiguracja grafu
Zwróć uwagę na adnotację back_edge
i alternatywną 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'
}