Đồ thị

Biểu đồ

Proto CalculatorGraphConfig chỉ định cấu trúc liên kết và chức năng của một Biểu đồ MediaPipe. Mỗi node trong biểu đồ đại diện cho một máy tính cụ thể hoặc đồ thị con và chỉ định các cấu hình cần thiết, chẳng hạn như các cấu hình đã đăng ký loại máy tính/đồ thị con, dữ liệu đầu vào, dữ liệu đầu ra và các trường tuỳ chọn, chẳng hạn như các tùy chọn cụ thể theo nút, chính sách đầu vào và bộ thực thi, được thảo luận trong Đồng bộ hoá.

CalculatorGraphConfig có một số trường khác để định cấu hình cấp biểu đồ toàn cầu cài đặt, ví dụ: cấu hình trình thực thi biểu đồ, số lượng luồng và kích thước hàng đợi tối đa của luồng đầu vào. Một số cài đặt cấp biểu đồ rất hữu ích trong việc điều chỉnh hiệu suất của biểu đồ trên các nền tảng khác nhau (ví dụ: máy tính để bàn so với thiết bị di động). Cho chẳng hạn như trên thiết bị di động, việc gắn một máy tính suy luận mô hình nặng vào một máy tính executor có thể cải thiện hiệu suất của ứng dụng theo thời gian thực vì đây là cho phép xác định vị trí của luồng.

Dưới đây là một ví dụ quan trọng về CalculatorGraphConfig, trong đó chúng ta có một loạt công cụ tính giá trị chuyển nhượng :

# 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 cung cấp cách biểu diễn C++ thay thế cho các biểu đồ phức tạp (ví dụ: quy trình học máy, siêu dữ liệu mô hình xử lý, các nút không bắt buộc, v.v.). Biểu đồ trên có thể hiển thị như sau:

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();
}

Xem thêm thông tin chi tiết trong bài viết Xây dựng biểu đồ trong C++.

Đồ thị con

Để mô-đun hoá CalculatorGraphConfig thành các mô-đun con và hỗ trợ sử dụng lại về giải pháp nhận thức, biểu đồ MediaPipe có thể được định nghĩa là một Subgraph. Chiến lược phát hành đĩa đơn giao diện công khai của một đồ thị con bao gồm tập hợp các luồng đầu vào và đầu ra tương tự như giao diện công khai của một máy tính. Sau đó, đồ thị con có thể được đưa vào CalculatorGraphConfig giống như một máy tính. Khi biểu đồ MediaPipe là được tải từ CalculatorGraphConfig, mỗi nút đồ thị con sẽ được thay thế bằng đồ thị tương ứng của máy tính. Do đó, ngữ nghĩa và hiệu suất của đồ thị con giống hệt với đồ thị tương ứng của máy tính.

Dưới đây là ví dụ về cách tạo đồ thị con có tên TwoPassThroughSubgraph.

  1. Xác định đồ thị con.

    # 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"
    }
    

    Giao diện công khai của đồ thị con bao gồm:

    • Vẽ biểu đồ luồng đầu vào
    • Vẽ đồ thị các luồng đầu ra
    • Vẽ đồ thị gói bên đầu vào
    • Vẽ đồ thị các gói bên đầu ra
  2. Đăng ký đồ thị con bằng cách sử dụng quy tắc BUILD mediapipe_simple_subgraph. Chiến lược phát hành đĩa đơn tham số register_as định nghĩa tên thành phần cho đồ thị con mới.

    # 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",
        ],
    )
    
  3. Sử dụng đồ thị con trong biểu đồ chính.

    # 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"
    }
    

Tùy chọn biểu đồ

Có thể chỉ định "tuỳ chọn biểu đồ" protobuf cho đồ thị MediaPipe tương tự như Calculator Options protobuf được chỉ định cho máy tính MediaPipe. Các "tuỳ chọn biểu đồ" này có thể được chỉ định tại vị trí gọi biểu đồ và được sử dụng để điền các tuỳ chọn tính toán và tuỳ chọn đồ thị con trong biểu đồ.

Trong CalculatorGraphConfig, bạn có thể chỉ định các tuỳ chọn biểu đồ cho một đồ thị con giống hệt như các tuỳ chọn máy tính, như được trình bày dưới đây:

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
    }
  }
}

Trong CalculatorGraphConfig, các tuỳ chọn trong biểu đồ có thể được chấp nhận và dùng để điền dữ liệu tuỳ chọn máy tính, như được hiển thị dưới đây:

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"
}

Trong ví dụ này, FaceDetectionSubgraph chấp nhận protobuf tuỳ chọn biểu đồ FaceDetectionOptions FaceDetectionOptions dùng để xác định một số trường các giá trị trong tuỳ chọn máy tính ImageToTensorCalculatorOptions và một số trường trong các tuỳ chọn của đồ thị con InferenceCalculatorOptions. Các giá trị trường được xác định bằng cú pháp option_value:.

Trong protobuf CalculatorGraphConfig::Node, các trường node_options:option_value: cùng xác định các giá trị tuỳ chọn cho một hàm tính, chẳng hạn như ImageToTensorCalculator. Trường node_options: xác định một tập hợp các giá trị cố định bằng cách sử dụng cú pháp protobuf văn bản. Mỗi trường option_value: xác định giá trị cho một trường protobuf bằng cách sử dụng thông tin từ trường bao gồm biểu đồ, cụ thể là từ các giá trị trường của các tuỳ chọn biểu đồ bao quanh biểu đồ. Trong ví dụ trên, option_value: "output_tensor_width:options/tensor_width" xác định trường ImageToTensorCalculatorOptions.output_tensor_width bằng cách sử dụng giá trị của FaceDetectionOptions.tensor_width.

