Il framework Python di MediaPipe garantisce l'accesso diretto ai componenti principali del il framework C++ MediaPipe, come Timestamp, Packet e CalculatorGraph, mentre le soluzioni Python pronte all'uso nascondono i dettagli tecnici del framework e restituiscono semplicemente il modello leggibile i risultati dell'inferenza ai chiamanti.
Il framework MediaPipe si basa su la libreria pybind11. Il framework di base C++ è esposto in Python tramite un'associazione di linguaggio C++/Python. I contenuti di seguito presuppongono che il lettore abbia già una conoscenza di base il framework MediaPipe C++. In caso contrario, puoi trovare informazioni utili in Concetti del framework.
Pacchetto
Il pacchetto è l'unità di flusso di dati di base in MediaPipe. Un pacchetto è composto da
un timestamp numerico e un puntatore condiviso a un payload immutabile. In Python,
Il pacchetto MediaPipe può essere creato chiamando uno dei metodi del creatore di pacchetti
il
mp.packet_creator
in maggior dettaglio più avanti
in questo modulo. Di conseguenza, il payload dei pacchetti può essere recuperato utilizzando uno dei
i metodi getter di pacchetti
mp.packet_getter
in maggior dettaglio più avanti
in questo modulo. Tieni presente che il payload del pacchetto diventa immutabile dopo il pacchetto
per la creazione di contenuti. Di conseguenza, la modifica del contenuto del pacchetto recuperato non influisce
il payload effettivo nel pacchetto. L'API Python del framework MediaPipe supporta
i tipi di dati di MediaPipe più utilizzati (ad es. ImageFrame, Matrice, Protocollo
buffer e tipi di dati primitivi) nell'associazione principale. L'esaustivo
seguente mostra le mappature dei tipi tra il tipo di dati Python e C++
insieme al creatore del pacchetto e al metodo getter del contenuto per ogni tipo di dati
supportate dall'API del framework Python MediaPipe.
Tipo di dati Python | Tipo di dati C++ | Creatore pacchetti | Raccoglitore di contenuti |
---|---|---|---|
bool | bool | create_bool(True) | get_bool(packet) |
int o np.intc | int_t | create_int(1) | get_int(packet) |
int o np.int8 | int8_t | create_int8(2**7-1) | get_int(packet) |
int o np.int16 | int16_t | create_int16(2**15-1) | get_int(packet) |
int o np.int32 | int32_t | create_int32(2**31-1) | get_int(packet) |
int o np.int64 | int64_t | create_int64(2**63-1) | get_int(packet) |
int o np.uint8 | uint8_t | create_uint8(2**8-1) | get_uint(packet) |
int o np.uint16 | uint16_t | create_uint16(2**16-1) | get_uint(packet) |
int o np.uint32 | uint32_t | create_uint32(2**32-1) | get_uint(packet) |
int o np.uint64 | uint64_t | create_uint64(2**64-1) | get_uint(packet) |
numero in virgola mobile o np.float32 | numero in virgola mobile | create_float(1.1) | get_float(packet) |
numero in virgola mobile o np.double | double | create_double(1.1) | get_float(packet) |
str (UTF-8) | std::stringa | create_string('abc') | get_str(packet) |
byte | std::stringa | crea_stringa(b'\xd0\xd0\xd0') | get_bytes(packet) |
mp.Packet | mp::Packet | create_packet(p) | get_packet(packet) |
Elenco[bool] | std::vector<bool> | create_bool_vector([Vero, Falso]) | get_bool_list(packet) |
List[int] o List[np.intc] | int[] | create_int_array([1; 2; 3]) | get_int_list(pacchetto; dimensione=10) |
List[int] o List[np.intc] | std::vector<int> | create_int_vector([1; 2; 3]) | get_int_list(packet) |
Elenco[float] o Elenco[np.float] | float[] | create_float_arrary([0.1; 0.2]) | get_float_list(pacchetto; dimensione=10) |
Elenco[float] o Elenco[np.float] | std::vector<float> | create_float_vector([0,1; 0,2]) | get_float_list(pacchetto; dimensione=10) |
Elenco[str] | std::vector<std::string> | crea_vettore_stringa(['a']) | get_str_list(packet) |
Elenco[mp.Packet] | std::vector<mp::Packet> | create_packet_vector( [pacchetto1, pacchetto2]) |
get_packet_list(p) |
Mappatura[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 e 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) |
Messaggio Google Proto | Messaggio Google Proto | create_proto(proto) | get_proto(packet) |
Elenco[protocollo] | std::vector<Proto> | n/d | get_proto_list(packet) |
Non è raro che gli utenti creino classi C++ personalizzate e le inviino a grafici e calcolatrici. per consentire l'utilizzo delle classi personalizzate in Python con il framework MediaPipe, puoi estendere l'API Packet per un nuovo tipo di dati nel seguenti passaggi:
Scrivi il file pybind11 codice di associazione della classe o un caster di tipo personalizzato per il tipo personalizzato in un file 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(...); }
crea un nuovo metodo di creazione di pacchetti e getter di tipo personalizzato in un in un file Cc separato.
#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
Aggiungi due regole di build Bazel per l'associazione dei tipi personalizzati e il nuovo pacchetto nel file 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" ], )
Crea le destinazioni dell'estensione pybind (con il suffisso .so) da Bazel e sposta le librerie dinamiche generate in una delle directory $LD_LIBRARY_PATH.
Utilizzare i moduli di associazione 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)
Timestamp
Ogni pacchetto contiene un timestamp espresso in microsecondi. In Python,
l'API Packet fornisce un metodo di convenienza packet.at()
per definire il numero
di un pacchetto. Più in generale, packet.timestamp
è la classe del pacchetto
per accedere al timestamp sottostante. Per convertire un'epoca Unix in un
Timestamp di MediaPipe,
l'API Timestamp
offre a questo scopo un metodo mp.Timestamp.from_seconds()
.
ImageFrame
ImageFrame è il contenitore per l'archiviazione di un'immagine o di un frame video. Formati
supportati da ImageFrame sono elencati in
l'enumerazione ImageFormat.
I pixel sono codificati in prima riga con componenti di colore interleato e ImageFrame
supporta uint8, uint16 e float come tipi di dati. MediaPipe fornisce
Un'API ImageFrame Python
per accedere alla classe C++ di ImageFrame. In Python, il modo più semplice per recuperare
i dati pixel devono chiamare image_frame.numpy_view()
per ottenere un ndarray numpy. Nota
che il ndarray numpy restituito, un riferimento ai dati dei pixel interni, è
non scrivibile. Se i chiamanti devono modificare il ndarray numpy, è necessario
di richiamare esplicitamente un'operazione di copia per ottenerne una. Quando MediaPipe accetta un numero
ndarray per creare un ImageFrame, presuppone che i dati siano memorizzati in modo contiguo.
Di conseguenza, i dati dei pixel di un ImageFrame saranno riallineati
contigua quando viene restituita al lato Python.
Grafico
Nel framework MediaPipe, tutta l'elaborazione avviene nel contesto di una Calcolatrice grafica. L'API Python CalculatorGraph è un'associazione diretta alla classe CalculatorGraph di C++. La differenza principale è che l'API Python CalculatorGraph genera un errore Python invece di restituire un non OK quando si verifica un errore. Di conseguenza, come utente Python, puoi gestire le eccezioni come fai normalmente. Il ciclo di vita di un grafico di Calcolatrice contiene tre fasi: inizializzazione e configurazione, esecuzione del grafico e chiusura del grafico.
Inizializzare un CalculatorGraph con un protobuf o un file binario CalculatorGraphConfig protobuf e fornire metodi di callback per osservare l'output flussi.
Opzione 1. Inizializzare un CalculatorGraph con un protobuf CalculatorGraphConfig o la sua rappresentazione testuale e osserva i flussi di output:
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)))
Opzione 2. Inizializzare un CalculatorGraph con un file binario protobuf e osserva i flussi di output.
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}'))
Avvia l'esecuzione del grafico e inserisci i pacchetti nel grafico.
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))
Al termine, chiudi il grafico. Puoi riavviare il grafico per visualizzarne un altro dopo la chiamata a
close()
.graph.close()
Lo script Python può essere eseguito dal tuo runtime Python locale.