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:

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:

dtype

elemento

mappatura per un valore di pixel a 8 bit v

'B'

uint8

v (grezzo)

'b'

int8

v - 128 (ricentrato intorno allo zero)

'f'

float32

float(v) (0.0 … 255.0)

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) – immagine GRAYSCALE.

  • forma (h, w, 3) – immagine RGB565.

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à.