Câmara de Eventos GENX320

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

GENX320 Event Camera

Para ficha técnica completa, fotografias e encomendas, consulte a página de produto da Câmara de Eventos GENX320.

Nota

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

Destaques

  • Sensor de visão baseado em eventos 320x320

  • Intervalo dinâmico de 140 dB, sem desfocagem de movimento

  • Taxa de saída de histograma de eventos de 375 Hz ou superior

  • Consumo proporcional à atividade da cena — começa em ~3 mW

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

  • Emite fotogramas em escala de cinzentos ou fluxos de eventos em bruto

Utilização

O GENX320 é um sensor de visão baseado em eventos — em vez de ler todo o array 320x320 com um relógio de fotograma fixo, cada pixel reporta «eventos» assíncronos no instante em que deteta uma variação de luminosidade. Cada evento contém uma coordenada X/Y, uma polaridade ON/OFF (claro→escuro ou escuro→claro) e um carimbo temporal em microssegundos. É daí que provêm a precisão temporal em microssegundos, a ausência de desfocagem de movimento, o intervalo dinâmico muito elevado e o consumo proporcional à atividade. Cenas estáticas não geram quaisquer dados.

O firmware OpenMV expõe o GENX320 através de csi.CSI com cid= csi.GENX320. Estão disponíveis dois modos de funcionamento:

  • Modo histograma (predefinição) — os eventos são acumulados no chip em contadores por pixel e reportados como um fotograma em escala de cinzentos 320x320 a uma taxa configurável (~20-350 FPS). O sensor comporta-se como uma câmara normal, pelo que todas as rotinas padrão de processamento de imagem (Image.find_blobs, paletas, etc.) funcionam diretamente.

  • Modo evento — os eventos em bruto são transmitidos para um ndarray numpy com carimbos temporais completos em microssegundos, para aplicações que necessitem do detalhe temporal em vez de um fotograma pré-binado.

Modo histograma

No modo histograma, o GENX320 emite fotogramas em escala de cinzentos em que cada pixel codifica a atividade recente de eventos nessa localização. Pixels acima do nível de luminosidade de referência são eventos ON (luminosidade a aumentar); abaixo são eventos OFF (luminosidade a diminuir). O nível de luminosidade de referência predefinido é 128 e o passo de contraste por evento é 16 — aumente o contraste para tornar os eventos mais visíveis:

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 parâmetros que moldam a saída do histograma.

Saída colorizada

Defina csi.CSI.color_palette para image.PALETTE_EVT_LIGHT para um fundo claro ou image.PALETTE_EVT_DARK para um fundo escuro — o controlador emite fotogramas RGB565 utilizando a paleta diretamente:

csi0.color_palette(image.PALETTE_EVT_LIGHT)

Calibração de pixels quentes

Os sensores de eventos acumulam «pixels quentes» que disparam de forma espúria. Execute csi.IOCTL_GENX320_CALIBRATE apontando para uma cena estática para os desativar. O controlador constrói um mapa de contagem de ocorrências por pixel 320x320, calcula a média e o desvio padrão, e desativa qualquer pixel cuja contagem esteja acima de mean + sigma * stddev — os pixels desativados deixam de emitir eventos ao nível do sensor.

Dois parâmetros controlam a calibração:

  • event_count — número de eventos a contabilizar antes de calcular as estatísticas. O ciclo captura fotogramas até que o total acumulado de eventos ultrapasse este limite. Contagens mais elevadas produzem uma estimativa mais fiável ao custo de um tempo de calibração mais longo. 10000 é um bom ponto de partida.

  • 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 valor predefinido.

Aponte o sensor para uma cena estática primeiro, para que os eventos provocados por movimento não sejam contabilizados contra pixels que funcionam corretamente:

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-cintilação (AFK)

Fontes de luz periódicas (fluorescentes, ecrãs com retroiluminação LED) geram grandes volumes de eventos redundantes. O filtro AFK rejeita eventos cujos pixels comutem a uma frequência dentro de uma banda — ative-o através de csi.IOCTL_GENX320_SET_AFK com os limites 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 polarização

