Câmera de Eventos GENX320

O Módulo de Câmera de Eventos GENX320 é um sensor de visão baseado em eventos da Prophesee com resolução de 320x320 e precisão temporal de microssegundos.

Câmera de Eventos GENX320

Para o datasheet completo, fotos e informações de compra, consulte a página do produto Câmera de Eventos GENX320.

Nota

Compatível com a OpenMV H7 Plus, RT1062 e N6.

Destaques

  • Sensor de visão baseado em eventos de 320x320

  • Faixa dinâmica de 140 dB, sem desfoque de movimento

  • Taxa de saída de histograma de eventos de 375 Hz+

  • O consumo de energia escala com a atividade da cena — começa em ~3 mW

  • Opera de <5 lux até luz solar intensa sem exposição automática

  • Gera quadros em escala de cinza ou fluxos de eventos brutos

Uso

O GENX320 é um sensor de visão baseado em eventos — em vez de ler todo o array de 320x320 em um clock de quadro fixo, cada pixel reporta “eventos” assíncronos no instante em que detecta uma mudança de brilho. Cada evento carrega uma coordenada X/Y, uma polaridade ON/OFF (claro→escuro ou escuro→claro) e um timestamp em microssegundos. É daí que vêm a precisão temporal de microssegundos do sensor, a ausência de desfoque de movimento, a faixa dinâmica muito alta e o consumo de energia proporcional à atividade. Cenas estáticas não geram dados.

O firmware da OpenMV expõe o GENX320 através de csi.CSI com cid= csi.GENX320. Dois modos de operação estão disponíveis:

  • Modo histograma (padrão) — os eventos são acumulados no chip em bins por pixel e reportados como um quadro em escala de cinza de 320x320 a uma taxa configurável (~20-350 FPS). O sensor se comporta como uma câmera comum, então todas as rotinas padrão de processamento de imagem (Image.find_blobs, paletas, etc.) funcionam diretamente.

  • Modo de eventos — os eventos brutos são transmitidos para um ndarray do numpy com timestamps completos em microssegundos, para aplicações que precisam do detalhe temporal em vez de um quadro pré-binado.

Modo histograma

No modo histograma, o GENX320 gera quadros em escala de cinza onde cada pixel codifica a atividade de eventos recente naquela posição. Pixels acima da linha de base de brilho são eventos ON (brilho aumentando), abaixo são eventos OFF (brilho diminuindo). A linha de base de brilho padrão é 128 e o passo de contraste por evento é 16 — aumente o contraste para fazer os eventos se destacarem:

import csi
import time

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize((320, 320))
csi0.brightness(128)  # baseline (default 128)
csi0.contrast(16)     # per-event step
csi0.framerate(50)    # 20-350 FPS

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

csi.CSI.brightness, csi.CSI.contrast e csi.CSI.framerate são os três controles que moldam a saída do histograma.

Saída colorida

Defina csi.CSI.color_palette como image.PALETTE_EVT_LIGHT para um fundo claro ou image.PALETTE_EVT_DARK para um fundo escuro — o driver emite quadros RGB565 usando a paleta diretamente:

csi0.color_palette(image.PALETTE_EVT_LIGHT)

Calibração de pixels quentes

Sensores de eventos acumulam “pixels quentes” que disparam espuriamente. Execute csi.IOCTL_GENX320_CALIBRATE contra uma cena estática para desativá-los. O driver constrói uma contagem de acertos por pixel de 320x320, calcula a média e o desvio padrão, e desativa qualquer pixel cuja contagem esteja acima de mean + sigma * stddev — então os pixels desativados param de emitir eventos no nível do sensor.

Dois parâmetros controlam a calibração:

  • event_count — quantos eventos contabilizar antes de calcular as estatísticas. O loop captura quadros até que o total acumulado de eventos cruze esse orçamento. Contagens mais altas dão uma estimativa mais confiável ao custo de um tempo de calibração mais longo. 10000 é um ponto de partida razoável.

  • sigma — multiplicador de limiar sobre o desvio padrão. Valores mais baixos são mais agressivos (mais pixels desativados); valores mais altos são mais conservadores. 0.5 é um bom padrão.

Aponte o sensor primeiro para uma cena estática para que quaisquer eventos causados por movimento não sejam contabilizados contra pixels que estão realmente bons:

csi0.snapshot(time=5000)  # let the user steady the camera
disabled = csi0.ioctl(csi.IOCTL_GENX320_CALIBRATE, 10000, 0.5)
print(f"disabled {disabled} hot pixels")

Filtro anti-flicker (AFK)

Fontes de luz periódicas (fluorescentes, displays acionados por LED) geram volumes enormes de eventos redundantes. O filtro AFK rejeita eventos cujo pixel alterna em uma frequência dentro de uma banda — habilite-o através de csi.IOCTL_GENX320_SET_AFK com as bordas da banda em hertz:

csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 1, 130, 160)  # 130-160 Hz
csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 0)            # disable

Predefinições de bias

Cada pixel no GenX320 executa um front-end analógico com vários biases configuráveis. Eles governam conjuntamente a sensibilidade, o ruído, a largura de banda do pixel e a taxa de eventos — a combinação certa depende da cena. Os biases individuais são:

  • DIFF_ON — o limiar de contraste do comparador positivo. Um pixel emite um evento ON quando sua log-iluminação subiu nesse valor. Mais baixo = mais sensível a transições para o claro.

  • DIFF_OFF — o limiar de contraste do comparador negativo (a contraparte simétrica para eventos OFF). Mais baixo = mais sensível a transições para o escuro.

  • FO — a frequência de corte passa-baixas do pixel. Mais alto = largura de banda do pixel mais ampla (resposta mais rápida, menor latência), porém mais atividade de ruído de fundo.

  • HPF — a frequência de corte passa-altas. Mais alto = rejeição mais forte de mudanças lentas de brilho; apenas transições rápidas alcançam os comparadores. Útil para ignorar a deriva ambiente.

  • REFR — o período refratário. Após um pixel disparar, ele permanece em reset por esse tempo antes de poder disparar novamente. Mais alto = tempo morto mais longo, útil para limitar a taxa de eventos por pixel.

Após csi.CSI.reset, o driver aplica csi.GENX320_BIASES_LOW_NOISE, não csi.GENX320_BIASES_DEFAULT — os padrões do datasheet emitem uma taxa de eventos de fundo muito mais alta, então LOW_NOISE é usado como ponto de partida para manter o fluxo silencioso. Chame csi.IOCTL_GENX320_SET_BIASES com uma predefinição diferente quando a aplicação precisar de mais sensibilidade ou largura de banda.

csi.IOCTL_GENX320_SET_BIASES aplica uma de cinco predefinições:

  • csi.GENX320_BIASES_DEFAULT — padrões do datasheet do GenX320. Sensibilidade, ruído e largura de banda equilibrados para cenas gerais.

  • csi.GENX320_BIASES_LOW_LIGHT — ambos os limiares de contraste afrouxados para maior sensibilidade, FO reduzido para manter o ruído baixo, e HPF definido como 0 para que mudanças lentas de brilho ainda sejam registradas — uma cena com pouca luz gera poucos eventos por si só, então queremos que o máximo possível seja captado.

  • csi.GENX320_BIASES_ACTIVE_MARKER — ajustado para rastrear LEDs piscantes de alto contraste. Limiares de contraste elevados para que apenas transições nítidas disparem; FO e HPF aumentados ao máximo para maximizar a largura de banda do pixel e rejeitar qualquer deriva ambiente lenta; REFR reduzido a 0 para que cada borda de pisca seja capturada uma após a outra. O resultado: um fluxo composto quase inteiramente de bordas de LED, fácil de rastrear.

  • csi.GENX320_BIASES_LOW_NOISE — padrão do driver. Ambos os limiares de contraste elevados em relação ao DEFAULT (menos sensível) e FO reduzido (pixel mais lento = pixel mais silencioso). Ideal para cenas estáticas ou lentas onde eventos falsos dominariam de outra forma.

  • csi.GENX320_BIASES_HIGH_SPEED — FO aumentado para que cada pixel possa responder mais rápido, HPF elevado para rejeitar a deriva lenta de brilho, e REFR elevado para que uma única borda de movimento rápido não inunde a leitura — o tempo morto mais longo mantém o volume de eventos limitado sob movimento intenso.

Sobrescreva biases individuais com csi.IOCTL_GENX320_SET_BIAS mais um de csi.GENX320_BIAS_DIFF_ON, csi.GENX320_BIAS_DIFF_OFF, csi.GENX320_BIAS_FO, csi.GENX320_BIAS_HPF ou csi.GENX320_BIAS_REFR e um valor de DAC. Cada bias é definido independentemente — escolha uma predefinição como ponto de partida e então ajuste os biases que sua cena precisar:

csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_LOW_LIGHT)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIAS, csi.GENX320_BIAS_HPF, 20)

Rastreamento

Como a saída do modo histograma é apenas uma imagem em escala de cinza, o rastreamento de blob comum funciona diretamente. Para rastrear um LED de marcador ativo, carregue a predefinição de bias de marcador ativo e encontre blobs na extremidade clara do histograma:

import csi
import time

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize((320, 320))
csi0.brightness(128)
csi0.contrast(16)
csi0.framerate(200)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_ACTIVE_MARKER)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    for blob in img.find_blobs([(120, 140)], invert=True,
                               pixels_threshold=2, area_threshold=4,
                               merge=True):
        img.draw_detection(blob)
    print(clock.fps())

