MediaPipe Framework ב-Python

מסגרת ה-Python של MediaPipe מעניקה גישה ישירה לרכיבי הליבה של באמצעות framework C++ של MediaPipe, כמו Timestamp, Packet ו-מחשבון, ואילו פתרונות Python המוכנים לשימוש מסתירים את הפרטים הטכניים של ה-framework ולהחזיר את המודל הקריא תוצאות הסקת המסקנות חזרה למתקשרים.

מסגרת MediaPipe נמצאת מעל ספריית pybind11. מסגרת הליבה של C++ נחשפת ב-Python באמצעות קישור שפות ב-C++ או ב-Python. התוכן הבא מניח שלקורא יש כבר הבנה בסיסית של במסגרת MediaPipe C++. אחרת, אפשר למצוא מידע שימושי ב מושגי framework.

מנה

החבילה היא היחידה הבסיסית של זרימת הנתונים ב-MediaPipe. חבילה מורכבת חותמת זמן מספרית וסמן משותף למטען ייעודי (payload) שלא ניתן לשינוי. ב-Python, אפשר ליצור חבילת MediaPipe באמצעות הפעלה של אחת מהשיטות ליצירת חבילות ב- ה mp.packet_creator של מודל טרנספורמר. בנוסף, ניתן לאחזר את המטען הייעודי של המנות באמצעות אחד שיטות למקבל החבילות mp.packet_getter של מודל טרנספורמר. שימו לב שמטען הייעודי (Payload) של המנות הופך ללא ניתן לשינוי אחרי המנה במהלך היצירה. לכן, השינוי של תוכן המנות שאוחזר לא משפיע על את המטען הייעודי (Payload) בפועל בחבילה. API של MediaPipe framework Python תומך סוגי הנתונים הנפוצים ביותר של MediaPipe (למשל, ImageFrame, מטריצה, פרוטוקול מאגרי נתונים זמניים וסוגי נתונים ראשוניים) בקישור הליבה. התוכנית המקיפה בטבלה הבאה מוצגים מיפויי הסוגים בין סוג הנתונים Python לסוג הנתונים C++ יחד עם יוצר החבילות והשיטה של מקבל התוכן עבור כל סוג נתונים. שנתמך על ידי 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.float32 מספר ממשי (float) create_float(1.1) get_float(packet)
מספר ממשי (float) או 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::Packet create_packet(p) get_packet(packet)
רשימה[בוליאני] std::vector<bool> create_bool_vector([True, False]) get_bool_list(packet)
List[int] או List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(package, size=10)
List[int] או List[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list(packet)
List[float] או List[np.float] float[] create_float_arrary([0.1, 0.2]) get_float_list(package, size=10)
List[float] או List[np.float] std::vector&lt;float&gt; create_float_vector([0.1, 0.2]) get_float_list(package, size=10)
List[str] std::vector&lt;std::string&gt; create_string_vector(['a']) get_str_list(packet)
רשימה[mp.Packet] std::vector&lt;mp::Packet&gt; create_package_vector(
[package1, package2])
get_packet_list(p)
מיפוי[str, Packet] std::map<std::string, package=""></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)
רשימה[Proto] std::vector<Proto> לא רלוונטי get_proto_list(packet)

במקרים מסוימים משתמשים יוצרים כיתות C++ מותאמות אישית ושולחים אותן אל את התרשימים ומחשבונים. כדי לאפשר שימוש בכיתות המותאמות אישית ב-Python עם MediaPipe Framework, תוכלו להרחיב את 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. ליצור שיטה חדשה של יוצר חבילות ושיטה חדשה מהסוג המותאם אישית עותק נפרד של 'עותקים'.

    #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. מוסיפים שני כללים build של 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 ומעבירים את הספריות הדינמיות שנוצרו לאחת מ-dirs של $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, ה-API של חותמת הזמן מציע את השיטה mp.Timestamp.from_seconds() למטרה הזו.

ImageFrame

ImageFrame הוא הקונטיינר לאחסון תמונה או פריים בסרטון. פורמטים שנתמכות על ידי ImageFrame, הן enum ב-ImageFormat. פיקסלים מקודדים שורה גדולה עם רכיבי צבע משולבים ו-ImageFrame. שתומך ב-uint8, ב-uint16 וב-float בתור סוגי הנתונים שלו. אפליקציית MediaPipe מספקת API של ImageFrame ב-Python כדי לגשת למחלקה של ImageFrame C++. ב-Python, הדרך הקלה ביותר לאחזר את נתוני פיקסלים יקבלו קריאה ל-image_frame.numpy_view() כדי לקבל ndסידור נומרי. הערה שהמערך המספרי המוחזר, שהוא הפניה לנתוני הפיקסלים הפנימיים, בלתי ניתן לכתיבה. אם המתקשרים צריכים לשנות את ה-ndarray המספרי, הוא צריך להפעיל פעולת העתקה באופן מפורש כדי לקבל עותק. כש-MediaPipe לוקחת מספר ndarray כדי ליצור ImageFrame, מבוסס על ההנחה שהנתונים מאוחסנים באופן רציף. לאחר מכן, נתוני הפיקסלים של ImageFrame יותאמו מחדש הוא רציף כשחוזרים לצד של Python.

תרשים

במסגרת MediaPipe, כל העיבוד מתרחש בהקשר של תרשים המחשבון. ה-API של המחשבון ל-Python הוא קישור ישיר למחלקה 'מחשבון המחשבון C++'. ההבדל העיקרי הוא ממשק ה-API של Python שירותי Python מעלה שגיאת Python במקום להחזיר שגיאת Python סטטוס לא תקין כשמתרחשת שגיאה. לכן, כמשתמשי Python, אתם יכולים החריגים, כרגיל. מחזור החיים של מחשבון Chart Graph מכיל שלושה שלבים: אתחול והגדרה, הרצת תרשים וכיבוי תרשים.

  1. אתחול phoneGraph באמצעות קובץ protobuf או בינארי של GraphConfig קובץ protobuf, ולספק שיטות קריאה חוזרת כדי לצפות בפלט זרמים.

    אפשרות 1. אתחול גרף הפונקציה עם Protobuf של שירותימחשבון גרפי או ייצוג טקסטואלי שלו, ובוחנים את זרמי הפלט:

    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. לאתחל Computer Graph עם קובץ פרוטובוט בינארי, לבחון את זרמי הפלט.

    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.