6.1. Dlaczego tablice¶
Klasa Image jest właściwym narzędziem do pracy z pikselami, ponieważ każda jej metoda działa bezpośrednio na natywnym buforze pikseli kamery w jednym szybkim wywołaniu. Większość tego, co aplikacja robi z ramką – progowanie, wykrywanie plam (blob), detekcja AprilTag, filtry krawędziowe – już się tam znajduje.
Czego biblioteka obrazów nie udostępnia, to reszta pracy numerycznej, z którą styka się aplikacja OpenMV:
bufory sensorów, które nie są pikselami – próbki z ADC, osie z IMU (jednostki pomiarów inercyjnych), dźwięk z mikrofonu,
liczby pochodne z obrazu, których nie zwraca żadna wbudowana metoda – kolumna histogramu, niestandardowe zmieszanie dwóch ramek, transformacja per-piksel nieujęta w katalogu,
małą algebrę liniową – macierz kalibracji prostująca obiektyw, rotacja scalająca dane z IMU,
matematykę przetwarzania sygnałów – zawartość częstotliwościowa bufora drgań, wygładzanie zastosowane do wyjścia sensora, wektor cech, którego klasyfikator oczekuje jako wejścia.
Wszystkie te przypadki wymagają tej samej formy: bufora liczb z jedną operacją zastosowaną do każdego elementu. Pythonowa pętla for to oczywisty sposób, by to napisać:
for i in range(len(samples)):
samples[i] = samples[i] * cal
Pętla działa. Jest też wolna. Python jest językiem interpretowanym, a każda iteracja pętli Pythona niesie koszt jednorazowego uruchomienia interpretera: wyszukanie samples, odczyt elementu i, mnożenie, zapis z powrotem, zwiększenie licznika pętli, sprawdzenie warunku pętli. Dla bufora tysiąca próbek z sensora te koszty interpretera sumują się do dziesiątek milisekund na to, co jest w gruncie rzeczy szybką operacją.
Ten narzut uderza za każdym razem, gdy skrypt sięga do bufora. Ramka QVGA w skali szarości to 76 800 pikseli; akcelerometr przy 100 Hz dostarcza sto trójosiowych próbek na sekundę; mikrofon co 64 ms zapełnia bufor 1024 próbek. Czysto pythonowa pętla for po którymkolwiek z nich zamienia zadanie, które powinno zająć kilka mikrosekund, w takie, które zajmuje dziesiątki milisekund – a na buforze wielkości obrazu znów mniej więcej dziesięciokrotnie dłużej.
6.1.1. Funkcje biblioteczne są szybsze niż pętle¶
Rozwiązaniem jest wyrażenie operacji jako pojedynczego wywołania funkcji na całym buforze, zamiast pętli Pythona po jego elementach. numpy jest dokładnie tym: biblioteką matematyki tablic, gdzie każda operacja to jedna, już zoptymalizowana funkcja, która raz przechodzi bufor od początku do końca. np.multiply(samples, cal) mnoży każdy element samples przez cal w obrębie jednego wywołania – ta sama arytmetyka, którą wykonywała pętla, bez kosztu interpretera na iterację. To samo mnożenie 1000 elementów, które jako pętla Pythona zajmowało dziesiątki milisekund, jako wywołanie numpy zajmuje dziesiątki mikrosekund.
Taką umowę numpy oferuje na całej linii: suma, średnia, sin, exp, mnożenie macierzy, prymitywy przetwarzania sygnałów – każde z nich to pojedyncza funkcja biblioteczna działająca na całym buforze naraz. Kompromis jest taki, że dane muszą znajdować się w typie tablicowym numpy, a operacja musi być wyrażona wobec tej tablicy, a nie wobec jej elementów po kolei.
6.1.2. Dlaczego lista się nie nadaje¶
Pythonowa list nie może zastąpić tablicy. Lista może przechowywać dowolną mieszankę obiektów – liczby całkowite, zmiennoprzecinkowe, łańcuchy, inne listy – a funkcja biblioteczna, która ją odczytuje, wciąż musi zajrzeć do każdego gniazda, aby dowiedzieć się, co w nim jest, i wydobyć wartość, zanim dojdzie do jakiejkolwiek arytmetyki. Ten narzut na gniazdo to dokładnie ten sam koszt, który płaci pętla Pythona. Listy źle pasują do szybkiej matematyki tablic.
6.1.3. Dlaczego bytearray również nie wystarcza¶
bytearray jest właściwą formą – jeden typowany bufor, jeden bajt na element, wszystko w jednym ciągłym bloku. To właśnie zwraca większość bajtowo zorientowanych API urządzeń peryferyjnych. Brakuje mu natomiast matematyki. bytearray * 2 powtarza bufor, zamiast podwoić każdą wartość, a element po elemencie bytearray + bytearray nie ma sensownego znaczenia.
Strukturą danych, która łączy typowany bufor z matematyką element po elemencie, jest ndarray. Co znajduje się w pudełku i jak każde pole kształtuje zachowanie szybkiej ścieżki, to fundamenty, na których opiera się reszta tego rozdziału.