계산기

각 계산기는 그래프의 노드입니다. 새 포드를 만드는 방법을 계산기, 계산기 초기화 방법, 계산 방법, 입력 및 출력 스트림, 타임스탬프, 옵션 등이 있습니다. 그래프의 각 노드는 Calculator로 구현됩니다. 대부분의 그래프 실행은 계산기. 계산기는 0개 이상의 입력 스트림 또는 사이드를 수신할 수 있습니다. 0개 이상의 출력 스트림 또는 부 패킷을 생성합니다.

CalculatorBase

계산기는 CalculatorBase 드림 여러 메서드를 구현하며 새 서브클래스를 등록할 때 Mediapipe. 새 계산기는 최소한 다음 네 가지 방법을 구현해야 합니다.

  • GetContract()
    • 계산기 작성자는 예상되는 입력 및 출력 유형 지정 가능 GetContract()의 계산기를 살펴보세요. 그래프가 초기화되면 프레임워크는 정적 메서드를 호출하여 연결된 입력과 출력은 지정할 수도 있습니다
  • Open()
    • 그래프가 시작된 후 프레임워크는 Open()를 호출합니다. 입력 측 이 시점에서 계산기에서 사용할 수 있습니다. Open() 노드 구성 작업을 해석합니다 (그래프 참고). 계산기의 그래프 실행별 상태를 준비합니다. 이 함수는 계산기 출력에 패킷을 씁니다. Open() 중에 오류는 그래프 실행을 종료합니다.
  • Process()
    • 입력이 있는 계산기의 경우 프레임워크는 Process()를 반복적으로 호출합니다. 하나 이상의 입력 스트림에 패킷을 사용할 수 있을 때마다. 프레임워크 기본적으로 모든 입력의 타임스탬프가 동일함을 보장합니다( 동기화 참조). 여러 항목 병렬 실행 시 Process() 호출을 동시에 호출할 수 있음 사용 설정되어 있는지 확인합니다. Process() 중에 오류가 발생하면 프레임워크는 다음을 호출합니다. Close() 및 그래프 실행이 종료됩니다.
  • Close()
    • 모든 Process() 호출이 완료된 후 또는 모든 입력 스트림이 닫히면 프레임워크는 Close()를 호출합니다. 이 함수는 그래프 실행이 종료된 경우에도 Open()가 호출되어 성공함 오류가 있을 수 있습니다. 입력 스트림을 통해 사용할 수 있는 입력이 없습니다. Close() 동안에도 여전히 입력 측 패킷에 액세스할 수 있으며 출력을 쓸 수 있습니다. Close()가 반환되면 계산기는 불량 노드로 간주되어야 합니다 계산기 객체는 바로 실행됩니다.

다음은 CalculatorBase.h

class CalculatorBase {
 public:
  ...

  // The subclasses of CalculatorBase must implement GetContract.
  // ...
  static absl::Status GetContract(CalculatorContract* cc);

  // Open is called before any Process() calls, on a freshly constructed
  // calculator.  Subclasses may override this method to perform necessary
  // setup, and possibly output Packets and/or set output streams' headers.
  // ...
  virtual absl::Status Open(CalculatorContext* cc) {
    return absl::OkStatus();
  }

  // Processes the incoming inputs. May call the methods on cc to access
  // inputs and produce outputs.
  // ...
  virtual absl::Status Process(CalculatorContext* cc) = 0;

  // Is called if Open() was called and succeeded.  Is called either
  // immediately after processing is complete or after a graph run has ended
  // (if an error occurred in the graph).  ...
  virtual absl::Status Close(CalculatorContext* cc) {
    return absl::OkStatus();
  }

  ...
};

계산기의 수명

MediaPipe 그래프를 초기화하는 동안 프레임워크에서 필요한 패킷 종류를 결정하는 GetContract() 정적 메서드

프레임워크는 그래프가 실행될 때마다 전체 계산기를 생성하고 폐기합니다. (예: 동영상당 한 번 또는 이미지당 한 번). 비싸거나 큰 객체가 남아 있는 경우 입력 측 패킷으로 제공되어야 하므로 계산은 후속 실행에서 반복되지 않습니다.

