6.1. Varför arrayer¶
Klassen Image är rätt verktyg för pixelarbete eftersom varje metod på den arbetar direkt på kamerans nativa pixelbuffert i ett enda snabbt anrop. Det mesta som applikationen gör med en bildruta – tröskling, blob-sökning, AprilTag-detektering, kantfilter – finns redan där.
Vad bildbiblioteket inte exponerar är resten av det numeriska arbete som en OpenMV-applikation stöter på:
sensorbuffertar som inte är pixlar – ADC-sampel, axlar från en IMU (inertial measurement unit), mikrofonljud,
härledda tal från bilden som ingen inbyggd metod returnerar – en histogramkolumn, en anpassad blandning av två bildrutor, en per-pixel-transform som katalogen inte täcker,
liten linjär algebra – kalibreringsmatrisen som rätar ut linsen, rotationen som fuserar IMU:n,
signalbehandlingsmatematik – frekvensinnehållet i en vibrationsbuffert, utjämning tillämpad på en sensors utdata, en särdragsvektor som en klassificerare vill ha som indata.
Allt detta vill ha samma form: en buffert av tal med en operation tillämpad på varje element. En Python-for-loop är det uppenbara sättet att skriva det:
for i in range(len(samples)):
samples[i] = samples[i] * cal
Loopen fungerar. Den är också långsam. Python är ett tolkat språk, och varje iteration av en Python-loop bär kostnaden av att köra tolken en gång: slå upp samples, läsa element i, multiplicera, skriva tillbaka, stega loopräknaren, kontrollera loopvillkoret. På en buffert med tusen sensorsampel adderas dessa tolkkostnader till tiotals millisekunder för vad som i grunden är en snabb operation.
Den overheaden biter varje gång ett skript når en buffert. En QVGA-gråskalebildruta är 76 800 pixlar; en accelerometer vid 100 Hz levererar hundra treaxliga sampel per sekund; en mikrofon fyller en 1024-sampels buffert var 64:e ms. En ren Python-for-loop över någon av dessa förvandlar ett jobb som borde ta några mikrosekunder till ett som tar tiotals millisekunder – och ungefär tio gånger längre igen på en bildstor buffert.
6.1.1. Biblioteksfunktioner är snabbare än loopar¶
Lösningen är att uttrycka operationen som ett enda funktionsanrop mot hela bufferten, i stället för en Python-loop över dess element. numpy är precis det: ett bibliotek av array-matematik där varje operation är en redan optimerad funktion som vandrar bufferten en gång från början till slut. np.multiply(samples, cal) multiplicerar varje element i samples med cal inuti ett enda anrop – samma aritmetik som loopen gjorde, utan tolkkostnaden per iteration. Samma 1000-elements multiplikation som tog tiotals millisekunder som en Python-loop tar tiotals mikrosekunder som ett numpy-anrop.
Detta är den uppgörelse numpy erbjuder över hela linjen: summa, medelvärde, sin, exp, matrismultiplikation, signalbehandlingsprimitiver – var och en är en enda biblioteksfunktion som arbetar på en hel buffert på en gång. Bytet är att datan måste leva i numpys array-typ och operationen måste uttryckas mot den arrayen, inte mot dess element ett i taget.
6.1.2. Varför en lista inte duger¶
En Python-list kan inte träda in. En lista kan hålla vilken blandning av objekt som helst – heltal, flyttal, strängar, andra listor – och en biblioteksfunktion som läser den måste fortfarande titta på varje plats för att ta reda på vad som finns i den och dra ut värdet innan någon aritmetik sker. Den overheaden per plats är exakt den kostnad som Python-loopen betalar. Listor passar dåligt för snabb array-matematik.
6.1.3. Varför bytearray inte heller räcker¶
En bytearray är rätt form – en typad buffert, en byte per element, allt i ett sammanhängande block. Det är vad de flesta byteorienterade API:er för kringutrustning lämnar tillbaka. Vad den saknar är matematiken. bytearray * 2 upprepar bufferten i stället för att dubbla varje värde, och det finns ingen meningsfull innebörd för bytearray + bytearray element för element.
Den datastruktur som kombinerar en typad buffert med elementvis matematik är ndarray. Vad som finns inuti lådan och hur varje fält formar snabbvägsbeteendet är de grunder som resten av detta kapitel vilar på.