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

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

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

แพ็คเก็ต

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

ประเภทข้อมูล Python ประเภทข้อมูล C++ ผู้สร้างแพ็กเก็ต ตัวรับเนื้อหา
bool bool 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 float 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::แพ็กเก็ต 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_เวกเตอร์([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<float> create_Flo_เวกเตอร์([0.1, 0.2]) get_Flo_list(แพ็กเก็ต, ขนาด=10)
รายการ[str] std::vector<std::string> สร้าง_สตริง_เวกเตอร์(['a']) get_str_list(packet)
รายการ [mp.Packet] std::vector<mp::Packet> create_packet_ector(
[packet1, packet2])
get_packet_list(p)
การแมป [str, แพ็กเก็ต] std::map<std::string, pack=""></std::string,> create_string_to_packet_map(
        {'a': packet1, 'b': 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::เมทริกซ์ create_matrix(data) get_matrix(packet)
ข้อความ Google Proto ข้อความ Google Proto create_proto(proto) get_proto(packet)
รายการ [Proto] std::vector<Proto> ไม่มี get_proto_list(packet)

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

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

    #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 ของประเภทที่กำหนดเองในไฟล์ CC แยกต่างหาก

    #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. เพิ่มกฎบิลด์แบบบาเซล 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)
    

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

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

ImageFrame

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

กราฟ

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

  1. เริ่มต้น CalculatorGraph ด้วย CalculatorGraphConfig Protobuf หรือไบนารีไฟล์ Protobuf และให้วิธีเรียกกลับเพื่อดูสตรีมเอาต์พุต

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

    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 ได้