Framework MediaPipe Python memberikan akses langsung ke komponen inti framework MediaPipe C++ seperti Stempel Waktu, Paket, dan CalculatorGraph, sedangkan solusi Python yang siap digunakan menyembunyikan detail teknis framework dan cukup menampilkan kembali hasil inferensi model yang dapat dibaca ke pemanggil.
Framework MediaPipe berada di atas library pybind11. Framework inti C++ diekspos di Python melalui binding bahasa C++/Python. Konten di bawah ini mengasumsikan bahwa pembaca sudah memiliki pemahaman dasar tentang framework MediaPipe C++. Jika tidak, Anda dapat menemukan informasi berguna di Konsep Framework.
Paket
Paket adalah unit aliran data dasar di MediaPipe. Paket terdiri dari
stempel waktu numerik dan pointer bersama ke payload yang tidak dapat diubah. Di Python, paket MediaPipe dapat dibuat dengan memanggil salah satu metode pembuat paket dalam
modul
mp.packet_creator
. Selanjutnya, payload paket dapat diambil dengan menggunakan salah satu
metode pengambil paket dalam
modul
mp.packet_getter
. Perhatikan bahwa payload paket menjadi tidak dapat diubah setelah pembuatan
paket. Dengan demikian, modifikasi konten paket yang diambil tidak memengaruhi
payload sebenarnya dalam paket. Framework MediaPipe Python API mendukung
jenis data MediaPipe yang paling umum digunakan (misalnya, ImageFrame, Matrix, Buffers
Protokol, dan jenis data primitif) di binding inti. Tabel komprehensif
di bawah ini menunjukkan pemetaan jenis antara Python dan jenis data C++
beserta pembuat paket dan metode pengambil konten untuk setiap jenis data
yang didukung oleh API framework MediaPipe Python.
Jenis Data Python | Jenis Data C++ | Pembuat Paket | Pengambil Konten |
---|---|---|---|
bool | bool | create_bool(True) | get_bool(packet) |
int atau np.intc | int_t | create_int(1) | get_int(packet) |
int atau np.int8 | int8_t | create_int8(2**7-1) | get_int(packet) |
int atau np.int16 | int16_t | create_int16(2**15-1) | get_int(packet) |
int atau np.int32 | int32_t | create_int32(2**31-1) | get_int(packet) |
int atau np.int64 | int64_t | create_int64(2**63-1) | get_int(packet) |
int atau np.uint8 | uint8_t | create_uint8(2**8-1) | get_uint(packet) |
int atau np.uint16 | uint16_t | create_uint16(2**16-1) | get_uint(packet) |
int atau np.uint32 | uint32_t | create_uint32(2**32-1) | get_uint(packet) |
int atau np.uint64 | uint64_t | create_uint64(2**64-1) | get_uint(packet) |
float atau np.float32 | float | create_float(1.1) | get_float(packet) |
float atau np.double | double | create_double(1.1) | get_float(packet) |
str (UTF-8) | std::string | create_string('abc') | get_str(packet) |
byte | std::string | create_string(b'\xd0\xd0\xd0') | get_bytes(packet) |
mp.Packet | mp::Paket | create_packet(p) | get_packet(packet) |
Daftar[bool] | std::vector<bool> | create_bool_vector([Benar, Salah]) | get_bool_list(packet) |
List[int] atau List[np.intc] | int[] | create_int_array([1, 2, 3]) | get_int_list(paket, ukuran=10) |
List[int] atau List[np.intc] | std::vector<int> | create_int_vector([1, 2, 3]) | get_int_list(packet) |
Daftar[float] atau Daftar[np.float] | float[] | create_float_arrary([0,1, 0,2]) | get_float_list(paket, ukuran=10) |
Daftar[float] atau Daftar[np.float] | std::vector<float> | create_float_vector([0.1, 0,2]) | get_float_list(paket, ukuran=10) |
List[str] | std::vector<std::string> | create_string_vector(['a']) | get_str_list(packet) |
Daftar[mp.Packet] | std::vector<mp::Packet> | create_packet_vector( [paket1, paket2]) |
get_packet_list(p) |
Pemetaan[str, Paket] | 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 dan PIL.Image) |
mp::ImageFrame | create_image_frame( format=ImageFormat.SRGB, data=mat) |
get_image_frame(packet) |
np.ndarray | mp::Matriks | create_matrix(data) | get_matrix(packet) |
Pesan Proto Google | Pesan Proto Google | create_proto(proto) | get_proto(packet) |
Daftar[Proto] | std::vector<Proto> | t/a | get_proto_list(packet) |
Tidak jarang pengguna membuat class C++ kustom dan mengirimkannya ke grafik dan kalkulator. Agar class kustom dapat digunakan di Python dengan MediaPipe Framework, Anda dapat memperluas Packet API untuk jenis data baru melalui langkah-langkah berikut:
Tulis kode binding class pybind11 atau caster jenis kustom untuk jenis kustom dalam file 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(...); }
Buat pembuat paket dan metode pengambil baru dari jenis kustom dalam file cc terpisah.
#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
Tambahkan dua aturan build bazel untuk binding jenis kustom dan metode paket baru di file 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" ], )
Bangun target ekstensi pybind (dengan akhiran .so) oleh Bazel dan pindahkan library dinamis yang dihasilkan ke dalam salah satu direktori $LD_LIBRARY_PATH.
Menggunakan modul binding di 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)
Stempel waktu
Setiap paket berisi stempel waktu dalam satuan mikrodetik. Dalam Python, Packet API menyediakan metode praktis packet.at()
untuk menentukan stempel waktu numerik sebuah paket. Secara lebih umum, packet.timestamp
adalah properti class
paket untuk mengakses stempel waktu yang mendasarinya. Untuk mengonversi epoch Unix menjadi
stempel waktu MediaPipe,
Timestamp API
menawarkan metode mp.Timestamp.from_seconds()
untuk tujuan ini.
ImageFrame
ImageFrame adalah penampung untuk menyimpan gambar atau frame video. Format
yang didukung oleh ImageFrame tercantum dalam
enum ImageFormat.
Piksel dienkode baris utama dengan komponen warna yang disisipkan, dan ImageFrame
mendukung uint8, uint16, dan float sebagai jenis datanya. MediaPipe menyediakan
ImageFrame Python API
untuk mengakses class ImageFrame C++. Di Python, cara termudah untuk mengambil data piksel adalah dengan memanggil image_frame.numpy_view()
untuk mendapatkan numpy ndarray. Perhatikan
bahwa numpy ndarray yang ditampilkan, yang merupakan referensi ke data piksel internal, tidak dapat ditulis. Jika pemanggil perlu memodifikasi numpy ndarray, Anda harus
memanggil operasi penyalinan secara eksplisit untuk mendapatkan salinan. Saat MediaPipe mengambil numpy
ndarray untuk membuat ImageFrame, MediaPipe mengasumsikan bahwa data disimpan secara berurutan.
Sejalan dengan itu, data piksel ImageFrame akan disejajarkan kembali agar
berdekatan saat dikembalikan ke sisi Python.
Grafik
Pada MediaPipe Framework, semua pemrosesan dilakukan dalam konteks CalculatorGraph. CalculatorGraph Python API adalah binding langsung ke class CalculatorGraph C++. Perbedaan utamanya adalah CalculatorGraph Python API memunculkan error Python, bukan menampilkan Status non-OK saat terjadi error. Oleh karena itu, sebagai pengguna Python, Anda dapat menangani pengecualian seperti biasa. Siklus proses CalculatorGraph berisi tiga tahap: inisialisasi dan penyiapan, operasi grafik, dan penonaktifan grafik.
Melakukan inisialisasi CalculatorGraph dengan file protobuf atau protobuf biner CalculatorGraphConfig, dan menyediakan metode callback untuk mengamati aliran output.
Opsi 1. Melakukan inisialisasi CalculatorGraph dengan protobuf CalculatorGraphConfig atau representasi teksnya, dan mengamati aliran output:
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)))
Opsi 2. Melakukan inisialisasi CalculatorGraph dengan file protobuf biner, dan mengamati aliran output.
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}'))
Mulai jalankan grafik dan masukkan paket ke grafik.
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))
Tutup grafik setelah selesai. Anda dapat memulai ulang grafik untuk grafik lain yang dijalankan setelah panggilan ke
close()
.graph.close()
Skrip Python dapat dijalankan oleh runtime Python lokal Anda.