چارچوب MediaPipe در پایتون

چارچوب MediaPipe Python به اجزای اصلی چارچوب MediaPipe C++ مانند Timestamp، Packet و CalculatorGraph دسترسی مستقیم می دهد، در حالی که راه حل های آماده Python جزئیات فنی چارچوب را پنهان می کنند و به سادگی نتایج استنتاج مدل قابل خواندن را برمی گرداند. به تماس گیرندگان

چارچوب MediaPipe در بالای کتابخانه pybind11 قرار دارد. چارچوب اصلی C++ در پایتون از طریق پیوند زبان C++/Python در معرض دید قرار می‌گیرد. محتوای زیر فرض می‌کند که خواننده قبلاً درک اساسی از چارچوب MediaPipe C++ دارد. در غیر این صورت، می توانید اطلاعات مفیدی را در Framework Concepts پیدا کنید.

بسته

بسته واحد اصلی جریان داده در MediaPipe است. یک بسته شامل یک مهر زمان عددی و یک اشاره گر مشترک به یک بار غیرقابل تغییر است. در پایتون، یک بسته MediaPipe را می توان با فراخوانی یکی از متدهای ایجاد کننده بسته در ماژول mp.packet_creator ایجاد کرد. به همین ترتیب، بار بسته را می توان با استفاده از یکی از روش های دریافت کننده بسته در ماژول mp.packet_getter بازیابی کرد. توجه داشته باشید که بار بسته پس از ایجاد بسته تغییرناپذیر می شود. بنابراین، اصلاح محتوای بسته بازیابی شده بر بار واقعی بسته تأثیری ندارد. چارچوب MediaPipe Python API از متداول‌ترین انواع داده‌های MediaPipe (مانند ImageFrame، Matrix، بافرهای پروتکل و انواع داده‌های اولیه) در اتصال هسته پشتیبانی می‌کند. جدول جامع زیر نگاشت نوع بین Python و نوع داده ++C را به همراه سازنده بسته و روش دریافت کننده محتوا برای هر نوع داده پشتیبانی شده توسط MediaPipe Framework API Python نشان می دهد.

نوع داده پایتون نوع داده C++ سازنده بسته دریافت کننده محتوا
بوول بوول create_bool (True) get_bool (بسته)
int یا np.intc int_t create_int (1) get_int (بسته)
int یا np.int8 int8_t create_int8 (2**7-1) get_int (بسته)
int یا np.int16 int16_t create_int16(2**15-1) get_int (بسته)
int یا np.int32 int32_t create_int32 (2**31-1) get_int (بسته)
int یا np.int64 int64_t create_int64(2**63-1) get_int (بسته)
int یا np.uint8 uint8_t create_uint8 (2**8-1) get_uint (بسته)
int یا np.uint16 uint16_t create_uint16(2**16-1) get_uint (بسته)
int یا np.uint32 uint32_t create_uint32(2**32-1) get_uint (بسته)
int یا np.uint64 uint64_t create_uint64(2**64-1) get_uint (بسته)
float یا np.float32 شناور create_float (1.1) get_float (بسته)
float یا np.double دو برابر create_double (1.1) get_float (بسته)
خیابان (UTF-8) std::string create_string ('abc') get_str (بسته)
بایت ها std::string create_string(b'\xd0\xd0\xd0') get_bytes (بسته)
mp.Packet mp:: بسته create_packet (p) get_packet (بسته)
فهرست[bool] std::vector<bool> create_bool_vector([درست، نادرست]) get_bool_list (بسته)
فهرست[int] یا فهرست[np.intc] بین المللی[] create_int_array([1, 2, 3]) get_int_list (بسته، اندازه = 10)
فهرست[int] یا فهرست[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list (بسته)
فهرست[float] یا فهرست[np.float] شناور[] create_float_arrary ([0.1، 0.2]) get_float_list (بسته، اندازه = 10)
فهرست[float] یا فهرست[np.float] std::vector<float> create_float_vector([0.1, 0.2]) get_float_list (بسته، اندازه = 10)
فهرست[خ] std:: vector<std::string> create_string_vector(['a']) get_str_list (بسته)
فهرست [mp.Packet] std:: vector<mp::Packet> create_packet_vector(
[packet1, packet2])
get_packet_list (p)
نقشه برداری [خیابان، بسته] std:: map 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,
داده=مت)
get_image_frame (بسته)
np.ndarray mp::ماتریس create_matrix (داده) get_matrix (بسته)
پیام Google Proto پیام Google Proto create_proto(proto) get_proto (بسته)
فهرست [پروتو] std::vector<Proto> n/a get_proto_list (بسته)

