4.14. Noções básicas de CSI

O módulo csi é como o código Python controla o sensor da câmera. Todo script que captura um quadro segue o mesmo formato de três partes: imports no topo, configuração única no meio e um laço while True na parte inferior que puxa quadros da câmera um de cada vez.

4.14.1. O laço 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

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

csi.CSI()

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

csi0.reset()

Liga e configura o sensor. Por padrão, ele pulsa o pino de reset do sensor e então grava os registradores I2C do sensor em um estado inicial conhecido. Chamadas de configuração subsequentes – pixformat, framesize, os controles automáticos – enviam mais gravações de registrador pelo mesmo barramento de controle I2C.

csi0.pixformat(csi.RGB565)

Grava os registradores do sensor que escolhem o formato de pixel de saída. As opções disponíveis são os formatos que a página formatos de pixel apresentou: RGB565, GRAYSCALE, BAYER, YUV422 e JPEG nos sensores que o suportam.

csi0.framesize(csi.QVGA)

Grava os registradores que escolhem a resolução de saída. QVGA é 320 × 240; os tamanhos nomeados chegam até WQXGA2 (2592 × 1944, cerca de 5 MP) nos sensores que os suportam. Uma tupla (width, height) personalizada também funciona, desde que esteja de acordo com as capacidades de saída do sensor.

clock = time.clock()

Cria um auxiliar de relógio. Cada chamada a clock.tick() dentro do laço registra o horário de início da iteração; time.clock.fps() informa a taxa recente do laço em quadros por segundo.

img = csi0.snapshot()

Captura um quadro do sensor e o retorna como uma Image. Vale a pena observar mais de perto a mecânica de como esse quadro vai parar na memória.

4.14.3. Como o snapshot preenche a memória

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

Em vez disso, o MCU delega a transferência ao Direct Memory Access (DMA) – um mecanismo de hardware separado da CPU que copia bytes de um lugar para outro dentro do MCU sem envolver a CPU em nada. O periférico de entrada da câmera captura cada byte de pixel que chega em um pequeno FIFO on-chip; quaisquer estágios do ISP executados no lado do MCU processam os dados durante a passagem; e o mecanismo DMA grava os pixels finalizados em um framebuffer na RAM no deslocamento de pixel correspondente. Nada nessa cadeia precisa da CPU uma vez que o canal DMA tenha sido programado.

Quando snapshot() é chamado:

  1. O driver CSI programa o mecanismo DMA com o endereço do framebuffer, o comprimento da transferência (os pixels equivalentes a um quadro) e um callback para a interrupção de conclusão do DMA.

  2. O driver habilita o periférico de entrada da câmera e aguarda o sensor sinalizar o início do próximo quadro.

  3. À medida que o sensor envia o quadro em fluxo, o periférico passa cada byte de pixel pelo ISP e em seguida para o mecanismo DMA, que grava o resultado na RAM no próximo deslocamento do framebuffer. A CPU fica livre para executar outro código durante a transferência.

  4. Quando o último pixel do quadro chega, o DMA dispara sua interrupção de conclusão, o driver encapsula o framebuffer em uma Image, e snapshot() o retorna ao código do usuário.

A Image retornada não possui uma cópia dos dados de pixel – ela aponta para um dos framebuffers da câmera na RAM. Quantos framebuffers a câmera mantém, e como eles são repassados entre o DMA e o código do usuário a cada chamada de snapshot(), depende do modo de buffering que a aplicação selecionou por meio de framebuffers().