Hesap Makineleri

Her hesap makinesi, bir grafik düğümüdür. Yeni bir hesap makinesi, hesap makinesi nasıl başlatılır, hesaplama nasıl yapılır, giriş ve çıkış akışları, zaman damgaları ve seçenekler. Grafikteki her düğüm Calculator olarak uygulanır. Grafik yürütmenin büyük kısmı hesap makineleri. Bir hesap makinesi sıfır veya daha fazla giriş akışı ve/veya taraf alabilir hiçbir çıkış akışı ve/veya yan paket üretmez.

CalculatorBase

Yeni bir alt sınıf tanımlanarak bir hesap makinesi CalculatorBase bir dizi yöntem uygulayarak ve yeni alt sınıfını kullanarak Mediapipe. Yeni bir hesap makinesi, en azından aşağıdaki dört yöntemi kullanmalıdır

  • GetContract()
    • Hesap makinesi yazarları, beklenen giriş ve çıkış türlerini belirtebilir bir hesap makinesinin karma değerini oluşturun. Bir grafik başlatıldığında, çerçeve, paket türlerinin paket türlerinin doğru olup olmadığını doğrulamak için statik bir yöntem giriş ve çıkışların bu sayfadaki bilgilerle eşleştiğinden, bakın.
  • Open()
    • Grafik başladıktan sonra çerçeve, Open() öğesini çağırır. Giriş tarafı hesap makinesinde kullanılabilir hale gelir. Open(). düğüm yapılandırma işlemlerini yorumlar (bkz. Grafikler) ve hesap makinesinin grafik başına çalıştırma durumunu hazırlar. Bu işlev, hesap makinesi çıkışlarına da paket yazar. Open() sırasındaki bir hata sonlandırır.
  • Process()
    • Girişleri olan bir hesap makinesi için çerçeve art arda Process() çağırıyor en az bir giriş akışında paket olduğunda. Çerçeve varsayılan olarak tüm girişlerin aynı zaman damgasına sahip olmasını garanti eder (bkz. Senkronizasyon'u tıklayın). Birden çok Paralel yürütme sırasında Process() aramaları aynı anda başlatılabilir etkin olduğundan emin olun. Process() sırasında bir hata oluşursa çerçeve Close() ve grafik çalıştırma sonlandırılır.
  • Close()
    • Process() için yapılan tüm çağrılar tamamlandıktan veya tüm giriş akışları kapandığında çerçeve, Close() öğesini çağırır. Bu işlev her zaman Open() çağrıldı ve başarılı oldu ve grafik çalıştırması sonlandırılsa bile bir hata nedeniyle oluştu. Giriş akışları üzerinden kullanılabilir giriş yok Close() sırasında ancak giriş tarafı paketlerine ve ve çıkışlar yazabilir. Close() geri geldikten sonra hesap makinesi ölü düğüm olarak kabul edilmelidir. Hesap makinesi nesnesi yok edildiğinde hemen ardından otomatik olarak gösterilir.

Aşağıdakiler CalculatorBase.h ifadesini kullanın.

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

  ...
};

Hesap makinesinin ömrü

Bir MediaPipe grafiğinin başlatılması sırasında çerçeve, GetContract() statik yöntemini kullanarak hangi tür paketlerin beklendiğini belirler.

Bu çerçeve, her grafik çalıştırması için hesap makinesinin tamamını oluşturur ve kaldırır (ör. video başına veya resim başına bir defa). Kalan pahalı veya büyük nesneler grafik çalıştırmalarda sabit değer, giriş tarafı paketleri olarak sağlanmalıdır; hesaplamalar sonraki çalıştırmalarda tekrarlanmaz.

Başlatma işleminden sonra, grafiğin her çalışması için aşağıdaki sıra gerçekleşir:

  • Open()
  • Process() (tekrarlanan)
  • Close()

Çerçeve, hesap makinesini başlatmak için Open() komutunu çağırır. Open(), seçenekleri yorumlayın ve hesap makinesinin grafik başına durumunu ayarlayın. Open(). giriş tarafı paketleri alabilir ve hesap makinesi çıkışlarına paketler yazabilir. Eğer olması durumunda, olası paket arabelleğine almayı azaltmak için SetOffset() komutunu çağırmalıdır sayısı.

Open() veya Process() sırasında bir hata oluşursa (birinin belirttiği şekilde) Ok dışında bir durum döndürürse) grafik çalıştırma, başka arama yapılmadan sonlandırılır. ve hesap makinesi yok olur.