Cada pixel do GenX320 executa uma frente analógica com várias polarizações configuráveis. Em conjunto, governam a sensibilidade, o ruído, a largura de banda do pixel e a taxa de eventos — a combinação correta depende da cena. As polarizações individuais são:

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

  • 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 escuridão.

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

  • HPF — a frequência de corte passa-alto. Mais alto = rejeição mais forte de variações lentas de luminosidade; apenas transições rápidas chegam aos comparadores. Útil para ignorar a deriva do ambiente.

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

Após csi.CSI.reset, o controlador aplica csi.GENX320_BIASES_LOW_NOISE, não csi.GENX320_BIASES_DEFAULT — os valores predefinidos da ficha técnica emitem uma taxa de eventos de fundo muito mais elevada, pelo que 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 necessitar de maior sensibilidade ou largura de banda.

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

  • csi.GENX320_BIASES_DEFAULT — valores predefinidos da ficha técnica do GenX320. Sensibilidade, ruído e largura de banda equilibrados para cenas gerais.

  • csi.GENX320_BIASES_LOW_LIGHT — ambos os limiares de contraste alargados para maior sensibilidade, FO reduzido para manter o ruído baixo, e HPF a 0 para que variações lentas de luminosidade ainda sejam registadas — uma cena com pouca luz gera poucos eventos por si só, pelo que queremos que o maior número possível passe.

  • csi.GENX320_BIASES_ACTIVE_MARKER — otimizado para rastreamento de LEDs intermitentes de alto contraste. Limiares de contraste aumentados para que apenas transições nítidas sejam acionadas; FO e HPF elevados ao máximo para maximizar a largura de banda do pixel e rejeitar qualquer deriva lenta do ambiente; REFR a 0 para que cada flanco de intermitência seja capturado consecutivamente. O resultado: um fluxo composto quase exclusivamente por flancos de LED, fácil de rastrear.

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

  • csi.GENX320_BIASES_HIGH_SPEED — FO aumentado para que cada pixel responda mais rapidamente, HPF aumentado para rejeitar deriva lenta de luminosidade, e REFR aumentado para que um único flanco de movimento rápido não inunde a leitura — o maior tempo morto mantém o volume de eventos limitado sob movimento intenso.

Substitua polarizações individuais com csi.IOCTL_GENX320_SET_BIAS mais uma das constantes 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 DAC. Cada polarização é definida independentemente — escolha uma predefinição como ponto de partida e ajuste as polarizações que a sua cena necessitar:

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 no modo histograma é apenas uma imagem em escala de cinzentos, o rastreamento por manchas funciona diretamente. Para rastrear um LED marcador ativo, carregue a predefinição de polarização para marcador ativo e encontre manchas no extremo brilhante 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 evento

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

  • [0] tipo de evento — ver abaixo

  • [1] carimbo temporal em segundos

  • [2] carimbo temporal em milissegundos

  • [3] carimbo temporal em microssegundos

  • [4] coordenada X, 0-319

  • [5] coordenada Y, 0-319

O controlador emite seis tipos de eventos na coluna [0]:

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

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

  • csi.EXT_TRIGGER_FALLING — o pino de acionamento externo do sensor detetou um flanco descendente. X/Y não são utilizados.

  • csi.EXT_TRIGGER_RISING — o pino de acionamento externo do sensor detetou um flanco ascendente. X/Y não são utilizados.

  • csi.RST_TRIGGER_FALLING — acionamento de reset de pixel, flanco descendente. X/Y não são utilizados. Não gerado pelo firmware neste momento.

  • csi.RST_TRIGGER_RISING — acionamento de reset de pixel, flanco ascendente. X/Y não são utilizados. Não gerado pelo firmware neste momento.

A entrada de acionamento externo do GENX320 está ligada à linha de sincronização de fotogramas da câmara, que também está encaminhada para P10 tanto no processador como no conector de pinos — acione P10 para injetar flancos de sincronização no fluxo de eventos e receba-os como eventos EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING juntamente com os dados de pixel.

A maioria das aplicações apenas precisa de PIX_OFF_EVENT e PIX_ON_EVENT; os tipos de acionamento permitem correlacionar eventos com sinais de temporização externos.

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

Image.draw_event_histogram rasteriza eventos numa imagem em escala de cinzentos — para cada evento ON adiciona contrast ao bin; para cada evento OFF subtrai. clear=True repõe a imagem para brightness primeiro; clear=False acumula ao longo de várias 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 polarização do modo histograma, o filtro AFK e os ioctls de calibração de pixels quentes funcionam da mesma forma no modo evento — 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 brilhante) 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 a empilhar eventos na mesma imagem ao longo de muitos fotogramas — o resultado é uma visualização de rastros de movimento. Reinicie 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

