6.18. Immagini e ndarray¶
La classe Image è la superficie veloce per il lavoro sui pixel nativi della camera: ogni suo metodo opera direttamente sul frame buffer nel formato di pixel nativo della camera. numpy è la superficie numerica generica per tutto il resto. Due metodi fanno da ponte tra le due:
image.Image.to_ndarray()– copia i pixel di un’immagine in unndarray.Il costruttore di
image.Image– costruisce una nuova immagine a partire da unndarray.
Insieme permettono a un’applicazione di acquisire un frame, passarlo a numpy per una trasformazione personalizzata e poi riportare il risultato in un’immagine da visualizzare, salvare o reinserire nel resto della libreria di immagini.
6.18.1. Da immagine a ndarray¶
to_ndarray() alloca un nuovo ndarray e vi copia i dati dei pixel dell’immagine (con la mappatura dei dtype riportata sotto). Non è mai una vista sul frame buffer dell’immagine – l’array numpy possiede sempre i propri byte. La firma è to_ndarray(dtype, *, buffer=None) e la forma dell’output dipende dal formato dell’immagine:
GRAYSCALE – array 2-D, forma
(height, width).RGB565 – array 3-D, forma
(height, width, 3), piani nell’ordine R/G/B.
L’argomento dtype controlla come viene mappato ogni valore di pixel a 8 bit v:
|
elemento |
mappatura per un valore di pixel a 8 bit |
|---|---|---|
|
|
|
|
|
|
|
|
|
Esempio – visualizzare un frame in scala di grigi come matrice uint8
import csi
from ulab import numpy as np
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
img = csi0.snapshot()
a = img.to_ndarray('B') # shape (240, 320), dtype=uint8
print(a.shape, a.dtype)
print("mean brightness:", np.mean(a))
La parola chiave buffer= consente all’applicazione di riutilizzare un bytearray già allocato, così la camera non deve allocarne uno nuovo a ogni frame:
buf = bytearray(320 * 240)
while True:
img = csi0.snapshot()
a = img.to_ndarray('B', buffer=buf)
# ... process a ...
6.18.2. Da ndarray a immagine¶
Nella direzione opposta, passa il ndarray come primo argomento a image.Image. Il costruttore alloca un nuovo buffer immagine e vi copia i valori dell’array, limitati e arrotondati a 0..255
image.Image(arr, *, buffer=None, copy_to_fb=False)
Il costruttore deduce la geometria e il formato dei pixel dalla forma dell’array:
forma
(h, w)– immagineGRAYSCALE.forma
(h, w, 3)– immagineRGB565.
Il ndarray deve avere dtype float; al momento il costruttore supporta solo questo caso. I valori vengono arrotondati e limitati all’intervallo 0..255.
buffer= consente all’applicazione di fornire un bytearray già allocato per l’immagine risultante. copy_to_fb=True scrive il risultato nel frame buffer della camera, scelta corretta quando il risultato deve comparire nell’anteprima dell’IDE.
6.18.3. Andata e ritorno¶
import csi
import image
from ulab import numpy as np
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
img = csi0.snapshot()
a = img.to_ndarray('f') # work in float space
a = 255.0 * (a / 255.0) ** 0.5 # gamma correction
out = image.Image(a, copy_to_fb=True) # back to an image
6.18.4. Quando fare da ponte¶
Questo ponte è la risposta giusta quando l’applicazione necessita di un’operazione numerica generica che i metodi integrati di image non forniscono – filtri personalizzati, fusioni personalizzate, non linearità insolite – oppure quando i dati dei pixel devono essere combinati con dati non-immagine (assi della IMU, campioni audio) in un unico calcolo.
Non è la risposta giusta per l’elaborazione di pixel ad alto throughput già coperta dalla classe Image. I metodi integrati operano direttamente sul frame buffer nel formato di pixel nativo della camera e sono molto più veloci dell’espressione numpy equivalente. Ricorri al ponte per le operazioni che la libreria di immagini non fornisce già.