เฟรมเวิร์ก Python ของ MediaPipe ให้สิทธิ์เข้าถึงองค์ประกอบหลักของเฟรมเวิร์ก MediaPipe C++ เช่น Timestamp, Packet และ Calculator Graph โดยตรง ในขณะที่โซลูชัน Python ที่พร้อมใช้งานจะซ่อนรายละเอียดทางเทคนิคของเฟรมเวิร์กและแสดงผลการอนุมานโมเดลที่อ่านได้กลับไปยังผู้โทร
เฟรมเวิร์ก MediaPipe จะอยู่ด้านบนของไลบรารี pybind11 เฟรมเวิร์ก C++ หลักแสดงใน Python ผ่านการเชื่อมโยงภาษา C++/Python เนื้อหาด้านล่างนี้มีสมมติฐานว่าผู้อ่านมีความเข้าใจเบื้องต้นเกี่ยวกับเฟรมเวิร์ก MediaPipe C++ อยู่แล้ว หรือดูข้อมูลที่เป็นประโยชน์ในแนวคิดของเฟรมเวิร์กก็ได้
แพ็คเก็ต
แพ็กเก็ตเป็นหน่วยโฟลว์ข้อมูลพื้นฐานใน MediaPipe แพ็กเก็ตประกอบด้วยการประทับเวลาที่เป็นตัวเลขและตัวชี้ที่แชร์ไปยังเพย์โหลดที่เปลี่ยนแปลงไม่ได้ ใน Python คุณจะสร้างแพ็กเก็ต MediaPipe ได้โดยเรียกใช้เมธอดเครื่องมือสร้างแพ็กเก็ตวิธีหนึ่งในโมดูล mp.packet_creator
นอกจากนี้ คุณจะเรียกข้อมูลเพย์โหลดแพ็กเก็ตได้โดยใช้เมธอด Getter แพ็กเก็ตวิธีหนึ่งในโมดูล mp.packet_getter
โปรดทราบว่าเพย์โหลดแพ็กเก็ตจะเปลี่ยนแปลงไม่ได้หลังจากสร้างแพ็กเก็ต ดังนั้น การแก้ไขเนื้อหาแพ็กเก็ตที่ดึงมาจะไม่ส่งผลต่อเพย์โหลดจริงในแพ็กเก็ต เฟรมเวิร์ก MediaPipe Python API รองรับประเภทข้อมูลที่ใช้กันโดยทั่วไปของ MediaPipe (เช่น ImageFrame, Matrix, Protocol
Buffers และประเภทข้อมูลพื้นฐาน) ในการเชื่อมโยงหลัก ตารางที่ครอบคลุมด้านล่างแสดงการแมปประเภทระหว่างประเภทข้อมูล Python กับ C++ พร้อมด้วยผู้สร้างแพ็กเก็ตและเมธอด Getter เนื้อหาสำหรับข้อมูลแต่ละประเภทที่ API เฟรมเวิร์กของ MediaPipe Python รองรับ
ประเภทข้อมูล Python | ประเภทข้อมูล C++ | ผู้สร้างแพ็กเก็ต | ตัวรับเนื้อหา |
---|---|---|---|
bool | bool | create_bool(True) | get_bool(packet) |
int หรือ np.intc | int_t | create_int(1) | get_int(packet) |
int หรือ np.int8 | int8_t | create_int8(2**7-1) | get_int(packet) |
int หรือ np.int16 | int16_t | create_int16(2**15-1) | get_int(packet) |
int หรือ np.int32 | int32_t | create_int32(2**31-1) | get_int(packet) |
int หรือ np.int64 | int64_t | create_int64(2**63-1) | get_int(packet) |
int หรือ np.uint8 | uint8_t | create_uint8(2**8-1) | get_uint(packet) |
int หรือ np.uint16 | uint16_t | create_uint16(2**16-1) | get_uint(packet) |
int หรือ np.uint32 | uint32_t | create_uint32(2**32-1) | get_uint(packet) |
int หรือ np.uint64 | uint64_t | create_uint64(2**64-1) | get_uint(packet) |
Float หรือ np.Flo32 | float | create_float(1.1) | get_float(packet) |
Float หรือ np.double | double | create_double(1.1) | get_float(packet) |
str (UTF-8) | std::string | create_string('abc') | get_str(packet) |
ไบต์ | std::string | create_string(b'\xd0\xd0\xd0') | get_bytes(packet) |
mp.Packet | mp::แพ็กเก็ต | create_packet(p) | get_packet(packet) |
รายการ[บูลีน] | std::vector<bool> | create_bool_vector([จริง, เท็จ]) | get_bool_list(packet) |
List[int] หรือ List[np.intc] | int[] | create_int_array([1, 2, 3]) | get_int_list(แพ็กเก็ต, ขนาด=10) |
List[int] หรือ List[np.intc] | std::vector<int> | create_int_เวกเตอร์([1, 2, 3]) | get_int_list(packet) |
List[Flo] หรือ List[np.Flo] | float[] | create_Flo_arrary([0.1, 0.2]) | get_Flo_list(แพ็กเก็ต, ขนาด=10) |
List[Flo] หรือ List[np.Flo] | std::vector<float> | create_Flo_เวกเตอร์([0.1, 0.2]) | get_Flo_list(แพ็กเก็ต, ขนาด=10) |
รายการ[str] | std::vector<std::string> | สร้าง_สตริง_เวกเตอร์(['a']) | get_str_list(packet) |
รายการ [mp.Packet] | std::vector<mp::Packet> | create_packet_ector( [packet1, packet2]) |
get_packet_list(p) |
การแมป [str, แพ็กเก็ต] | std::map<std::string, pack=""></std::string,> | create_string_to_packet_map( {'a': packet1, 'b': packet2}) |
get_str_to_packet_dict(packet) |
np.ndarray (cv.mat และ PIL.Image) |
mp::ImageFrame | create_image_frame( format=ImageFormat.SRGB, data=mat) |
get_image_frame(packet) |
np.ndarray | mp::เมทริกซ์ | create_matrix(data) | get_matrix(packet) |
ข้อความ Google Proto | ข้อความ Google Proto | create_proto(proto) | get_proto(packet) |
รายการ [Proto] | std::vector<Proto> | ไม่มี | get_proto_list(packet) |
การสร้างคลาส C++ แบบกำหนดเองแล้วส่งไปใส่กราฟและเครื่องคิดเลขนั้นถือเป็นเรื่องปกติ หากต้องการอนุญาตให้ใช้คลาสที่กำหนดเองใน Python ด้วยเฟรมเวิร์ก MediaPipe คุณสามารถขยาย Packet API สำหรับประเภทข้อมูลใหม่ได้โดยทำตามขั้นตอนต่อไปนี้
เขียนรหัสการเชื่อมโยงคลาส pybind11 หรือ ตัวแคสต์ประเภทที่กำหนดเอง สำหรับประเภทที่กำหนดเองในไฟล์ 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(...); }
สร้างผู้สร้างแพ็กเก็ตและเมธอด Getter ของประเภทที่กำหนดเองในไฟล์ CC แยกต่างหาก
#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
เพิ่มกฎบิลด์แบบบาเซล 2 กฎสำหรับการเชื่อมโยงประเภทที่กำหนดเองและเมธอดแพ็กเก็ตใหม่ในไฟล์ 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" ], )
สร้างเป้าหมายส่วนขยาย Pybind (ที่มีส่วนต่อท้าย .so) โดย Bazel และย้ายไลบรารีแบบไดนามิกที่สร้างขึ้นไปอยู่ในหนึ่งในไดเรกทอรี $LD_LIBRARY_PATH
ใช้โมดูลการเชื่อมโยงใน 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)
การประทับเวลา
แต่ละแพ็กเก็ตมีการประทับเวลาในหน่วยไมโครวินาที Packet API จะมีเมธอดอำนวยความสะดวก packet.at()
ใน Python เพื่อระบุการประทับเวลาที่เป็นตัวเลขของแพ็กเก็ต โดยทั่วไปแล้ว packet.timestamp
เป็นพร็อพเพอร์ตี้คลาสแพ็กเก็ตสำหรับเข้าถึงการประทับเวลาที่มีอยู่ หากต้องการแปลง Unix Epoch เป็นการประทับเวลา MediaPipe Timetamp API จะมีเมธอด mp.Timestamp.from_seconds()
สำหรับวัตถุประสงค์นี้
ImageFrame
กรอบรูปเป็นที่เก็บรูปภาพหรือเฟรมวิดีโอ รูปแบบที่ ImageFrame รองรับจะแสดงอยู่ใน enum ของ ImageFormat
พิกเซลจะแสดงเป็นแถวหลักที่เข้ารหัสพร้อมคอมโพเนนต์สีแบบแทรกสลับ และ ImageFrame รองรับ uint8, uint16 และ Float เป็นประเภทข้อมูล MediaPipe มี ImageFrame Python API
เพื่อเข้าถึงคลาส ImageFrame C++ ใน Python วิธีที่ง่ายที่สุดในการเรียกข้อมูลพิกเซลคือการเรียกใช้ image_frame.numpy_view()
เพื่อรับ numpy ndarray โปรดทราบว่า numpy ndarray ที่แสดงผล ที่อ้างอิงไปยังข้อมูลพิกเซลภายใน จะไม่สามารถเขียนได้ หากผู้โทรต้องแก้ไข numpy ndarray จะต้องเรียกการดำเนินการคัดลอกอย่างชัดแจ้งเพื่อรับสำเนา เมื่อ MediaPipe ใช้ NUMpy ndarray เพื่อสร้าง ImageFrame จะถือว่าข้อมูลมีการจัดเก็บไว้อย่างต่อเนื่อง
ข้อมูลพิกเซลของ ImageFrame จะปรับให้สอดคล้องกันเมื่อส่งกลับไปยังด้าน Python
กราฟ
ใน MediaPipe Framework การประมวลผลทั้งหมดจะเกิดขึ้นภายในบริบทของ CalculatorGraph CalculatorGraph Python API เป็นการเชื่อมโยงโดยตรงกับคลาส C++ CalculatorGraph ความแตกต่างที่สำคัญคือ CalculatorGraph Python API จะเพิ่มข้อผิดพลาดของ Python แทนการแสดงสถานะไม่ใช่ OK เมื่อเกิดข้อผิดพลาด ดังนั้น ในฐานะผู้ใช้ Python คุณสามารถจัดการกับข้อยกเว้นได้ตามปกติ วงจรของ CalculatorGraph มี 3 ขั้นตอน ได้แก่ การเริ่มต้นและการตั้งค่า การเรียกใช้กราฟ และการปิดกราฟ
เริ่มต้น CalculatorGraph ด้วย CalculatorGraphConfig Protobuf หรือไบนารีไฟล์ Protobuf และให้วิธีเรียกกลับเพื่อดูสตรีมเอาต์พุต
วิธีที่ 1: เริ่มต้น CalculatorGraph ด้วย CalculatorGraphConfig Proxybuf หรือการนำเสนอข้อความ และสังเกตสตรีมเอาต์พุต ดังนี้
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)))
วิธีที่ 2: เริ่มต้น CalculatorGraph ด้วยไฟล์ไบนารี Protobuf และสังเกตสตรีมเอาต์พุต
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}'))
เริ่มการเรียกใช้กราฟและแพ็กเก็ตฟีดลงในกราฟ
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))
ปิดกราฟเมื่อกำหนดค่าเสร็จ คุณอาจเริ่มกราฟใหม่สำหรับกราฟอื่นที่ทำงานหลังการเรียก
close()
graph.close()
รันไทม์ของ Python ในเครื่องจะเรียกใช้สคริปต์ Python ได้