Platforma MediaPipe w Pythonie

Platforma MediaPipe Python zapewnia bezpośredni dostęp do podstawowych komponentów platformę MediaPipe C++, takie jak Timestamp, Packet i CalculatorGraph, a gotowe do użycia rozwiązania w języku Python ukrywają się szczegóły techniczne platformy i zwracać czytelny model wnioskowania z powrotem do elementów wywołujących.

Platforma MediaPipe opiera się na biblioteki pybind11, Podstawowa platforma C++ jest udostępniana w Pythonie przez powiązanie języka C++/Python. W treści poniżej zakładamy, że czytelnik zna już podstawowe platformy MediaPipe C++. W przeciwnym razie przydatne informacje znajdziesz w Framework Concepts (Pojęcia związane z ramami).

Pakiet

Pakiet to podstawowa jednostka przepływu danych w MediaPipe. Pakiet składa się z liczbowa sygnatura czasowa i udostępniony wskaźnik do stałego ładunku. W Pythonie Pakiet MediaPipe można utworzyć, wywołując jedną z metod twórcy pakietów w mp.packet_creator . Ładunek pakietu można też pobrać za pomocą jednej metody pobierania pakietów mp.packet_getter . Pamiętaj, że ładunek pakietu staje się niezmienny po jego zakończeniu proces tworzenia. Dzięki temu zmiana treści pobranego pakietu nie wpływa rzeczywisty ładunek w pakiecie. MediaPipe Framework Python API obsługuje najczęściej używane typy danych MediaPipe (np. ImageFrame, matrix, protokół bufory i podstawowe typy danych) w podstawowym powiązaniu. Kompleksowe rozwiązanie ta tabela zawiera mapowania typów danych w Pythonie i C++ wraz z twórcą pakietów i metodą pobierania treści w przypadku każdego typu danych obsługiwane przez interfejs MediaPipe Python Framework API.

Typ danych w Pythonie Typ danych C++ Twórca pakietów Narzędzie do pobierania treści
wartość logiczna wartość logiczna create_bool(True) get_bool(packet)
int lub np.intc int_t create_int(1) get_int(packet)
int lub np.int8 int8_t create_int8(2**7-1) get_int(packet)
int lub np.int16 int16_t, create_int16(2**15-1) get_int(packet)
int lub np.int32 int32_t, create_int32(2**31-1) get_int(packet)
int lub np.int64 int64_t, create_int64(2**63-1) get_int(packet)
int lub np.uint8 uint8_t create_uint8(2**8-1) get_uint(packet)
int lub np.uint16 uint16_t create_uint16(2**16-1) get_uint(packet)
int lub np.uint32 uint32_t create_uint32(2**32-1) get_uint(packet)
int lub np.uint64 uint64_t create_uint64(2**64-1) get_uint(packet)
float lub np.float32 liczba zmiennoprzecinkowa create_float(1.1) get_float(packet)
float lub np.double double, create_double(1.1) get_float(packet)
ciąg znaków (UTF-8) std::string utwórz_ciąg('abc') get_str(packet)
B std::string create_string(b'\xd0\xd0\xd0') get_bytes(packet)
mp.Packet mp::Packet create_packet(p) get_packet(packet)
Lista[bool] std::vector<bool>, create_bool_vector([Prawda, Fałsz]) get_bool_list(packet)
List[int] lub List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(pakiet; rozmiar=10)
List[int] lub List[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list(packet)
List[float] lub List[np.float] float[] create_float_arrary([0,1; 0,2]) get_float_list(pakiet; rozmiar=10)
List[float] lub List[np.float] std::vector&lt;float&gt; create_float_vector([0,1; 0,2]) get_float_list(pakiet; rozmiar=10)
Lista[tekst] std::vector<std::string>, create_string_vector(['a']) get_str_list(packet)
Lista[mp.Packet] std::vector&lt;mp::Packet&gt; create_packet_vector(
[pakiet1, pakiet2])
get_packet_list(p)
Mapowanie[str, Pakiet] std::map<std::string, Pack=""></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 i 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)
Wiadomość Google Proto Wiadomość Google Proto create_proto(proto) get_proto(packet)
Lista [proto] std::vector<Proto> nie dotyczy get_proto_list(packet)

