5.32. Salvarea și compresia

Toate paginile de până acum au lucrat cu imagini pe cameră: capturate în buffer-ul de cadre sau alocate pe heap-ul MicroPython, manipulate prin metodele modulului image și fie afișate în previzualizarea din IDE, fie transmise unui stadiu următor din același script. Majoritatea aplicațiilor au nevoie la un moment dat să facă opusul: să ia o imagine aflată în RAM și să o plaseze undeva persistent — pe cardul SD, pe un host USB, printr-o rețea — unde altceva decât camera o poate citi.

Modulul image expune două căi pentru această operație. Calea save scrie imaginea într-un fișier pe sistemul de fișiere, cu formatul ales în funcție de extensie și detaliile de codare gestionate de metodă. Calea to-format returnează un obiect Image care conține fluxul de octeți codați, potrivit pentru a fi transmis unui apel de streaming sau rețea fără a atinge sistemul de fișiere. Fiecare se potrivește unei aplicații diferite; ambele se bazează pe același motor de compresie.

5.32.1. Salvarea într-un fișier

save() scrie imaginea pe sistemul de fișiere la o cale:

img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)

Formatul este ales din extensia fișierului. Sunt recunoscute cinci extensii: .bmp scrie un Windows bitmap (fără pierderi, fără compresie, pixelii capturați octet cu octet); .pgm scrie un portable graymap (fără pierderi, doar tonuri de gri); .ppm scrie un portable pixmap (fără pierderi, RGB); .jpg și .jpeg scriu ambele un JPEG (cu pierderi, comprimat). Imaginea destinatar trebuie să fie deja în formatul de culoare corect pentru containerul ales – o imagine color salvată ca .pgm este o eroare.

roi restricționează salvarea la un sub-dreptunghi al imaginii, la fel ca parametrul roi al celorlalte metode ale modulului image. Implicit se folosește imaginea completă. Parametrul este ignorat la salvarea unei imagini comprimate JPEG, deoarece forma de pe disc acoperă deja cadrul complet, iar recodificarea prin decupare ar anula scopul salvării octeților comprimați existenți.

quality este calitatea compresiei JPEG, de la 0 la 100, și are sens doar când ieșirea este JPEG (cuvântul-cheie este ignorat pentru formatele fără pierderi). Valoarea implicită de 50 este echilibrul potrivit pentru majoritatea aplicațiilor; 70 până la 85 este intervalul pentru o calitate vizuală mai ridicată, 30 până la 50 este intervalul potrivit pentru miniaturi mici și transmisii limitate de lățime de bandă, iar 90 și peste este rezervat cazurilor în care imaginea va fi inspectată manual sau prelucrată printr-un algoritm ulterior sensibil la artefactele de compresie.

Imaginea destinatar este returnată astfel încât apelul să poată fi înlănțuit: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). Obiectul returnat este aceeași imagine din memorie; salvarea este un efect secundar.

O utilizare tipică este tiparul capture-and-log. Un declanșator se activează (este detectat un blob, este apăsat un buton, expiră un temporizator); scriptul capturează un cadru; adaugă un marcaj temporal la numele fișierului; și apelează save() pentru a împinge imaginea pe cardul SD. Previzualizarea din IDE continuă să ruleze, următorul declanșator se activează, iar fișierele salvate se acumulează.

5.32.2. Codificarea în memorie

Când destinația nu este sistemul de fișiere, ci o conexiune de rețea, un port serial sau intrarea altui modul, aplicația are nevoie de fluxul de octeți codificat în memorie, nu pe disc. to_jpeg() și to_png() produc exact acest lucru:

encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)

Comportamentul implicit este conversia pe loc: destinatarul este convertit într-o imagine JPEG (sau PNG) și se returnează același obiect. Cu copy=True conversia scrie într-un obiect proaspăt alocat pe heap; cu copy_to_fb=True ieșirea ajunge în tamponul de cadre (frame buffer). Alegerea este aceeași pe care o oferă orice altă metodă de conversie – pe loc în mod implicit, copiere atunci când originalul este necesar ulterior.

