เฟรมเวิร์ก MediaPipe ใน Python

เฟรมเวิร์ก MediaPipe Python ให้สิทธิ์เข้าถึงคอมโพเนนต์หลักของ เฟรมเวิร์ก MediaPipe C++ เช่น Timestamp, Packet และ CalculatorGraph ขณะที่โซลูชัน Python ที่พร้อมใช้งาน รายละเอียดทางเทคนิคของเฟรมเวิร์ก แล้วแสดงผลโมเดลที่อ่านได้ ผลการอนุมานกลับไปยังผู้โทร

เฟรมเวิร์ก MediaPipe อยู่ด้านบน ไลบรารี pybind11 เฟรมเวิร์กหลักของ C++ จะแสดงใน Python ผ่านการเชื่อมโยงภาษา C++/Python เนื้อหาด้านล่างนี้ถือว่าผู้อ่านมีความเข้าใจเบื้องต้นเกี่ยวกับ เฟรมเวิร์ก MediaPipe C++ มิฉะนั้น คุณสามารถค้นหาข้อมูลที่เป็นประโยชน์ได้ใน แนวคิดของเฟรมเวิร์ก

แพ็คเก็ต

แพ็กเก็ตคือหน่วยโฟลว์ข้อมูลพื้นฐานใน MediaPipe แพ็กเก็ตประกอบด้วย การประทับเวลาที่เป็นตัวเลขและตัวชี้ที่แชร์ไปยังเพย์โหลดที่เปลี่ยนแปลงไม่ได้ ใน Python พารามิเตอร์ สามารถสร้างแพ็กเก็ต MediaPipe ได้โดยการเรียกใช้เมธอดผู้สร้างแพ็กเก็ตหนึ่งในวิธี เวลา mp.packet_creator นอกจากนี้ สามารถดึงเพย์โหลดแพ็กเก็ตได้โดยใช้ เมธอด Getter แพ็กเก็ตใน mp.packet_getter โปรดทราบว่าเพย์โหลดแพ็กเก็ตจะเปลี่ยนแปลงไม่ได้หลังจากแพ็กเก็ต งานสร้างสรรค์ ดังนั้น การแก้ไขเนื้อหาของแพ็กเก็ตที่เรียกมาจะไม่ส่งผล เพย์โหลดจริงในแพ็กเก็ต Python API เฟรมเวิร์ก MediaPipe สนับสนุน ประเภทข้อมูลที่ใช้กันโดยทั่วไปของ MediaPipe (เช่น เฟรมรูปภาพ เมทริกซ์ โปรโตคอล บัฟเฟอร์ และประเภทข้อมูลพื้นฐาน) ในการเชื่อมโยงหลัก ภาพรวม ตารางด้านล่างแสดงการแมปประเภทระหว่างประเภทข้อมูล Python และ C++ พร้อมด้วยเครื่องมือสร้างแพ็กเก็ตและเมธอด Getter เนื้อหาสำหรับข้อมูลแต่ละประเภท รองรับโดย MediaPipe Python Framework API