초기화 후에는 그래프를 실행할 때마다 다음 순서가 발생합니다.

  • Open()
  • Process() (반복)
  • Close()

프레임워크는 Open()를 호출하여 계산기를 초기화합니다. Open()이(가) 해야 하는 작업 모든 옵션을 해석하고 계산기의 그래프 실행별 상태를 설정합니다. Open() 입력 측 패킷을 가져오고 계산기 출력에 패킷을 쓸 수 있습니다. 만약 SetOffset()를 호출하여 잠재적 패킷 버퍼링을 줄여야 합니다. 학습합니다.

Open() 또는 Process() 중에 오류가 발생하는 경우 (이러한 이벤트 중 하나로 표시됨) Ok가 아닌 상태 반환) 더 이상 호출 없이 그래프 실행이 종료됩니다. 계산기가 소멸됩니다.

입력이 있는 계산기의 경우 프레임워크는 적어도 다음의 경우 Process()를 호출합니다. 한 입력에는 사용 가능한 패킷이 있습니다. 프레임워크는 모든 입력에 동일한 타임스탬프로, Process()를 호출할 때마다 타임스탬프가 증가합니다. 모든 패킷이 전달되는 것을 의미합니다. 따라서 일부 입력에는 Process()가 호출될 때 패킷의 개수에 영향을 주지 않습니다. 패킷이 누락된 입력이 빈 패킷 (타임스탬프 없음)을 생성합니다.

프레임워크는 모든 Process() 호출 후에 Close()를 호출합니다. 모든 입력은 소진되었지만 Close()는 입력 측 패킷에 액세스할 수 있으며 출력을 씁니다. Close가 반환되면 계산기가 소멸됩니다.

입력이 없는 계산기는 소스라고 합니다. 소스 계산기 Ok 상태를 반환하는 한 Process()가 계속 호출됩니다. 가 소스 계산기는 중지 상태를 반환하여 소진되었음을 나타냅니다. (예: mediaPipe::tool::StatusStop()).

입력 및 출력 식별

계산기의 공개 인터페이스는 일련의 입력 스트림과 출력 스트림. CalculatorGraphConfiguration에서 일부 입력의 출력은 계산기는 이름이 지정된 있습니다. 스트림 이름은 일반적으로 소문자인 반면 입력 및 출력 태그는 대문자여야 합니다. 아래 예에서 태그 이름이 VIDEO인 출력은 다음과 같습니다. 이름이 VIDEO_IN인 스트림을 사용하여 video_stream입니다.

# Graph describing calculator SomeAudioVideoCalculator
node {
  calculator: "SomeAudioVideoCalculator"
  input_stream: "INPUT:combined_input"
  output_stream: "VIDEO:video_stream"
}
node {
  calculator: "SomeVideoCalculator"
  input_stream: "VIDEO_IN:video_stream"
  output_stream: "VIDEO_OUT:processed_video"
}

입력 및 출력 스트림은 색인 번호, 태그 이름 또는 조합될 수 있습니다. 입력의 몇 가지 예시를 살펴보고 출력 식별자를 참조하세요. SomeAudioVideoCalculator는 태그별 동영상 출력 및 태그별 오디오 출력과 색인 VIDEO 태그가 있는 입력이 video_stream입니다. 태그가 AUDIO이고 색인이 01인 출력은 다음과 같습니다. 이름이 audio_leftaudio_right인 스트림에 연결되었습니다. SomeAudioCalculator는 색인으로만 오디오 입력을 식별합니다 (태그 필요 없음).

# Graph describing calculator SomeAudioVideoCalculator
node {
  calculator: "SomeAudioVideoCalculator"
  input_stream: "combined_input"
  output_stream: "VIDEO:video_stream"
  output_stream: "AUDIO:0:audio_left"
  output_stream: "AUDIO:1:audio_right"
}

node {
  calculator: "SomeAudioCalculator"
  input_stream: "audio_left"
  input_stream: "audio_right"
  output_stream: "audio_energy"
}

