6.1. De ce tablouri¶
Clasa Image este instrumentul potrivit pentru lucrul cu pixeli deoarece fiecare metodă a sa operează direct pe tamponul de pixeli nativ al camerei într-un singur apel rapid. Cea mai mare parte din ceea ce face aplicația cu un cadru – aplicarea pragurilor, găsirea blob-urilor, detectarea AprilTag, filtre de muchii – se află deja acolo.
Ce nu expune biblioteca de imagini este restul lucrului numeric peste care dă o aplicație OpenMV:
tampoane de la senzori care nu sunt pixeli – probe de la ADC, axe de la un IMU (unitate de măsurare inerțială), audio de la microfon,
numere derivate din imagine pe care nicio metodă încorporată nu le returnează – o coloană de histogramă, o îmbinare personalizată a două cadre, o transformare per-pixel pe care catalogul nu o acoperă,
algebră liniară de mici dimensiuni – matricea de calibrare care corectează lentila, rotația care fuzionează datele IMU,
operații matematice de procesare a semnalului – conținutul de frecvențe al unui tampon de vibrații, netezirea aplicată ieșirii unui senzor, un vector de caracteristici pe care un clasificator îl dorește ca intrare.
Toate acestea cer aceeași formă: un tampon de numere cu o operație aplicată fiecărui element. O buclă for în Python este modul evident de a o scrie:
for i in range(len(samples)):
samples[i] = samples[i] * cal
Bucla funcționează. Este de asemenea lentă. Python este un limbaj interpretat, iar fiecare iterație a unei bucle Python poartă costul rulării interpretorului o dată: căutarea samples, citirea elementului i, înmulțirea, scrierea înapoi, avansarea contorului buclei, verificarea condiției buclei. Pe un tampon de o mie de probe de la senzori, aceste costuri ale interpretorului se adună la zeci de milisecunde pentru ceea ce este în fond o operație rapidă.
Acel overhead mușcă de fiecare dată când un script ajunge la un tampon. Un cadru QVGA în tonuri de gri are 76.800 de pixeli; un accelerometru la 100 Hz livrează o sută de probe pe trei axe pe secundă; un microfon umple un tampon de 1024 de probe la fiecare 64 ms. O buclă for pur Python peste oricare dintre acestea transformă o sarcină care ar trebui să dureze câteva microsecunde în una care durează zeci de milisecunde – și de aproximativ zece ori mai mult din nou pe un tampon de mărimea unei imagini.
6.1.1. Funcțiile de bibliotecă sunt mai rapide decât buclele¶
Soluția este exprimarea operației ca un singur apel de funcție pe întregul tampon, în loc de o buclă Python peste elementele sale. numpy este exact asta: o bibliotecă de operații matematice pe tablouri unde fiecare operație este o singură funcție deja optimizată care parcurge tamponul o dată de la început până la sfârșit. np.multiply(samples, cal) înmulțește fiecare element din samples cu cal într-un singur apel – aceeași aritmetică pe care a făcut-o bucla, fără costul interpretorului per iterație. Aceeași înmulțire pe 1000 de elemente care a durat zeci de milisecunde ca buclă Python durează zeci de microsecunde ca apel numpy.
Aceasta este înțelegerea pe care o oferă numpy peste tot: sumă, medie, sin, exp, înmulțire de matrice, primitive de procesare a semnalului – fiecare este o singură funcție de bibliotecă care operează pe un întreg tampon deodată. Compromisul este că datele trebuie să existe în tipul tablou al numpy, iar operația trebuie exprimată pe acel tablou, nu pe elementele sale unul câte unul.
6.1.2. De ce nu este suficientă o listă¶
O list Python nu poate înlocui. O listă poate conține orice amestec de obiecte – numere întregi, numere în virgulă mobilă, șiruri de caractere, alte liste – iar o funcție de bibliotecă care o citește tot trebuie să se uite la fiecare slot pentru a afla ce conține și să extragă valoarea înainte ca vreo aritmetică să aibă loc. Acel overhead per slot este exact costul pe care îl plătește bucla Python. Listele se potrivesc prost pentru operații matematice rapide pe tablouri.
6.1.3. De ce nici bytearray nu este suficient¶
Un bytearray este forma potrivită – un singur tampon tipizat, un octet per element, totul într-un singur bloc contiguu. Este ceea ce returnează majoritatea API-urilor de periferice orientate pe octeți. Ce îi lipsește sunt operațiile matematice. bytearray * 2 repetă tamponul în loc să dubleze fiecare valoare, iar nu există un sens rezonabil pentru bytearray + bytearray element cu element.
Structura de date care combină un tampon tipizat cu operații matematice element cu element este ndarray. Ce se află în interiorul cutiei și cum modelează fiecare câmp comportamentul căii rapide sunt fundațiile pe care se sprijină restul acestui capitol.