Framework MediaPipe di Python

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:

  1. 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(...);
    }
    
  2. 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
    
  3. 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"
        ],
    )
    
  4. Bangun target ekstensi pybind (dengan akhiran .so) oleh Bazel dan pindahkan library dinamis yang dihasilkan ke dalam salah satu direktori $LD_LIBRARY_PATH.

  5. 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.

  1. 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}'))
    
  2. 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))
    
  3. 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.