13.3.1.3. Transmisión de fotogramas

Un script que captura fotogramas en la cámara puede transmitir cada fotograma de vuelta al host a través de USB. El patrón consta de dos llamadas sobre la instancia openmv.Camera: streaming() para activar o desactivar la transmisión, y read_frame() para extraer el siguiente fotograma del canal.

13.3.1.3.1. Un bucle mínimo de transmisión y visualización

El script del lado de la cámara es el bucle de captura habitual; lo nuevo es que el host abre la transmisión y lee el resultado de vuelta:

from openmv import Camera

script = """
import csi
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
while True:
    csi0.snapshot()
"""

with Camera('/dev/ttyACM0') as cam:
    cam.stop()
    cam.exec(script)
    cam.streaming(True)

    while True:
        if frame := cam.read_frame():
            print(f"{frame['width']}x{frame['height']}, "
                  f"{frame['raw_size']} bytes")

La cámara captura fotogramas de forma continua; el host extrae cada uno del búfer de transmisión a medida que llega. La cámara sobrescribe el búfer de transmisión en cada nueva captura, por lo que un host que consulte más lento de lo que la cámara captura descartará silenciosamente fotogramas – ese es el comportamiento correcto para casos de uso de tipo visor.

13.3.1.3.2. El diccionario del fotograma

read_frame() devuelve None (no hay ningún fotograma esperando) o un dict con cinco entradas:

Clave

Significado

width

Ancho del fotograma en píxeles.

height

Alto del fotograma en píxeles.

format

Identificador del formato de píxel que declaró la cámara (un entero de las constantes csi de la cámara).

depth

Para formatos comprimidos (JPEG, PNG), el tamaño de la imagen comprimida en bytes. No se utiliza en formatos sin comprimir.

data

El fotograma como un búfer bytes en RGB888. Cada píxel son tres bytes (R, G, B); la longitud total es width * height * 3.

raw_size

Bytes que la cámara envió a través de USB antes de la decodificación. Útil para el cálculo del rendimiento real.

El paquete convierte el formato nativo de la cámara (GRAYSCALE, RGB565, JPEG) a RGB888 antes de devolverlo, de modo que el host nunca tiene que lidiar por sí mismo con el RGB565 empaquetado a nivel de bits ni con la ruta de descompresión JPEG. Los fotogramas en escala de grises se devuelven con el valor de luma replicado en los tres canales.

El búfer data se organiza fila por fila, de arriba hacia abajo; pasarlo directamente a una biblioteca de visualización o guardarlo como un archivo RGB sin procesar funciona sin ninguna reorganización adicional.

13.3.1.3.3. Modo de transmisión en bruto

De forma predeterminada, la cámara comprime en JPEG cada fotograma capturado antes de colocarlo en el canal de transmisión, y read_frame() lo descomprime en el host. En cámaras sin soporte de JPEG por hardware, la compresión por software es el paso más lento del bucle. Pasar raw=True lo omite:

cam.streaming(True, raw=True, resolution=(320, 240))

La cámara entonces envía el búfer de píxeles sin comprimir. Los fotogramas sin comprimir son mucho más grandes que sus equivalentes en JPEG, por lo que la cámara reduce la escala de cada fotograma capturado para que quepa en el canal de transmisión antes de enviarlo; el argumento resolution=(width, height) establece ese objetivo. El host sigue recibiendo RGB888 en el campo data – el paquete realiza la conversión desde cualquier formato de píxel que la cámara haya indicado en format.

13.3.1.3.4. Dejar que los eventos controlen el bucle

Un bucle de sondeo que llama a read_frame() más rápido de lo que la cámara produce fotogramas pasa la mayor parte de su tiempo recibiendo None. Cuando el host también tiene otras tareas que hacer (una interfaz que actualizar, otros canales que sondear), read_status() es la comprobación más económica: devuelve un diccionario que asigna a cada nombre de canal registrado un booleano que indica si «hay datos listos»:

while True:
    status = cam.read_status()

    if status.get('stream'):
        frame = cam.read_frame()
        # ... process the frame ...

    if status.get('stdout'):
        text = cam.read_stdout()
        print(text, end='')

    if status.get('my_channel'):
        data = cam.channel_read('my_channel')
        # ... process custom-channel data ...

Esta es la forma de bucle que utiliza el propio visor de la CLI.

13.3.1.3.5. Detener la transmisión

Llama a streaming() con enable=False para detenerla. La cámara sigue ejecutando su script pero ya no llena el búfer de transmisión; read_frame() simplemente devuelve None a partir de ese momento. Llamar a stop() hace lo mismo de forma implícita al detener el script.