Khung MediaPipe trong Python

Khung MediaPipe Python cấp quyền truy cập trực tiếp vào các thành phần cốt lõi của khung MediaPipe C++ như Timestamp, Packet và CalculatorGraph, trong khi các giải pháp Python sẵn sàng sử dụng ẩn các chi tiết kỹ thuật của khung và chỉ cần trả về mô hình dễ đọc kết quả suy luận trở lại cho phương thức gọi.

Khung MediaPipe nằm ở trên cùng thư viện pybind11. Khung cốt lõi C++ được cung cấp trong Python thông qua liên kết ngôn ngữ C++/Python. Nội dung dưới đây giả định rằng người đọc đã có hiểu biết cơ bản về khung MediaPipe C++. Nếu không, bạn có thể tìm thấy thông tin hữu ích trong Framework Concepts (Khái niệm về khung).

Gói

Gói là đơn vị luồng dữ liệu cơ bản trong MediaPipe. Một gói dữ liệu bao gồm dấu thời gian dạng số và con trỏ dùng chung đến tải trọng không thể thay đổi. Trong Python, một Có thể tạo gói MediaPipe bằng cách gọi một trong các phương thức trình tạo gói trong thời gian mp.packet_creator . Tương ứng, tải trọng gói có thể được truy xuất bằng cách sử dụng một trong trong phương thức getter gói tin mp.packet_getter . Lưu ý rằng tải trọng gói sẽ không thể thay đổi sau gói dữ liệu sáng tạo. Do đó, việc sửa đổi nội dung gói được truy xuất không ảnh hưởng đến tải trọng thực tế trong gói. API Python khung MediaPipe hỗ trợ các loại dữ liệu MediaPipe thường dùng nhất (ví dụ: Khung hình ảnh, Ma trận, Giao thức Vùng đệm và kiểu dữ liệu gốc) trong liên kết lõi. Giải pháp toàn diện bảng dưới đây cho thấy mối liên kết kiểu giữa Python và kiểu dữ liệu C++ cùng với trình tạo gói và phương thức getter nội dung cho từng loại dữ liệu được API khung MediaPipe Python hỗ trợ.

Loại dữ liệu Python Loại dữ liệu C++ Người tạo gói tin Phương thức getter nội dung
bool bool create_bool(True) get_bool(packet)
int hoặc np.intc int_t create_int(1) get_int(packet)
int hoặc np.int8 int8_t create_int8(2**7-1) get_int(packet)
int hoặc np.int16 int16_t create_int16(2**15-1) get_int(packet)
int hoặc np.int32 int32_t create_int32(2**31-1) get_int(packet)
int hoặc np.int64 int64_t create_int64(2**63-1) get_int(packet)
int hoặc np.uint8 uint8_t create_uint8(2**8-1) get_uint(packet)
int hoặc np.uint16 uint16_t create_uint16(2**16-1) get_uint(packet)
int hoặc np.uint32 uint32_t create_uint32(2**32-1) get_uint(packet)
int hoặc np.uint64 uint64_t create_uint64(2**64-1) get_uint(packet)
float hoặc np.float32 số thực dấu phẩy động create_float(1.1) get_float(packet)
độ chính xác đơn hoặc np.double gấp đôi create_double(1.1) get_float(packet)
str (UTF-8) std::chuỗi create_string('abc') get_str(packet)
byte std::chuỗi create_string(b'\xd0\xd0\xd0') get_bytes(packet)
mp.Packet mp::Packet create_packet(p) get_packet(packet)
Danh sách[bool] std::vector<bool> create_bool_vector([Đúng, Sai]) get_bool_list(packet)
List[int] hoặc List[np.intc] int[] create_int_array([1, 2, 3]) get_int_list(gói; kích thước=10)
List[int] hoặc List[np.intc] std::vector<int> create_int_vector([1, 2, 3]) get_int_list(packet)
List[float] hoặc List[np.float] float[] create_float_arrary([0.1, 0.2]) get_float_list(gói; kích thước=10)
List[float] hoặc List[np.float] std::vector&lt;float&gt; create_float_vector([0,1; 0,2]) get_float_list(gói; kích thước=10)
Danh sách[str] std::vector<std::string> create_string_vector(['a']) get_str_list(packet)
Danh sách[mp.Packet] std::vector&lt;mp::Packet&gt; create_packet_vector(
[packet1, packageet2])
get_packet_list(p)
ánh xạ[str, Packet] std::map<std::string, packageet=""></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 và 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)
Thông báo Proto của Google Thông báo Proto của Google create_proto(proto) get_proto(packet)
Danh sách[Proto] std::vector<Proto> không áp dụng get_proto_list(packet)

