เครื่องคิดเลข

เครื่องคิดเลขแต่ละตัวคือโหนดของกราฟ เราได้อธิบายวิธีการสร้าง เครื่องคิดเลข, วิธีเริ่มต้นเครื่องคิดเลข, วิธีการคำนวณ สตรีมอินพุตและเอาต์พุต การประทับเวลา และตัวเลือกต่างๆ แต่ละโหนดในกราฟคือ เป็น Calculator การดำเนินการกับกราฟจำนวนมากเกิดขึ้นภายใน เครื่องคิดเลข เครื่องคิดเลขอาจได้รับสตรีมอินพุตและ/หรือฝั่งเป็น 0 หรือมากกว่า แพ็กเก็ตและสร้างสตรีมเอาต์พุตและ/หรือแพ็กเก็ตด้านข้างเป็นศูนย์หรือมากกว่า

CalculatorBase

เครื่องคิดเลขจะสร้างขึ้นโดยกำหนดคลาสย่อยใหม่ของ CalculatorBase การใช้วิธีการต่างๆ และลงทะเบียนคลาสย่อยใหม่ด้วย Mediapipe อย่างน้อยที่สุด เครื่องคำนวณใหม่ต้องใช้ 4 วิธีด้านล่างนี้

  • GetContract()
    • ผู้เขียนเครื่องคิดเลขสามารถระบุประเภทอินพุตและเอาต์พุตที่คาดไว้ได้ ของเครื่องคิดเลขใน GetContract() เมื่อเริ่มต้นกราฟ จะเรียกใช้เมธอดแบบคงที่เพื่อตรวจสอบว่าประเภทแพ็กเก็ตของฟิลด์ อินพุตและเอาต์พุตที่เชื่อมต่ออยู่ตรงกับข้อมูลใน
  • Open()
    • หลังจากที่กราฟเริ่มขึ้น เฟรมเวิร์กจะเรียกใช้ Open() ด้านอินพุต เครื่องคิดเลขสามารถใช้แพ็กเก็ตได้ Open() ตีความการดำเนินการกำหนดค่าโหนด (ดูกราฟ) และเตรียมสถานะต่อการเรียกใช้กราฟของเครื่องคำนวณ ฟังก์ชันนี้อาจ เขียนแพ็กเก็ตไปยังเอาต์พุตเครื่องคิดเลขด้วย เกิดข้อผิดพลาดระหว่างOpen() ก็ยุติการเรียกใช้กราฟ
  • Process()
    • สําหรับเครื่องคิดเลขที่มีอินพุต เฟรมเวิร์กจะเรียกใช้ Process() ซ้ำๆ เมื่อใดก็ตามที่สตรีมอินพุตอย่างน้อย 1 รายการมีแพ็กเก็ต เฟรมเวิร์ก โดยค่าเริ่มต้น จะช่วยรับประกันว่าอินพุตทั้งหมดจะมีการประทับเวลาเหมือนกัน (โปรดดู การซิงค์สำหรับข้อมูลเพิ่มเติม) หลายสกุลเงิน คุณเรียกใช้ 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() เมธอดแบบคงที่เพื่อกำหนดประเภทแพ็กเก็ตที่ต้องการ

เฟรมเวิร์กนี้จะสร้างและทำลายเครื่องคำนวณทั้งหมดสำหรับการเรียกใช้กราฟแต่ละครั้ง (เช่น 1 ครั้งต่อวิดีโอหรือ 1 ครั้งต่อรูปภาพ) วัตถุราคาแพงหรือวัตถุขนาดใหญ่ที่ยังหลงเหลืออยู่ ควรระบุค่าคงที่ในการเรียกใช้กราฟเป็นแพ็กเก็ตด้านอินพุต ดังนั้น ไม่ทำการคำนวณซ้ำในการเรียกใช้ครั้งต่อๆ ไป

หลังจากการเริ่มต้น สำหรับการเรียกใช้กราฟแต่ละครั้ง ลำดับต่อไปนี้จะเกิดขึ้น

  • Open()
  • Process() (ซ้ำ)
  • Close()

เฟรมเวิร์กจะเรียกใช้ Open() เพื่อเริ่มต้นใช้งานเครื่องคิดเลข Open() ควร ตีความตัวเลือกใดๆ และตั้งค่าสถานะต่อการเรียกใช้กราฟของเครื่องคิดเลข Open() อาจรับแพ็กเก็ตด้านอินพุตและเขียนแพ็กเก็ตไปยังเอาต์พุตของเครื่องคำนวณ ถ้า เหมาะสม ควรจะเรียก SetOffset() เพื่อลดการบัฟเฟอร์แพ็กเก็ตที่อาจเกิดขึ้น ของสตรีมอินพุต