Cú pháp của option_value: tương tự như cú pháp của input_stream:. Chiến lược phát hành đĩa đơn là option_value: "LHS:RHS". LHS xác định một lựa chọn tính toán và RHS xác định một trường tuỳ chọn trên biểu đồ. Cụ thể hơn, LHS và RHS đều bao gồm một loạt tên trường protobuf để xác định các tên được lồng vào nhau các trường và thông báo protobuf được phân tách bằng dấu "/". Đây được gọi là "ProtoPath" của bạn. Các thông báo lồng nhau được tham chiếu trong LHS hoặc RHS phải được xác định trong protobuf kèm theo để có thể truyền tải bằng cách sử dụng option_value:.

Bốn mùa

Theo mặc định, MediaPipe yêu cầu biểu đồ tính toán phải có tính chu kỳ và xử lý các chu kỳ trong biểu đồ dưới dạng lỗi. Nếu biểu đồ dự định có chu kỳ, các chu kỳ cần phải được chú thích trong cấu hình biểu đồ. Trang này sẽ mô tả cách thực hiện việc đó.

LƯU Ý: Phương pháp hiện tại đang trong giai đoạn thử nghiệm và có thể thay đổi. Chúng tôi rất vui được hỗ trợ bạn ý kiến phản hồi của bạn.

Vui lòng sử dụng kiểm thử đơn vị CalculatorGraphTest.Cycle trong mediapipe/framework/calculator_graph_test.cc làm mã mẫu. Hiển thị bên dưới là đồ thị tuần hoàn trong phép kiểm thử. Đầu ra sum của bộ cộng là tổng của số nguyên được tạo bởi máy tính nguồn số nguyên.

đồ thị tuần hoàn cộng luồng số nguyên

Biểu đồ đơn giản này minh hoạ tất cả các vấn đề trong việc hỗ trợ đồ thị tuần hoàn.

Chú thích ở cạnh sau

Chúng tôi yêu cầu một cạnh trong mỗi chu kỳ phải được chú thích là cạnh sau. Điều này cho phép Phương thức sắp xếp cấu trúc liên kết của MediaPipe hoạt động, sau khi loại bỏ tất cả các cạnh sau.

Thông thường, có nhiều cách để chọn cạnh sau. Những cạnh nào được đánh dấu vì cạnh sau ảnh hưởng đến nút nào được coi là thượng nguồn và nút nào được coi là hạ nguồn, từ đó ảnh hưởng đến mức độ ưu tiên mà MediaPipe chỉ định vào các nút.

Ví dụ: phép kiểm thử CalculatorGraphTest.Cycle đánh dấu cạnh old_sum là một cạnh sau, nên nút Delay được coi là một nút hạ nguồn của bộ cộng nút và được gán mức độ ưu tiên cao hơn. Ngoài ra, chúng ta có thể đánh dấu sum đầu vào vào nút trễ làm cạnh sau, trong trường hợp này, nút trễ sẽ là được coi là nút ngược dòng của nút bộ cộng và có mức độ ưu tiên thấp hơn.

Gói tin ban đầu

Để hàm tính bộ cộng có thể chạy được khi số nguyên đầu tiên trong số nguyên đó nguồn đến, chúng ta cần một gói ban đầu có giá trị 0 và có cùng dấu thời gian, trên luồng đầu vào old_sum đến trình thêm. Gói ban đầu này phải được xuất ra bằng máy tính độ trễ trong phương thức Open().

Độ trễ trong một vòng lặp

Mỗi vòng lặp phải có độ trễ để căn chỉnh đầu ra sum trước đó với đầu ra tiếp theo đầu vào là số nguyên. Việc này cũng được thực hiện bằng nút trễ. Vì vậy, nút trễ cần phải hãy xem những thông tin sau đây về dấu thời gian của hàm tính nguồn số nguyên:

  • Dấu thời gian của lần xuất đầu tiên.

  • delta dấu thời gian giữa các dữ liệu đầu ra liên tiếp.

Chúng tôi dự định thêm một chính sách lập lịch thay thế chỉ quan tâm đến gói dữ liệu sắp xếp thứ tự và bỏ qua dấu thời gian của gói. Điều này giúp loại bỏ sự bất tiện này.

Chấm dứt sớm một máy tính khi hoàn tất một luồng đầu vào

Theo mặc định, MediaPipe gọi phương thức Close() của một công cụ tính không phải nguồn khi tất cả các luồng đầu vào đã hoàn tất. Trong biểu đồ ví dụ, chúng tôi muốn dừng nút bộ cộng ngay khi nguồn số nguyên hoàn tất. Điều này được thực hiện bằng định cấu hình nút bộ cộng bằng một trình xử lý luồng đầu vào thay thế, EarlyCloseInputStreamHandler.

Mã nguồn có liên quan

Công cụ tính độ trễ

Lưu ý mã trong Open() xuất ra gói tin ban đầu và mã trong Process() thêm độ trễ (đơn vị) cho các gói đầu vào. Như đã lưu ý ở trên, đây là nút trễ giả định rằng luồng đầu ra của nó được dùng cùng với luồng đầu vào có dấu thời gian gói tin 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();
  }
};

Cấu hình biểu đồ

Hãy lưu ý chú giải back_edgeinput_stream_handler thay thế.

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'
}