5.32. Enregistrement et compression

Jusqu’à présent, chaque page a manipulé des images sur la caméra : capturées dans le tampon d’image ou allouées sur le tas MicroPython, transformées au moyen des méthodes du module image, puis affichées dans l’aperçu de l’IDE ou transmises à une étape ultérieure du même script. La plupart des applications doivent à un moment faire l’inverse : prendre une image qui se trouve actuellement en RAM et la placer quelque part de manière persistante – sur la carte SD, sur un hôte USB, à travers un réseau – où quelque chose d’autre que la caméra peut la lire.

Le module image expose deux voies pour cette tâche. La voie enregistrement écrit l’image dans un fichier du système de fichiers, le format de fichier étant choisi d’après l’extension et les détails d’encodage étant gérés par la méthode. La voie conversion en format renvoie un objet Image contenant le flux d’octets encodé, prêt à être transmis à un appel de diffusion en continu ou de réseau sans jamais toucher au système de fichiers. Chacune convient à une application différente ; toutes deux reposent sur le même moteur de compression sous-jacent.

5.32.1. Enregistrement dans un fichier

save() écrit l’image dans le système de fichiers à un chemin donné :

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

Le format est déterminé d’après l’extension du fichier. Cinq extensions sont reconnues : .bmp écrit un bitmap Windows (sans perte, sans compression, les pixels capturés octet pour octet) ; .pgm écrit un portable graymap (sans perte, niveaux de gris uniquement) ; .ppm écrit un portable pixmap (sans perte, RGB) ; .jpg et .jpeg écrivent tous deux un JPEG (avec perte, compressé). L’image réceptrice doit déjà être dans le bon format de couleur pour le conteneur choisi – une image couleur enregistrée en .pgm est une erreur.

roi restreint l’enregistrement à un sous-rectangle de l’image, à la manière du mot-clé roi de toutes les autres méthodes du module image. L’image complète est la valeur par défaut. Ce mot-clé est ignoré lors de l’enregistrement d’une image compressée en JPEG, car la forme stockée sur disque couvre déjà la trame entière et un réencodage à travers un recadrage irait à l’encontre de l’intérêt d’enregistrer les octets compressés existants.

quality est la qualité de compression JPEG de 0 à 100 et n’a de sens que lorsque la sortie est en JPEG (le mot-clé est ignoré pour les formats sans perte). La valeur par défaut de 50 constitue le bon équilibre pour la plupart des applications ; la plage 70 à 85 correspond à une qualité visuelle supérieure, 30 à 50 à des petites vignettes et à une transmission limitée en bande passante, et 90 et au-delà est réservée aux cas où l’image sera inspectée manuellement ou traitée par un algorithme en aval sensible aux artefacts de compression.

L’image réceptrice est renvoyée pour permettre le chaînage d’appels : img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). L’objet renvoyé est la même image en mémoire ; l’enregistrement est un effet de bord.

Un usage typique est le motif capture-et-journalisation. Un déclencheur se produit (un blob est détecté, un bouton est pressé, un minuteur arrive à échéance) ; le script capture une trame ; il ajoute un horodatage au nom de fichier ; et il appelle save() pour envoyer l’image vers la carte SD. L’aperçu de l’IDE continue de fonctionner, le déclencheur suivant se produit, et les fichiers enregistrés s’accumulent.

5.32.2. Encodage en mémoire

Lorsque la destination n’est pas le système de fichiers mais une connexion réseau, un port série ou l’entrée d’un autre module, l’application a besoin du flux d’octets encodé en mémoire plutôt que sur disque. to_jpeg() et to_png() produisent exactement cela :

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

Le comportement par défaut est une conversion sur place : l’image réceptrice est convertie en image JPEG (ou PNG) et le même objet est renvoyé. Avec copy=True, la conversion écrit dans un objet du tas nouvellement alloué ; avec copy_to_fb=True, la sortie atterrit dans le tampon d’image. Le choix est le même que celui qu’offre toute autre méthode de conversion – sur place par défaut, copie lorsque l’original est nécessaire par la suite.

