MediaPipe-Framework in Python

Das Python-Framework von MediaPipe gewährt direkten Zugriff auf die Kernkomponenten des C++-Frameworks von MediaPipe, z. B. Timestamp, Packet und CalculatorGraph. Die sofort einsatzbereiten Python-Lösungen verbergen die technischen Details des Frameworks und geben einfach die lesbaren Modellinferenzergebnisse an die Aufrufer zurück.

Das MediaPipe-Framework basiert auf der pybind11-Bibliothek. Das C++ Core-Framework wird in Python über eine C++/Python-Sprachbindung bereitgestellt. Im Folgenden wird davon ausgegangen, dass der Leser bereits grundlegende Kenntnisse des C++-Frameworks von MediaPipe hat. Andernfalls finden Sie unter Framework-Konzepte nützliche Informationen.

Paket

Das Paket ist die grundlegende Datenflusseinheit in MediaPipe. Ein Paket besteht aus einem numerischen Zeitstempel und einem gemeinsamen Zeiger auf eine unveränderliche Nutzlast. In Python kann ein MediaPipe-Paket durch Aufrufen einer der Paketerstellungsmethoden im Modul mp.packet_creator erstellt werden. Entsprechend kann die Paketnutzlast mithilfe einer der Paket-Getter-Methoden im Modul mp.packet_getter abgerufen werden. Beachten Sie, dass die Paketnutzlast nach der Paketerstellung unveränderlich wird. Daher wirkt sich die Änderung des abgerufenen Paketinhalts nicht auf die tatsächliche Nutzlast im Paket aus. Die Python API des MediaPipe-Frameworks unterstützt die am häufigsten verwendeten Datentypen von MediaPipe (z.B. ImageFrame, Matrix, Protokollzwischenspeicher und primitiven Datentypen) in der Kernbindung zu konfigurieren. Die umfassende Tabelle unten zeigt die Typzuordnungen zwischen dem Python- und C++-Datentyp sowie den Paketersteller und die Content-Getter-Methode für jeden Datentyp, der von der MediaPipe Python Framework API unterstützt wird.

Datentyp „Python“ C++-Datentyp Paketersteller Content-Abruf
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)
Gleitkommazahl oder np.float32 float create_float(1.1) get_float(packet)
Gleitkommazahl oder np.double Double create_double(1.1) get_float(packet)
str (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::Paket 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<float> create_float_vector([0.1; 0.2]) get_float_list(packet, size=10)
List[str] std::vector<std::string> create_string_vector(['a']) get_str_list(packet)
Liste[mp.Packet] std::vector<mp::Packet> create_packet_vector(
[packet1, Paket2])
get_packet_list(p)
Zuordnung[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 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-Protokollnachricht Google-Protokollnachricht create_proto(proto) get_proto(packet)
Liste [Proto] std::vector<Proto> get_proto_list(packet)

Es ist nicht ungewöhnlich, dass Nutzer benutzerdefinierte C++-Klassen erstellen und diese in die Grafiken und Rechner einfügen. Damit die benutzerdefinierten Klassen in Python mit MediaPipe-Framework verwendet werden können, können Sie die Packet API in den folgenden Schritten um einen neuen Datentyp erweitern:

  1. Schreiben Sie den Klassenbindungscode von pybind11 oder einen benutzerdefinierten Typkonverter für den benutzerdefinierten Typ in eine 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 einen neuen Paketersteller und eine neue Getter-Methode des benutzerdefinierten Typs in einer separaten 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 der build-Datei zwei Baizel-Build-Regeln für die benutzerdefinierte Typbindung und die neuen Paketmethoden hinzu.

    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 Qwiklabs 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 Mikrosekundeneinheiten. In Python bietet die Packet API eine praktische Methode packet.at(), um den numerischen Zeitstempel eines Pakets zu definieren. Im Allgemeinen ist packet.timestamp das Paketklassenattribut für den Zugriff auf den zugrunde liegenden Zeitstempel. Zum Konvertieren einer Unix-Epoche in einen MediaPipe-Zeitstempel bietet die Zeitstempel-API zu diesem Zweck die Methode mp.Timestamp.from_seconds().

ImageFrame

ImageFrame ist der Container zum Speichern eines Bildes oder eines Videoframes. Die von ImageFrame unterstützten Formate sind in der ImageFormat-Enum aufgeführt. Pixel sind im Zeilen-Hauptbereich mit verschränkten Farbkomponenten codiert und ImageFrame unterstützt die Datentypen uint8, uint16 und float. MediaPipe bietet eine ImageFrame Python API für den Zugriff auf die C++-Klasse „ImageFrame“. In Python können Sie die Pixeldaten am einfachsten abrufen, indem Sie image_frame.numpy_view() aufrufen, um ein „numpy ndarray“ zu erhalten. Das zurückgegebene numpy ndarray, ein Verweis auf die internen Pixeldaten, ist nicht beschreibbar. Wenn die Aufrufer numpy ndarray ändern müssen, muss explizit ein Kopiervorgang aufgerufen werden, um eine Kopie zu erhalten. Wenn MediaPipe einen numpy ndarray verwendet, um einen ImageFrame zu erstellen, wird davon ausgegangen, dass die Daten zusammenhängend gespeichert sind. Entsprechend werden die Pixeldaten eines ImageFrames neu ausgerichtet, sodass sie zusammenhängend sind, wenn sie an die Python-Seite zurückgegeben werden.

Grafik

Im MediaPipe-Framework erfolgt die gesamte Verarbeitung im Kontext von CalculatorGraph. Die CalculatorGraph Python API ist eine direkte Bindung an die C++ CalculatorGraph-Klasse. Der Hauptunterschied besteht darin, dass die CalculatorGraph Python API bei einem Fehler einen Python-Fehler ausgibt, anstatt einen Status mit dem Status "Nicht OK" zurückzugeben. Als Python-Nutzer können Sie die Ausnahmen daher wie gewohnt handhaben. Der Lebenszyklus einer CalculatorGraph besteht aus drei Phasen: Initialisierung und Einrichtung, Diagrammausführung und Herunterfahren der Grafik.

  1. Initialisieren Sie einen CalculatorGraph mit einem CalculatorGraphConfig-Protokollpuffer oder einer binären Protokollpuffer-Datei und stellen Sie Callback-Methoden bereit, um den oder die Ausgabestreams zu beobachten.

    Option 1: Initialisieren Sie einen CalculatorGraph mit einem CalculatorGraphConfig-Protokollpuffer 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 die Grafik 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. Schließen Sie das Diagramm nach dem Abschluss. Sie können die Grafik nach dem Aufruf von close() für eine andere Grafik neu starten.

    graph.close()
    

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