계산기 구현에서는 입력과 출력도 태그로 식별됩니다. 색인 번호가 포함됩니다. 아래 함수에서 입력과 출력이 식별됩니다.

  • 색인 번호 기준: 결합된 입력 스트림이 색인으로 간단히 식별됩니다. 0
  • 태그 이름별: 동영상 출력 스트림은 'VIDEO' 태그 이름으로 식별됩니다.
  • 태그 이름 및 색인 번호 기준: 출력 오디오 스트림은 태그 이름 AUDIO과 색인 번호 01의 조합입니다.
// c++ Code snippet describing the SomeAudioVideoCalculator GetContract() method
class SomeAudioVideoCalculator : public CalculatorBase {
 public:
  static absl::Status GetContract(CalculatorContract* cc) {
    cc->Inputs().Index(0).SetAny();
    // SetAny() is used to specify that whatever the type of the
    // stream is, it's acceptable.  This does not mean that any
    // packet is acceptable.  Packets in the stream still have a
    // particular type.  SetAny() has the same effect as explicitly
    // setting the type to be the stream's type.
    cc->Outputs().Tag("VIDEO").Set<ImageFrame>();
    cc->Outputs().Get("AUDIO", 0).Set<Matrix>();
    cc->Outputs().Get("AUDIO", 1).Set<Matrix>();
    return absl::OkStatus();
  }

처리 중

비소스 노드에서 호출된 Process()는 다음과 같이 absl::OkStatus()를 반환해야 합니다. 모두 잘 진행되었음을 표시하거나 오류를 알리는 다른 상태 코드를 표시할 수 있습니다.

소스 계산기가 아닌 계산기에서 tool::StatusStop()를 반환하면 그래프가 일찍 취소됩니다. 이 경우 모든 소스 계산기와 그래프는 입력 스트림이 종료되고 나머지 패킷은 그래프)에서 찾을 수 있습니다.

그래프의 소스 노드는 계속해서 Process()가 호출되는 동안 absl::OkStatus(를 반환하므로). 더 이상 표시할 데이터가 없음을 나타내기 위해 반환하면 tool::StatusStop()이 반환됩니다. 다른 상태는 오류가 있음을 나타냅니다. 수 있습니다.

Close()absl::OkStatus()를 반환하여 성공을 나타냅니다. 기타 상태 실패를 나타냅니다.

다음은 기본 Process() 함수입니다. Input() 메서드( 는 계산기에 단일 입력이 있는 경우에만 사용) 입력 데이터를 요청합니다. 그것은 그런 다음 std::unique_ptr를 사용하여 출력 패킷에 필요한 메모리를 할당합니다. 계산을 수행합니다. 완료되면 출력을 생성합니다.

absl::Status MyCalculator::Process() {
  const Matrix& input = Input()->Get<Matrix>();
  std::unique_ptr<Matrix> output(new Matrix(input.rows(), input.cols()));
  // do your magic here....
  //    output->row(n) =  ...
  Output()->Add(output.release(), InputTimestamp());
  return absl::OkStatus();
}

계산기 옵션

계산기는 (1) 입력 스트림 패킷 (2)을 통해 처리 매개변수 허용 입력 측 패킷 및 (3) 계산기 옵션 등입니다. 계산기 옵션 지정된 경우 node_options 필드에 리터럴 값으로 표시됩니다. CalculatorGraphConfiguration.Node 메시지

  node {
    calculator: "TfLiteInferenceCalculator"
    input_stream: "TENSORS:main_model_input"
    output_stream: "TENSORS:main_model_output"
    node_options: {
      [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
        model_path: "mediapipe/models/detection_model.tflite"
      }
    }
  }

node_options 필드는 proto3 구문을 허용합니다. 대신 계산기를 proto2 구문을 사용하여 options 필드에 옵션을 지정할 수 있습니다.

  node {
    calculator: "TfLiteInferenceCalculator"
    input_stream: "TENSORS:main_model_input"
    output_stream: "TENSORS:main_model_output"
    node_options: {
      [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
        model_path: "mediapipe/models/detection_model.tflite"
      }
    }
  }

