O framework MediaPipe Python concede acesso direto aos principais componentes do framework do MediaPipe C++, como carimbo de data/hora, pacote e CalculatorGraph, enquanto as soluções Python prontas para uso ocultam os detalhes técnicos do framework e simplesmente retornam os resultados de inferência do modelo legível para os autores da chamada.
O framework MediaPipe fica sobre a biblioteca pybind11 (link em inglês). O framework principal do C++ é exposto em Python por uma vinculação de linguagem C++/Python. O conteúdo abaixo pressupõe que o leitor já tenha uma compreensão básica do framework MediaPipe C++. Também é possível encontrar informações úteis em Conceitos de framework.
Pacote
O pacote é a unidade básica de 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. No Python, um pacote do MediaPipe pode ser criado chamando um dos métodos do criador de pacotes
no
módulo
mp.packet_creator
. Da mesma forma, o payload do pacote pode ser recuperado usando um dos
métodos getter de pacote no
módulo
mp.packet_getter
. Observe que o payload do pacote se torna imutável após a criação dele. Portanto, a modificação do conteúdo do pacote recuperado não afeta
o payload real no pacote. A API Python do framework do MediaPipe
oferece suporte aos tipos de dados mais usados do MediaPipe (por exemplo, ImageFrame, Matrix, buffers de
protocolo e os tipos de dados primitivos) na vinculação principal. Confira na tabela abaixo os mapeamentos de tipos entre os tipos de dados Python e C++, o criador de pacotes e o método getter de conteúdo para cada tipo de dado aceito pela API MediaPipe Python.
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) |
flutuação ou np.double | dupla | 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::Pacote | 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) |
Mapping[str, Packet] | 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::Matriz | create_matrix(data) | get_matrix(packet) |
Mensagem do Google Proto | Mensagem do Google Proto | 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. Para permitir que as classes personalizadas sejam usadas em Python com o MediaPipe Framework, amplie a API Packet para um novo tipo de dados nas seguintes etapas:
Escreva o código de vinculação de classe do pybind11 ou um caster de tipo 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(...); }
Crie um novo criador de pacote e um método getter do tipo personalizado em um 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 os novos métodos de pacote 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 de 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 Packet fornece um método de conveniência packet.at()
para definir o
carimbo de data/hora numérico de um pacote. De modo mais geral, packet.timestamp
é a propriedade da classe de pacote
para acessar o carimbo de data/hora. Para converter uma época Unix em um
carimbo de data/hora do MediaPipe,
a API Timestamp
oferece um método mp.Timestamp.from_seconds()
para essa finalidade.
ImageFrame
ImageFrame é o contêiner para armazenar uma imagem ou um frame de vídeo. Os formatos
compatíveis com o ImageFrame estão listados na
enumeração ImageFormat.
Os pixels são codificados em linha principal com componentes de cores intercaladas, e o ImageFrame
oferece suporte a uint8, uint16 e flutuação como tipos de dados. O MediaPipe fornece uma API ImageFrame Python para acessar a classe C++ do ImageFrame. Em Python, a maneira mais fácil de recuperar os
dados de pixels é chamar image_frame.numpy_view()
para gerar um ndarray numpy. Observe
que o numpy ndarray retornado, uma referência aos dados de pixels internos, é
não gravável. Se os autores da chamada precisarem modificar a matriz numpy, será necessário chamar explicitamente uma operação de cópia para conseguir uma cópia. Quando o MediaPipe usa um numpy
ndarray para criar um ImageFrame, ele pressupõe que os dados são armazenados de forma contígua.
Da mesma forma, os dados de pixel de um ImageFrame serão realinhados para serem
contíguos quando retornarem para o lado do Python.
Gráfico
No framework do MediaPipe, todo o processamento ocorre no contexto de um CalculatorGraph. A API CalculatorGraph Python é uma vinculação direta com a classe C++ CalculatorGraph. A principal diferença é que a API CalculatorGraph Python gera um erro do Python em vez de retornar um status diferente de OK quando ocorre um erro. Portanto, como usuário de Python, você pode lidar com as exceções normalmente. O ciclo de vida de um CalculatorGraph contém três estágios: inicialização e configuração, execução e encerramento de gráfico.
Inicialize um CalculatorGraph com um arquivo protobuf CalculatorGraphConfig ou um arquivo protobuf binário e forneça métodos de callback para observar os streams de saída.
Opção 1: Inicialize um CalculatorGraph com um protobuf CalculatorGraphConfig ou a representação de texto dele 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. Inicialize um CalculatorGraph com um arquivo protobuf binário e observe os streams 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 alimente os pacotes no gráfico.
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))
Fechar o gráfico após o término. É possível reiniciar o gráfico para outra execução após a chamada para
close()
.graph.close()
O script Python pode ser executado pelo ambiente de execução local do Python.