Girişleri olan bir hesap makinesi için çerçeve en az şu zamanlarda Process() çağırır: girişlerden birinde kullanılabilir paket var. Bu çerçeve, tüm girişlerin doğru şekilde aynı zaman damgasına sahip olduğundan zaman damgaları, Process() çağrısıyla birlikte artar ve teslim edilir. Sonuç olarak bazı girişlerde Process() çağrıldığında gönderilir. Eksik olan bir giriş, boş bir paket oluşturabilir (zaman damgası olmadan).

Çerçeve, tüm Process() çağrılarından sonra Close() öğesini çağırır. Tüm girişler tükendi, ancak Close(), giriş tarafı paketlerine erişebilir ve çıkışlar yazma. Kapat iade edildikten sonra, hesap makinesi yok edilir.

Giriş içermeyen hesap makinelerine kaynak adı verilir. Kaynak hesap makinesi Ok durumu döndürdüğü sürece Process() çağrılmaya devam eder. CEVAP kaynak hesaplayıcı, bir durdurma durumu döndürdüğünde tükendiğini gösterir (ör. mediaPipe::tool::StatusStop()).

Giriş ve çıkışları belirleme

Hesap makinesinin herkese açık arayüzü, bir dizi giriş akışından ve çıkış akışı sağlar. HesaplayıcıGraphConfiguration'da, oluşturulan bazı hesap makineleri, adlandırılmış adlar kullanılarak diğer hesap makinelerinin girişlerine bağlanır. akışlar. Akış adları normalde küçüktür, giriş ve çıkış etiketleri ise BÜYÜK HARF olmalıdır. Aşağıdaki örnekte, etiket adı VIDEO olan çıkış , VIDEO_IN etiketli girişe, adlı akış kullanılarak bağlandı 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"
}

Giriş ve çıkış akışları dizin numarası, etiket adı veya bir etiket adı ve dizin numarası kombinasyonundan oluşur. Bazı girdi örneklerini ve çıkış tanımlayıcılarını kullanın. SomeAudioVideoCalculator, kendini tanımlar etiketine göre video çıkışı ve dizin. VIDEO etiketine sahip giriş, adlı akışa bağlı video_stream. AUDIO etiketine ve 0 ile 1 dizinlerine sahip çıkışlar şu şekildedir: audio_left ve audio_right adlı akışlara bağlı. SomeAudioCalculator, ses girişlerini yalnızca dizine göre tanımlar (etiket gerekmez).

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

Hesap makinesi uygulamasında, girişler ve çıkışlar etiket tarafından da tanımlanır. ad ve dizin numarası. Aşağıdaki fonksiyonda giriş ve çıkış tanımlanmıştır:

  • Dizin numarasına göre: Birleştirilmiş giriş akışı dizin ile tanımlanır 0
  • Etiket adına göre: Video çıkış akışı, "VIDEO" etiket adıyla tanımlanır.
  • Etiket adına ve dizin numarasına göre: Çıkış ses akışları AUDIO etiket adı ile 0 ve 1 dizin numaralarının kombinasyonudur.
// 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();
  }

İşleniyor

Kaynak olmayan bir düğümde çağrılan Process(), absl::OkStatus() değerini döndürmelidir her şeyin iyi gittiğini veya hata sinyalini sağlayan başka bir durum kodunu belirtmek

Kaynak olmayan bir hesap makinesi tool::StatusStop() sonucunu döndürürse bu iptal ediliyor. Bu örnekte, tüm kaynak hesaplayıcılar ve grafik giriş akışları kapatılır (ve kalan Paketler , grafiğidir).

