6.1. Miért tömbök¶
Az Image osztály a megfelelő eszköz a képpontmunkához, mert minden metódusa közvetlenül a kamera natív képpont-pufferén dolgozik egyetlen gyors hívásban. Annak nagy része, amit az alkalmazás egy képkockával tesz – küszöbölés, foltkeresés, AprilTag-észlelés, élszűrők – már ott él.
Amit a képkönyvtár nem tesz elérhetővé, az a numerikus munka maradéka, amelybe egy OpenMV-alkalmazás belefut:
olyan érzékelőpufferek, amelyek nem képpontok – ADC-minták, egy IMU (inerciális mérőegység) tengelyei, mikrofonhang,
a képből származtatott számok, amelyeket egyetlen beépített metódus sem ad vissza – egy hisztogramoszlop, két képkocka egyedi keveréke, egy képpontonkénti transzformáció, amelyet a katalógus nem fed le,
kis lineáris algebra – a lencsét korrigáló kalibrációs mátrix, az IMU-t fuzionáló forgatás,
jelfeldolgozási matematika – egy rezgéspuffer frekvenciatartalma, egy érzékelő kimenetére alkalmazott simítás, egy jellemzővektor, amelyet egy osztályozó bemenetként kíván.
Mindezek ugyanazt a formát igénylik: egy számokból álló puffert, amelynek minden elemére egyetlen művelet kerül alkalmazásra. Egy Python for ciklus a kézenfekvő módja a megírásának:
for i in range(len(samples)):
samples[i] = samples[i] * cal
A ciklus működik. Lassú is. A Python értelmezett nyelv, és egy Python-ciklus minden iterációja viseli az értelmező egyszeri futtatásának költségét: keresd ki a samples-t, olvasd be az i-edik elemet, szorozz, írj vissza, léptesd a ciklusszámlálót, ellenőrizd a ciklusfeltételt. Ezer érzékelőminta pufferén ezek az értelmezőköltségek tízmilliszekundumokká adódnak össze ahhoz, ami alapvetően egy gyors művelet.
Ez a többletköltség minden alkalommal harap, amikor egy szkript egy pufferhez nyúl. Egy QVGA szürkeárnyalatos képkocka 76 800 képpont; egy 100 Hz-es gyorsulásmérő másodpercenként száz háromtengelyes mintát szállít; egy mikrofon 64 ms-onként megtölt egy 1024 mintás puffert. Egy tisztán Python for ciklus ezek bármelyike felett egy néhány mikroszekundumot igénylő feladatot olyanná változtat, amely tízmilliszekundumokat vesz igénybe – és nagyjából tízszer ennyit egy kép méretű pufferen.
6.1.1. A könyvtári függvények gyorsabbak a ciklusoknál¶
A megoldás az, hogy a műveletet az egész puffer elleni egyetlen függvényhívásként fejezzük ki, ahelyett, hogy egy Python-ciklust futtatnánk az elemei felett. A numpy pontosan ez: tömbmatematika könyvtára, ahol minden művelet egy már optimalizált függvény, amely egyszer bejárja a puffert az elejétől a végéig. Az np.multiply(samples, cal) a samples minden elemét megszorozza cal-lal egyetlen híváson belül – ugyanazt az aritmetikát, amit a ciklus végzett, az iterációnkénti értelmezőköltség nélkül. Ugyanaz az 1000 elemű szorzás, amely Python-ciklusként tízmilliszekundumokat vett igénybe, numpy-hívásként tízmikroszekundumokat vesz igénybe.
Ez az az alku, amelyet a numpy mindenütt kínál: összeg, átlag, szinusz, exponenciális, mátrixszorzás, jelfeldolgozási primitívek – mindegyik egyetlen könyvtári függvény, amely egy egész pufferen dolgozik egyszerre. A csere az, hogy az adatoknak a numpy tömbtípusában kell élniük, és a műveletet az ellen a tömb ellen kell kifejezni, nem pedig egyenként az elemei ellen.
6.1.2. Miért nem felel meg egy lista¶
Egy Python list nem helyettesíthet. Egy lista bármilyen objektumkeveréket tárolhat – egész számokat, lebegőpontosokat, sztringeket, más listákat –, és egy ezt olvasó könyvtári függvénynek továbbra is minden helyet meg kell néznie, hogy kiderítse, mi van benne, és ki kell húznia az értéket, mielőtt bármilyen aritmetika történne. Ez a helyenkénti többletköltség pontosan az a költség, amelyet a Python-ciklus fizet. A listák rosszul illeszkednek a gyors tömbmatematikához.
6.1.3. Miért nem elég a bytearray sem¶
Egy bytearray a megfelelő forma – egy típusos puffer, elemenként egy bájt, mind egyetlen összefüggő blokkban. Ezt adja vissza a legtöbb bájtorientált perifériás API. Ami hiányzik belőle, az a matematika. A bytearray * 2 megismétli a puffert, ahelyett, hogy minden értéket megduplázna, és nincs értelmes jelentése a bytearray + bytearray-nek elemenként.
Az az adatszerkezet, amely egy típusos puffert elemenkénti matematikával egyesít, az ndarray. Hogy mi van a dobozban, és hogyan alakítja minden mező a gyors útvonal viselkedését, azok az alapok, amelyekre a fejezet többi része épül.