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<float> | 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<mp::Packet> | create_packet_vector( [pakiet1, pakiet2]) |
get_packet_list(p) |
Mapowanie[str, Pakiet] | std::map<std::string, Pack=""></std::string,> | create_string_to_packet_map( {'a': packet1, 'b': 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:
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(...); }
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
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" ], )
Utwórz cele rozszerzenia pybind (z sufiksem .so) przez Bazel i przenieś wygenerowane biblioteki dynamiczne do jednego z katalogów $LD_LIBRARY_PATH.
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.
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}'))
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))
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.