6.18. Imágenes y ndarrays

La clase Image es la superficie rápida para el trabajo con píxeles en el formato nativo de la cámara: cada método de esta opera directamente sobre el búfer de fotogramas (frame buffer) en el formato de píxel nativo de la cámara. numpy es la superficie numérica genérica para todo lo demás. Dos métodos las conectan:

Juntos permiten que una aplicación tome una captura, la entregue a numpy para una transformación personalizada y luego coloque el resultado de nuevo en una imagen para mostrarla, guardarla o reincorporarla al resto de la biblioteca de imágenes.

6.18.1. De imagen a ndarray

to_ndarray() reserva un nuevo ndarray y copia en él los datos de píxeles de la imagen (con el mapeo de dtype que se muestra a continuación). Nunca es una vista del búfer de fotogramas de la imagen: el array de numpy siempre posee sus propios bytes. La firma es to_ndarray(dtype, *, buffer=None) y la forma de la salida depende del formato de la imagen:

  • GRAYSCALE – array 2-D, forma (height, width).

  • RGB565 – array 3-D, forma (height, width, 3), planos en orden R/G/B.

El argumento dtype controla cómo se mapea cada valor de píxel de 8 bits v:

dtype

elemento

mapeo para un valor de píxel de 8 bits v

'B'

uint8

v (en bruto)

'b'

int8

v - 128 (recentrado alrededor de cero)

'f'

float32

float(v) (0.0 … 255.0)

Ejemplo – ver un fotograma en escala de grises como una matriz 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 palabra clave buffer= permite que la aplicación reutilice un bytearray que ya reservó, de modo que la cámara no tenga que reservar uno nuevo en cada fotograma:

buf = bytearray(320 * 240)
while True:
    img = csi0.snapshot()
    a   = img.to_ndarray('B', buffer=buf)
    # ... process a ...

6.18.2. De ndarray a imagen

En sentido inverso, pasa el ndarray como primer argumento a image.Image. El constructor reserva un nuevo búfer de imagen y copia en él los valores del array, acotados y redondeados a 0..255:

image.Image(arr, *, buffer=None, copy_to_fb=False)

El constructor infiere la geometría y el formato de píxel a partir de la forma del array:

  • forma (h, w) – imagen GRAYSCALE.

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

El ndarray debe tener dtype float; el constructor solo admite ese caso por ahora. Los valores se redondean y se acotan al rango 0..255.

buffer= permite que la aplicación proporcione un bytearray que ya reservó para la imagen resultante. copy_to_fb=True escribe el resultado en el búfer de fotogramas de la cámara, que es la opción correcta cuando el resultado debe aparecer en la vista previa del IDE.

6.18.3. Ida y vuelta

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. Cuándo usar el puente

Este puente es la respuesta adecuada cuando la aplicación necesita una operación numérica genérica que los métodos integrados de image no proporcionan – filtros personalizados, mezclas personalizadas, no linealidades inusuales – o cuando los datos de píxeles deben combinarse con datos que no son de imagen (ejes de la IMU, muestras de audio) en un único cálculo.

No es la respuesta adecuada para el procesamiento de píxeles de alto rendimiento que la clase Image ya cubre. Los métodos integrados operan directamente sobre el búfer de fotogramas en el formato de píxel nativo de la cámara y son mucho más rápidos que la expresión equivalente de numpy. Recurre al puente para las operaciones que la biblioteca de imágenes no proporciona ya.