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<float> | create_float_vector([0,1, 0,2]) | get_float_list(pacote, tamanho=10) |
List[str] | std::vector<std::string> | create_string_vector(['a']) | get_str_list(packet) |
Lista[mp.Packet] | std::vector<mp::Packet> | create_packet_vector( [pacote1, pacote2]) |
get_packet_list(p) |
Mapeamento[str, pacote] | 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) |
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:
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(...); }
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
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" ], )
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.
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.
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}'))
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))
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.