MediaPipe-Framework in Python

Das Python-Framework von MediaPipe ermöglicht direkten Zugriff auf die Kernkomponenten MediaPipe C++ Framework wie Timestamp, Packet und CalculatorGraph Die gebrauchsfertigen Python-Lösungen die technischen Details des Frameworks und geben einfach das lesbare Modell Inferenzergebnisse an die Aufrufer zurück.

Das MediaPipe-Framework basiert der pybind11-Bibliothek. Das C++ Core-Framework wird in Python über eine C++/Python-Sprachbindung verfügbar gemacht. Im Folgenden wird davon ausgegangen, dass die Lesenden bereits über ein grundlegendes Verständnis das Framework von MediaPipe C++. Ansonsten finden Sie nützliche Informationen in Framework-Konzepte.

Paket

Das Paket ist die grundlegende Datenflusseinheit in MediaPipe. Ein Paket besteht aus einer einen numerischen Zeitstempel und einen gemeinsamen Zeiger auf eine unveränderliche Nutzlast. In Python wird ein Um ein MediaPipe-Paket zu erstellen, rufen Sie eine der Methoden zur Paketerstellung in die mp.packet_creator -Modul. Entsprechend kann die Paketnutzlast mit einer der Paket-Getter-Methoden in der mp.packet_getter -Modul. Beachten Sie, dass die Paketnutzlast nach dem Paket unveränderlich wird Erstellung. Daher hat die Änderung des abgerufenen Paketinhalts keine Auswirkungen der tatsächlichen Nutzlast im Paket. Die Python API des MediaPipe-Frameworks unterstützt die am häufigsten verwendete Datentypen von MediaPipe (z.B. ImageFrame, Matrix, Protocol Puffer und die primitiven Datentypen) in der Kernbindung. Die umfassende Die folgende Tabelle zeigt die Typzuordnungen zwischen dem Python- und dem C++-Datentyp. zusammen mit dem Paketersteller und der Content Getter-Methode für jeden Datentyp. die von der MediaPipe Python Framework API unterstützt wird.