일부 계산기는 계산기 옵션을 사용할 수 없습니다. 옵션을 수락하려면 계산기는 일반적으로 새로운 protobuf 메시지 유형을 정의하여 옵션(예: PacketClonerCalculatorOptions) 그러면 계산기가 CalculatorBase::Open 메서드에서 protobuf 메시지를 읽고 가능한 경우 CalculatorBase::GetContract 함수 또는 CalculatorBase::Process 메서드를 사용하여 지도 가장자리에 패딩을 추가할 수 있습니다. 일반적으로 새로운 protobuf 메시지 유형은 '.proto'를 사용하여 protobuf 스키마로 정의해야 합니다. 파일 및 mediapipe_proto_library() 빌드 규칙

  mediapipe_proto_library(
      name = "packet_cloner_calculator_proto",
      srcs = ["packet_cloner_calculator.proto"],
      visibility = ["//visibility:public"],
      deps = [
          "//mediapipe/framework:calculator_options_proto",
          "//mediapipe/framework:calculator_proto",
      ],
  )

계산기 예

이 섹션에서는 PacketClonerCalculator의 구현에 관해 설명합니다. 비교적 간단한 작업을 수행하며 많은 계산기 그래프에서 사용됩니다. PacketClonerCalculator는 단순히 가장 최근 입력 패킷의 사본을 생성합니다. 온디맨드 방식으로 작동합니다

PacketClonerCalculator는 도착하는 데이터 패킷의 타임스탬프가 완전히 정렬되지 않습니다. 방에 마이크와 조명이 있다고 가정해 봅시다. 센서와 감각 데이터를 수집하는 비디오 카메라가 있습니다. 각각의 센서는 독립적으로 작동하고 간헐적으로 데이터를 수집합니다. 출력이 각 센서의 특징은 다음과 같습니다.

  • 마이크 = 방 안의 사운드 크기(데시벨)(정수)
  • 광 센서 = 방의 밝기 (정수)
  • video camera = 공간의 RGB 이미지 프레임 (ImageFrame)

우리의 단순한 인지 파이프라인은 이 3가지 요소의 감각 데이터를 처리하도록 즉, 카메라의 이미지 프레임 데이터가 있을 때 마지막으로 수집된 마이크 음량 데이터 및 표시등과 동기화됩니다. 센서 밝기 데이터 MediaPipe를 통해 이 작업을 수행하기 위한 인식 파이프라인에는 입력 스트림:

  • Room_mic_signal: 이 입력 스트림의 각 데이터 패킷은 정수 데이터입니다. 타임스탬프가 있는 방에서의 오디오 볼륨을 나타냅니다.
  • Room_lightening_sensor: 이 입력 스트림의 각 데이터 패킷은 정수입니다. 타임스탬프로 방을 비추는 밝기를 나타내는 데이터입니다.
  • Room_video_tick_signal - 이 입력 스트림의 각 데이터 패킷은 카메라에서 수집된 동영상을 나타내는 동영상 데이터의 타임스탬프가 있는 방

다음은 PacketClonerCalculator의 구현입니다. 이 GetContract(), Open(), Process() 메서드 및 인스턴스 변수 current_ - 가장 최근 입력 패킷을 포함합니다.

// This takes packets from N+1 streams, A_1, A_2, ..., A_N, B.
// For every packet that appears in B, outputs the most recent packet from each
// of the A_i on a separate stream.

#include <vector>

#include "absl/strings/str_cat.h"
#include "mediapipe/framework/calculator_framework.h"