quality și subsampling sunt aceleași butoane de reglare JPEG pe care le expune calea de salvare. subsampling alege schema de subeșantionare a crominanței: image.JPEG_SUBSAMPLING_AUTO o alege pe cea mai bună pentru calitatea aleasă, image.JPEG_SUBSAMPLING_444 păstrează crominanța la rezoluție completă (cel mai mare fișier, cea mai bună acuratețe a culorilor), image.JPEG_SUBSAMPLING_422 și image.JPEG_SUBSAMPLING_420 înjumătățesc rezoluția crominanței pe una sau ambele axe (fișiere mai mici, o ușoară atenuare a culorilor care este invizibilă la distanțele tipice de vizualizare). Valoarea implicită AUTO este alegerea potrivită, cu excepția cazului în care aplicația are o nevoie specifică.

PNG prin to_png() este fără pierderi, dar mai lent de codificat și produce fișiere mai mari decât JPEG pentru conținut fotografic (conținutul fotografic se comprimă slab sub schema de predicție a PNG). Folosiți PNG când imaginea este grafică liniară, o captură de ecran sau conține elemente grafice cu margini dure desenate peste un cadru capturat – codificarea fără pierderi păstrează muchiile clare pe care JPEG le-ar atenua. În rest, JPEG este valoarea implicită potrivită.

Atât to_jpeg(), cât și to_png() acceptă aceleași cuvinte-cheie poziționale și de scalare în stil de desenare pe care le iau celelalte metode de conversie – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – astfel încât același apel poate codifica o versiune scalată, decupată sau mapată cu paletă a sursei într-un singur pas. compress() este denumirea moștenită a to_jpeg(); cele două iau aceleași argumente și produc același rezultat.

5.32.3. Ce aduce compresia

Cifrele din spatele compromisului JPEG-versus-brut merită parcurse o dată.

Un cadru RGB565 de 320 pe 240 are 153.600 de octeți (un cadru capturat la QVGA). Un cadru de 640 pe 480 are 614.400 de octeți; un cadru de 1280 pe 960 are 2.457.600 de octeți. Niciunul dintre acestea nu este mare în comparație cu un ecran de desktop sau de telefon, dar sunt considerabile în contextul unei camere care are în total câțiva MB de RAM, un card SD cu o lățime de bandă finită de scriere și o legătură cu gazda care de obicei rulează prin USB CDC, un UART sau un modul wireless la viteze modeste.

JPEG la quality=50 comprimă de obicei un cadru fotografic capturat de 10x până la 20x: acel cadru de 640 pe 480 de 614 KB devine un flux de octeți codificat de 30 până la 60 KB. La quality=85 compresia scade la 5x până la 10x (60 până la 120 KB pentru același cadru). La quality=10 – plin de artefacte, dar încă recognoscibil – compresia ajunge la 30x până la 50x (12 până la 20 KB).

Aceste cifre determină ce este practic de făcut cu cadrele salvate. O cale către un card SD care susține 10 MB/s gestionează 30 de cadre pe secundă de conținut VGA codificat JPEG la quality=50 cu loc de rezervă (aproximativ 1 până la 2 MB/s); salvarea aceluiași conținut necomprimat necesită peste 18 MB/s, dincolo de ceea ce susține calea sistemului de fișiere al camerei către card. O gazdă USB care extrage cadre codificate JPEG prin CDC la 1 MB/s primește cadre de 30 până la 60 KB la aproximativ 15 până la 30 de cadre pe secundă; extrăgând cadre brute la aceeași rată, obține unul sau două cadre pe secundă.

Pe scurt: metodele de compresie nu sunt doar o comoditate pentru salvare. Ele sunt cele care fac cadrul capturat utilizabil în afara camerei la ratele de cadre care contează pentru aplicație. Alegerea compresiei potrivite – JPEG quality 50 pentru jurnalizare generală, 80 pentru lucru de calitate, PNG pentru capturarea graficii liniare – face parte din munca de rutină a oricărei aplicații de cameră non-trivială.