Nhiều người dùng tạo các lớp C++ tuỳ chỉnh rồi gửi các lớp đó vào đồ thị và máy tính. Để cho phép sử dụng các lớp tuỳ chỉnh trong Python bằng Khung MediaPipe, bạn có thể mở rộng Packet API cho một loại dữ liệu mới trong các bước sau:

  1. Viết pybind11 mã liên kết lớp hoặc một công cụ nhập loại tuỳ chỉnh cho loại tuỳ chỉnh trong tệp 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. Tạo một trình tạo gói và phương thức getter mới thuộc loại tuỳ chỉnh trong một tệp cc riêng.

    #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. Thêm 2 quy tắc xây dựng bazel cho liên kết kiểu tuỳ chỉnh và gói mới trong tệp 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. Tạo các mục tiêu tiện ích pybind (có hậu tố .so) của Bazel và di chuyển các thư viện động đã tạo vào một trong các thư viện $LD_ từng_PATH.

  5. Sử dụng các mô-đun liên kết bằng 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)
    

Dấu thời gian

Mỗi gói chứa dấu thời gian tính bằng đơn vị micrô giây. Trong Python, Packet API cung cấp một phương thức packet.at() tiện lợi để xác định giá trị của một gói. Nhìn chung, packet.timestamp là lớp gói để truy cập vào dấu thời gian cơ bản. Để chuyển đổi thời gian bắt đầu của hệ thống Unix thành Dấu thời gian MediaPipe, API Dấu thời gian cung cấp một phương thức mp.Timestamp.from_seconds() cho mục đích này.

ImageFrame

ImageFrame là vùng chứa để lưu trữ hình ảnh hoặc khung video. Định dạng được hỗ trợ bởi ImageFrame được liệt kê trong enum ImageFormat. Các pixel được mã hoá hàng chính với các thành phần màu xen kẽ và ImageFrame hỗ trợ uint8, uint16 và float làm loại dữ liệu. MediaPipe cung cấp API ImageFrame Python để truy cập lớp ImageFrame C++. Trong Python, cách dễ nhất để truy xuất dữ liệu pixel là gọi image_frame.numpy_view() để lấy một ndarray numpy. Ghi chú rằng numpy ndarray được trả về, tham chiếu đến dữ liệu pixel nội bộ, là không thể ghi. Nếu phương thức gọi cần sửa đổi numpy ndarray, thì bắt buộc phải gọi rõ ràng thao tác sao chép để lấy bản sao. Khi MediaPipe nhận một số ndarray để tạo ImageFrame, giả định rằng dữ liệu được lưu trữ liền kề. Tương ứng, dữ liệu pixel của ImageFrame sẽ được căn chỉnh lại để tiếp giáp khi được trả về phía Python.

Biểu đồ

Trong Khung MediaPipe, tất cả quá trình xử lý đều diễn ra trong bối cảnh CalculatorGraph. API CalculatorGraph Python là liên kết trực tiếp với lớp C++ CalculatorGraph. Điểm khác biệt chính là API CalculatorGraph Python phát sinh lỗi Python thay vì trả về một Trạng thái không OK khi xảy ra lỗi. Do đó, là người dùng Python, bạn có thể xử lý như bạn thường làm. Vòng đời của CalculatorGraph chứa ba giai đoạn: khởi tạo và thiết lập, chạy biểu đồ và tắt biểu đồ.

  1. Khởi chạy CalculatorGraph có protobuf hoặc tệp nhị phân CalculatorGraphConfig tệp protobuf và cung cấp(các) phương thức gọi lại để quan sát kết quả luồng.

    Tùy chọn 1. Khởi chạy CalculatorGraph bằng một protobuf CalculatorGraphConfig hoặc phần trình bày văn bản của văn bản đó và quan sát(các) luồng đầu ra:

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

    Tùy chọn 2. Khởi chạy CalculatorGraph có tệp protobuf nhị phân, và quan sát(các) luồng đầu ra.

    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. Bắt đầu chạy biểu đồ và đưa các gói vào biểu đồ.

    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. Đóng biểu đồ sau khi hoàn tất. Bạn có thể chạy lại biểu đồ cho một đồ thị khác chạy sau lệnh gọi đến close().

    graph.close()
    

Tập lệnh Python có thể được chạy bởi môi trường thời gian chạy Python cục bộ của bạn.