Grafiku
Një proto CalculatorGraphConfig specifikon topologjinë dhe funksionalitetin e një grafi MediaPipe. Çdo node në grafik përfaqëson një kalkulator ose nëngraf të veçantë dhe specifikon konfigurimet e nevojshme, të tilla si lloji i kalkulatorit/nëngrafit të regjistruar, hyrjet, daljet dhe fushat opsionale, si opsionet specifike të nyjeve, politika e hyrjes dhe ekzekutuesi, të diskutuara në Sinkronizimi .
CalculatorGraphConfig ka disa fusha të tjera për të konfiguruar cilësimet globale të nivelit të grafikut, p.sh. konfigurimet e ekzekutuesit të grafikut, numrin e thread-ve dhe madhësinë maksimale të radhës së rrjedhave hyrëse. Disa cilësime të nivelit të grafikut janë të dobishme për akordimin e performancës së grafikut në platforma të ndryshme (p.sh. desktop vs celular). Për shembull, në celular, bashkëngjitja e një kalkulatori të rëndë të konkluzionit të modelit në një ekzekutues të veçantë mund të përmirësojë performancën e një aplikacioni në kohë reale pasi kjo mundëson lokalitetin e temave.
Më poshtë është një shembull i parëndësishëm CalculatorGraphConfig ku kemi seri kalkulatorësh kalimi:
# 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 ofron një paraqitje alternative C++ për grafikë komplekse (p.sh. tubacionet ML, trajtimi i meta të dhënave të modelit, nyjet opsionale, etj.). Grafiku i mësipërm mund të duket si ky:
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();
}
Shihni më shumë detaje në Ndërtimi i Grafikëve në C++ .
Nëngrafi
Për të modularizuar një CalculatorGraphConfig në nën-module dhe për të ndihmuar me ripërdorimin e zgjidhjeve të perceptimit, një grafik MediaPipe mund të përkufizohet si një Subgraph . Ndërfaqja publike e një nëngrafi përbëhet nga një grup rrymash hyrëse dhe dalëse të ngjashme me ndërfaqen publike të një kalkulatori. Nëngrafi më pas mund të përfshihet në një CalculatorGraphConfig sikur të ishte një kalkulator. Kur një grafik MediaPipe ngarkohet nga një CalculatorGraphConfig , çdo nyje nëngrafike zëvendësohet nga grafiku përkatës i kalkulatorëve. Si rezultat, semantika dhe performanca e nëngrafit është identike me grafikun përkatës të kalkulatorëve.
Më poshtë është një shembull se si të krijoni një nëngraf të quajtur TwoPassThroughSubgraph .
Përcaktimi i nëngrafit.
# 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" }Ndërfaqja publike e nëngrafit përbëhet nga:
- Rrjedhat e hyrjes në grafik
- Rrjedhat e daljes së grafikut
- Paketat anësore të hyrjes në grafik
- Paketat anësore të daljes së grafikut
Regjistroni nëngrafin duke përdorur rregullin BUILD
mediapipe_simple_subgraph. Parametriregister_aspërcakton emrin e komponentit për nëngrafin e ri.# 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", ], )Përdorni nëngrafin në grafikun kryesor.
# 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" }
Opsionet e grafikut
Është e mundur të specifikohet një protobuf "opsionet e grafikut" për një grafik MediaPipe i ngjashëm me protobufin Calculator Options të specifikuar për një kalkulator MediaPipe. Këto "opsione grafiku" mund të specifikohen aty ku thirret një grafik dhe të përdoren për të mbushur opsionet e llogaritësit dhe opsionet e nëngrafit brenda grafikut.
Në një CalculatorGraphConfig , opsionet e grafikut mund të specifikohen për një nëngraf saktësisht si opsionet e kalkulatorit, siç tregohet më poshtë:
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
}
}
}
Në një CalculatorGraphConfig , opsionet e grafikut mund të pranohen dhe të përdoren për të mbushur opsionet e kalkulatorit, siç tregohet më poshtë:
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"
}
Në këtë shembull, FaceDetectionSubgraph pranon opsionin e grafikut protobuf FaceDetectionOptions . FaceDetectionOptions përdoret për të përcaktuar disa vlera fushash në opsionet e kalkulatorit ImageToTensorCalculatorOptions dhe disa vlera të fushës në opsionet e nëngrafit InferenceCalculatorOptions . Vlerat e fushës përcaktohen duke përdorur sintaksën option_value:
Në protobuf-in CalculatorGraphConfig::Node , fushat node_options: dhe option_value: së bashku përcaktojnë vlerat e opsioneve për një kalkulator të tillë si ImageToTensorCalculator . Fusha node_options: përcakton një grup vlerash konstante fjalë për fjalë duke përdorur sintaksën e tekstit protobuf. Çdo option_value: fushë përcakton vlerën për një fushë protobuf duke përdorur informacionin nga grafiku mbyllës, veçanërisht nga vlerat e fushës së opsioneve të grafikut të grafikut mbyllës. Në shembullin e mësipërm, option_value: "output_tensor_width:options/tensor_width" përcakton fushën ImageToTensorCalculatorOptions.output_tensor_width duke përdorur vlerën e FaceDetectionOptions.tensor_width .
Sintaksa e option_value: është e ngjashme me sintaksën e input_stream: . Sintaksa është option_value: "LHS:RHS" . LHS identifikon një fushë opsioni të kalkulatorit dhe RHS identifikon një fushë opsioni grafiku. Më konkretisht, LHS dhe RHS secila përbëhet nga një seri emrash fushash protobuf që identifikojnë mesazhet e ndërlidhura protobuf dhe fushat të ndara me '/'. Kjo njihet si sintaksa "ProtoPath". Mesazhet e ndërlidhura që janë referuar në LHS ose RHS duhet të përcaktohen tashmë në protobuf-in mbyllës në mënyrë që të përshkohen duke përdorur option_value: .
Ciklet
Si parazgjedhje, MediaPipe kërkon që grafikët e kalkulatorit të jenë aciklik dhe i trajton ciklet në një grafik si gabime. Nëse një grafik synohet të ketë cikle, ciklet duhet të shënohen në konfigurimin e grafikut. Kjo faqe përshkruan se si ta bëni këtë.
SHËNIM: Qasja aktuale është eksperimentale dhe mund të ndryshojë. Ne mirëpresim komentet tuaja.
Ju lutemi përdorni testin e njësisë CalculatorGraphTest.Cycle në mediapipe/framework/calculator_graph_test.cc si kod mostër. Më poshtë është paraqitur grafiku ciklik në test. sum e prodhimit të grumbulluesit është shuma e numrave të plotë të gjeneruar nga llogaritësi i burimit të plotë.
Ky grafik i thjeshtë ilustron të gjitha çështjet në mbështetjen e grafikëve ciklik.
Annotation Edge Back
Ne kërkojmë që një skaj në çdo cikël të shënohet si skaj i pasmë. Kjo lejon që renditja topologjike e MediaPipe të funksionojë, pasi të keni hequr të gjitha skajet e pasme.
Zakonisht ka shumë mënyra për të zgjedhur skajet e pasme. Cilat skaje janë shënuar si skajet e pasme ndikon se cilat nyje konsiderohen si në rrjedhën e sipërme dhe cilat nyje konsiderohen si në rrjedhën e poshtme, gjë që nga ana tjetër ndikon në prioritetet që MediaPipe u cakton nyjeve.
Për shembull, testi CalculatorGraphTest.Cycle shënon skajin old_sum si një skaj të pasëm, kështu që nyja Delay konsiderohet si një nyje në rrjedhën e poshtme të nyjes së grumbullimit dhe i jepet një përparësi më e lartë. Përndryshe, ne mund të shënojmë hyrjen e sum në nyjen e vonesës si skajin e pasëm, në të cilin rast nyja e vonesës do të konsiderohet si një nyje në rrjedhën e sipërme të nyjes së grumbullimit dhe i jepet një përparësi më e ulët.
Paketa fillestare
Që kalkulatori i grumbullimit të mund të ekzekutohet kur të mbërrijë numri i parë i plotë nga burimi i numrit të plotë, na duhet një paketë fillestare, me vlerë 0 dhe me të njëjtën stampë kohore, në rrjedhën hyrëse të old_sum në grumbullues. Kjo paketë fillestare duhet të dalë nga kalkulatori i vonesës në metodën Open() .
Vonesa në një lak
Çdo lak duhet të ketë një vonesë për të lidhur daljen e sum së mëparshme me hyrjen e numrit të plotë tjetër. Kjo bëhet edhe nga nyja e vonesës. Pra, nyja e vonesës duhet të dijë sa më poshtë në lidhje me vulat kohore të kalkulatorit të burimit të plotë:
Vula kohore e daljes së parë.
Delta e vulës kohore ndërmjet daljeve të njëpasnjëshme.
Ne planifikojmë të shtojmë një politikë alternative të planifikimit që kujdeset vetëm për porositjen e paketave dhe injoron stampat kohore të paketave, gjë që do ta eliminojë këtë shqetësim.
Përfundimi i hershëm i një kalkulatori kur përfundon një transmetim i vetëm
Si parazgjedhje, MediaPipe thërret metodën Close() të një kalkulatori jo-burimor kur të gjitha transmetimet e tij hyrëse janë përfunduar. Në grafikun e shembullit, ne duam të ndalojmë nyjen e grumbullimit sapo të përfundojë burimi i plotë. Kjo arrihet duke konfiguruar nyjen e grumbullimit me një mbajtës alternativ të rrymës hyrëse, EarlyCloseInputStreamHandler .
Kodi burimor përkatës
Llogaritësi i vonesës
Vini re kodin në Open() që nxjerr paketën fillestare dhe kodin në Process() që shton një vonesë (njësi) në paketat hyrëse. Siç u përmend më lart, kjo nyje vonese supozon se rryma e saj e daljes përdoret së bashku me një rrymë hyrëse me stampat kohore të paketave 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();
}
};
Konfigurimi i grafikut
Vini re shënimin back_edge dhe input_stream_handler alternativ.
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'
}