Modo de eventos

O modo de eventos contorna o histograma no chip e transmite eventos brutos para um ndarray do numpy. Cada evento é uma linha de seis colunas uint16:

  • [0] tipo de evento — veja abaixo

  • [1] timestamp em segundos

  • [2] timestamp em milissegundos

  • [3] timestamp em microssegundos

  • [4] coordenada X, 0-319

  • [5] coordenada Y, 0-319

O driver emite seis tipos de evento na coluna [0]:

  • csi.PIX_OFF_EVENT — um pixel detectou uma diminuição de brilho (o limiar do comparador DIFF_OFF foi cruzado). X/Y apontam para o pixel que disparou.

  • csi.PIX_ON_EVENT — um pixel detectou um aumento de brilho (o limiar DIFF_ON foi cruzado). X/Y apontam para o pixel.

  • csi.EXT_TRIGGER_FALLING — o pino de trigger externo do sensor viu uma borda de descida. X/Y não são usados.

  • csi.EXT_TRIGGER_RISING — o pino de trigger externo do sensor viu uma borda de subida. X/Y não são usados.

  • csi.RST_TRIGGER_FALLING — trigger de reset de pixel, borda de descida. X/Y não são usados. Não gerado pelo firmware no momento.

  • csi.RST_TRIGGER_RISING — trigger de reset de pixel, borda de subida. X/Y não são usados. Não gerado pelo firmware no momento.

A entrada de trigger externo do GENX320 está conectada à linha de sincronização de quadro da câmera, que também é roteada para o P10 tanto no processador quanto no cabeçalho de pinos — acione P10 para injetar bordas de sincronização no fluxo de eventos e captá-las como eventos EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING junto com os dados de pixel.

A maioria das aplicações só se importa com PIX_OFF_EVENT e PIX_ON_EVENT; os tipos de trigger permitem correlacionar eventos com sinais de temporização externos.

Aloque o buffer de eventos com formato (EVT_res, 6) onde EVT_res é uma potência de dois entre 1024 e 65536, então entre no modo de eventos através de csi.IOCTL_GENX320_SET_MODE com csi.GENX320_MODE_EVENT e o tamanho do buffer. Leia os eventos com csi.IOCTL_GENX320_READ_EVENTS, que preenche o buffer até sua capacidade e retorna o número de linhas válidas.

Image.draw_event_histogram rasteriza eventos em uma imagem em escala de cinza — para cada evento ON ele adiciona contrast ao bin; para cada evento OFF ele subtrai. clear=True redefine a imagem para brightness primeiro; clear=False acumula ao longo de muitas chamadas:

import csi
import image
import time
from ulab import numpy as np

img = image.Image(320, 320, image.GRAYSCALE)
events = np.zeros((2048, 6), dtype=np.uint16)

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT, events.shape[0])

clock = time.clock()
while True:
    clock.tick()
    n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
    img.draw_event_histogram(events[:n], clear=True, brightness=128, contrast=64)
    img.flush()
    print(n, clock.fps())

As predefinições de bias do modo histograma, o filtro AFK e os ioctls de calibração de pixels quentes funcionam todos da mesma forma no modo de eventos — chame-os após csi.IOCTL_GENX320_SET_MODE.

Filtragem por polaridade

Fatie o array de eventos com ulab para manter apenas eventos ON (movimento para um estado mais claro) ou apenas eventos OFF:

TARGET = csi.PIX_ON_EVENT  # or csi.PIX_OFF_EVENT

events_slice = events[:n]
indices = np.nonzero(events_slice[:, 0] == TARGET)[0]
if len(indices):
    target_events = np.take(events_slice, indices, axis=0)
    img.draw_event_histogram(target_events, clear=True,
                             brightness=128, contrast=64)

Acumulação de longa exposição

Defina clear=False para continuar empilhando eventos na mesma imagem ao longo de muitos quadros — o resultado é uma visualização de rastro de movimento. Redefina periodicamente para iniciar uma nova exposição:

EXPOSURE_FRAMES = 30
i = 0
while True:
    n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
    clear = (i % EXPOSURE_FRAMES) == 0
    img.draw_event_histogram(events[:n], clear=clear, brightness=128, contrast=64)
    img.flush()
    i += 1

Processamento de alta velocidade

Descarte a visualização para liberar CPU para o processamento de eventos. Imprima as estatísticas apenas a cada N-ésima iteração — emitir uma linha de print a cada iteração se torna o gargalo em altas taxas de eventos:

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT, events.shape[0])

clock = time.clock()
i = 0
while True:
    clock.tick()
    n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
    i += 1
    if not i % 10:
        print(f"{n} events  {clock.fps()} fps")

Filtro de contraste espaço-temporal (STC)

