6.18. Images et ndarrays

La classe Image est l’interface rapide pour le traitement des pixels au format natif de la caméra : chacune de ses méthodes opère directement sur le tampon d’image dans le format de pixel natif de la caméra. numpy est l’interface numérique générique pour tout le reste. Deux méthodes font le pont entre elles :

Ensemble, ils permettent à une application de capturer une trame, de la confier à numpy pour une transformation personnalisée, puis de réinjecter le résultat dans une image afin de l’afficher, de l’enregistrer ou de la réintroduire dans le reste de la bibliothèque d’images.

6.18.1. Image vers ndarray

to_ndarray() alloue un nouveau ndarray et y copie les données de pixels de l’image (avec la correspondance de dtype ci-dessous). Il ne s’agit jamais d’une vue sur le tampon d’image – le tableau numpy possède toujours ses propres octets. La signature est to_ndarray(dtype, *, buffer=None), et la forme de sortie dépend du format de l’image :

  • GRAYSCALE – tableau 2D, de forme (height, width).

  • RGB565 – tableau 3D, de forme (height, width, 3), plans dans l’ordre R/G/B.

L’argument dtype contrôle la manière dont chaque valeur de pixel 8 bits v est convertie :

dtype

élément

correspondance pour une valeur de pixel 8 bits v

'B'

uint8

v (brut)

'b'

int8

v - 128 (recentré autour de zéro)

'f'

float32

float(v) (0.0 … 255.0)

Exemple – voir une trame en niveaux de gris comme une 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))

Le mot-clé buffer= permet à l’application de réutiliser un bytearray qu’elle a déjà alloué, afin que la caméra n’ait pas à en allouer un nouveau à chaque trame

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

6.18.2. ndarray vers image

Dans l’autre sens, passez le ndarray comme premier argument à image.Image. Le constructeur alloue un nouveau tampon d’image et y copie les valeurs du tableau, bornées et arrondies à 0..255

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

Le constructeur déduit la géométrie et le format de pixel à partir de la forme du tableau :

  • forme (h, w) – image GRAYSCALE.

  • forme (h, w, 3) – image RGB565.

Le ndarray doit avoir le dtype float ; le constructeur ne prend en charge que ce cas pour le moment. Les valeurs sont arrondies et bornées à la plage 0..255.

buffer= permet à l’application de fournir un bytearray qu’elle a déjà alloué pour l’image résultante. copy_to_fb=True écrit le résultat dans le tampon d’image de la caméra, ce qui est le bon choix lorsque le résultat doit apparaître dans l’aperçu de l’IDE.

6.18.3. Aller-retour

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. Quand faire le pont

Ce pont est la bonne réponse lorsque l’application a besoin d’une opération numérique générique que les méthodes intégrées de image ne fournissent pas – filtres personnalisés, fusions personnalisées, non-linéarités inhabituelles – ou lorsque les données de pixels doivent être combinées avec des données non liées à l’image (axes d’IMU, échantillons audio) dans un même calcul.

Ce n’est pas la bonne réponse pour le traitement de pixels à haut débit que la classe Image couvre déjà. Les méthodes intégrées opèrent directement sur le tampon d’image dans le format de pixel natif de la caméra et sont bien plus rapides que l’expression numpy équivalente. Faites appel au pont pour les opérations que la bibliothèque d’images ne fournit pas déjà.