Python-Datentyp Datentyp C++ Paketersteller Content-Getter
bool bool create_bool(True) get_bool(packet)
int oder np.intc int_t create_int(1) get_int(packet)
int oder np.int8 int8_t create_int8(2**7-1) get_int(packet)
int oder np.int16 int16_t create_int16(2**15-1) get_int(packet)
int oder np.int32 int32_t create_int32(2**31-1) get_int(packet)
int oder np.int64 int64_t create_int64(2**63-1) get_int(packet)
int oder np.uint8 Uint8_t create_uint8(2**8-1) get_uint(packet)
int oder np.uint16 uint16_t create_uint16(2**16-1) get_uint(packet)
int oder np.uint32 uint32_t create_uint32(2**32-1) get_uint(packet)
int oder np.uint64 uint64_t create_uint64(2**64-1) get_uint(packet)
float oder np.float32 float create_float(1.1) get_float(packet)
float oder np.double double create_double(1.1) get_float(packet)
String (UTF-8) std::string create_string('abc') get_str(packet)
Byte std::string create_string(b'\xd0\xd0\xd0') get_bytes(packet)
mp.Packet mp::Packet create_packet(p) get_packet(packet)
Liste[bool] std::vector<bool> create_bool_vector([True, False]) get_bool_list(packet)
List[int] oder List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(packet, size=10)
List[int] oder List[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list(packet)
List[float] oder List[np.float] float[] create_float_arrary([0,1; 0,2]) get_float_list(packet, size=10)
List[float] oder List[np.float] std::vector&lt;float&gt; create_float_vector([0.1; 0.2]) get_float_list(packet, size=10)
List[str] std::vector&lt;std::string&gt; create_string_vector(['a']) get_str_list(packet)
Liste[mp.Packet] std::vector&lt;mp::Packet&gt; create_packet_vector(
[packet1, package2])
get_packet_list(p)
Mapping[str, Packet] (Zuordnung [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 und 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-Nachricht Google Proto-Nachricht create_proto(proto) get_proto(packet)
Liste[Protokoll] std::vector<Proto> get_proto_list(packet)

Es ist nicht ungewöhnlich, dass Nutzer benutzerdefinierte C++ -Klassen erstellen und diese an Grafiken und Rechnern. So lassen Sie die Verwendung der benutzerdefinierten Klassen in Python zu mit MediaPipe-Framework können Sie die Packet API im folgenden Schritten:

  1. pybind11 schreiben Klassenbindungscode oder eine Umwandlung des benutzerdefinierten Typs für den benutzerdefinierten Typ in einer Cc-Datei.

    #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. Erstellen Sie eine neue Paketersteller- und Getter-Methode des benutzerdefinierten Typs in einem separate cc-Datei.

    #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. Fügen Sie zwei Bali-Build-Regeln für die benutzerdefinierte Typbindung und das neue Paket hinzu -Methoden in der Build-Datei.

    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. Erstellen Sie die pybind-Erweiterungsziele (mit dem Suffix .so) von Bazel und verschieben Sie die generierten dynamischen Bibliotheken in eines der $LD_LIBRARY_PATH-Verzeichnisse.

  5. Verwenden Sie die Bindungsmodule in 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)
    

Zeitstempel

Jedes Paket enthält einen Zeitstempel in Mikrosekunden. In Python Die Packet API bietet die einfache Methode packet.at() zum Definieren des numerischen Werts Zeitstempel eines Pakets. Im Allgemeinen ist packet.timestamp die Paketklasse. für den Zugriff auf den zugrunde liegenden Zeitstempel. So konvertieren Sie eine Unix-Epoche in eine MediaPipe-Zeitstempel Zeitstempel-API bietet hierfür die Methode mp.Timestamp.from_seconds().

ImageFrame

ImageFrame ist der Container zum Speichern eines Bild- oder Videoframes. Formate die von ImageFrame unterstützt werden, ImageFormat-Enum. Pixel sind zeilenweise mit verschränkten Farbkomponenten codiert. ImageFrame unterstützt uint8, uint16 und float als Datentypen. MediaPipe bietet ImageFrame-Python-API um auf die ImageFrame C++-Klasse zuzugreifen. In Python besteht die einfachste Methode zum Abrufen des Pixeldaten besteht darin, image_frame.numpy_view() aufzurufen, um ein NumPy und Array zu erhalten. Hinweis dass das zurückgegebene numpy ndarray, ein Verweis auf die internen Pixeldaten, unbeschreibbar. Wenn die Aufrufer das numpy ndarray ändern müssen, explizit einen Kopiervorgang auf, um eine Kopie zu erhalten. Wenn MediaPipe einen NumPy annimmt ndarray , um einen ImageFrame zu erstellen, geht es davon aus, dass die Daten zusammenhängend gespeichert sind. Entsprechend werden die Pixeldaten eines ImageFrames wenn sie an die Python-Seite zurückgegeben werden.

Grafik

Im MediaPipe Framework erfolgt die gesamte Verarbeitung im Kontext eines CalculatorGraph Die CalculatorGraph Python API ist eine direkte Bindung an die C++ CalculatorGraph-Klasse. Der größte Unterschied das CalculatorGraph Python-API einen Python-Fehler ausgibt, anstatt einen nicht-OK-Status, wenn ein Fehler auftritt. Als Python-Nutzer können Sie wie Sie es normalerweise tun. Der Lebenszyklus eines CalculatorGraph enthält drei Phasen: Initialisierung und Einrichtung, Ausführung der Grafik und Deaktivierung.

  1. RechnerGraph mit einem RechnerGraphConfig-Protokollzwischenspeicher oder Binärprogramm initialisieren protobuf-Datei und stellen Sie Callback-Methoden zur Beobachtung der Ausgabe bereit. Streams.

    Option 1: RechnerGraph mit einem CalculatorGraphConfig-protobuf initialisieren oder dessen Textdarstellung und beobachten Sie den oder die Ausgabestreams:

    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)))
    

    Option 2: Initialisieren Sie einen CalculatorGraph mit einer binären protobuf-Datei und beobachten Sie den oder die Ausgabestreams.

    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. Starten Sie die Grafikausführung und fügen Sie Pakete in das Diagramm ein.

    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. Grafik nach Abschluss schließen. Sie können die Grafik für eine andere Grafik neu starten. nach dem Aufruf von close() ausgeführt werden.

    graph.close()
    

Das Python-Skript kann von Ihrer lokalen Python-Laufzeit ausgeführt werden.