Framework MediaPipe en Python

Le framework Python MediaPipe offre un accès direct aux principaux composants le framework MediaPipe C++ comme Timestamp, Packet et CalculatorGraph, tandis que les solutions Python prêtes à l'emploi masquent les détails techniques du framework et renvoie simplement le modèle lisible les résultats d'inférence aux appelants.

Le framework MediaPipe s'appuie la bibliothèque pybind11. Le framework de base C++ est exposé en Python via une liaison de langage C++/Python. Le contenu ci-dessous suppose que le lecteur possède déjà des connaissances de base en le framework C++ MediaPipe. Sinon, vous trouverez des informations utiles dans Concepts de framework.

Paquet

Le paquet est l'unité de flux de données de base dans MediaPipe. Un paquet est constitué d'un un horodatage numérique et un pointeur partagé vers une charge utile immuable. En Python, Le paquet MediaPipe peut être créé en appelant l'une des méthodes de création de paquets dans la mp.packet_creator de ce module. En conséquence, la charge utile du paquet peut être récupérée en utilisant l'une des méthodes getter de paquets dans mp.packet_getter de ce module. Notez que la charge utile du paquet devient immuable après le paquet. création. Ainsi, la modification du contenu des paquets récupérés n'affecte pas la charge utile réelle du paquet. L'API Python du framework MediaPipe est compatible avec types de données les plus couramment utilisés dans MediaPipe (par exemple, ImageFrame, Matrix, Protocol les tampons et les types de données primitifs) dans la liaison de base. Fonctionnalité complète Le tableau ci-dessous présente les mappages de types entre les types de données Python et C++ ainsi que le créateur de paquets et la méthode getter de contenu pour chaque type de données compatibles avec l'API MediaPipe du framework Python.

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::chaîne create_string('abc') get_str(packet)
bytes std::chaîne create_string(b'\xd0\xd0\xd0') get_bytes(packet)
mp.Packet mp::Packet create_packet(p) get_packet(packet)
Liste std::vecteur<bool> create_bool_vector([Vrai, Faux]) get_bool_list(packet)
List[int] ou List[np.intc] entier[] create_int_array([1, 2, 3]) get_int_list(paquet, taille=10)
List[int] ou List[np.intc] std::vecteur<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, taille=10)
List[float] ou List[np.float] std::vector&lt;float&gt; create_float_vector([0,1, 0,2]) get_float_list(paquet, taille=10)
Liste[chaîne] std::vector&lt;std::string&gt; create_string_vector(['a']) get_str_list(packet)
Liste[mp.Packet] std::vector&lt;mp::Packet&gt; create_packet_vector(
[paquet1, paquet2])
get_packet_list(p)
Mapping[str, Packet] (Mise en correspondance) std::map<std::string, Packet=""></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 et 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)
Message Google Proto Message Google Proto create_proto(proto) get_proto(packet)
Liste[Proto] std::vecteur<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 les graphiques et les calculatrices. Pour autoriser l'utilisation des classes personnalisées dans Python avec MediaPipe Framework, vous pouvez étendre l'API Paquet pour un nouveau type de données dans le procédez comme suit:

  1. Écrire la commande pybind11 code de liaison de classe ou une machine de roulement de caractères personnalisée 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(...);
    }
    
  2. 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
    
  3. Ajouter deux règles de compilation Bazel pour la liaison de type personnalisée et le nouveau paquet dans le fichier CREATE.

    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. Créez les cibles d'extension pybind (avec le suffixe .so) par Bazel et déplacez les bibliothèques dynamiques générées dans l'un des répertoires $LD_LIBRARY_PATH.

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

Horodatage

Chaque paquet contient un code temporel exprimé en microsecondes. Dans Python, L'API Paquet fournit une méthode pratique, packet.at(), qui permet de définir les valeurs le code temporel d'un paquet. Plus généralement, packet.timestamp est la classe des paquets permettant d'accéder à l'horodatage sous-jacent. Pour convertir une époque Unix en Code temporel 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. Formats pris en charge par ImageFrame sont répertoriés dans Énumération ImageFormat. Les pixels sont encodés "ligne principale" avec des composants de couleur entrelacés et ImageFrame. prend en charge uint8, uint16 et float comme types de données. MediaPipe propose une API Python ImageFrame pour accéder à la classe C++ ImageFrame. En Python, le moyen le plus simple de récupérer les données de pixel consiste à appeler image_frame.numpy_view() pour obtenir un tableau Numpy. Remarque que le numpy ndarray renvoyé, une référence aux données de pixels internes, est non accessible en écriture. Si les appelants doivent modifier le ndarray numpy, il est nécessaire de appeler explicitement une opération de copie pour obtenir une copie. Quand MediaPipe prend une requête Numpy ndarray pour créer un ImageFrame, il suppose que les données sont stockées contiguës. En conséquence, les données de pixel d'un ImageFrame seront réalignées pour être contiguës lorsqu'elles sont renvoyées du côté Python.

Graphique

Dans le framework MediaPipe, tout le traitement a lieu dans le contexte CalculatorGraph. API Python CalculatorGraph est une liaison directe vers la classe C++ CalculatorGraph. La principale différence réside dans L'API Python CalculatorGraph génère une erreur Python au lieu de renvoyer une un état non OK en cas d'erreur. Par conséquent, en tant qu'utilisateur Python, les exceptions comme vous le faites habituellement. Le cycle de vie d'un CalculatorGraph contient trois étapes: l'initialisation et la configuration, l'exécution du graphique et l'arrêt du graphique.

  1. Initialiser un CalculatorGraph avec un protobuf ou un binaire de CalculatorGraphConfig fichier protobuf et fournir une ou plusieurs méthodes de rappel pour observer le résultat flux.

    Option 1. Initialiser un CalculatorGraph avec un protobuf CalculatorGraphConfig ou sa représentation textuelle, et observez le ou 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. Initialiser un CalculatorGraph avec un fichier protobuf binaire observer 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}'))
    
  2. Lancez l'exécution du graphique et ajoutez-y les paquets.

    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. Fermez le graphique une fois l'opération terminée. Vous pouvez relancer le graphique pour en voir un autre. s'exécute après l'appel à close().

    graph.close()
    

Le script Python peut être exécuté par votre environnement d'exécution Python local.