Bir grafikteki kaynak düğüm üzerinde Process() çağrılmaya devam eder (absl::OkStatus( döndürdüğünden) emin olun. Yani, bu kitle için tool::StatusStop() oranında bir getiri elde etti. Diğer tüm durumlar, bir hatanın olduğunu gösterir meydana geldi.

Close(), başarılı olduğunu belirtmek için absl::OkStatus() değerini döndürür. Diğer durumlar bir hata olduğunu gösterir.

Temel Process() işlevi aşağıdaki gibidir. Input() yöntemini kullanır ( yalnızca hesap makinesinde tek bir giriş varsa) kullanılmalıdır. Google ardından çıkış paketi için gereken belleği ayırmak üzere std::unique_ptr politikasını kullanır. ve hesaplamalar yapar. İşlem tamamlandığında, çıkış akışı anlamına gelir.

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

Hesap makinesi seçenekleri

Hesap makineleri, (1) giriş akışı paketleri (2) aracılığıyla işleme parametrelerini kabul eder giriş tarafı paketleri ve (3) hesap makinesi seçenekleri. Hesap makinesi seçenekleri (varsa) değeri belirtilen değerin node_options alanında değişmez değerler olarak görünür CalculatorGraphConfiguration.Node mesaj.

  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 alanı, proto3 söz dizimini kabul eder. Alternatif olarak, hesap makinesi seçenekler, proto2 söz dizimi kullanılarak options alanında belirtilebilir.

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

Bazı hesap makineleri, hesap makinesi seçeneklerini kabul etmez. Seçenekleri kabul etmek için bir calculator, normal şartlarda PacketClonerCalculatorOptions gibi seçenekler. Hesap makinesi, bu protobuf mesajını CalculatorBase::Open yönteminde okuyabilir ve muhtemelen ayrıca CalculatorBase::GetContract işlevinde veya CalculatorBase::Process yöntemi. Normalde yeni protobuf mesaj türü ".proto" kullanan bir protobuf şeması olarak tanımlanabilir dosya ve bir mediapipe_proto_library() derleme kuralı.

  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",
      ],
  )

Örnek hesap makinesi

Bu bölümde, PacketClonerCalculator uygulamak için kullanılan nispeten basit bir iş yapar ve birçok hesap makinesi grafiğinde kullanılmaktadır. PacketClonerCalculator, en son giriş paketlerinin bir kopyasını oluşturur isteğe bağlı.

PacketClonerCalculator, gelen veri paketlerinin zaman damgaları olduğunda kullanışlıdır birbirine tam olarak hizalanmaz. Mikrofonu ve ışıklandırması olan bir odamız olduğunu varsayalım. sensör ve duyusal veriler toplayan bir video kamera. Sensörlerin her biri bağımsız olarak çalışır ve aralıklı olarak veri toplar. Diyelim ki çıktı her bir sensörün boyutu:

  • mikrofon = odadaki sesin desibel cinsinden yüksekliği (Tam sayı)
  • ışık sensörü = odanın parlaklığı (Tam sayı)
  • video kamera = Odanın RGB resim çerçevesi (ImageFrame)

Basit algılama ardışık düzenimiz, bu üç unsurdan alınan duyusal verileri işleyecek şekilde tasarlanmıştır. kameradan alınan resim çerçevesi verileri gibi, böyle bir kameradan toplanan son mikrofon ses düzeyi verisi ve ışığıyla senkronize edilir sensör parlaklık verileri. Bunu MediaPipe ile yapmak için, algı ardışık düzenimizde 3 giriş akışları:

  • oda_mikrofonu_signal: Bu giriş akışındaki her veri paketi tam sayıdır odadaki sesin ne kadar yüksek olduğunu zaman damgasıyla gösteren resim.
  • Room_lightening_sensor: Bu giriş akışındaki her veri paketi tam sayıdır odanın ne kadar parlak olduğunu zaman damgasıyla gösteren veriler.
  • Room_video_tick_signal - Bu giriş akışındaki her bir veri paketi kameradan toplanan videoyu temsil eden video verilerinin resim çerçevesi zaman damgası bulunan bir oda.

Aşağıda PacketClonerCalculator'ın uygulanması gösterilmektedir. Örneğin GetContract(), Open() ve Process() yöntemlerinin yanı sıra örneğin current_ değişkeni en son giriş paketlerini içerir.

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

Genellikle, bir hesap makinesinin yalnızca bir .cc dosyası vardır. .h gerekli değildir, çünkü mediapipe, hesap makinelerini tanıması için kayıttan yararlanır. Bu hesap makinesi sınıfınızı tanımladıysanız, makro çağrısıyla kaydedin REGISTER_CALCULATOR(calculator_class_name).

Aşağıda 3 giriş akışı ve 1 düğüm içeren basit bir MediaPipe grafiği verilmiştir. (PacketClonerCalculator) ve 2 çıkış akışı içeriyor.

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

Aşağıdaki şemada PacketClonerCalculator öğesinin, çıkışını nasıl tanımladığı gösterilmektedir paketleri (altta) gösterir.

PacketClonerCalculator&#39;ı kullanarak grafik
Paket Cloner Hesaplayıcı, TICK giriş akışında her paket aldığında her bir giriş akışından en son paketi çıkarır. Çıkış paketlerinin sırası (altta), giriş paketlerinin sırasına (üst) ve zaman damgalarına göre belirlenir. Zaman damgaları, şemanın sağ tarafında gösterilir.