6.1. Tại sao dùng mảng

Lớp Image là công cụ phù hợp cho công việc xử lý điểm ảnh vì mọi phương thức trên đó đều hoạt động trực tiếp trên buffer điểm ảnh gốc của camera trong một lần gọi nhanh. Phần lớn những gì ứng dụng làm với một khung hình -- ngưỡng hóa, tìm vùng màu (blob), phát hiện AprilTag, bộ lọc cạnh -- đã có sẵn ở đó.

Những gì thư viện ảnh không cung cấp là phần còn lại của công việc số học mà một ứng dụng OpenMV gặp phải:

  • các buffer cảm biến không phải điểm ảnh -- mẫu ADC, các trục từ IMU (đơn vị đo lường quán tính), âm thanh từ microphone,

  • các số dẫn xuất từ ảnh mà không có phương thức dựng sẵn nào trả về -- một cột biểu đồ tần suất, một hỗn hợp tùy chỉnh của hai khung hình, một phép biến đổi theo từng điểm ảnh mà danh mục không đề cập,

  • đại số tuyến tính nhỏ -- ma trận hiệu chỉnh để chỉnh sửa ống kính, phép quay để kết hợp IMU,

  • phép toán xử lý tín hiệu -- nội dung tần số của một buffer rung động, làm mịn áp dụng cho đầu ra của cảm biến, một vector đặc trưng mà bộ phân loại muốn làm đầu vào.

Tất cả những điều này đều muốn cùng dạng: một buffer số với một phép toán áp dụng cho mỗi phần tử. Một vòng lặp for Python là cách viết rõ ràng nhất:

for i in range(len(samples)):
    samples[i] = samples[i] * cal

Vòng lặp hoạt động. Nhưng nó cũng chậm. Python là ngôn ngữ thông dịch, và mỗi lần lặp của vòng lặp Python mang theo chi phí chạy trình thông dịch một lần: tra cứu samples, đọc phần tử i, nhân, ghi lại, tăng bộ đếm vòng lặp, kiểm tra điều kiện vòng lặp. Trên một buffer gồm nghìn mẫu cảm biến, những chi phí trình thông dịch đó cộng lại thành hàng chục mili giây cho một phép toán về cơ bản là nhanh.

Chi phí đó xuất hiện mỗi khi tập lệnh xử lý buffer. Một khung hình QVGA thang xám có 76.800 điểm ảnh; gia tốc kế ở 100 Hz cung cấp một trăm mẫu ba trục mỗi giây; microphone điền đầy buffer 1024 mẫu mỗi 64 ms. Một vòng lặp for Python thuần túy trên bất kỳ tập dữ liệu nào trong số đó biến một công việc đáng lẽ mất vài micro giây thành một công việc mất hàng chục mili giây -- và khoảng mười lần lâu hơn nữa trên buffer kích thước ảnh.

6.1.1. Hàm thư viện nhanh hơn vòng lặp

Giải pháp là biểu diễn phép toán như một lần gọi hàm duy nhất trên toàn bộ buffer, thay vì vòng lặp Python trên các phần tử của nó. numpy chính xác là như vậy: một thư viện toán học mảng nơi mỗi phép toán là một hàm đã được tối ưu hóa duyệt buffer một lần từ đầu đến cuối. np.multiply(samples, cal) nhân mỗi phần tử của samples với cal trong một lần gọi duy nhất -- cùng phép toán số học mà vòng lặp đã làm, không có chi phí trình thông dịch theo từng lần lặp. Cùng phép nhân 1000 phần tử mất hàng chục mili giây khi dùng vòng lặp Python chỉ mất hàng chục micro giây khi dùng lời gọi numpy.

Đây là thỏa thuận mà numpy cung cấp xuyên suốt: sum, mean, sin, exp, nhân ma trận, các hàm nguyên thủy xử lý tín hiệu -- mỗi cái là một hàm thư viện duy nhất hoạt động trên toàn bộ buffer cùng lúc. Điều kiện đánh đổi là dữ liệu phải sống trong kiểu mảng của numpy và phép toán phải được biểu diễn trên mảng đó, không phải trên từng phần tử một.

6.1.2. Tại sao list không dùng được

Một list Python không thể thay thế. Một list có thể chứa bất kỳ hỗn hợp đối tượng nào -- số nguyên, số thực, chuỗi, các list khác -- và một hàm thư viện đọc nó vẫn phải xem từng slot để tìm hiểu nó chứa gì và lấy giá trị ra trước khi bất kỳ phép toán nào xảy ra. Chi phí theo từng slot đó chính xác là chi phí mà vòng lặp Python phải trả. List không phù hợp cho toán học mảng nhanh.

6.1.3. Tại sao bytearray cũng chưa đủ

Một bytearraydạng đúng -- một buffer có kiểu, một byte mỗi phần tử, tất cả trong một khối liền mạch. Đây là những gì hầu hết các API ngoại vi hướng byte trả về. Thứ nó thiếu là phép toán. bytearray * 2 lặp lại buffer thay vì nhân đôi mỗi giá trị, và không có ý nghĩa hợp lý nào cho bytearray + bytearray theo từng phần tử.

Cấu trúc dữ liệu kết hợp buffer có kiểu với phép toán theo từng phần tử chính là ndarray. Bên trong hộp là gì và cách mỗi trường định hình hành vi con đường nhanh là nền tảng mà phần còn lại của chương này dựa vào.