quality et subsampling sont les mêmes réglages JPEG que ceux exposés par la voie d’enregistrement. subsampling choisit le schéma de sous-échantillonnage de la chrominance : image.JPEG_SUBSAMPLING_AUTO sélectionne le meilleur pour la qualité choisie, image.JPEG_SUBSAMPLING_444 conserve la chrominance à pleine résolution (fichier le plus volumineux, meilleure fidélité des couleurs), image.JPEG_SUBSAMPLING_422 et image.JPEG_SUBSAMPLING_420 réduisent de moitié la résolution de chrominance sur un axe ou sur les deux (fichiers plus petits, léger adoucissement des couleurs invisible aux distances de visionnage habituelles). La valeur par défaut de AUTO est le bon choix sauf si l’application a un besoin spécifique.

Le PNG via to_png() est sans perte mais plus lent à encoder et produit des fichiers plus volumineux que le JPEG pour du contenu photographique (le contenu photographique se compresse mal sous le schéma de prédiction du PNG). Utilisez le PNG lorsque l’image est un dessin au trait, une capture d’écran, ou contient des graphiques aux contours nets dessinés par-dessus une trame capturée – l’encodage sans perte préserve les bords nets que le JPEG adoucirait. Sinon, le JPEG est la bonne valeur par défaut.

to_jpeg() et to_png() acceptent tous deux les mêmes mots-clés positionnels et d’échelle de style dessin que les autres méthodes de conversion – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – de sorte que le même appel peut encoder une version mise à l’échelle, recadrée ou mappée par palette de la source en une seule étape. compress() est l’ancienne écriture de to_jpeg() ; les deux prennent les mêmes arguments et produisent le même résultat.

5.32.3. Ce qu’apporte la compression

Les chiffres derrière le compromis JPEG-contre-brut méritent d’être détaillés une fois.

Une trame RGB565 de 320 sur 240 fait 153 600 octets (une trame capturée en QVGA). Une trame de 640 sur 480 fait 614 400 octets ; une trame de 1280 sur 960 fait 2 457 600 octets. Aucune de ces tailles n’est importante comparée à un écran d’ordinateur ou de téléphone, mais elles sont considérables dans le contexte d’une caméra disposant de quelques Mo de RAM au total, d’une carte SD à la bande passante d’écriture limitée, et d’une liaison hôte fonctionnant généralement sur USB CDC, sur une UART ou sur un module sans fil à des vitesses modestes.

À quality=50, le JPEG compresse généralement une trame photographique capturée d’un facteur 10 à 20 : cette trame de 614 Ko en 640 sur 480 devient un flux d’octets encodé de 30 à 60 Ko. À quality=85, la compression tombe à un facteur 5 à 10 (60 à 120 Ko pour la même trame). À quality=10 – chargée d’artefacts mais encore reconnaissable – la compression atteint un facteur 30 à 50 (12 à 20 Ko).

Ces chiffres déterminent ce qu’il est réaliste de faire avec les trames enregistrées. Une voie vers une carte SD soutenant 10 Mo/s gère 30 trames par seconde de contenu VGA encodé en JPEG à quality=50 avec de la marge (environ 1 à 2 Mo/s) ; enregistrer le même contenu sans compression exige plus de 18 Mo/s, au-delà de ce que la voie du système de fichiers de la caméra soutient vers la carte. Un hôte USB tirant des trames encodées en JPEG via CDC à 1 Mo/s reçoit des trames de 30 à 60 Ko à environ 15 à 30 trames par seconde ; en tirant des trames brutes au même débit, il en obtient une ou deux par seconde.

En bref : les méthodes de compression ne sont pas qu’une commodité pour l’enregistrement. Ce sont elles qui rendent la trame capturée utilisable en dehors de la caméra aux cadences qui importent à l’application. Choisir la bonne compression – JPEG qualité 50 pour la journalisation générale, 80 pour un travail de qualité, PNG pour la capture de dessins au trait – fait partie du travail courant de toute application de caméra non triviale.