Nie jest niczym niezwykłym, że użytkownicy tworzą niestandardowe klasy C++ i wysyłają je do wykresy i kalkulatory. Aby umożliwić używanie niestandardowych klas w Pythonie za pomocą platformy MediaPipe Framework, możesz rozszerzyć interfejs Packet API na potrzeby nowego typu danych w następujące kroki:

  1. Napisz pybind11 kod powiązania zajęć lub niestandardowy rzutnik typu dla niestandardowego typu w pliku DW.

    #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. Utwórz nowy twórca pakietów i metodę pobierania o niestandardowym typie w pliku oddzielnym pliku DW.

    #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. Dodaj 2 reguły kompilacji bazel dla wiązania typu niestandardowego i nowego pakietu w pliku 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. Utwórz cele rozszerzenia pybind (z sufiksem .so) przez Bazel i przenieś wygenerowane biblioteki dynamiczne do jednego z katalogów $LD_LIBRARY_PATH.

  5. Użyj modułów powiązań w Pythonie.

    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)
    

Sygnatura czasowa

Każdy pakiet zawiera sygnaturę czasową wyrażoną w mikrosekundach. W Pythonie Interfejs Packet API udostępnia udogodnienie packet.at() do definiowania liczbowych sygnatury czasowej pakietu. Ogólnie rzecz biorąc, packet.timestamp jest klasą pakietów do uzyskiwania dostępu do bazowej sygnatury czasowej. Aby przekonwertować epokę uniksową na Sygnatura czasowa MediaPipe, interfejsu Timestamp API oferuje metodę mp.Timestamp.from_seconds() do tego celu.

ImageFrame

Element ImageFrame to kontener do przechowywania obrazu lub klatki wideo. Formaty obsługiwane przez ImageFrame są wymienione w wyliczenie ImageFormat. Piksele są zakodowane w postaci wiersza-główny z komponentami przeplatanymi kolorami oraz ImageFrame. obsługuje typy danych uint8, uint16 i „float”. MediaPipe zapewnia interfejs ImageFrame Python API aby uzyskać dostęp do klasy C++ w ImageFrame. W Pythonie najprostszym sposobem na pobranie dane piksela to wywołanie funkcji image_frame.numpy_view() w celu uzyskania numpy ndarray. Notatka zwracany numpy ndarray, odniesienie do wewnętrznych danych piksela, nie można zapisać. Jeśli rozmówca musi zmodyfikować numpy ndarray, należy: bezpośrednio wywołać operację kopiowania w celu uzyskania kopii. Kiedy MediaPipe przyjmuje ndarray do utworzenia obiektu ImageFrame, z założeniem, że dane są przechowywane w sposób ciągły. Dane pikselowe ramki ImageFrame zostaną odpowiednio wyrównane sąsiaduje po zwróceniu na stronę Pythona.

Wykres

W MediaPipe Framework całe przetwarzanie odbywa się w kontekście Kalkulator Interfejs CalculatorGraph Python API to bezpośrednie powiązanie z klasą CalculatorGraph w C++. Główna różnica to interfejs CalculatorGraph Python API zwraca błąd Pythona, zamiast zwracać błąd brak stanu OK w przypadku wystąpienia błędu. Dzięki temu jako użytkownik języka Python możesz wyjątki, jak zwykle. Cykl życia kalkulatora zawiera 3 etapy: inicjalizacja i konfiguracja, uruchomienie wykresu oraz wyłączenie grafu.

  1. Inicjowanie CalculatorGraph za pomocą protokołu lub pliku binarnego CalculatorGraphConfig oraz metody wywołania zwrotnego w celu obserwowania danych wyjściowych strumieni(e).

    Opcja 1. Inicjowanie wykresu CalculatorGraph za pomocą protokołu CalculatorGraphConfig lub jej reprezentacji tekstowej i obserwować strumienie wyjściowe:

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

    Opcja 2. zainicjować CalculatorGraph z binarnym plikiem protokołu; obserwować strumienie wyjściowe.

    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. Rozpocznij uruchomienie wykresu i przekaż do niego pakiety.

    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. Po zakończeniu zamknij wykres. Możesz uruchomić ponownie wykres, aby zobaczyć inny wykres po wywołaniu close().

    graph.close()
    

Skrypt Pythona można uruchomić przez lokalne środowisko wykonawcze Pythona.