Framework de MediaPipe en Python

El framework de MediaPipe Python otorga acceso directo a los componentes centrales de el framework de MediaPipe C++, como Timestamp, Package y CalculatorGraph, mientras que las soluciones de Python listas para usar los detalles técnicos del framework y solo devolver el modelo legible los resultados de la inferencia a los llamadores.

El framework de MediaPipe se apoya en la biblioteca pybind11. El framework principal de C++ se expone en Python a través de una vinculación de lenguaje C++/Python. El siguiente contenido da por sentado que el lector ya tiene un conocimiento básico de el framework de C++ de MediaPipe. De lo contrario, puedes encontrar información útil en Conceptos del framework.

Paquete

El paquete es la unidad básica de flujo de datos en MediaPipe. Un paquete consta de un y un puntero compartido a una carga útil inmutable. En Python, un Un paquete MediaPipe se puede crear llamando a uno de los métodos de creación de paquetes en el mp.packet_creator módulo. En consecuencia, la carga útil del paquete se puede recuperar usando una de las métodos get de paquetes en la mp.packet_getter módulo. Ten en cuenta que la carga útil del paquete se vuelve inmutable después de que el paquete de la creación de cuentas de servicio. Por lo tanto, la modificación del contenido del paquete recuperado no afecta la carga útil real en el paquete. La API de Python del framework MediaPipe admite las los tipos de datos de MediaPipe más utilizados (p.ej., ImageFrame, matriz, protocolo búferes y tipos de datos primitivos) en la vinculación principal. La solución integral En la siguiente tabla, se muestran las asignaciones de tipos entre los tipos de datos de Python y C++. junto con el creador de paquetes y el método get de contenido para cada tipo de datos compatibles con la API del framework de MediaPipe para Python.

Tipo de datos de Python Tipo de datos de C++ Creador de paquetes Método get de contenido
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)
float o np.float32 float create_float(1.1) get_float(packet)
float o np.double doble create_double(1.1) get_float(packet)
str (UTF-8) std::cadena create_string('abc') get_str(packet)
bytes std::cadena 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([Verdadero, Falso]) get_bool_list(packet)
List[int] o List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(paquete, tamaño=10)
List[int] o List[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list(packet)
List[float] o List[np.float] float[] create_float_arrary([0.1, 0.2]) get_float_list(paquete, tamaño=10)
List[float] o List[np.float] std::vector&lt;float&gt; create_float_vector([0.1, 0.2]) get_float_list(paquete, tamaño=10)
List[str] std::vector&lt;std::string&gt; create_string_vector(['a']) get_str_list(packet)
Lista[mp.Paquete] std::vector&lt;mp::Packet&gt; create_packet_vector(
[paquete1, paquete2])
get_packet_list(p)
Asignación[str, paquete] std::map<std::string, package=""></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 y 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)
Mensaje de protocolo de Google Mensaje de protocolo de Google create_proto(proto) get_proto(packet)
Lista[Proto] std::vector<Proto> N/A get_proto_list(packet)

Es común que los usuarios creen clases de C++ personalizadas y las envíen a los gráficos y las calculadoras. Para permitir que las clases personalizadas se usen en Python con el framework de MediaPipe, puedes extender la API de paquetes para un nuevo tipo de datos en la los siguientes pasos:

  1. Escribe el pybind11 código de vinculación de clase o una conversión de tipos personalizada para el tipo personalizado en un archivo 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. Crearás un nuevo método get y creador de paquetes del tipo personalizado en un archivo cc separado.

    #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. Agrega dos reglas de compilación de Bazel para la vinculación de tipo personalizado y el paquete nuevo. en el archivo 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. Compila los destinos de extensión pybind (con el sufijo .so) de Bazel y mueve las bibliotecas dinámicas generadas a uno de los dirs $LD_LIBRARY_PATH.

  5. Usar los módulos de vinculación 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)
    

Marca de tiempo

Cada paquete contiene una marca de tiempo que está en unidades de microsegundos. En Python, La API de Packaged proporciona un método de conveniencia packet.at() para definir el valor numérico marca de tiempo de un paquete. En términos más generales, packet.timestamp es la clase del paquete. para acceder a la marca de tiempo subyacente. Para convertir un ciclo de entrenamiento Unix en Marca de tiempo de MediaPipe, la API de Timestamp ofrece un método mp.Timestamp.from_seconds() para este fin.

ImageFrame

ImageFrame es el contenedor para almacenar una imagen o un fotograma de video. Formatos compatibles con ImageFrame se enumeran en la enumeración ImageFormat. Los píxeles están codificados en filas principales con componentes de color intercalados, y ImageFrame admite uint8, uint16 y float como sus tipos de datos. MediaPipe proporciona una API de ImageFrame para Python para acceder a la clase C++ de ImageFrame. En Python, la forma más fácil de recuperar el datos de píxeles es llamar a image_frame.numpy_view() para obtener un ndarray de NumPy. Nota que se muestra ndarray de NumPy, una referencia a los datos de píxeles internos, que no se pueda escribir. Si los llamadores necesitan modificar el ndarray de NumPy, es necesario llamar de manera explícita a una operación de copia para obtener una copia. Cuando MediaPipe toma un archivo ndarray para crear un ImageFrame, este supone que los datos se almacenan de forma contigua. En consecuencia, los datos de píxeles de un ImageFrame se volverán a alinear para que es contiguo cuando regresa al lado de Python.

Gráfico

En el marco de trabajo de MediaPipe, todo el procesamiento tiene lugar en el contexto de un CalculatorGraph. La API de CalculatorGraph para Python es una vinculación directa con la clase C++ CalculatorGraph. La principal diferencia es la API de CalculatorGraph para Python genera un error de Python en lugar de mostrar un un estado de no OK cuando se produce un error. Por lo tanto, como usuario de Python, puedes controlar las excepciones como lo haces normalmente. El ciclo de vida de un CalculatorGraph contiene tres etapas: inicialización y configuración, ejecución del grafo y cierre del grafo.

  1. Cómo inicializar un CalculatorGraph con un objeto binario o protobuf de CalculatorGraphConfig archivo protobuf y proporcionar métodos de devolución de llamada para observar el resultado transmisiones.

    Opción 1: Cómo inicializar un CalculatorGraph con un protobuf de CalculatorGraphConfig o su representación de texto, y observa los flujos de salida:

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

    Opción 2. Inicializar un CalculatorGraph con un archivo binario protobuf observar los flujos de salida.

    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. Inicia la ejecución del grafo y envía paquetes al grafo.

    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. Cierra el gráfico después de finalizar. Puedes reiniciar el gráfico para otro gráfico se ejecuta después de la llamada a close().

    graph.close()
    

Tu entorno de ejecución local de Python puede ejecutar la secuencia de comandos de Python.