그래프

그래프

CalculatorGraphConfig proto는 MediaPipe 그래프의 토폴로지와 기능을 지정합니다. 그래프의 각 node는 특정 계산기 또는 하위 그래프를 나타내며 동기화에서 설명한 등록된 계산기/하위 그래프 유형, 입력, 출력, 선택적 필드(예: 노드별 옵션, 입력 정책, 실행자)와 같은 필수 구성을 지정합니다.

CalculatorGraphConfig에는 그래프 실행기 구성, 스레드 수, 입력 스트림의 최대 큐 크기 등 전역 그래프 수준 설정을 구성하는 기타 여러 필드가 있습니다. 여러 그래프 수준 설정은 다양한 플랫폼 (예: 데스크톱과 모바일)에서 그래프의 성능을 조정하는 데 유용합니다. 예를 들어 모바일에서 많은 모델 추론 계산기를 별도의 실행자에 연결하면 스레드 지역성이 가능하므로 실시간 애플리케이션의 성능을 개선할 수 있습니다.

다음은 일련의 패스 스루 계산기가 있는 간단한 CalculatorGraphConfig 예시입니다.

# 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는 복잡한 그래프 (예: ML 파이프라인, 모델 메타데이터 처리, 선택적 노드 등)를 위한 대체 C++ 표현을 제공합니다. 위의 그래프는 다음과 같이 표시될 수 있습니다.

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

자세한 내용은 C++로 그래프 빌드를 참고하세요.

하위 그래프

CalculatorGraphConfig를 하위 모듈로 모듈화하고 인식 솔루션의 재사용을 지원하기 위해 MediaPipe 그래프를 Subgraph로 정의할 수 있습니다. 하위 그래프의 공개 인터페이스는 계산기의 공개 인터페이스와 유사한 일련의 입력 및 출력 스트림으로 구성됩니다. 그런 다음 하위 그래프는 계산기처럼 CalculatorGraphConfig에 포함될 수 있습니다. MediaPipe 그래프가 CalculatorGraphConfig에서 로드되면 각 하위 그래프 노드가 계산기의 그래프로 대체됩니다. 따라서 하위 그래프의 의미 체계와 성능은 해당하는 계산기 그래프와 동일합니다.

다음은 TwoPassThroughSubgraph라는 하위 그래프를 만드는 방법을 보여주는 예입니다.

  1. 하위 그래프 정의

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

    하위 그래프의 공개 인터페이스는 다음과 같이 구성됩니다.

    • 그래프 입력 스트림
    • 그래프 출력 스트림
    • 그래프 입력 측 패킷
    • 그래프 출력 측 패킷
  2. BUILD 규칙 mediapipe_simple_subgraph를 사용하여 하위 그래프를 등록합니다. register_as 매개변수는 새 하위 그래프의 구성요소 이름을 정의합니다.

    # 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. 기본 그래프에 하위 그래프를 사용합니다.

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

그래프 옵션

MediaPipe 계산기에 지정된 Calculator Options protobuf와 유사한 MediaPipe 그래프의 '그래프 옵션' protobuf를 지정할 수 있습니다. 이러한 '그래프 옵션'은 그래프를 호출하는 위치를 지정할 수 있으며 그래프 내에서 계산기 옵션과 하위 그래프 옵션을 채우는 데 사용할 수 있습니다.

CalculatorGraphConfig에서 아래와 같이 계산기 옵션과 정확히 같이 하위 그래프에 그래프 옵션을 지정할 수 있습니다.

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

CalculatorGraphConfig에서 그래프 옵션을 수락하고 아래와 같이 계산기 옵션을 채우는 데 사용할 수 있습니다.

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

이 예에서 FaceDetectionSubgraph는 그래프 옵션 protobuf FaceDetectionOptions을 허용합니다. FaceDetectionOptions는 계산기 옵션 ImageToTensorCalculatorOptions에서 일부 필드 값을 정의하고 하위 그래프 옵션 InferenceCalculatorOptions에서 일부 필드 값을 정의하는 데 사용됩니다. 필드 값은 option_value: 구문을 사용하여 정의됩니다.

CalculatorGraphConfig::Node protobuf에서 node_options: 필드와 option_value: 필드가 함께 ImageToTensorCalculator와 같은 계산기의 옵션 값을 정의합니다. node_options: 필드는 텍스트 protobuf 구문을 사용하여 리터럴 상수 값 집합을 정의합니다. 각 option_value: 필드는 인클로징 그래프, 특히 인클로징 그래프 그래프 옵션의 필드 값에서 가져온 정보를 사용하여 protobuf 필드 값을 정의합니다. 위의 예에서 option_value: "output_tensor_width:options/tensor_width"FaceDetectionOptions.tensor_width 값을 사용하여 ImageToTensorCalculatorOptions.output_tensor_width 필드를 정의합니다.