Elimine a visualização para libertar CPU para processamento de eventos. Imprima estatísticas apenas a cada N-ésima iteração — enviar uma linha de impressão em cada iteração torna-se o bottleneck a taxas elevadas 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 espácio-temporal (STC)

Uma aresta de contraste em movimento real tende a acionar uma rajada de eventos ruidosos no mesmo pixel num curto intervalo de tempo — desalinhamento de pixel e ruído analógico produzem eventos extras em torno 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 poucos) eventos por rajada e descarta os restantes.

Implementa três estratégias, selecionadas através de csi.IOCTL_GENX320_SET_STC e uma constante GENX320_STC_*. Cada modo é definido pelos eventos que 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

primeiro + flancos subsequentes

apenas ruído redundante

Em detalhe:

  • csi.GENX320_STC_DISABLE — filtro desativado, todos os eventos passam (predefinição).

  • csi.GENX320_STC_ONLY — mantém o segundo evento de uma rajada. Parâmetro: stc_threshold (ms). Se um novo evento num pixel chegar dentro de stc_threshold de um evento anterior, é considerado o «segundo» de uma rajada e é encaminhado — o primeiro evento e quaisquer eventos subsequentes na mesma rajada são filtrados. Ideal quando se pretende uma transição confirmada por ruído em vez do primeiro impacto.

  • csi.GENX320_STC_TRAIL_ONLY — mantém o primeiro evento de uma rajada. Parâmetro: trail_threshold (ms). Após um pixel disparar, eventos subsequentes no mesmo pixel são descartados até que trail_threshold tenha decorrido. Preserva a temporização precisa do flanco de subida — útil quando o momento de comutação 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 o flanco de subida por modo Trail mais flancos subsequentes por modo STC, pelo que múltiplos eventos de uma rajada ainda passam — maior débito de eventos do que os filtros de modo único, mas o sinal mais rico.

Os dois limiares devem manter-se dentro de um rácio de aproximadamente 13:1 — o sensor rejeita configurações em que 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 aumentam rapidamente, o pipeline de triplo buffer predefinido favorece o fotograma mais recente e descarta os antigos. Aumente a profundidade do FIFO através de csi.CSI.framebuffers para enfileirar eventos — ao custo de processar dados ligeiramente mais antigos quando o host fica para trás:

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

Transmissão e visualização para desktop

Para visualização GUI em tempo real num PC, a ferramenta GenX320 Event Streaming no repositório openmv-projects combina a câmara com uma interface DearPyGui. A GUI do PC executa duas visualizações lado a lado: uma tela de acumulação de eventos (mesmo conceito que Image.draw_event_histogram mas com paletas selecionáveis e modos de janela deslizante vs. limpeza automática) e um mapa de frequência por pixel alimentado por um filtro IIR passa-banda — útil para detetar sinais periódicos (ventoinhas giratórias, LEDs intermitentes, etc.) diretamente no fluxo de eventos.

Inclui dois scripts de transmissão na câmara:

  • Modo processado (genx320_event_mode_streaming_on_cam.py) — a câmara descodifica os eventos com csi.IOCTL_GENX320_READ_EVENTS e transmite cada linha como 12 bytes via USB ([0] tipo, [1] sec, [2] ms, [3] us, [4] x, [5] y). Fácil de consumir no PC porque o formato de transmissão corresponde ao formato ndarray na câmara.

  • Modo bruto (genx320_raw_event_mode_streaming_on_cam.py) — a câmara transmite as palavras de evento nativas de 32 bits compactadas do chip através de csi.IOCTL_GENX320_READ_EVENTS_RAW. São 4 bytes por evento versus 12 no modo processado (cerca de 3x menos dados via USB), portanto ~3x maior taxa de eventos alcançável quando a ligação é o bottleneck. O PC descodifica as palavras compactadas para o mesmo esquema de evento de 6 colunas usando numpy vetorizado, pelo que o código do visualizador a jusante é idêntico.

O modo bruto é o predefinido na GUI porque o débito USB é o fator limitante às taxas que o GenX320 pode produzir; mude para o modo processado se precisar de incorporar lógica de processamento no script na câmara.