4.14. Conceitos básicos de CSI

O módulo csi é a forma como o código Python controla o sensor da câmara. Cada script que captura um fotograma segue a mesma estrutura de três partes: importações no topo, configuração inicial no meio, e um ciclo while True na parte inferior que vai buscar fotogramas à câmara um de cada vez.

4.14.1. O ciclo típico

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. O que cada chamada faz

import csi, image, time

Importa três módulos. O csi controla o sensor, o image define a classe Image que snapshot() devolve, e o time fornece o auxiliar time.clock() utilizado para medir os fotogramas por segundo.

csi.CSI()

Constrói uma instância de CSI que encapsula um sensor de câmara físico. O construtor reivindica o periférico da câmara e regista a configuração por sensor. Câmaras com um único sensor têm uma instância de CSI; câmaras com dois sensores (cor mais térmico, cor mais eventos) têm duas, cada uma selecionada por um argumento cid ao construtor.

csi0.reset()

Liga e configura o sensor. Por defeito, envia um pulso no pino de reset do sensor e depois escreve os registos I2C do sensor para um estado inicial conhecido. As chamadas de configuração subsequentes – pixformat, framesize, os controlos automáticos – enviam mais escritas de registo pelo mesmo barramento de controlo I2C.

csi0.pixformat(csi.RGB565)

Escreve os registos do sensor que selecionam o formato de pixel de saída. As opções disponíveis são os formatos apresentados na página de formatos de pixel: RGB565, GRAYSCALE, BAYER, YUV422, e JPEG nos sensores que o suportam.

csi0.framesize(csi.QVGA)

Escreve os registos que selecionam a resolução de saída. O QVGA é 320 × 240; os tamanhos nomeados vão até WQXGA2 (2592 × 1944, cerca de 5 MP) nos sensores que os suportam. Um tuplo personalizado (width, height) também funciona, desde que se alinhe com as capacidades de saída do sensor.

clock = time.clock()

Cria um auxiliar de relógio. Cada chamada a clock.tick() dentro do ciclo regista o tempo de início da iteração; o time.clock.fps() reporta a taxa de ciclo recente em fotogramas por segundo.

img = csi0.snapshot()

Captura um fotograma do sensor e devolve-o como uma Image. A mecânica de como esse fotograma fica em memória merece uma análise mais atenta.

4.14.3. Como snapshot preenche a memória

O sensor entrega pixels no barramento de dados de pixel descrito em barramentos do sensor a velocidades de centenas de megabytes por segundo – demasiado rápido para o CPU copiar pixel a pixel em software.

Em vez disso, o MCU delega a transferência ao Direct Memory Access (DMA) – um motor de hardware separado do CPU que copia bytes de um lugar para outro dentro do MCU sem envolver o CPU. O periférico de entrada da câmara recebe cada byte de pixel recebido para um pequeno FIFO no chip; as fases ISP que correm no lado do MCU processam os dados durante a passagem; e o motor DMA escreve os pixels acabados num framebuffer na RAM no offset de pixel correspondente. Nada nessa cadeia necessita do CPU uma vez que o canal DMA tenha sido programado.

Quando snapshot() é chamado:

  1. O driver CSI programa o motor DMA com o endereço do framebuffer, o comprimento da transferência (os pixels de um fotograma completo), e um callback para a interrupção de DMA concluído.

  2. O driver ativa o periférico de entrada da câmara e aguarda que o sensor sinalize o início do próximo fotograma.

  3. À medida que o sensor transmite o fotograma, o periférico passa cada byte de pixel pelo ISP e para o motor DMA, que escreve o resultado na RAM no próximo offset do framebuffer. O CPU está livre para executar outro código durante a transferência.

  4. Quando o último pixel do fotograma chega, o DMA dispara a sua interrupção de conclusão, o driver encapsula o framebuffer numa Image, e snapshot() devolve-a ao código do utilizador.

A Image devolvida não possui uma cópia dos dados de pixel – aponta para um dos framebuffers da câmara na RAM. O número de framebuffers que a câmara mantém, e como são passados entre o DMA e o código do utilizador em cada chamada a snapshot(), depende do modo de buffering que a aplicação selecionou através de framebuffers().