ประเภทข้อมูล Python ประเภทข้อมูล C++ ผู้สร้างแพ็กเก็ต ตัวรับเนื้อหา
บูลีน บูลีน create_bool(True) get_bool(packet)
int หรือ np.intc int_t create_int(1) get_int(packet)
int หรือ np.int8 int8_t create_int8(2**7-1) get_int(packet)
int หรือ np.int16 int16_t create_int16(2**15-1) get_int(packet)
int หรือ np.int32 int32_t create_int32(2**31-1) get_int(packet)
int หรือ np.int64 int64_t create_int64(2**63-1) get_int(packet)
int หรือ np.uint8 Uint8_t create_uint8(2**8-1) get_uint(packet)
int หรือ np.uint16 uint16_t create_uint16(2**16-1) get_uint(packet)
int หรือ np.uint32 uint32_t create_uint32(2**32-1) get_uint(packet)
int หรือ np.uint64 uint64_t create_uint64(2**64-1) get_uint(packet)
Float หรือ np.Flo32 จำนวนลอยตัว create_float(1.1) get_float(packet)
Float หรือ np.double double create_double(1.1) get_float(packet)
str (UTF-8) std::string create_string('abc') get_str(packet)
ไบต์ std::string create_string(b'\xd0\xd0\xd0') get_bytes(packet)
mp.Packet mp::Packet create_packet(p) get_packet(packet)
รายการ[บูลีน] std::vector<bool> create_bool_vector([จริง, เท็จ]) get_bool_list(packet)
List[int] หรือ List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(แพ็กเก็ต, ขนาด=10)
List[int] หรือ List[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list(packet)
List[Flo] หรือ List[np.Flo] float[] create_Flo_arrary([0.1, 0.2]) get_Flo_list(แพ็กเก็ต, ขนาด=10)
List[Flo] หรือ List[np.Flo] std::vector&lt;float&gt; create_Flo_vector([0.1, 0.2]) get_Flo_list(แพ็กเก็ต, ขนาด=10)
รายการ[str] std::vector<std::string> สร้าง_สตริง_เวกเตอร์(['a']) get_str_list(packet)
รายการ [mp.Packet] std::vector&lt;mp::Packet&gt; create_packet_vector(
[packet1, packet2])
get_packet_list(p)
การแมป[str, แพ็กเก็ต] std::map<std::string, multipack=""></std::string,> create_string_to_packet_map(
        {&#39;a&#39;: packet1, &#39;b&#39;: packet2})
get_str_to_packet_dict(packet)
np.ndarray
(cv.mat และ PIL.Image)
mp::ImageFrame create_image_frame(
format=ImageFormat.SRGB,
data=mat)
get_image_frame(packet)
np.ndarray mp::Matrix create_matrix(data) get_matrix(packet)
ข้อความ Google Proto ข้อความ Google Proto create_proto(proto) get_proto(packet)
รายการ [โปรโตคอล] std::vector<โปรโตคอล> ไม่มี get_proto_list(packet)

ผู้ใช้มักจะสร้างชั้นเรียน C++ ที่กำหนดเองแล้วส่งคลาสเหล่านั้นไปยัง กราฟและเครื่องคิดเลข เมื่อต้องการอนุญาตให้ใช้คลาสที่กำหนดเองใน Python เมื่อใช้เฟรมเวิร์ก MediaPipe คุณสามารถขยาย Packet API สำหรับข้อมูลประเภทใหม่ใน ขั้นตอนต่อไปนี้

  1. เขียน pybind11 รหัสการเชื่อมโยงชั้นเรียน หรือ แคสต์ประเภทที่กำหนดเอง สำหรับประเภทที่กำหนดเองในไฟล์สำเนา

    #include "path/to/my_type/header/file.h"
    #include "pybind11/pybind11.h"
    
    namespace py = pybind11;
    
    PYBIND11_MODULE(my_type_binding, m) {
      // Write binding code or a custom type caster for MyType.
      py::class_<MyType>(m, "MyType")
          .def(py::init<>())
          .def(...);
    }
    
  2. สร้างเครื่องมือสร้างแพ็กเก็ตและเมธอด Getter ของประเภทที่กำหนดเองใน ไฟล์สำเนา

    #include "path/to/my_type/header/file.h"
    #include "mediapipe/framework/packet.h"
    #include "pybind11/pybind11.h"
    
    namespace mediapipe {
    namespace py = pybind11;
    
    PYBIND11_MODULE(my_packet_methods, m) {
      m.def(
          "create_my_type",
          [](const MyType& my_type) { return MakePacket<MyType>(my_type); });
    
      m.def(
          "get_my_type",
          [](const Packet& packet) {
            if(!packet.ValidateAsType<MyType>().ok()) {
              PyErr_SetString(PyExc_ValueError, "Packet data type mismatch.");
              return py::error_already_set();
            }
            return packet.Get<MyType>();
          });
    }
    }  // namespace mediapipe
    
  3. เพิ่มกฎการสร้าง Bazel 2 ข้อสำหรับการเชื่อมโยงประเภทที่กำหนดเองและแพ็กเก็ตใหม่ ในไฟล์ BUILD

    load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
    
    pybind_extension(
        name = "my_type_binding",
        srcs = ["my_type_binding.cc"],
        deps = [":my_type"],
    )
    
    pybind_extension(
        name = "my_packet_methods",
        srcs = ["my_packet_methods.cc"],
        deps = [
            ":my_type",
            "//mediapipe/framework:packet"
        ],
    )
    
  4. สร้างเป้าหมายส่วนขยาย pybind (ที่มีคำต่อท้าย .so) โดย Bazel และย้ายไลบรารีแบบไดนามิกที่สร้างขึ้นไปยังหนึ่งในไดเรกทอรี $LD_LIBRARY_PATH

  5. ใช้โมดูลการเชื่อมโยงใน Python

    import my_type_binding
    import my_packet_methods
    
    packet = my_packet_methods.create_my_type(my_type_binding.MyType())
    my_type = my_packet_methods.get_my_type(packet)
    

การประทับเวลา

แต่ละแพ็กเก็ตมีการประทับเวลาในหน่วยไมโครวินาที ใน Python Packet API มีวิธีอำนวยความสะดวก packet.at() ในการกำหนดตัวเลข การประทับเวลาของแพ็กเก็ต โดยทั่วไป packet.timestamp คือคลาสแพ็กเก็ต สำหรับการเข้าถึงการประทับเวลาที่สำคัญ วิธีแปลง Unix Epoch เป็น การประทับเวลา MediaPipe Timestamp API เสนอ Method mp.Timestamp.from_seconds() สำหรับวัตถุประสงค์นี้

ImageFrame

ImageFrame เป็นคอนเทนเนอร์สำหรับจัดเก็บรูปภาพหรือเฟรมวิดีโอ รูปแบบ ที่ ImageFrame รองรับจะแสดงอยู่ใน enum สำหรับ ImageFormat พิกเซลจะเป็นแถวหลักที่เข้ารหัสโดยมีองค์ประกอบสีแบบแทรกสลับ และ ImageFrame รองรับ uint8, uint16 และ Float เป็นประเภทข้อมูล MediaPipe มี API ของ ImageFrame Python API เพื่อเข้าถึงคลาส ImageFrame C++ ใน Python วิธีที่ง่ายที่สุดในการเรียกข้อมูล ข้อมูลพิกเซลคือการเรียก image_frame.numpy_view() เพื่อรับอาร์เรย์ numpy หมายเหตุ ที่ส่งคืน numpy ndarray ซึ่งเป็นการอ้างอิงข้อมูลพิกเซลภายใน ไม่สามารถเขียนได้ ถ้าผู้โทรจำเป็นต้องแก้ไข numpy ndarray จะต้อง เรียกใช้การดำเนินการคัดลอกอย่างชัดแจ้งเพื่อรับสำเนา เมื่อ MediaPipe ใช้ค่าตัวเลข ndarray สร้าง ImageFrame จะถือว่าเก็บข้อมูลไว้ต่อเนื่องกัน ข้อมูลพิกเซลของ ImageFrame จะได้รับการปรับใหม่ตาม ต่อเนื่องกันเมื่อส่งกลับไปที่ด้าน Python

กราฟของฟังก์ชัน

ในเฟรมเวิร์ก MediaPipe การประมวลผลทั้งหมดจะเกิดขึ้นในบริบทของ CalculatorGraph ClculatorGraph Python API จะเชื่อมโยงโดยตรงกับคลาส CalculatorGraph ของ C++ ความแตกต่างที่สำคัญคือ CalculatorGraph Python API จะทําให้เกิดข้อผิดพลาด Python แทนการส่งคืน สถานะ "ไม่ปกติ" เมื่อเกิดข้อผิดพลาด ดังนั้น ในฐานะผู้ใช้ Python คุณสามารถจัดการกับ ยกเว้นตามปกติ วงจรชีวิตของ CalculatorGraph มี 3 ขั้นตอน ได้แก่ การเริ่มต้นและการตั้งค่า การเรียกใช้กราฟ และการปิดกราฟ

  1. เริ่มต้น CalculatorGraph ด้วย CalculatorGraphConfig ต้นแบบหรือไบนารี Protobuf ของคุณ และระบุ Method ของ Callback เพื่อสังเกตผลลัพธ์ สตรีม

    ตัวเลือกที่ 1 เริ่มต้น CalculatorGraph ด้วย CalculatorGraphConfig ต้นแบบ หรือการนำเสนอแบบข้อความ และสังเกตสตรีมเอาต์พุต:

    import mediapipe as mp
    
    config_text = """
      input_stream: 'in_stream'
      output_stream: 'out_stream'
      node {
        calculator: 'PassThroughCalculator'
        input_stream: 'in_stream'
        output_stream: 'out_stream'
      }
    """
    graph = mp.CalculatorGraph(graph_config=config_text)
    output_packets = []
    graph.observe_output_stream(
        'out_stream',
        lambda stream_name, packet:
            output_packets.append(mp.packet_getter.get_str(packet)))
    

    วิธีที่ 2: เริ่มต้น CalculatorGraph ด้วยไฟล์ Protobuf แบบไบนารี และ สังเกตสตรีมเอาต์พุต

    import mediapipe as mp
    # resources dependency
    
    graph = mp.CalculatorGraph(
        binary_graph=os.path.join(
            resources.GetRunfilesDir(), 'path/to/your/graph.binarypb'))
    graph.observe_output_stream(
        'out_stream',
        lambda stream_name, packet: print(f'Get {packet} from {stream_name}'))
    
  2. เริ่มการเรียกใช้กราฟและส่งแพ็กเก็ตข้อมูลลงในกราฟ

    graph.start_run()
    
    graph.add_packet_to_input_stream(
        'in_stream', mp.packet_creator.create_string('abc').at(0))
    
    rgb_img = cv2.cvtColor(cv2.imread('/path/to/your/image.png'), cv2.COLOR_BGR2RGB)
    graph.add_packet_to_input_stream(
        'in_stream',
        mp.packet_creator.create_image_frame(image_format=mp.ImageFormat.SRGB,
                                             data=rgb_img).at(1))
    
  3. ปิดกราฟเมื่อเสร็จแล้ว คุณอาจเริ่มกราฟอีกครั้งเพื่อสร้างกราฟอื่น ทำงานหลังจากการเรียกไปยัง close()

    graph.close()
    

รันไทม์ Python ในเครื่องสามารถเรียกใช้สคริปต์ Python ได้