6.1. Waarom arrays¶
De Image-klasse is het juiste gereedschap voor pixelwerk omdat elke methode erop rechtstreeks op de native pixelbuffer van de camera werkt in een enkele snelle aanroep. Het meeste van wat de applicatie met een frame doet – thresholding, blobs zoeken, AprilTag-detectie, randfilters – bestaat daar al.
Wat de afbeeldingsbibliotheek niet blootstelt is de rest van het numerieke werk waar een OpenMV-applicatie tegenaan loopt:
sensorbuffers die geen pixels zijn – ADC-samples, assen van een IMU (inertial measurement unit), microfoonaudio,
afgeleide getallen uit de afbeelding die geen enkele ingebouwde methode teruggeeft – een histogramkolom, een aangepaste mix van twee frames, een transformatie per pixel die de catalogus niet dekt,
kleine lineaire algebra – de kalibratiematrix die de lens rectificeert, de rotatie die de IMU fuseert,
signaalverwerkingswiskunde – de frequentie-inhoud van een trillingsbuffer, smoothing toegepast op de uitvoer van een sensor, een kenmerkvector die een classificator als invoer wil.
Al deze willen dezelfde vorm: een buffer van getallen met één bewerking toegepast op elk element. Een Python-for-lus is de voor de hand liggende manier om het te schrijven:
for i in range(len(samples)):
samples[i] = samples[i] * cal
De lus werkt. Hij is ook traag. Python is een geïnterpreteerde taal, en elke iteratie van een Python-lus draagt de kost van het eenmaal draaien van de interpreter: samples opzoeken, element i lezen, vermenigvuldigen, terugschrijven, de luscounter ophogen, de lusconditie controleren. Op een buffer van duizend sensorsamples tellen die interpreterkosten op tot tientallen milliseconden voor wat in wezen een snelle bewerking is.
Die overhead bijt elke keer dat een script een buffer bereikt. Een QVGA-grijswaardenframe is 76.800 pixels; een versnellingsmeter op 100 Hz levert honderd drie-assige samples per seconde; een microfoon vult elke 64 ms een buffer van 1024 samples. Een pure-Python-for-lus over een van deze verandert een taak die een paar microseconden zou moeten duren in een die tientallen milliseconden kost – en ruwweg tienmaal zo lang weer op een buffer ter grootte van een afbeelding.
6.1.1. Bibliotheekfuncties zijn sneller dan lussen¶
De oplossing is om de bewerking uit te drukken als een enkele functieaanroep tegen de hele buffer, in plaats van een Python-lus over de elementen. numpy is precies dat: een bibliotheek van array-wiskunde waarin elke bewerking één reeds geoptimaliseerde functie is die de buffer eenmaal van begin tot eind doorloopt. np.multiply(samples, cal) vermenigvuldigt elk element van samples met cal binnen een enkele aanroep – dezelfde rekenkunde die de lus deed, zonder de interpreterkost per iteratie. Dezelfde vermenigvuldiging van 1000 elementen die als Python-lus tientallen milliseconden kostte, kost tientallen microseconden als numpy-aanroep.
Dit is de afspraak die numpy over de hele linie biedt: som, gemiddelde, sin, exp, matrixvermenigvuldiging, signaalverwerkingsprimitieven – elk is een enkele bibliotheekfunctie die in één keer op een hele buffer werkt. De ruil is dat de data in numpy’s array-type moet leven en de bewerking tegen die array moet worden uitgedrukt, niet tegen de elementen ervan één voor één.
6.1.2. Waarom een lijst niet voldoet¶
Een Python-list kan niet inspringen. Een lijst kan elke mix van objecten bevatten – gehele getallen, floats, strings, andere lijsten – en een bibliotheekfunctie die hem leest moet nog steeds naar elk slot kijken om uit te vinden wat erin zit en de waarde eruit halen voordat er rekenkunde plaatsvindt. Die overhead per slot is precies de kost die de Python-lus betaalt. Lijsten passen niet bij snelle array-wiskunde.
6.1.3. Waarom bytearray ook niet genoeg is¶
Een bytearray is de juiste vorm – één getypeerde buffer, één byte per element, alles in één aaneengesloten blok. Het is wat de meeste byte-georiënteerde randapparaat-API’s teruggeven. Wat het mist is de wiskunde. bytearray * 2 herhaalt de buffer in plaats van elke waarde te verdubbelen, en er is geen zinnige betekenis voor bytearray + bytearray element voor element.
De datastructuur die een getypeerde buffer combineert met elementsgewijze wiskunde is de ndarray. Wat er in de doos zit en hoe elk veld het snelle-pad-gedrag vormt zijn de funderingen waarop de rest van dit hoofdstuk rust.