กราฟ

กราฟ

Proto ของ CalculatorGraphConfig ระบุโทโพโลยีและฟังก์ชันการทำงานของกราฟ MediaPipe node แต่ละรายการในกราฟแสดงถึงเครื่องคำนวณหรือกราฟย่อยหนึ่งๆ และระบุการกำหนดค่าที่จำเป็น เช่น ประเภทเครื่องคำนวณ/ย่อยที่ลงทะเบียนไว้ อินพุต เอาต์พุต และช่องที่ไม่บังคับ เช่น ตัวเลือกเฉพาะโหนด นโยบายอินพุตและเครื่องมือดำเนินการ ซึ่งจะกล่าวถึงในการซิงค์

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

ด้านล่างนี้เป็นตัวอย่าง CalculatorGraphConfig ที่มีชุดเครื่องคำนวณ Passthrough :

# 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 นำเสนอ C++ ทางเลือกสำหรับกราฟที่ซับซ้อน (เช่น ไปป์ไลน์ ML, การจัดการข้อมูลเมตาของโมเดล, โหนดที่ไม่บังคับ เป็นต้น) กราฟด้านบนอาจมีลักษณะดังนี้

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. ลงทะเบียนกราฟย่อยโดยใช้กฎการสร้าง 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"
    }
    

ตัวเลือกกราฟ

คุณระบุ Protobuf "ตัวเลือกกราฟ" สำหรับกราฟ MediaPipe ที่คล้ายกับ Calculator Options Protobuf ที่ระบุสำหรับเครื่องคำนวณ MediaPipe "ตัวเลือกกราฟ" เหล่านี้สามารถระบุเมื่อมีการเรียกใช้กราฟ และใช้เพื่อป้อนข้อมูลตัวเลือกเครื่องคิดเลขและตัวเลือกกราฟย่อยภายในกราฟ

ใน 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 ยอมรับตัวเลือกกราฟ โพรโทบุฟ FaceDetectionOptions FaceDetectionOptions ใช้เพื่อระบุค่าของช่องบางส่วนในตัวเลือกเครื่องคิดเลข ImageToTensorCalculatorOptions และค่าของช่องบางค่าในตัวเลือกกราฟย่อย InferenceCalculatorOptions ค่าในช่องจะกำหนดโดยใช้ไวยากรณ์ option_value:

ใน CalculatorGraphConfig::Node Protobuf ช่อง node_options: และ option_value: ร่วมกันจะกำหนดค่าตัวเลือกสำหรับเครื่องคิดเลข เช่น ImageToTensorCalculator ฟิลด์ node_options: กำหนดชุดของค่าคงที่แบบลิเทอรัลโดยใช้ไวยากรณ์ Protobuf ของข้อความ ช่อง option_value: แต่ละช่องจะกําหนดค่าให้กับช่อง Protobuf 1 ช่องโดยใช้ข้อมูลจากกราฟที่แนบมา โดยเฉพาะจากค่าในช่องของตัวเลือกกราฟของกราฟที่แนบมา ในตัวอย่างด้านบน option_value: "output_tensor_width:options/tensor_width" จะกำหนดช่อง ImageToTensorCalculatorOptions.output_tensor_width โดยใช้ค่าของ FaceDetectionOptions.tensor_width

ไวยากรณ์ของ option_value: คล้ายกับไวยากรณ์ของ input_stream: ไวยากรณ์คือ option_value: "LHS:RHS" LHS จะระบุช่องตัวเลือกเครื่องคิดเลข และ RHS จะระบุช่องตัวเลือกกราฟ กล่าวอย่างเจาะจงก็คือ LHS และ RHS แต่ละฟิลด์ประกอบไปด้วยชุดชื่อช่อง Protobuf ที่ระบุข้อความ Protobuf ที่ซ้อนกันและฟิลด์ที่คั่นด้วยเครื่องหมาย "/" ซึ่งเรียกว่าไวยากรณ์ "ProtoPath" ข้อความที่ซ้อนกันที่มีการอ้างอิงใน LHS หรือ RHS จะต้องกำหนดแล้วใน Protobuf ที่ล้อมรอบอยู่เพื่อให้มีการส่งผ่านโดยใช้ option_value:

ช่วงเวลา

โดยค่าเริ่มต้น MediaPipe กำหนดให้กราฟจากเครื่องคำนวณต้องเป็นแบบวงกลมและถือว่ารอบในกราฟเป็นข้อผิดพลาด หากต้องการให้กราฟมีวงจร คุณต้องใส่คำอธิบายประกอบลงในการกำหนดค่ากราฟ หน้านี้อธิบายวิธีดำเนินการดังกล่าว

