Framework MediaPipe em Python

O framework Python do MediaPipe concede acesso direto aos principais componentes do o framework em C++ do MediaPipe, como carimbo de data/hora, pacote e CalculatorGraph, enquanto as soluções Python prontas para uso escondem os detalhes técnicos do framework e retorna o modelo legível os resultados de inferência de volta para os autores da chamada.

O framework MediaPipe, biblioteca pybind11. A estrutura básica do C++ é exposta em Python por uma vinculação de linguagem C++/Python. O conteúdo abaixo pressupõe que o leitor já tem um conhecimento básico framework C++ do MediaPipe. Caso contrário, você pode encontrar informações úteis em Conceitos de framework.

Pacote

O pacote é a unidade básica do fluxo de dados no MediaPipe. Um pacote consiste em um carimbo de data/hora numérico e um ponteiro compartilhado para um payload imutável. Em Python, um O pacote MediaPipe pode ser criado chamando um dos métodos criadores de pacotes no as mp.packet_creator mais tarde neste módulo. Correspondentemente, o payload do pacote pode ser recuperado por meio de um dos métodos getter de pacotes na mp.packet_getter mais tarde neste módulo. Observe que o payload do pacote se torna imutável após o pacote criação. Assim, a modificação do conteúdo do pacote recuperado não afeta o payload real dele. A API Python do framework do MediaPipe oferece suporte à os tipos de dados mais usados do MediaPipe (por exemplo, ImageFrame, matriz, protocolo buffers e os tipos de dados primitivos) na vinculação de núcleo. A abrangência a tabela abaixo mostra os mapeamentos de tipo entre os tipos de dados Python e C++ com o criador do pacote e o método content getter para cada tipo de dados suportado pela API MediaPipe Python do framework.

Tipo de dados Python Tipo de dados C++ Criador de pacotes Getter de conteúdo
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 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([Verdadeiro, Falso]) get_bool_list(packet)
List[int] ou List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(pacote, tamanho=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(pacote, tamanho=10)
List[float] ou List[np.float] std::vector&lt;float&gt; create_float_vector([0,1, 0,2]) get_float_list(pacote, tamanho=10)
List[str] std::vector&lt;std::string&gt; create_string_vector(['a']) get_str_list(packet)
Lista[mp.Packet] std::vector&lt;mp::Packet&gt; create_packet_vector(
[pacote1, pacote2])
get_packet_list(p)
Mapeamento[str, pacote] 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 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)
Mensagem Proto do Google Mensagem Proto do Google create_proto(proto) get_proto(packet)
Lista[Proto] std::vector<Proto> N/A get_proto_list(packet)

Não é incomum que os usuários criem classes C++ personalizadas e as enviem para gráficos e calculadoras. Permitir que as classes personalizadas sejam usadas no Python com o MediaPipe Framework, é possível ampliar a API Package para um novo tipo de dados na etapas a seguir:

  1. Escrever o comando pybind11 código de vinculação de classe ou um caster de tipos personalizado para o tipo personalizado em um arquivo 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. Cria um novo criador de pacotes e um método getter do tipo personalizado em uma arquivo 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. Adicione duas regras de build do Bazel para a vinculação de tipo personalizado e o novo pacote. métodos no arquivo 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. Crie os destinos da extensão pybind (com o sufixo .so) pelo Bazel e mova as bibliotecas dinâmicas geradas para um dos diretórios $LD_LIBRARY_PATH.

  5. Usar os módulos de vinculação no 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)
    

Carimbo de data/hora

Cada pacote contém um carimbo de data/hora em unidades de microssegundos. No Python, a API Bundle fornece um método de conveniência packet.at() para definir o valor ou carimbo de data/hora de um pacote. De modo mais geral, packet.timestamp é a classe de pacote para acessar o carimbo de data/hora subjacente. Para converter uma época Unix em uma Carimbo de data/hora do MediaPipe, API Timestamp oferece um método mp.Timestamp.from_seconds() para essa finalidade.

ImageFrame

O ImageFrame é o contêiner para armazenar uma imagem ou um frame de vídeo. Formatos suportados pelo ImageFrame estão listados em o tipo enumerado ImageFormat. Os pixels são codificados como linha principal com componentes de cores intercaladas, e o ImageFrame oferece suporte a uint8, uint16 e float como tipos de dados. O MediaPipe oferece uma API ImageFrame Python para acessar a classe C++ do ImageFrame. Em Python, a maneira mais fácil de recuperar os dados de pixel é chamar image_frame.numpy_view() para obter uma ndarray numpy. Observação que o numpy ndarray retornado, uma referência aos dados internos de pixel, está. não gravável. Se os autores da chamada precisarem modificar o ndarray "numpy", será necessário chamar explicitamente uma operação de cópia para obter uma cópia. Quando o MediaPipe tira um valor numpy ndarray para criar um ImageFrame, ele pressupõe que os dados estão armazenados de forma contígua. Dessa forma, os dados de pixel de um ImageFrame são realinhados contíguos quando é retornado para o lado do Python.

Gráfico

No MediaPipe Framework, todo o processamento ocorre no contexto de uma Gráfico da Calculadora. API CalculatorGraph em Python é uma vinculação direta à classe CalculatorGraph do C++. A principal diferença é a API Python CalculatorGraph gera um erro de Python em vez de retornar um status diferente de OK quando ocorre um erro. Portanto, como usuário Python, você pode lidar as exceções, como de costume. O ciclo de vida de um CalculatorGraph contém três etapas: inicialização e configuração, execução do grafo e encerramento do gráfico.

  1. Inicialize um CalculatorGraph com um protobuf ou binário da CalculatorGraphConfig protobuf e são fornecidos métodos de callback para observar a saída fluxo(s).

    Opção 1. Inicializar um CalculatorGraph com um protobuf CalculatorGraphConfig ou a representação de texto dela, e observe os streams de saída:

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

    Opção 2. Inicializar um CalculatorGraph com um arquivo protobuf binário e observe os fluxos de saída.

    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. Inicie a execução do gráfico e inclua os pacotes nele.

    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. Feche o gráfico após a conclusão. Você pode reiniciar o gráfico para outro ser executado após a chamada para close().

    graph.close()
    

O script Python pode ser executado pelo ambiente de execução local do Python.