หากเกิดข้อผิดพลาดระหว่าง Open() หรือ Process() (ตามที่แจ้งไว้รายการใดรายการหนึ่ง ส่งคืนสถานะที่ไม่ใช่ Ok) การเรียกใช้กราฟจะสิ้นสุดลงโดยไม่มีการเรียกเพิ่มเติม กับวิธีการของเครื่องคิดเลข และเครื่องคิดเลขก็ถูกทำลาย

สำหรับเครื่องคิดเลขที่มีอินพุต เฟรมเวิร์กจะเรียกใช้ Process() เมื่ออย่างน้อย หนึ่งอินพุตมีแพ็กเก็ต เฟรมเวิร์กนี้รับประกันว่า อินพุตทั้งหมดจะมี การประทับเวลาเดียวกัน การประทับเวลาจะเพิ่มขึ้นเมื่อมีการเรียกไปยัง Process() แต่ละครั้งและ ว่าจะมีการนำส่งแพ็กเก็ตทั้งหมด ดังนั้น อินพุตบางรายการจึงอาจไม่มี แพ็กเก็ตเมื่อมีการเรียกใช้ Process() อินพุตที่แพ็กเก็ตหายไป สร้างแพ็กเก็ตที่ว่างเปล่า (ไม่มีการประทับเวลา)

เฟรมเวิร์กนี้จะเรียกใช้ Close() หลังจากการเรียกใช้ Process() ทั้งหมด อินพุตทั้งหมดจะ หมดแล้ว แต่ Close() มีสิทธิ์เข้าถึงแพ็กเก็ตด้านข้างของอินพุตและอาจ เขียนเอาต์พุต หลังจากที่ระยะ "ปิด" กลับมา เครื่องคิดเลขจะถูกทำลาย

เครื่องคิดเลขที่ไม่มีอินพุตเรียกว่าแหล่งที่มา เครื่องคำนวณแหล่งที่มา จะยังคงมีการเรียก Process() ต่อไปตราบใดที่แสดงผลสถานะ Ok ต เครื่องคำนวณแหล่งที่มาระบุว่าหมดแล้วโดยการส่งคืนสถานะหยุด (เช่น 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 และดัชนี 0 และ 1 มีดังนี้ เชื่อมต่อกับสตรีมชื่อ audio_left และ audio_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 และหมายเลขดัชนี 0 และ 1
// 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 ยอมรับไวยากรณ์ Prot3 อีกวิธีคือ เครื่องคิดเลข ระบุตัวเลือกในช่อง options ได้โดยใช้ไวยากรณ์ Proto2

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

เครื่องคำนวณบางรุ่นไม่ยอมรับตัวเลือกเครื่องคำนวณ หากต้องการยอมรับตัวเลือก ตามปกติแล้วเครื่องคิดเลขจะกำหนด ประเภทข้อความ protocolbuf เพื่อแสดง ตัวเลือกอื่น เช่น PacketClonerCalculatorOptions จากนั้นเครื่องคิดเลขจะ อ่านข้อความ Protobuf ในเมธอด CalculatorBase::Open และอาจ ในฟังก์ชัน CalculatorBase::GetContract หรือ CalculatorBase::Process วิธี โดยปกติแล้ว ข้อความ Protocolbuf ประเภทใหม่จะ เป็นสคีมา Protobuf โดยใช้ ".prod" และ กฎบิลด์ 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 มีประโยชน์เมื่อการประทับเวลาของแพ็กเก็ตข้อมูลขาเข้า ไม่ได้จัดตำแหน่งอย่างสมบูรณ์แบบ สมมติว่าเรามีห้องที่มีไมโครโฟน แสงไฟ เซ็นเซอร์และกล้องวิดีโอที่เก็บรวบรวมข้อมูลการรับรู้ความรู้สึก เซ็นเซอร์แต่ละตัว ทำงานอย่างอิสระและรวบรวมข้อมูลเป็นระยะๆ สมมติว่าเอาต์พุต ของเซ็นเซอร์แต่ละตัว

  • ไมโครโฟน = ความดังในหน่วยเดซิเบลของเสียงในห้อง (จำนวนเต็ม)
  • เซ็นเซอร์แสง = ความสว่างของห้อง (จำนวนเต็ม)
  • กล้องวิดีโอ = เฟรมภาพ 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).

ด้านล่างคือกราฟ MediaPipe ที่ไม่สำคัญซึ่งมีสตรีมอินพุต 3 รายการ 1 โหนด (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
ทุกครั้งที่ได้รับแพ็กเก็ตในสตรีมอินพุต TICK เอง PacketClonerCalculator จะแสดงแพ็กเก็ตล่าสุดจากสตรีมอินพุตแต่ละรายการ ลำดับของแพ็กเก็ตเอาต์พุต (ด้านล่าง) จะกำหนดโดยลำดับของแพ็กเก็ตอินพุต (ด้านบน) และการประทับเวลา โดยการประทับเวลาจะแสดงอยู่ทางด้านขวาของแผนภาพ