5.32. Guardado y compresión

Hasta ahora, todas las páginas han trabajado con imágenes en la cámara: capturadas en el búfer de fotogramas (frame buffer) o asignadas en el montón (heap) de MicroPython, manipuladas mediante los métodos del módulo image, y bien mostradas en la vista previa del IDE o bien introducidas en una etapa posterior dentro del mismo script. La mayoría de las aplicaciones necesitan en algún momento hacer lo contrario: tomar una imagen que está actualmente en la RAM y ponerla en algún lugar persistente – en la tarjeta SD, en un host USB, a través de una red – donde algo distinto de la cámara pueda leerla.

El módulo image expone dos vías para esa tarea. La vía de guardado escribe la imagen en un archivo del sistema de archivos, con el formato de archivo elegido por la extensión y los detalles de codificación gestionados por el método. La vía de conversión a formato devuelve un objeto Image que contiene el flujo de bytes codificado, adecuado para entregarlo a una llamada de transmisión o de red sin tocar nunca el sistema de archivos. Cada una se adapta a una aplicación distinta; ambas se basan en el mismo motor de compresión subyacente.

5.32.1. Guardar en un archivo

save() escribe la imagen en el sistema de archivos en una ruta:

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

El formato se elige a partir de la extensión del archivo. Se reconocen cinco extensiones: .bmp escribe un mapa de bits de Windows (sin pérdidas, sin compresión, los píxeles capturados byte por byte); .pgm escribe un portable graymap (sin pérdidas, solo escala de grises); .ppm escribe un portable pixmap (sin pérdidas, RGB); .jpg y .jpeg ambos escriben un JPEG (con pérdidas, comprimido). La imagen receptora ya debe estar en el formato de color correcto para el contenedor elegido – guardar una imagen en color como .pgm es un error.

roi restringe el guardado a un subrectángulo de la imagen, de la misma manera que lo hace la palabra clave roi de todos los demás métodos del módulo image. El valor por defecto es la imagen completa. La palabra clave se ignora al guardar una imagen comprimida en JPEG, porque la forma en disco ya cubre el fotograma completo y volver a codificar a través de un recorte echaría por tierra el sentido de guardar los bytes comprimidos existentes.

quality es la calidad de compresión JPEG de 0 a 100 y solo tiene sentido cuando la salida es JPEG (la palabra clave se ignora para los formatos sin pérdidas). El valor por defecto de 50 es el equilibrio adecuado para la mayoría de las aplicaciones; de 70 a 85 es la franja para una mayor calidad visual, de 30 a 50 es el rango adecuado para miniaturas pequeñas y transmisión con ancho de banda limitado, y de 90 en adelante se reserva para casos en los que la imagen se inspeccionará manualmente o se procesará mediante un algoritmo posterior sensible a los artefactos de compresión.

La imagen receptora se devuelve para encadenar la llamada: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). El objeto devuelto es la misma imagen en memoria; el guardado es un efecto secundario.

Un uso típico es el patrón de captura y registro. Se dispara un evento (se detecta una mancha (blob), se pulsa un botón, vence un temporizador); el script captura un fotograma; añade una marca de tiempo al nombre del archivo; y llama a save() para enviar la imagen a la tarjeta SD. La vista previa del IDE sigue funcionando, se dispara el siguiente evento, y los archivos guardados se van acumulando.

5.32.2. Codificar en memoria

Cuando el destino no es el sistema de archivos, sino una conexión de red, un puerto serie o la entrada de otro módulo, la aplicación necesita el flujo de bytes codificado en memoria en lugar de en disco. to_jpeg() y to_png() producen exactamente eso:

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

El comportamiento por defecto es la conversión in situ: el receptor se convierte en una imagen JPEG (o PNG) y se devuelve el mismo objeto. Con copy=True la conversión escribe en un objeto del montón (heap) recién asignado; con copy_to_fb=True la salida cae en el búfer de fotogramas (frame buffer). La elección es la misma que ofrece cualquier otro método de conversión – in situ por defecto, copia cuando se necesita el original después.

