6.1. Proč pole¶
Třída Image je správným nástrojem pro práci s pixely, protože každá její metoda pracuje přímo s nativním pixelovým bufferem kamery v jediném rychlém volání. Většina toho, co aplikace se snímkem dělá – prahování, hledání blobů, detekce AprilTag, hranové filtry – už tam je.
Co knihovna pro obraz neposkytuje, je zbytek numerické práce, na kterou aplikace OpenMV narazí:
buffery senzorů, které nejsou pixely – vzorky z ADC, osy z IMU (inerciální měřicí jednotka), zvuk z mikrofonu,
odvozená čísla z obrazu, která žádná vestavěná metoda nevrací – sloupec histogramu, vlastní smíchání dvou snímků, transformace na pixel, kterou katalog nepokrývá,
drobná lineární algebra – kalibrační matice, která koriguje objektiv, rotace, jež fúzuje IMU,
matematika zpracování signálu – frekvenční obsah bufferu vibrací, vyhlazení aplikované na výstup senzoru, vektor příznaků, který chce klasifikátor jako vstup.
Všechny tyto chtějí stejnou formu: buffer čísel s jednou operací aplikovanou na každý prvek. Python smyčka for je zjevný způsob, jak to napsat:
for i in range(len(samples)):
samples[i] = samples[i] * cal
Smyčka funguje. Je také pomalá. Python je interpretovaný jazyk a každá iterace Python smyčky nese náklady na jeden běh interpretu: vyhledat samples, přečíst prvek i, vynásobit, zapsat zpět, posunout čítač smyčky, zkontrolovat podmínku smyčky. Na bufferu tisíce vzorků ze senzoru se tyto náklady na interpret nasčítají na desítky milisekund za něco, co je v zásadě rychlá operace.
Tato režie zatěžuje pokaždé, když skript sáhne na buffer. Snímek QVGA ve stupních šedi má 76 800 pixelů; akcelerometr při 100 Hz dodá sto tříosých vzorků za sekundu; mikrofon naplní 1024prvkový buffer každých 64 ms. Čistě Python smyčka for nad kterýmkoli z nich promění úlohu, která by měla trvat několik mikrosekund, v úlohu trvající desítky milisekund – a zhruba desetkrát déle u bufferu velikosti obrazu.
6.1.1. Funkce knihovny jsou rychlejší než smyčky¶
Řešením je vyjádřit operaci jako jediné volání funkce nad celým bufferem, místo Python smyčky nad jeho prvky. numpy je přesně tím: knihovnou maticové matematiky, kde každá operace je jednou již optimalizovanou funkcí, která projde buffer jednou od začátku do konce. np.multiply(samples, cal) vynásobí každý prvek samples hodnotou cal uvnitř jediného volání – tatáž aritmetika, jakou dělala smyčka, bez nákladů na interpret v každé iteraci. Totéž násobení 1000 prvků, které trvalo jako Python smyčka desítky milisekund, trvá jako volání numpy desítky mikrosekund.
Toto je dohoda, kterou numpy nabízí napříč celou škálou: součet, průměr, sinus, exponenciála, maticové násobení, primitiva pro zpracování signálu – každé z nich je jedinou funkcí knihovny, která pracuje s celým bufferem najednou. Cenou je, že data musí žít v poli typu numpy a operace musí být vyjádřena vůči tomuto poli, nikoli vůči jeho prvkům jednomu po druhém.
6.1.2. Proč seznam nestačí¶
Python list nemůže zaskočit. Seznam může obsahovat libovolnou směsici objektů – celá čísla, čísla s plovoucí desetinnou čárkou, řetězce, jiné seznamy – a funkce knihovny, která jej čte, se stejně musí podívat na každý slot, aby zjistila, co v něm je, a vytáhnout hodnotu ven, než dojde k jakékoli aritmetice. Tato režie na slot je přesně tím nákladem, který platí Python smyčka. Seznamy se pro rychlou maticovou matematiku nehodí.
6.1.3. Proč nestačí ani bytearray¶
bytearray má správnou formu – jeden typovaný buffer, jeden bajt na prvek, vše v jednom souvislém bloku. Je tím, co většina bajtově orientovaných API periferií vrací. Co mu chybí, je matematika. bytearray * 2 buffer zopakuje, místo aby zdvojnásobil každou hodnotu, a bytearray + bytearray po prvcích nemá žádný rozumný význam.
Datovou strukturou, která kombinuje typovaný buffer s matematikou po prvcích, je ndarray. Co je uvnitř krabice a jak každé pole utváří chování rychlé cesty, jsou základy, na nichž stojí zbytek této kapitoly.