13.3.1.3. Transmissão de fotogramas em tempo real

Um script que captura fotogramas na câmara pode transmitir cada fotograma de volta ao anfitrião via USB. O padrão consiste em duas chamadas à instância openmv.Camera: streaming() para ativar ou desativar a transmissão, e read_frame() para extrair o próximo fotograma do canal.

13.3.1.3.1. Um ciclo mínimo de transmissão e visualização

O script do lado da câmara é o ciclo de captura habitual; o que é novo é que o anfitrião abre a transmissão e lê o resultado de volta:

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")

A câmara captura fotogramas continuamente; o anfitrião extrai cada um do buffer de transmissão assim que chega. A câmara sobrescreve o buffer de transmissão a cada nova captura de imagem, pelo que um anfitrião que faça polling mais lentamente do que a câmara captura irá perder fotogramas silenciosamente – esse é o comportamento correto para casos de uso de visualizador.

13.3.1.3.2. O dicionário do fotograma

read_frame() devolve None (nenhum fotograma em espera) ou um dict com cinco entradas:

Chave

Significado

width

Largura do fotograma em pixels.

height

Altura do fotograma em pixels.

format

Identificador de formato de pixel declarado pela câmara (um inteiro das constantes csi da câmara).

depth

Para formatos comprimidos (JPEG, PNG), o tamanho da imagem comprimida em bytes. Não utilizado para formatos não comprimidos.

data

O fotograma como buffer bytes em RGB888. Cada pixel ocupa três bytes (R, G, B); o comprimento total é width * height * 3.

raw_size

Bytes enviados pela câmara via USB antes da descodificação. Útil para calcular o débito real.

O pacote converte o formato nativo da câmara (GRAYSCALE, RGB565, JPEG) para RGB888 antes de devolver, pelo que o anfitrião nunca tem de lidar com o RGB565 em bit-packed ou com a descompressão JPEG. Os fotogramas em escala de cinzentos são devolvidos com o valor de luminância replicado nos três canais.

O buffer data está disposto linha por linha, de cima para baixo; alimentá-lo diretamente a uma biblioteca de visualização ou guardá-lo como ficheiro RGB em bruto funciona sem qualquer reorganização adicional.

13.3.1.3.3. Modo de transmissão em bruto

Por defeito, a câmara comprime com JPEG cada fotograma capturado antes de o colocar no canal de transmissão, e read_frame() descomprime no anfitrião. Em câmaras sem suporte de hardware JPEG, a compressão por software é o passo mais lento do ciclo. Passar raw=True ignora essa etapa:

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

A câmara envia então o buffer de pixels sem compressão. Os fotogramas não comprimidos são muito maiores do que os seus equivalentes JPEG, pelo que a câmara redimensiona cada fotograma capturado para caber no canal de transmissão antes de o enviar; o argumento resolution=(width, height) define essa resolução alvo. O anfitrião continua a receber RGB888 no campo data – o pacote converte a partir do formato de pixel que a câmara indicou em format.

13.3.1.3.4. Deixar os eventos conduzir o ciclo

Um ciclo de polling que chama read_frame() mais rápido do que a câmara produz fotogramas passa a maior parte do tempo a obter None como resposta. Quando o anfitrião tem também outro trabalho a fazer (uma interface a atualizar, outros canais a monitorizar), read_status() é a verificação mais económica: devolve um dicionário que mapeia cada nome de canal registado para um booleano de «dados prontos»:

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 é a forma de ciclo que o próprio visualizador da linha de comandos utiliza.

13.3.1.3.5. Parar a transmissão

Chame streaming() com enable=False para parar. A câmara continua a executar o seu script mas deixa de preencher o buffer de transmissão; read_frame() passa a devolver apenas None a partir desse momento. Chamar stop() faz o mesmo implicitamente ao interromper o script.