option_value:의 문법은 input_stream:의 문법과 비슷합니다. 구문은 option_value: "LHS:RHS"입니다. LHS는 계산기 옵션 필드를 나타내고 RHS는 그래프 옵션 필드를 식별합니다. 더 구체적으로 LHS와 RHS는 각각 중첩된 protobuf 메시지를 식별하는 일련의 protobuf 필드 이름과 '/'로 구분된 필드로 구성됩니다. 이를 'ProtoPath' 문법이라고 합니다. LHS 또는 RHS에서 참조되는 중첩된 메시지가 이미 option_value:를 사용하여 순회하려면 동봉한 protobuf에 정의되어 있어야 합니다.

계절과 시간

기본적으로 MediaPipe는 계산기 그래프가 비순환식이어야 하며 그래프의 주기를 오류로 처리합니다. 그래프에 주기가 있는 경우 주기를 그래프 구성에 주석 처리해야 합니다. 이 페이지에서는 그 방법을 설명합니다.

참고: 현재 접근 방식은 실험 단계이며 변경될 수 있습니다. Google은 여러분의 의견을 환영합니다.

mediapipe/framework/calculator_graph_test.ccCalculatorGraphTest.Cycle 단위 테스트를 샘플 코드로 사용하세요. 다음은 테스트의 순환 그래프입니다. 가산기의 sum 출력은 정수 소스 계산기에서 생성된 정수의 합입니다.

정수 스트림을 추가하는 순환 그래프

이 간단한 그래프는 순환 그래프를 지원하는 모든 문제를 보여줍니다.

뒤쪽 가장자리 주석

각 주기의 모서리를 후면 가장자리로 주석을 달아야 합니다. 이렇게 하면 모든 뒤쪽 가장자리를 삭제한 후 MediaPipe의 토폴로지 정렬이 작동합니다.

뒷면 가장자리는 일반적으로 여러 가지 방법으로 선택할 수 있습니다. 뒤쪽 가장자리로 표시되는 에지에 따라 업스트림으로 간주되는 노드와 다운스트림으로 간주되는 노드가 영향을 받게 됩니다. 이는 MediaPipe가 노드에 할당하는 우선순위에 영향을 미칩니다.

예를 들어 CalculatorGraphTest.Cycle 테스트는 old_sum 에지를 뒤쪽 가장자리로 표시하므로 지연 노드는 가더기 노드의 다운스트림 노드로 간주되며 더 높은 우선순위를 갖습니다. 또는 지연 노드의 sum 입력을 뒤쪽으로 표시할 수 있습니다. 이 경우 지연 노드는 가더기 노드의 업스트림 노드로 간주되고 더 낮은 우선순위가 부여됩니다.

초기 패킷

정수 소스의 첫 번째 정수가 도착할 때 가산기 계산기를 실행하려면 가산기의 old_sum 입력 스트림에 값이 0이고 타임스탬프가 동일한 초기 패킷이 필요합니다. 이 초기 패킷은 Open() 메서드의 지연 계산기에 의해 출력되어야 합니다.

루프 지연

각 루프에서는 이전 sum 출력을 다음 정수 입력과 정렬하는 데 지연이 발생해야 합니다. 이 작업은 지연 노드에 의해서도 수행됩니다. 따라서 지연 노드는 정수 소스 계산기의 타임스탬프에 대한 다음 정보를 알아야 합니다.

  • 첫 번째 출력의 타임스탬프입니다.

  • 연속된 출력 간의 타임스탬프 델타입니다.

패킷 순서만 고려하고 패킷 타임스탬프를 무시하는 대체 예약 정책을 추가하여 이러한 불편함을 없앨 계획입니다.

하나의 입력 스트림이 완료되면 계산기 조기 종료

기본적으로 MediaPipe는 모든 입력 스트림이 완료되면 비소스 계산기의 Close() 메서드를 호출합니다. 예시 그래프에서는 정수 소스가 완료되는 즉시 가산 노드를 중지합니다. 이렇게 하려면 대체 입력 스트림 핸들러인 EarlyCloseInputStreamHandler로 가더 노드를 구성합니다.

관련 소스 코드

지연 계산기

초기 패킷을 출력하는 Open()의 코드와 입력 패킷에 (단위) 지연을 추가하는 Process()의 코드를 확인합니다. 위에서 언급했듯이 이 지연 노드는 출력 스트림이 패킷 타임스탬프가 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();
  }
};

그래프 구성

back_edge 주석과 대체 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'
}