Cámara de eventos GENX320¶
El módulo de cámara de eventos GENX320 es un sensor de visión basado en eventos de Prophesee con resolución de 320x320 y precisión temporal de microsegundos.
Para ver la hoja de datos completa, fotos e información de pedido, consulta la página de producto de la cámara de eventos GENX320.
Nota
Compatible con la OpenMV H7 Plus, RT1062 y N6.
Aspectos destacados¶
Sensor de visión basado en eventos de 320x320
Rango dinámico de 140 dB, sin desenfoque de movimiento
Tasa de salida de histograma de eventos de 375 Hz o más
El consumo escala con la actividad de la escena: comienza en ~3 mW
Funciona desde <5 lux hasta luz solar intensa sin exposición automática
Genera fotogramas en escala de grises o flujos de eventos sin procesar
Uso¶
El GENX320 es un sensor de visión basado en eventos: en lugar de leer toda la matriz de 320x320 con un reloj de fotogramas fijo, cada píxel reporta «eventos» asíncronos en el instante en que detecta un cambio de brillo. Cada evento lleva una coordenada X/Y, una polaridad ON/OFF (brillante→oscuro u oscuro→brillante) y una marca de tiempo en microsegundos. De ahí provienen la precisión temporal de microsegundos del sensor, la ausencia de desenfoque de movimiento, el rango dinámico muy alto y el consumo de energía escalado con la actividad. Las escenas estáticas no generan datos.
El firmware de OpenMV expone el GENX320 a través de csi.CSI con cid= csi.GENX320. Hay dos modos de funcionamiento disponibles:
Modo histograma (predeterminado): los eventos se acumulan en el chip en contenedores por píxel y se reportan como un fotograma en escala de grises de 320x320 a una tasa configurable (~20-350 FPS). El sensor se comporta como una cámara normal, por lo que todas las rutinas estándar de procesamiento de imágenes (
Image.find_blobs, paletas, etc.) funcionan directamente.Modo evento: los eventos sin procesar se transmiten a un
ndarrayde numpy con marcas de tiempo completas en microsegundos, para aplicaciones que necesitan el detalle temporal en lugar de un fotograma preagrupado.
Modo histograma¶
En modo histograma, el GENX320 genera fotogramas en escala de grises donde cada píxel codifica la actividad reciente de eventos en esa ubicación. Los píxeles por encima de la línea base de brillo son eventos ON (brillo en aumento), y por debajo son eventos OFF (brillo en descenso). El brillo de línea base predeterminado es 128 y el paso de contraste por evento es 16; aumenta el contraste para que los eventos resalten:
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 y csi.CSI.framerate son los tres controles que dan forma a la salida del histograma.
Salida coloreada¶
Establece csi.CSI.color_palette en image.PALETTE_EVT_LIGHT para un fondo claro o image.PALETTE_EVT_DARK para uno oscuro: el controlador emite fotogramas RGB565 usando la paleta directamente:
csi0.color_palette(image.PALETTE_EVT_LIGHT)
Calibración de píxeles calientes¶
Los sensores de eventos acumulan «píxeles calientes» que se disparan de forma espuria. Ejecuta csi.IOCTL_GENX320_CALIBRATE sobre una escena estática para desactivarlos. El controlador construye un recuento de aciertos por píxel de 320x320, calcula la media y la desviación estándar, y desactiva cualquier píxel cuyo recuento esté por encima de mean + sigma * stddev; entonces los píxeles desactivados dejan de emitir eventos a nivel del sensor.
Dos parámetros controlan la calibración:
event_count: cuántos eventos contar antes de calcular las estadísticas. El bucle captura fotogramas hasta que el total acumulado de eventos supera este presupuesto. Recuentos más altos dan una estimación más fiable a costa de un tiempo de calibración mayor.10000es un buen punto de partida.sigma: multiplicador de umbral sobre la desviación estándar. Los valores más bajos son más agresivos (más píxeles desactivados); los valores más altos son más conservadores.0.5es un buen valor predeterminado.
Apunta primero el sensor a una escena estática para que los eventos provocados por el movimiento no se cuenten en contra de píxeles que en realidad están bien:
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-parpadeo (AFK)¶
Las fuentes de luz periódicas (fluorescentes, pantallas con LED) generan enormes volúmenes de eventos redundantes. El filtro AFK rechaza los eventos cuyo píxel conmuta a una frecuencia dentro de una banda; actívalo mediante csi.IOCTL_GENX320_SET_AFK con los bordes de la banda en hercios:
csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 1, 130, 160) # 130-160 Hz
csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 0) # disable
Preajustes de polarización (bias)¶
Cada píxel del GenX320 ejecuta un front-end analógico con varias polarizaciones (biases) configurables. Juntas gobiernan la sensibilidad, el ruido, el ancho de banda del píxel y la tasa de eventos; la combinación correcta depende de la escena. Las polarizaciones individuales son:
DIFF_ON: el umbral de contraste del comparador positivo. Un píxel emite un evento ON cuando su iluminación logarítmica ha aumentado en esta cantidad. Más bajo = más sensible a las transiciones brillantes.
DIFF_OFF: el umbral de contraste del comparador negativo (la contraparte simétrica para los eventos OFF). Más bajo = más sensible a las transiciones oscuras.
FO: la frecuencia de corte de paso bajo del píxel. Más alto = mayor ancho de banda del píxel (respuesta más rápida, menor latencia) pero más actividad por ruido de fondo.
HPF: la frecuencia de corte de paso alto. Más alto = rechazo más fuerte de cambios de brillo lentos; solo las transiciones rápidas llegan a los comparadores. Útil para ignorar la deriva ambiental.
REFR: el periodo refractario. Después de que un píxel se dispara, permanece en reinicio durante este tiempo antes de poder dispararse de nuevo. Más alto = mayor tiempo muerto, útil para limitar la tasa de eventos por píxel.
Después de csi.CSI.reset, el controlador aplica csi.GENX320_BIASES_LOW_NOISE, no csi.GENX320_BIASES_DEFAULT: los valores predeterminados de la hoja de datos emiten una tasa de eventos de fondo mucho más alta, por lo que se usa LOW_NOISE como punto de partida para mantener el flujo silencioso. Llama a csi.IOCTL_GENX320_SET_BIASES con un preajuste diferente cuando la aplicación necesite más sensibilidad o ancho de banda.
csi.IOCTL_GENX320_SET_BIASES aplica uno de cinco preajustes:
csi.GENX320_BIASES_DEFAULT: valores predeterminados de la hoja de datos del GenX320. Sensibilidad, ruido y ancho de banda equilibrados para escenas generales.csi.GENX320_BIASES_LOW_LIGHT: ambos umbrales de contraste relajados para una mayor sensibilidad, FO reducido para mantener bajo el ruido, y HPF establecido en 0 para que los cambios de brillo lentos sigan registrándose; una escena con poca luz genera pocos eventos por sí sola, así que queremos que pasen tantos como sea posible.csi.GENX320_BIASES_ACTIVE_MARKER: ajustado para rastrear LED parpadeantes de alto contraste. Umbrales de contraste elevados para que solo las transiciones nítidas se disparen; FO y HPF subidos al máximo para maximizar el ancho de banda del píxel y rechazar cualquier deriva ambiental lenta; REFR llevado a 0 para que cada borde de parpadeo se capture de forma consecutiva. El resultado: un flujo que es casi todo bordes de LED, fácil de rastrear.csi.GENX320_BIASES_LOW_NOISE: predeterminado del controlador. Ambos umbrales de contraste elevados respecto aDEFAULT(menos sensible) y FO reducido (píxel más lento = píxel más silencioso). Mejor para escenas estáticas o lentas donde los eventos falsos dominarían de otro modo.csi.GENX320_BIASES_HIGH_SPEED: FO aumentado para que cada píxel pueda responder más rápido, HPF elevado para rechazar la deriva de brillo lenta, y REFR elevado para que un único borde de movimiento rápido no inunde la lectura; el mayor tiempo muerto mantiene acotado el volumen de eventos bajo movimiento intenso.
Anula polarizaciones individuales con csi.IOCTL_GENX320_SET_BIAS más una de csi.GENX320_BIAS_DIFF_ON, csi.GENX320_BIAS_DIFF_OFF, csi.GENX320_BIAS_FO, csi.GENX320_BIAS_HPF o csi.GENX320_BIAS_REFR y un valor de DAC. Cada polarización se establece de forma independiente: elige un preajuste como punto de partida y luego ajusta las polarizaciones que tu escena necesite:
csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_LOW_LIGHT)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIAS, csi.GENX320_BIAS_HPF, 20)
Rastreo¶
Como la salida del modo histograma es simplemente una imagen en escala de grises, el rastreo normal de manchas (blobs) funciona directamente. Para rastrear un LED de marcador activo, carga el preajuste de polarización de marcador activo y busca manchas en el extremo brillante del 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¶
El modo evento omite el histograma del chip y transmite eventos sin procesar a un ndarray de numpy. Cada evento es una fila de seis columnas uint16:
[0]tipo de evento: ver más abajo[1]marca de tiempo en segundos[2]marca de tiempo en milisegundos[3]marca de tiempo en microsegundos[4]coordenada X, 0-319[5]coordenada Y, 0-319
El controlador emite seis tipos de evento en la columna [0]:
csi.PIX_OFF_EVENT: un píxel detectó una disminución de brillo (se cruzó el umbral del comparadorDIFF_OFF). X/Y apuntan al píxel que se disparó.csi.PIX_ON_EVENT: un píxel detectó un aumento de brillo (se cruzó el umbralDIFF_ON). X/Y apuntan al píxel.csi.EXT_TRIGGER_FALLING: el pin de disparo externo del sensor vio un flanco de bajada. X/Y no se usan.csi.EXT_TRIGGER_RISING: el pin de disparo externo del sensor vio un flanco de subida. X/Y no se usan.csi.RST_TRIGGER_FALLING: disparo de reinicio de píxel, flanco de bajada. X/Y no se usan. El firmware no lo genera por el momento.csi.RST_TRIGGER_RISING: disparo de reinicio de píxel, flanco de subida. X/Y no se usan. El firmware no lo genera por el momento.
La entrada de disparo externo del GENX320 está conectada a la línea de sincronización de fotogramas de la cámara, que también se enruta a P10 tanto en el procesador como en el conector de pines: controla P10 para inyectar flancos de sincronización en el flujo de eventos y captarlos como eventos EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING junto a los datos de píxeles.
A la mayoría de las aplicaciones solo les importan PIX_OFF_EVENT y PIX_ON_EVENT; los tipos de disparo te permiten correlacionar eventos con señales de temporización externas.
Asigna el búfer de eventos con forma (EVT_res, 6) donde EVT_res es una potencia de dos entre 1024 y 65536, luego entra en modo evento mediante csi.IOCTL_GENX320_SET_MODE con csi.GENX320_MODE_EVENT y el tamaño del búfer. Lee los eventos con csi.IOCTL_GENX320_READ_EVENTS, que llena el búfer hasta su capacidad y devuelve el número de filas válidas.
Image.draw_event_histogram rasteriza los eventos en una imagen en escala de grises: por cada evento ON suma contrast al contenedor; por cada evento OFF lo resta. clear=True reinicia primero la imagen a brightness; clear=False acumula a lo largo de muchas llamadas:
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())
Los preajustes de polarización del modo histograma, el filtro AFK y los ioctls de calibración de píxeles calientes funcionan todos de la misma manera en modo evento: llámalos después de csi.IOCTL_GENX320_SET_MODE.
Filtrado por polaridad¶
Recorta el arreglo de eventos con ulab para conservar solo los eventos ON (movimiento hacia un estado más brillante) o solo los 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)
Acumulación de larga exposición¶
Establece clear=False para seguir apilando eventos en la misma imagen a lo largo de muchos fotogramas: el resultado es una visualización de estela de movimiento. Reinicia periódicamente para comenzar una nueva exposición:
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
Procesamiento de alta velocidad¶
Elimina la visualización para liberar CPU para el procesamiento de eventos. Imprime estadísticas solo cada N iteraciones: enviar una línea de impresión en cada iteración se convierte en el cuello de botella a tasas de eventos altas:
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 espacio-temporal (STC)¶
Un borde de contraste en movimiento real tiende a desencadenar una ráfaga ruidosa de eventos en el mismo píxel dentro de una ventana de tiempo corta: el desajuste de píxeles y el ruido analógico producen eventos adicionales alrededor de la transición genuina que no son útiles para la aplicación. El filtro STC es un posprocesado en el chip que conserva solo uno (o unos pocos) eventos por ráfaga y descarta el resto.
Implementa tres estrategias, seleccionadas mediante csi.IOCTL_GENX320_SET_STC y una constante GENX320_STC_*. Cada modo se define por qué eventos reenvía de una ráfaga:
Modo |
Conserva |
Descarta |
|---|---|---|
todos los eventos |
nada |
|
el segundo evento de una ráfaga |
el primero + los eventos posteriores |
|
el primer evento de una ráfaga |
los eventos subsiguientes |
|
el primer borde + los bordes subsiguientes |
solo el ruido redundante |
En detalle:
csi.GENX320_STC_DISABLE: filtro desactivado, todos los eventos pasan (predeterminado).csi.GENX320_STC_ONLY: conserva el segundo evento de una ráfaga. Parámetro:stc_threshold(ms). Si un nuevo evento en un píxel llega dentro destc_thresholdrespecto a un evento anterior, se considera el «segundo» de una ráfaga y se reenvía; el primer evento y cualquier evento posterior de la misma ráfaga se filtran. Mejor cuando quieres una transición confirmada por ruido en lugar del primerísimo impacto.csi.GENX320_STC_TRAIL_ONLY: conserva el primer evento de una ráfaga. Parámetro:trail_threshold(ms). Después de que un píxel se dispara, los eventos posteriores en el mismo píxel se descartan hasta que haya transcurridotrail_threshold. Preserva el momento preciso del flanco de inicio: útil cuando el momento del cambio de polaridad importa más que la confirmación de la ráfaga.csi.GENX320_STC_TRAIL: combina ambos. Parámetros:stc_thresholdytrail_threshold(ambos en ms). Conserva el flanco de inicio según el modo Trail más los bordes subsiguientes según el modo STC, por lo que múltiples eventos de una ráfaga siguen pasando: mayor rendimiento de eventos que los filtros de modo único pero con la señal más rica.
Los dos umbrales deben mantenerse dentro de una proporción de aproximadamente 13:1; el sensor rechaza configuraciones en las que uno es más de ~13 veces el otro:
csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_TRAIL, 1, 2)
csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_DISABLE)
Profundidad del búfer¶
Cuando las tasas de eventos se disparan, el pipeline predeterminado de triple búfer favorece el fotograma más reciente y descarta los antiguos. Aumenta la profundidad de la FIFO mediante csi.CSI.framebuffers para encolar eventos en su lugar, a costa de procesar datos ligeramente más antiguos cuando el host se queda atrás:
csi0.framebuffers(10) # FIFO depth, > 3 enables queueing
Streaming y visualización en el escritorio¶
Para la visualización GUI en tiempo real en un PC anfitrión, la herramienta de streaming de eventos GenX320 del repositorio openmv-projects empareja la cámara con un front-end de DearPyGui. La GUI del PC ejecuta dos visualizaciones en paralelo: un lienzo de acumulación de eventos (la misma idea que Image.draw_event_histogram pero con paletas seleccionables y modos de ventana deslizante frente a autoborrado) y un mapa de frecuencia por píxel impulsado por un filtro de paso de banda IIR, útil para detectar señales periódicas (ventiladores giratorios, LED parpadeantes, etc.) directamente en el flujo de eventos.
Incluye dos scripts de streaming en la cámara:
Modo procesado (
genx320_event_mode_streaming_on_cam.py): la cámara decodifica los eventos concsi.IOCTL_GENX320_READ_EVENTSy transmite cada fila como 12 bytes por USB ([0]tipo,[1]seg,[2]ms,[3]us,[4]x,[5]y). Fácil de consumir en el PC porque el formato de cable coincide con el formato ndarray de la cámara.Modo sin procesar (
genx320_raw_event_mode_streaming_on_cam.py): la cámara transmite las palabras de evento empaquetadas nativas de 32 bits del chip a través decsi.IOCTL_GENX320_READ_EVENTS_RAW. Eso son 4 bytes por evento frente a 12 en el modo procesado (unas 3 veces menos datos por USB), por lo que se alcanza una tasa de eventos ~3 veces mayor cuando el enlace es el cuello de botella. El PC decodifica las palabras empaquetadas de vuelta a la misma disposición de eventos de 6 columnas usando numpy vectorizado, de modo que el código del visualizador posterior es idéntico.
El modo sin procesar es el predeterminado en la GUI porque el rendimiento del USB es la restricción vinculante a las tasas que el GenX320 puede producir; cambia al modo procesado si necesitas conectar lógica de procesamiento en el script de la cámara.