Uma borda de contraste em movimento real tende a disparar uma rajada ruidosa de eventos no mesmo pixel dentro de uma curta janela de tempo — o descasamento de pixel e o ruído analógico produzem eventos extras ao redor da transição genuína que não são úteis para a aplicação. O filtro STC é um pós-processamento no chip que mantém apenas um (ou alguns) eventos por rajada e descarta o restante.

Ele implementa três estratégias, selecionadas através de csi.IOCTL_GENX320_SET_STC e uma constante GENX320_STC_*. Cada modo é definido por quais eventos ele encaminha de uma rajada:

Modo

Mantém

Descarta

csi.GENX320_STC_DISABLE

todos os eventos

nada

csi.GENX320_STC_ONLY

segundo evento de uma rajada

primeiro + eventos posteriores

csi.GENX320_STC_TRAIL_ONLY

primeiro evento de uma rajada

eventos subsequentes

csi.GENX320_STC_TRAIL

primeira borda + bordas subsequentes

apenas ruído redundante

Em detalhe:

  • csi.GENX320_STC_DISABLE — filtro desligado, todos os eventos passam (padrão).

  • csi.GENX320_STC_ONLY — mantém o segundo evento de uma rajada. Parâmetro: stc_threshold (ms). Se um novo evento em um pixel chega dentro de stc_threshold de um evento anterior, ele é considerado o “segundo” de uma rajada e é encaminhado — o primeiro evento e quaisquer eventos subsequentes na mesma rajada são filtrados. Ideal quando você quer uma transição confirmada por ruído em vez do primeiríssimo acerto.

  • csi.GENX320_STC_TRAIL_ONLY — mantém o primeiro evento de uma rajada. Parâmetro: trail_threshold (ms). Após um pixel disparar, os eventos subsequentes no mesmo pixel são descartados até que trail_threshold tenha decorrido. Preserva a temporização precisa da borda de subida — útil quando o momento da troca de polaridade importa mais do que a confirmação da rajada.

  • csi.GENX320_STC_TRAIL — combina ambos. Parâmetros: stc_threshold e trail_threshold (ambos em ms). Mantém a borda de subida conforme o modo Trail mais as bordas subsequentes conforme o modo STC, então múltiplos eventos de uma rajada ainda passam — throughput de eventos mais alto do que os filtros de modo único, mas com o sinal mais rico.

Os dois limiares devem permanecer dentro de uma razão de aproximadamente 13:1 — o sensor rejeita configurações onde um é mais de ~13x o outro:

csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_TRAIL, 1, 2)
csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_DISABLE)

Profundidade do buffer

Quando as taxas de eventos disparam, o pipeline de buffer triplo padrão favorece o quadro mais recente e descarta os antigos. Aumente a profundidade do FIFO através de csi.CSI.framebuffers para enfileirar eventos em vez disso — ao custo de processar dados ligeiramente mais antigos quando o host fica para trás:

csi0.framebuffers(10)  # FIFO depth, > 3 enables queueing

Streaming e visualização em desktop

Para visualização de GUI em tempo real em um PC host, a ferramenta GenX320 Event Streaming no repositório openmv-projects combina a câmera com um front-end DearPyGui. A GUI do PC executa duas visualizações lado a lado: um canvas de acumulação de eventos (mesma ideia que Image.draw_event_histogram, mas com paletas selecionáveis e modos de janela deslizante vs. auto-limpeza) e um mapa de frequência por pixel acionado por um filtro passa-banda IIR — útil para detectar sinais periódicos (ventiladores rotativos, LEDs piscantes, etc.) diretamente no fluxo de eventos.

Ela inclui dois scripts de streaming na câmera:

  • Modo processado (genx320_event_mode_streaming_on_cam.py) — a câmera decodifica eventos com csi.IOCTL_GENX320_READ_EVENTS e transmite cada linha como 12 bytes por USB ([0] tipo, [1] seg, [2] ms, [3] us, [4] x, [5] y). Fácil de consumir no PC porque o formato de fio corresponde ao formato ndarray da câmera.

  • Modo bruto (genx320_raw_event_mode_streaming_on_cam.py) — a câmera transmite as palavras de evento empacotadas nativas de 32 bits do chip através de csi.IOCTL_GENX320_READ_EVENTS_RAW. Isso é 4 bytes por evento versus 12 no modo processado (cerca de 3x menos dados por USB), então uma taxa de eventos alcançável ~3x maior quando o link é o gargalo. O PC decodifica as palavras empacotadas de volta para o mesmo layout de evento de 6 colunas usando numpy vetorizado, então o código do visualizador a jusante é idêntico.

O modo bruto é o padrão na GUI porque o throughput do USB é a restrição limitante nas taxas que o GenX320 pode produzir; mude para o modo processado se você precisar inserir lógica de processamento no script da câmera.