หมายเหตุ: วิธีการปัจจุบันยังอยู่ในขั้นทดลองและอาจมีการเปลี่ยนแปลง เรายินดีรับฟังความคิดเห็นจากคุณ

โปรดใช้การทดสอบหน่วย CalculatorGraphTest.Cycle ใน mediapipe/framework/calculator_graph_test.cc เป็นโค้ดตัวอย่าง ด้านล่างนี้เป็นกราฟแบบวนซ้ำในการทดสอบ เอาต์พุต sum ของตัวเพิ่มคือผลรวมของจำนวนเต็มที่สร้างขึ้นจากเครื่องคำนวณแหล่งที่มาของจำนวนเต็ม

กราฟแบบวนซ้ำที่เพิ่มสตรีมของจำนวนเต็ม

กราฟง่ายๆ นี้แสดงให้เห็นถึงปัญหาทั้งหมดในกราฟแบบวนซ้ำที่สนับสนุน

หมายเหตุเกี่ยวกับขอบหลัง

เรากำหนดให้ระบุ EDGE ในแต่ละรอบเป็นขอบย้อนกลับ วิธีนี้ช่วยให้การจัดเรียงแบบโทโพโลยีของ MediaPipe ทำงานได้หลังจากนำขอบด้านหลังออกทั้งหมด

โดยปกติแล้ว การเลือกขอบด้านหลังทำได้หลายวิธี ขอบที่มีการทำเครื่องหมายเป็นขอบกลับจะส่งผลต่อโหนดที่ถือว่าเป็นอัปสตรีมและโหนดที่ถือว่าเป็นดาวน์สตรีม ซึ่งจะส่งผลต่อลำดับความสำคัญที่ MediaPipe กำหนดให้กับโหนด

เช่น การทดสอบ CalculatorGraphTest.Cycle จะทำเครื่องหมาย EDGE old_sum เป็น Edge ด้านหลัง ดังนั้นโหนด Delay จึงถือเป็นโหนดดาวน์สตรีมของโหนด Adder และมีลำดับความสำคัญสูงกว่า หรืออาจทำเครื่องหมายอินพุต sum ไปยังโหนดการหน่วงเวลาเป็นขอบด้านหลัง ซึ่งในกรณีนี้โหนดการหน่วงเวลาจะถือว่าเป็นโหนดอัปสตรีมของโหนดส่วนเสริมและมีลำดับความสำคัญต่ำกว่า

แพ็กเก็ตเริ่มต้น

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

หน่วงเวลาวนซ้ำ

แต่ละลูปควรมีการหน่วงเวลาเพื่อปรับเอาต์พุต sum ก่อนหน้ากับอินพุตจำนวนเต็มถัดไป ซึ่งโหนดล่าช้าก็ทำได้เช่นกัน โหนดการหน่วงเวลาจึงจำเป็นต้องทราบข้อมูลต่อไปนี้เกี่ยวกับการประทับเวลาของเครื่องคำนวณแหล่งที่มาของจำนวนเต็ม

  • การประทับเวลาของเอาต์พุตแรก

  • เดลต้าการประทับเวลาระหว่างเอาต์พุตต่อเนื่อง

เราวางแผนที่จะเพิ่มนโยบายการกำหนดเวลาทางเลือกที่ให้ความสำคัญกับการสั่งซื้อแพ็กเก็ตเท่านั้นและไม่สนใจการประทับเวลาแพ็กเก็ต ซึ่งจะช่วยลดความไม่สะดวกนี้

การสิ้นสุดเครื่องคิดเลขล่วงหน้าเมื่อสตรีมอินพุตหนึ่งเสร็จสิ้น

โดยค่าเริ่มต้น MediaPipe จะเรียกเมธอด Close() ของเครื่องคำนวณที่ไม่ใช่แหล่งที่มาเมื่อสตรีมอินพุตทั้งหมดเสร็จสิ้นแล้ว ในกราฟตัวอย่าง เราต้องการหยุดโหนด Adder ทันทีที่ต้นทางที่เป็นจำนวนเต็มเสร็จ ซึ่งทำได้ด้วยการกำหนดค่าโหนด Adder ด้วยตัวแฮนเดิลสตรีมอินพุตทางเลือก 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'
}