quality y subsampling son las mismas perillas de ajuste de JPEG que expone la vía de guardado. subsampling elige el esquema de submuestreo de croma: image.JPEG_SUBSAMPLING_AUTO elige el mejor para la calidad escogida, image.JPEG_SUBSAMPLING_444 mantiene el croma a resolución completa (archivo más grande, mejor precisión de color), image.JPEG_SUBSAMPLING_422 y image.JPEG_SUBSAMPLING_420 reducen a la mitad la resolución de croma en uno o ambos ejes (archivos más pequeños, un ligero suavizado de color que es invisible a las distancias de visualización habituales). El valor por defecto de AUTO es la opción adecuada a menos que la aplicación tenga una necesidad específica.

PNG mediante to_png() es sin pérdidas pero más lento de codificar y produce archivos más grandes que JPEG para contenido fotográfico (el contenido fotográfico se comprime mal con el esquema de predicción de PNG). Use PNG cuando la imagen sea arte lineal, una captura de pantalla, o contenga gráficos de bordes nítidos dibujados sobre un fotograma capturado – la codificación sin pérdidas conserva los bordes nítidos que JPEG suavizaría. De lo contrario, JPEG es el valor por defecto adecuado.

Tanto to_jpeg() como to_png() aceptan las mismas palabras clave de estilo de dibujo, posicionales y de escala que toman otros métodos de conversión – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – de modo que la misma llamada puede codificar una versión escalada, recortada o mapeada con paleta de la fuente en un solo paso. compress() es la grafía heredada de to_jpeg(); ambos toman los mismos argumentos y producen el mismo resultado.

5.32.3. Qué aporta la compresión

Vale la pena trabajar una vez con los números que hay detrás del compromiso entre JPEG y datos en bruto.

Un fotograma RGB565 de 320 por 240 ocupa 153.600 bytes (un fotograma capturado a QVGA). Un fotograma de 640 por 480 ocupa 614.400 bytes; un fotograma de 1280 por 960 ocupa 2.457.600 bytes. Ninguno de ellos es grande en comparación con una pantalla de escritorio o de teléfono, pero son considerables en el contexto de una cámara que tiene unos pocos MB de RAM en total, una tarjeta SD con un ancho de banda de escritura finito, y un enlace con el host que normalmente funciona sobre USB CDC, una UART o un módulo inalámbrico a velocidades modestas.

JPEG con quality=50 normalmente comprime un fotograma fotográfico capturado entre 10x y 20x: ese fotograma de 640 por 480 de 614 KB se convierte en un flujo de bytes codificado de 30 a 60 KB. Con quality=85 la compresión baja a entre 5x y 10x (de 60 a 120 KB para el mismo fotograma). Con quality=10 – cargado de artefactos pero aún reconocible – la compresión alcanza entre 30x y 50x (de 12 a 20 KB).

Esos números determinan qué es práctico hacer con los fotogramas guardados. Una ruta de tarjeta SD que sostiene 10 MB/s maneja 30 fotogramas por segundo de contenido VGA codificado en JPEG con quality=50 con margen de sobra (alrededor de 1 a 2 MB/s); guardar el mismo contenido sin comprimir requiere más de 18 MB/s, más de lo que la vía del sistema de archivos de la cámara sostiene hacia la tarjeta. Un host USB que extrae fotogramas codificados en JPEG sobre CDC a 1 MB/s recibe fotogramas de 30 a 60 KB a aproximadamente 15 a 30 fotogramas por segundo; extrayendo fotogramas en bruto a la misma velocidad obtiene uno o dos fotogramas por segundo.

En resumen: los métodos de compresión no son solo una comodidad para guardar. Son lo que hace que el fotograma capturado sea utilizable fuera de la cámara a las velocidades de fotogramas que le importan a la aplicación. Elegir la compresión adecuada – calidad JPEG 50 para registro general, 80 para trabajo de calidad, PNG para captura de arte lineal – forma parte del trabajo rutinario de cualquier aplicación de cámara no trivial.