إطار عمل MediaPipe في بايثون

يمنح إطار عمل MediaPipe Python إمكانية الوصول المباشر إلى المكونات الأساسية لإطار عمل MediaPipe C++ مثل الطابع الزمني وحزمة البيانات والحاسبة، بينما تخفي حلول بايثون الجاهزة للاستخدام التفاصيل الفنية لإطار العمل وتعيد ببساطة نتائج استنتاج النموذج القابل للقراءة إلى المتصلين.

يقع إطار عمل MediaPipe فوق مكتبة pybind11. يتم عرض إطار العمل الأساسي C++ في بايثون عن طريق ربط لغة C++/Python. يفترض المحتوى أدناه أن القارئ لديه بالفعل فهم أساسي لإطار عمل MediaPipe C++. بخلاف ذلك، يمكنك العثور على معلومات مفيدة في Framework Concepts.

الحزمة

تعتبر الحزمة وحدة تدفق البيانات الأساسية في MediaPipe. تتكون الحزمة من طابع زمني رقمي ومؤشر مشترك لحمولة غير قابلة للتغيير. في بايثون، يمكن إنشاء حزمة MediaPipe من خلال استدعاء إحدى طرق إنشاء الحزم في الوحدة mp.packet_creator. وفي المقابل، يمكن استرداد حمولة الحزمة باستخدام إحدى طرق الحصول على الحزمة في وحدة mp.packet_getter. لاحظ أن حمولة الحزمة تصبح غير قابلة للتغيير بعد إنشاء الحزمة. وبالتالي، لا يؤثر تعديل محتوى الحزمة المستردة في الحمولة الفعلية في الحزمة. إطار عمل MediaPipe يدعم Python API أنواع البيانات الأكثر استخدامًا من MediaPipe (مثل ImageFrame، ومصفوفة، والمخازن المؤقتة للبروتوكولات، وأنواع البيانات الأولية) في الربط الأساسي. يُظهر الجدول الشامل أدناه تعيينات النوع بين Python ونوع البيانات C++ بالإضافة إلى منشئ الحزمة وطريقة استرجاع المحتوى لكل نوع بيانات تدعمه واجهة برمجة تطبيقات إطار عمل MediaPipe 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.float32 float create_float(1.1) get_float(packet)
عدد عائم أو np.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)
قائمة[bool] std::vector<bool> create_bool_موجّه([True, False]) get_bool_list(packet)
List[int] أو List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(packet, size=10)
List[int] أو List[np.intc] std::vector<int> إنشاء_int_متجه([1, 2, 3]) get_int_list(packet)
List[float] أو List[np.float] float[] create_float_arrary([0.1, 0.2]) get_float_list(packet, size=10)
List[float] أو List[np.float] std::vector<float> إنشاء_عائم_متجه([0.1, 0.2]) get_float_list(packet, size=10)
القائمة[str] std::vector<std::string> إنشاء_سلسلة_متجه(['a']) get_str_list(packet)
القائمة[mp.Packet] std::vector<mp::Packet> create_packet_forward(
[packet1, package2])
get_packet_list(p)
تصميم الخرائط[str, Packet] std::map<std::string, package=""></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)
رسالة Proto من Google رسالة Proto من Google create_proto(proto) get_proto(packet)
قائمة [Proto] std::vector<Proto> timing fixed in amara get_proto_list(packet)

ليس من غير المألوف أن ينشئ المستخدمون فئات C++ مخصصة ويرسلونها إلى الرسوم البيانية والآلات الحاسبة. للسماح باستخدام الفئات المخصصة في بايثون مع إطار عمل 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. أضف قاعدتين لتصميم bazel لربط النوع المخصص وطرق الحزم الجديدة في ملف 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. استخدم وحدات الربط في بايثون.

    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.at() لتحديد الطابع الزمني الرقمي للحزمة. بشكل أكثر عمومية، packet.timestamp هي سمة فئة الحزمة للوصول إلى الطابع الزمني الأساسي. لتحويل حقبة Unix إلى طابع زمني MediaPipe، تقدّم Timetamp API طريقة mp.Timestamp.from_seconds() لهذا الغرض.

ImageFrame

ImageFrame هي حاوية تخزين صورة أو إطار فيديو. يتم إدراج التنسيقات المتوافقة مع ImageFrame في تعداد ImageFormat. تكون وحدات البكسل مرمّزة بصفوف رئيسية باستخدام مكونات ألوان متداخلة، ويتوافق إطار ImageFrame مع أنواع البيانات التالية: uint8 وuint16 وfloat. توفر MediaPipe واجهة برمجة تطبيقات ImageFrame Python للوصول إلى فئة ImageFrame C++. في لغة بايثون، أسهل طريقة لاسترداد بيانات البكسل هي استدعاء image_frame.numpy_view() للحصول على مصفوفة numpy. لاحظ أن مصفوفة numpy المعروضة، وهي مرجع إلى بيانات البكسل الداخلية، غير قابلة للكتابة. إذا احتاج المتصلون إلى تعديل مصفوفة numpy، يجب طلب عملية نسخ بشكل صريح للحصول على نسخة. عندما تأخذ MediaPipe مصفوفة numpy لإنشاء إطار ImageFrame، فإنها تفترض أنه يتم تخزين البيانات بشكل متجاور. وفي المقابل، ستتم إعادة محاذاة بيانات البكسل لإطار ImageFrame لتكون متجاورة عند إعادتها إلى جانب بايثون.

رسم بياني للدالة

في إطار عمل MediaPipe، تتم جميع عمليات المعالجة ضمن سياق الآلة الحاسبة للرسم البياني. واجهة برمجة تطبيقات حاسبة Python والفرق الرئيسي بينهما هو أن واجهة برمجة تطبيقات حاسبة Python تصدر خطأ في Python بدلاً من عرض حالة غير صحيحة عند حدوثها. لذلك، وبصفتك مستخدم Python، يمكنك التعامل مع الاستثناءات كما تفعل عادةً. تحتوي دورة حياة حاسبة الرسم البياني على ثلاث مراحل: التهيئة والإعداد، وتشغيل الرسم البياني، وإيقاف الرسم البياني.

  1. يجب إعداد notebookGraph باستخدام النموذج الأوّلي لحاسبة GraphConfig أو ملف Protobuf ثنائي، وتوفير طرق لمعاودة الاتصال لمراقبة ساحات المشاركات.

    الخيار رقم 1. قم بإعداد SketchGraph باستخدام النموذج الأوّلي SketchGraphConfig أو تمثيله النصي، وراقِب تدفق(تدفقات) الإخراج:

    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. قم بتهيئة حاسبة Graphs باستخدام ملف 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 المحلي.