namespace mediapipe {

// For every packet received on the last stream, output the latest packet
// obtained on all other streams. Therefore, if the last stream outputs at a
// higher rate than the others, this effectively clones the packets from the
// other streams to match the last.
//
// Example config:
// node {
//   calculator: "PacketClonerCalculator"
//   input_stream: "first_base_signal"
//   input_stream: "second_base_signal"
//   input_stream: "tick_signal"
//   output_stream: "cloned_first_base_signal"
//   output_stream: "cloned_second_base_signal"
// }
//
class PacketClonerCalculator : public CalculatorBase {
 public:
  static absl::Status GetContract(CalculatorContract* cc) {
    const int tick_signal_index = cc->Inputs().NumEntries() - 1;
    // cc->Inputs().NumEntries() returns the number of input streams
    // for the PacketClonerCalculator
    for (int i = 0; i < tick_signal_index; ++i) {
      cc->Inputs().Index(i).SetAny();
      // cc->Inputs().Index(i) returns the input stream pointer by index
      cc->Outputs().Index(i).SetSameAs(&cc->Inputs().Index(i));
    }
    cc->Inputs().Index(tick_signal_index).SetAny();
    return absl::OkStatus();
  }

  absl::Status Open(CalculatorContext* cc) final {
    tick_signal_index_ = cc->Inputs().NumEntries() - 1;
    current_.resize(tick_signal_index_);
    // Pass along the header for each stream if present.
    for (int i = 0; i < tick_signal_index_; ++i) {
      if (!cc->Inputs().Index(i).Header().IsEmpty()) {
        cc->Outputs().Index(i).SetHeader(cc->Inputs().Index(i).Header());
        // Sets the output stream of index i header to be the same as
        // the header for the input stream of index i
      }
    }
    return absl::OkStatus();
  }

  absl::Status Process(CalculatorContext* cc) final {
    // Store input signals.
    for (int i = 0; i < tick_signal_index_; ++i) {
      if (!cc->Inputs().Index(i).Value().IsEmpty()) {
        current_[i] = cc->Inputs().Index(i).Value();
      }
    }

    // Output if the tick signal is non-empty.
    if (!cc->Inputs().Index(tick_signal_index_).Value().IsEmpty()) {
      for (int i = 0; i < tick_signal_index_; ++i) {
        if (!current_[i].IsEmpty()) {
          cc->Outputs().Index(i).AddPacket(
              current_[i].At(cc->InputTimestamp()));
          // Add a packet to output stream of index i a packet from inputstream i
          // with timestamp common to all present inputs
        } else {
          cc->Outputs().Index(i).SetNextTimestampBound(
              cc->InputTimestamp().NextAllowedInStream());
          // if current_[i], 1 packet buffer for input stream i is empty, we will set
          // next allowed timestamp for input stream i to be current timestamp + 1
        }
      }
    }
    return absl::OkStatus();
  }

 private:
  std::vector<Packet> current_;
  int tick_signal_index_;
};

REGISTER_CALCULATOR(PacketClonerCalculator);
}  // namespace mediapipe

일반적으로 계산기에는 .cc 파일만 있습니다. .h가 필요하지 않습니다. mediapipe는 등록을 사용하여 계산기를 알 수 있도록 합니다. 작업을 완료한 후 계산기 클래스를 정의하고 매크로 호출에 등록 REGISTER_CALCULATOR(calculator_class_name).

다음은 3개의 입력 스트림, 1개의 노드가 있는 사소한 MediaPipe 그래프입니다. (PacketClonerCalculator) 및 2개의 출력 스트림.

input_stream: "room_mic_signal"
input_stream: "room_lighting_sensor"
input_stream: "room_video_tick_signal"

node {
   calculator: "PacketClonerCalculator"
   input_stream: "room_mic_signal"
   input_stream: "room_lighting_sensor"
   input_stream: "room_video_tick_signal"
   output_stream: "cloned_room_mic_signal"
   output_stream: "cloned_lighting_sensor"
 }

아래 다이어그램은 PacketClonerCalculator가 출력을 정의하는 방법을 보여줍니다. 일련의 입력 패킷 (위)을 기반으로 하는 패킷 (하단)을 확인할 수 있습니다.

PacketClonerCalculator를 사용한 그래프
PacketClonerCalculator는 TICK 입력 스트림에서 패킷을 수신할 때마다 각 입력 스트림에서 가장 최근의 패킷을 출력합니다. 출력 패킷의 순서 (아래)는 입력 패킷의 순서 (상단)와 타임스탬프에 따라 결정됩니다. 타임스탬프는 다이어그램 오른쪽에 표시되어 있습니다.