Đồ thị

Biểu đồ

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

CalculatorGraphConfig có một số trường khác để định cấu hình các chế độ cài đặt cấp biểu đồ chung, 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ố chế độ cài đặt cấp biểu đồ rất hữu ích khi đ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 so với thiết bị di động). Ví dụ: trên thiết bị di động, việc đính kèm một máy tính dự đoán mô hình nặng vào một trình thực thi riêng biệt có thể cải thiện hiệu suất của ứng dụng theo thời gian thực vì việc này cho phép định vị luồng.

Dưới đây là một ví dụ nhỏ về CalculatorGraphConfig, trong đó chúng tôi có một loạt các công cụ tính truyền qua :

# 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, xử lý siêu dữ liệu của mô hình, các nút không bắt buộc, v.v.). Biểu đồ trên có thể giống 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 phần Biểu đồ xây dựng trong C++.

Biểu đồ con

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

Dưới đây là ví dụ về cách tạo một biểu đồ 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 cho đồ thị con bao gồm:

    • Vẽ đồ thị luồng đầu vào
    • Vẽ đồ thị luồng đầu ra
    • Vẽ đồ thị các gói phía đầu vào
    • Vẽ đồ thị các gói phía đầu ra
  2. Đăng ký đồ thị con bằng cách sử dụng quy tắc BUILD mediapipe_simple_subgraph. Tham số register_as xác định tên thành phần cho biểu đồ 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 đồ

Bạn có thể chỉ định một protobuf "tuỳ chọn biểu đồ" cho biểu đồ MediaPipe tương tự như protobuf Calculator Options được chỉ định cho máy tính MediaPipe. Các "tuỳ chọn biểu đồ" này có thể được chỉ định khi một biểu đồ được gọi và dùng để điền sẵ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 biểu đồ con giống hệt như các tuỳ chọn trong máy tính, như minh hoạ 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 biểu đồ có thể được chấp nhận và sử dụng để điền các tuỳ chọn cho máy tính, như sau:

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 tuỳ chọn biểu đồ protobuf FaceDetectionOptions. FaceDetectionOptions dùng để xác định một số giá trị trường trong các tuỳ chọn tính toán ImageToTensorCalculatorOptions và một số giá trị trường trong các tuỳ chọn của đồ thị con InferenceCalculatorOptions. Các giá trị của 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 công cụ tính toán, chẳng hạn như ImageToTensorCalculator. Trường node_options: xác định một tập hợp các giá trị hằng số cố định bằng cách sử dụng cú pháp văn bản protobuf. 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 trên biểu đồ bao quanh, cụ thể là từ các giá trị trường của các tuỳ chọn biểu đồ của biểu đồ bao quanh. Trong ví dụ trên, "output_tensor_width:options/tensor_width" option_value: xác định trường ImageToTensorCalculatorOptions.output_tensor_width bằng cách sử dụng giá trị FaceDetectionOptions.tensor_width.

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

Bốn mùa

Theo mặc định, MediaPipe yêu cầu biểu đồ tính toán phải là biểu đồ tuần hoàn và coi các chu kỳ trong biểu đồ là lỗi. Nếu một biểu đồ dự định có chu kỳ, thì các chu kỳ đó cần được chú thích trong cấu hình biểu đồ. Trang này 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 hoan nghênh ý kiến phản hồi của bạn.

Vui lòng sử dụng bài kiểm thử đơn vị CalculatorGraphTest.Cycle trong mediapipe/framework/calculator_graph_test.cc làm mã mẫu. Dưới đây là biểu đồ tuần hoàn trong quá trình kiểm thử. Kết quả đầu ra sum của bộ cộng là tổng các số nguyên do công cụ tính nguồn số nguyên tạo ra.

đồ thị tuần hoàn thêm một dò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ợ biểu đồ tuần hoàn.

Chú thích ở cạnh sau

Chúng ta yêu cầu một cạnh trong mỗi chu kỳ phải được chú thích là cạnh sau. Việc 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 xoá tất cả các cạnh sau.

Thường có nhiều cách để chọn các cạnh mặt sau. Cạnh nào được đánh dấu là 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. Điều này sẽ ảnh hưởng đến mức độ ưu tiên mà MediaPipe gán cho các nút.

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

Gói ban đầu

Để máy tính bộ cộng có thể chạy được khi số nguyên đầu tiên từ nguồn số nguyên xuất hiệ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 tới bộ cộng. Gói ban đầu này sẽ được trình tính toán độ trễ xuất ra 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 dữ liệu đầu vào số nguyên tiếp theo. Việc này cũng do nút trì hoãn thực hiện. Vì vậy, nút trì hoãn cần biết những thông tin sau về dấu thời gian của công cụ tính nguồn số nguyên:

  • Dấu thời gian của kết quả đầu tiên.

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

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

Chấm dứt sớm 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 máy tính không phải nguồn khi tất cả luồng đầu vào đã hoàn tất. Trong biểu đồ ví dụ, chúng ta muốn dừng nút bộ thêm ngay khi nguồn số nguyên hoàn tất. Bạn có thể thực hiện việc này bằng cách định cấu hình nút bộ thêm với một trình xử lý luồng đầu vào thay thế là EarlyCloseInputStreamHandler.

Mã nguồn có liên quan

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

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