Le framework Python de MediaPipe offre un accès direct aux composants principaux du framework C++ MediaPipe, tels que Timestamp, Packet et CalculatorGraph, tandis que les solutions Python prêtes à l'emploi masquent les détails techniques du framework et renvoient simplement les résultats d'inférence lisibles aux appelants.
Le framework MediaPipe se trouve au-dessus de la bibliothèque pybind11. Le framework principal C++ est exposé en Python via une liaison en langage C++/Python. Le contenu ci-dessous suppose que le lecteur possède déjà des connaissances de base sur le framework C++ MediaPipe. Sinon, vous trouverez des informations utiles dans la section Concepts liés aux frameworks.
Paquet
Le paquet est l'unité de flux de données de base dans MediaPipe. Un paquet se compose d'un horodatage numérique et d'un pointeur partagé vers une charge utile immuable. En Python, vous pouvez créer un paquet MediaPipe en appelant l'une des méthodes de création de paquets dans le module mp.packet_creator
. En conséquence, la charge utile du paquet peut être récupérée en utilisant l'une des méthodes du getter de paquets du module mp.packet_getter
. Notez que la charge utile du paquet devient immuable après la création du paquet. Ainsi, la modification du contenu du paquet récupéré n'affecte pas la charge utile réelle dans le paquet. L'API Python du framework MediaPipe est compatible
avec les types de données MediaPipe les plus couramment utilisés (par exemple, ImageFrame, Matrix, Protocol Buffers et les types de données primitifs) dans la liaison principale. Le tableau complet ci-dessous présente les mappages de types entre les types de données Python et C++, ainsi que le créateur du paquet et la méthode getter de contenu pour chaque type de données compatible avec l'API du framework Python MediaPipe.
Type de données Python | Type de données C++ | Créateur de paquets | Getter de contenu |
---|---|---|---|
bool | bool | create_bool(True) | get_bool(packet) |
int ou np.intc | int_t | create_int(1) | get_int(packet) |
int ou np.int8 | int8_t | create_int8(2**7-1) | get_int(packet) |
int ou np.int16 | int16_t | create_int16(2**15-1) | get_int(packet) |
int ou np.int32 | int32_t | create_int32(2**31-1) | get_int(packet) |
int ou np.int64 | int64_t | create_int64(2**63-1) | get_int(packet) |
int ou np.uint8 | uint8_t | create_uint8(2**8-1) | get_uint(packet) |
int ou np.uint16 | uint16_t | create_uint16(2**16-1) | get_uint(packet) |
int ou np.uint32 | uint32_t | create_uint32(2**32-1) | get_uint(packet) |
int ou np.uint64 | uint64_t | create_uint64(2**64-1) | get_uint(packet) |
float ou np.float32 | float | create_float(1.1) | get_float(packet) |
float ou np.double | double | create_double(1.1) | get_float(packet) |
str (UTF-8) | std::string | create_string('abc') | get_str(packet) |
bytes | std::string | créer_chaîne(b'\xd0\xd0\xd0') | get_bytes(packet) |
mp.Packet | mp::Paquet | create_packet(p) | get_packet(packet) |
Liste[bool] | std::vector<bool> | create_bool_vector([Vrai, Faux]) | get_bool_list(packet) |
List[int] ou List[np.intc] | int[] | create_int_array([1, 2, 3]) | get_int_list(paquet, size=10) |
List[int] ou List[np.intc] | std::vector<int> | create_int_vector([1, 2, 3]) | get_int_list(packet) |
List[float] ou List[np.float] | float[] | create_float_arrary([0,1, 0,2]) | get_float_list(paquet, size=10) |
List[float] ou List[np.float] | std::vector<float> | create_float_vector([0.1, 0.2]) | get_float_list(paquet, size=10) |
Liste[chaîne] | std::vector<std::string> | create_string_vector(['a']) | get_str_list(packet) |
Liste[mp.Packet] | std::vector<mp::Packet> | create_packet_vector( [paquet1, paquet2]) |
get_packet_list(p) |
Mappage[str, Paquet] | std::map<std::string, paquets=""></std::string,> | create_string_to_packet_map( {'a': packet1, 'b': packet2}) |
get_str_to_packet_dict(packet) |
np.ndarray (cv.mat et PIL.Image) |
mp::ImageFrame | create_image_frame( format=ImageFormat.SRGB, data=mat) |
get_image_frame(packet) |
np.ndarray | mp::Matrice | create_matrix(data) | get_matrix(packet) |
Message Google Proto | Message Google Proto | create_proto(proto) | get_proto(packet) |
Liste[Proto] | std::vector<Proto> | N/A | get_proto_list(packet) |
Il n'est pas rare que les utilisateurs créent des classes C++ personnalisées et les envoient dans les graphiques et les calculatrices. Pour permettre l'utilisation des classes personnalisées en Python avec MediaPipe Framework, vous pouvez étendre l'API Packet à un nouveau type de données en procédant comme suit:
Écrivez le code de liaison de classe pybind11 ou un lanceur de type personnalisé pour le type personnalisé dans un fichier 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(...); }
Créez un créateur de paquets et une méthode getter du type personnalisé dans un fichier Cc distinct.
#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
Ajoutez deux règles de compilation Bazel pour la liaison de type personnalisé et les nouvelles méthodes de paquets dans le fichier 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" ], )
Créez les cibles d'extension pybind (avec le suffixe .so) par Bazel, puis déplacez les bibliothèques dynamiques générées vers l'un des répertoires $LD_LIBRARY_PATH.
Utilisez les modules de liaison en 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)
Code temporel
Chaque paquet contient un horodatage exprimé en microsecondes. En Python, l'API Packet fournit une méthode pratique packet.at()
pour définir l'horodatage numérique d'un paquet. Plus généralement, packet.timestamp
est la propriété de classe de paquet permettant d'accéder à l'horodatage sous-jacent. Pour convertir une epoch Unix en horodatage MediaPipe, l'API Timestamp propose une méthode mp.Timestamp.from_seconds()
à cet effet.
ImageFrame
"ImageFrame" est le conteneur permettant de stocker une image ou une image vidéo. Les formats compatibles avec ImageFrame sont répertoriés dans l'énumération ImageFormat.
Les pixels sont encodés par ligne principale avec des composants de couleur entrelacés, et ImageFrame prend en charge les types de données uint8, uint16 et float. MediaPipe fournit une API ImageFrame Python pour accéder à la classe C++ ImageFrame. En Python, le moyen le plus simple de récupérer les données de pixels consiste à appeler image_frame.numpy_view()
pour obtenir un tableau de données Numpy. Notez que le ndarray Numpy renvoyé, une référence aux données de pixels internes, n'est pas accessible en écriture. Si les appelants doivent modifier le ndarray Numpy, il est nécessaire d'appeler explicitement une opération de copie pour obtenir une copie. Lorsque MediaPipe utilise un ndarray Numpy pour créer un ImageFrame, il suppose que les données sont stockées de manière contiguë.
En conséquence, les données de pixels d'un ImageFrame sont réalignées pour être contiguës lorsqu'elles sont renvoyées du côté Python.
Graphique
Dans le framework MediaPipe, l'intégralité du traitement s'effectue dans le contexte d'un CalculatorGraph. L'API Python CalculatorGraph est une liaison directe à la classe CalculatorGraph C++. La principale différence est que l'API Python CalculatorGraph génère une erreur Python au lieu de renvoyer un état non OK en cas d'erreur. Par conséquent, en tant qu'utilisateur Python, vous pouvez gérer les exceptions comme vous le faites habituellement. Le cycle de vie d'un CalculatorGraph comporte trois étapes: l'initialisation et la configuration, l'exécution du graphique et son arrêt.
Initialisez un CalculatorGraph avec un fichier protobuf ou binaire CalculatorGraphConfig, puis fournissez une ou plusieurs méthodes de rappel pour observer les flux de sortie.
Option 1. Initialisez un CalculatorGraph avec un tampon de protocole CalculatorGraphConfig ou sa représentation textuelle, puis observez les flux de sortie :
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. Initialisez un CalculatorGraph avec un fichier protobuf binaire et observez le ou les flux de sortie.
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}'))
Démarrez l'exécution du graphe et insérez des paquets dans le graphe.
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))
Une fois l'opération terminée, fermez le graphique. Vous pouvez redémarrer le graphique pour une autre exécution de graphe après l'appel de
close()
.graph.close()
Le script Python peut être exécuté par votre environnement d'exécution Python local.