غیر معمول نیست که کاربران کلاس های C++ سفارشی ایجاد می کنند و آن ها را به نمودارها و ماشین حساب ها ارسال می کنند. برای اجازه دادن به استفاده از کلاس‌های سفارشی در پایتون با MediaPipe Framework، می‌توانید Packet API را برای یک نوع داده جدید در مراحل زیر گسترش دهید:

  1. کد کلاس pybind11 یا یک caster نوع سفارشی برای نوع سفارشی را در یک فایل 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. یک متد ایجاد کننده و دریافت کننده بسته جدید از نوع سفارشی در یک فایل 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. دو قانون ساخت bazel برای نوع سفارشی binding و روش های بسته جدید را در فایل 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. از ماژول های binding در پایتون استفاده کنید.

    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() برای تعریف مهر زمانی عددی یک بسته ارائه می کند. به طور کلی، packet.timestamp ویژگی کلاس بسته برای دسترسی به مهر زمانی است. برای تبدیل یک دوره یونیکس به مهر زمانی MediaPipe، Timestamp API یک روش mp.Timestamp.from_seconds() برای این منظور ارائه می دهد.

ImageFrame

ImageFrame محفظه ای برای ذخیره یک تصویر یا یک فریم ویدئو است. فرمت های پشتیبانی شده توسط ImageFrame در فهرست ImageFormat فهرست شده اند. پیکسل ها ردیف اصلی با مولفه های رنگی به هم پیوسته کدگذاری می شوند و ImageFrame از uint8، uint16 و شناور به عنوان انواع داده های خود پشتیبانی می کند. MediaPipe یک ImageFrame Python API برای دسترسی به کلاس ImageFrame C++ فراهم می کند. در پایتون، ساده‌ترین راه برای بازیابی داده‌های پیکسل، فراخوانی image_frame.numpy_view() برای دریافت یک ndarray numpy است. توجه داشته باشید که ndarray numpy برگردانده شده، که اشاره ای به داده های پیکسل داخلی است، غیرقابل نوشتن است. اگر تماس گیرندگان نیاز به تغییر ndarray numpy داشته باشند، لازم است که به طور صریح عملیات کپی را برای به دست آوردن یک کپی فراخوانی کنند. هنگامی که MediaPipe یک ndarray numpy را برای ایجاد یک ImageFrame می گیرد، فرض می کند که داده ها به طور پیوسته ذخیره می شوند. به همین ترتیب، داده‌های پیکسلی یک ImageFrame زمانی که به سمت پایتون بازگردانده می‌شود، مجدداً مرتب می‌شوند تا به هم پیوسته باشند.

نمودار

در چارچوب MediaPipe، تمام پردازش ها در چارچوب یک CalculatorGraph انجام می شود. CalculatorGraph Python API یک اتصال مستقیم به کلاس C++ CalculatorGraph است. تفاوت اصلی این است که CalculatorGraph Python API به جای برگرداندن وضعیت غیر OK در هنگام بروز خطا، یک خطای پایتون را ایجاد می کند. بنابراین، به عنوان یک کاربر پایتون، می‌توانید استثنائات را همانطور که معمولا انجام می‌دهید، مدیریت کنید. چرخه زندگی CalculatorGraph شامل سه مرحله است: مقداردهی اولیه و راه اندازی، اجرای نمودار و خاموش شدن گراف.

  1. یک CalculatorGraph را با یک CalculatorGraphConfig protobuf یا فایل protobuf باینری راه اندازی کنید و روش(های) برگشتی را برای مشاهده جریان(های) خروجی ارائه دهید.

    گزینه 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()
    

اسکریپت پایتون می تواند توسط زمان اجرای محلی پایتون شما اجرا شود.