Событийная камера GENX320

Модуль событийной камеры GENX320 — это событийный датчик зрения Prophesee с разрешением 320x320 и микросекундной временной точностью.

Событийная камера GENX320

Полный datasheet, фотографии и информацию для заказа см. на странице продукта GENX320 Event Camera.

Примечание

Поддерживается на OpenMV H7 Plus, RT1062 и N6.

Ключевые особенности

  • Событийный датчик зрения 320x320

  • Динамический диапазон 140 дБ, отсутствие смазывания при движении

  • Скорость вывода событийных гистограмм от 375 Гц

  • Энергопотребление масштабируется с активностью сцены — начинается с ~3 мВт

  • Работает от <5 люкс до яркого солнечного света без автоэкспозиции

  • Выводит кадры в оттенках серого или необработанные потоки событий

Использование

GENX320 — это событийный датчик зрения: вместо считывания всего массива 320x320 по фиксированному кадровому тактовому сигналу каждый пиксель сообщает об асинхронных «событиях» в момент обнаружения изменения яркости. Каждое событие несёт координату X/Y, полярность ON/OFF (светлее→темнее или темнее→светлее) и микросекундную метку времени. Отсюда и берутся микросекундная временная точность датчика, отсутствие смазывания при движении, очень высокий динамический диапазон и масштабируемое по активности энергопотребление. Статичные сцены не генерируют данных.

Прошивка OpenMV предоставляет доступ к GENX320 через csi.CSI с cid= csi.GENX320. Доступны два режима работы:

  • Режим гистограммы (по умолчанию) — события накапливаются на кристалле в бины по пикселям и выдаются как кадр 320x320 в оттенках серого с настраиваемой частотой (~20-350 FPS). Датчик ведёт себя как обычная камера, поэтому все стандартные процедуры обработки изображений (Image.find_blobs, палитры и т. д.) работают напрямую.

  • Событийный режим — необработанные события поступают в numpy ndarray с полными микросекундными метками времени для приложений, которым нужна временная детализация, а не предварительно разбитый на бины кадр.

Режим гистограммы

В режиме гистограммы GENX320 выводит кадры в оттенках серого, где каждый пиксель кодирует недавнюю событийную активность в данной точке. Пиксели выше базового уровня яркости — это события ON (яркость растёт), ниже — события OFF (яркость падает). Базовая яркость по умолчанию равна 128, а шаг контраста на событие равен 16 — увеличьте контраст, чтобы события выделялись:

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 и csi.CSI.framerate — это три регулятора, формирующие вывод гистограммы.

Цветной вывод

Установите csi.CSI.color_palette в image.PALETTE_EVT_LIGHT для светлого фона или в image.PALETTE_EVT_DARK для тёмного — драйвер выдаёт кадры RGB565, используя палитру напрямую:

csi0.color_palette(image.PALETTE_EVT_LIGHT)

Калибровка горячих пикселей

Событийные датчики накапливают «горячие пиксели», которые срабатывают ложно. Запустите csi.IOCTL_GENX320_CALIBRATE на статичной сцене, чтобы отключить их. Драйвер строит карту попаданий по пикселям 320x320, вычисляет среднее значение и стандартное отклонение и отключает любой пиксель, чей счётчик выше mean + sigma * stddev — после этого отключённые пиксели перестают выдавать события на уровне датчика.

Калибровкой управляют два параметра:

  • event_count — сколько событий подсчитать перед вычислением статистики. Цикл захватывает кадры, пока текущая суммарная сумма событий не превысит этот бюджет. Большие значения дают более надёжную оценку ценой более длительного времени калибровки. 10000 — разумная отправная точка.

  • sigma — множитель порога для стандартного отклонения. Меньшие значения более агрессивны (отключается больше пикселей); большие значения более консервативны. 0.5 — хорошее значение по умолчанию.

Сначала наведите датчик на статичную сцену, чтобы события, вызванные движением, не засчитывались пикселям, которые на самом деле в порядке:

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")

Фильтр подавления мерцания (AFK)

Периодические источники света (люминесцентные лампы, дисплеи со светодиодной подсветкой) генерируют огромные объёмы избыточных событий. Фильтр AFK отклоняет события, в которых пиксель переключается с частотой внутри заданной полосы — включите его через csi.IOCTL_GENX320_SET_AFK, указав границы полосы в герцах:

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

Предустановки смещений

Каждый пиксель в GenX320 работает с аналоговым входным каскадом с несколькими настраиваемыми смещениями. Они совместно управляют чувствительностью, шумом, полосой пропускания пикселя и частотой событий — правильная комбинация зависит от сцены. Отдельные смещения таковы:

  • DIFF_ON — порог контраста положительного компаратора. Пиксель выдаёт событие ON, когда его логарифмическая освещённость возросла на эту величину. Меньше = выше чувствительность к ярким переходам.

  • DIFF_OFF — порог контраста отрицательного компаратора (симметричный аналог для событий OFF). Меньше = выше чувствительность к тёмным переходам.

  • FO — частота среза фильтра нижних частот пикселя. Выше = шире полоса пропускания пикселя (более быстрый отклик, меньшая задержка), но больше активности фонового шума.

  • HPF — частота среза фильтра верхних частот. Выше = сильнее подавление медленных изменений яркости; до компараторов доходят только быстрые переходы. Полезно для игнорирования дрейфа окружающего освещения.

  • REFR — рефрактерный период. После срабатывания пиксель остаётся в состоянии сброса в течение этого времени, прежде чем сможет сработать снова. Выше = больше мёртвое время, полезно для ограничения частоты событий на пиксель.

После csi.CSI.reset драйвер применяет csi.GENX320_BIASES_LOW_NOISE, а не csi.GENX320_BIASES_DEFAULT — значения по умолчанию из datasheet выдают намного более высокую частоту фоновых событий, поэтому в качестве отправной точки используется LOW_NOISE, чтобы поток оставался тихим. Вызовите csi.IOCTL_GENX320_SET_BIASES с другой предустановкой, когда приложению нужна большая чувствительность или полоса пропускания.

csi.IOCTL_GENX320_SET_BIASES применяет одну из пяти предустановок:

  • csi.GENX320_BIASES_DEFAULT — значения по умолчанию из datasheet GenX320. Сбалансированные чувствительность, шум и полоса пропускания для общих сцен.

  • csi.GENX320_BIASES_LOW_LIGHT — оба порога контраста ослаблены для повышения чувствительности, FO понижен для снижения шума, а HPF установлен в 0, чтобы медленные изменения яркости всё ещё регистрировались — слабоосвещённая сцена сама по себе генерирует мало событий, поэтому мы хотим, чтобы их прошло как можно больше.

  • csi.GENX320_BIASES_ACTIVE_MARKER — настроена для отслеживания мигающих светодиодов с высоким контрастом. Пороги контраста повышены, чтобы срабатывали только резкие переходы; FO и HPF подняты до максимума, чтобы максимизировать полосу пропускания пикселя и отклонить любой медленный дрейф окружающего освещения; REFR установлен в 0, чтобы каждый фронт мигания захватывался один за другим. В результате получается поток, состоящий почти полностью из фронтов светодиода, который легко отслеживать.

  • csi.GENX320_BIASES_LOW_NOISE — значение по умолчанию драйвера. Оба порога контраста повышены по сравнению с DEFAULT (ниже чувствительность), а FO понижен (более медленный пиксель = более тихий пиксель). Лучше всего для статичных или медленных сцен, где ложные события иначе доминировали бы.

  • csi.GENX320_BIASES_HIGH_SPEED — FO повышен, чтобы каждый пиксель мог реагировать быстрее, HPF поднят для подавления медленного дрейфа яркости, а REFR поднят, чтобы один быстро движущийся фронт не переполнял считывание — более длительное мёртвое время удерживает объём событий ограниченным при интенсивном движении.

Переопределите отдельные смещения с помощью csi.IOCTL_GENX320_SET_BIAS плюс одно из csi.GENX320_BIAS_DIFF_ON, csi.GENX320_BIAS_DIFF_OFF, csi.GENX320_BIAS_FO, csi.GENX320_BIAS_HPF или csi.GENX320_BIAS_REFR и значение DAC. Каждое смещение задаётся независимо — выберите предустановку как отправную точку, затем подстройте те смещения, которые нужны вашей сцене:

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

Отслеживание

Поскольку вывод в режиме гистограммы — это просто изображение в оттенках серого, обычное отслеживание блобов работает напрямую. Чтобы отследить светодиод активного маркера, загрузите предустановку смещений активного маркера и найдите блобы в ярком конце гистограммы:

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())

Событийный режим

Событийный режим обходит гистограмму на кристалле и передаёт необработанные события в numpy ndarray. Каждое событие — это строка из шести столбцов uint16:

  • [0] тип события — см. ниже

  • [1] метка времени в секундах

  • [2] метка времени в миллисекундах

  • [3] метка времени в микросекундах

  • [4] координата X, 0-319

  • [5] координата Y, 0-319

Драйвер выдаёт шесть типов событий в столбце [0]:

  • csi.PIX_OFF_EVENT — пиксель обнаружил уменьшение яркости (был пересечён порог компаратора DIFF_OFF). X/Y указывают на сработавший пиксель.

  • csi.PIX_ON_EVENT — пиксель обнаружил увеличение яркости (был пересечён порог DIFF_ON). X/Y указывают на пиксель.

  • csi.EXT_TRIGGER_FALLING — вывод внешнего триггера датчика зафиксировал спадающий фронт. X/Y не используются.

  • csi.EXT_TRIGGER_RISING — вывод внешнего триггера датчика зафиксировал нарастающий фронт. X/Y не используются.

  • csi.RST_TRIGGER_FALLING — триггер сброса пикселей, спадающий фронт. X/Y не используются. В данный момент прошивкой не генерируется.

  • csi.RST_TRIGGER_RISING — триггер сброса пикселей, нарастающий фронт. X/Y не используются. В данный момент прошивкой не генерируется.

Вход внешнего триггера GENX320 подключён к линии кадровой синхронизации камеры, которая также выведена на P10 как на процессоре, так и на штыревом разъёме — подавайте сигнал на P10, чтобы вводить фронты синхронизации в поток событий и принимать их как события EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING наряду с данными пикселей.

Большинству приложений важны только PIX_OFF_EVENT и PIX_ON_EVENT; типы триггеров позволяют сопоставлять события с внешними сигналами синхронизации.

Выделите буфер событий формы (EVT_res, 6), где EVT_res — степень двойки от 1024 до 65536, затем войдите в событийный режим через csi.IOCTL_GENX320_SET_MODE с csi.GENX320_MODE_EVENT и размером буфера. Читайте события с помощью csi.IOCTL_GENX320_READ_EVENTS, который заполняет буфер до его ёмкости и возвращает количество действительных строк.

Image.draw_event_histogram растрирует события в изображение в оттенках серого — для каждого события ON он добавляет contrast к бину; для каждого события OFF — вычитает. clear=True сначала сбрасывает изображение к brightness; clear=False накапливает за множество вызовов:

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())

Предустановки смещений режима гистограммы, фильтр AFK и ioctl калибровки горячих пикселей работают точно так же в событийном режиме — вызывайте их после csi.IOCTL_GENX320_SET_MODE.

Фильтрация по полярности

Срежьте массив событий с помощью ulab, чтобы оставить только события ON (движение в более яркое состояние) или только события 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)

Накопление с длинной выдержкой

Установите clear=False, чтобы продолжать наслаивать события в одно и то же изображение за множество кадров — результатом будет визуализация шлейфа движения. Периодически сбрасывайте, чтобы начать новую выдержку:

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

Высокоскоростная обработка

Отбросьте визуализацию, чтобы освободить процессор для обработки событий. Печатайте статистику только на каждой N-й итерации — вывод строки print на каждой итерации становится узким местом при высоких частотах событий:

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")

Пространственно-временной контрастный (STC) фильтр

Реальный движущийся контрастный фронт обычно вызывает шумный всплеск событий на одном и том же пикселе в коротком временном окне — рассогласование пикселей и аналоговый шум производят дополнительные события вокруг подлинного перехода, которые бесполезны для приложения. STC-фильтр — это пост-обработка на кристалле, которая сохраняет только одно (или несколько) событий на всплеск и отбрасывает остальные.

Он реализует три стратегии, выбираемые через csi.IOCTL_GENX320_SET_STC и константу GENX320_STC_*. Каждый режим определяется тем, какие события он пропускает из всплеска:

Режим

Сохраняет

Отбрасывает

csi.GENX320_STC_DISABLE

каждое событие

ничего

csi.GENX320_STC_ONLY

второе событие всплеска

первое + последующие события

csi.GENX320_STC_TRAIL_ONLY

первое событие всплеска

последующие события

csi.GENX320_STC_TRAIL

первый + последующие фронты

только избыточный шум

Подробнее:

  • csi.GENX320_STC_DISABLE — фильтр выключен, каждое событие проходит насквозь (по умолчанию).

  • csi.GENX320_STC_ONLY — сохраняет второе событие всплеска. Параметр: stc_threshold (мс). Если новое событие на пикселе приходит в пределах stc_threshold от предыдущего события, оно считается «вторым» во всплеске и передаётся — первое событие и любые последующие события в том же всплеске отфильтровываются. Лучше всего, когда вам нужен подтверждённый шумом переход, а не самое первое срабатывание.

  • csi.GENX320_STC_TRAIL_ONLY — сохраняет первое событие всплеска. Параметр: trail_threshold (мс). После срабатывания пикселя последующие события на том же пикселе отбрасываются, пока не истечёт trail_threshold. Сохраняет точное время ведущего фронта — полезно, когда момент переключения полярности важнее подтверждения всплеска.

  • csi.GENX320_STC_TRAIL — объединяет оба. Параметры: stc_threshold и trail_threshold (оба в мс). Сохраняет ведущий фронт по режиму Trail плюс последующие фронты по режиму STC, так что несколько событий из всплеска всё равно проходят — выше пропускная способность по событиям, чем у одиночных фильтров, но самый богатый сигнал.

Два порога должны оставаться примерно в пределах соотношения 13:1 — датчик отклоняет конфигурации, где один больше другого более чем в ~13 раз:

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

Глубина буфера

Когда частота событий резко возрастает, тройной буферный конвейер по умолчанию отдаёт предпочтение последнему кадру и отбрасывает старые. Увеличьте глубину FIFO через csi.CSI.framebuffers, чтобы вместо этого ставить события в очередь — ценой обработки несколько более старых данных, когда хост отстаёт:

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

Потоковая передача и визуализация на ПК

Для визуализации в реальном времени через GUI на ПК инструмент GenX320 Event Streaming в репозитории openmv-projects соединяет камеру с фронтендом DearPyGui. GUI на ПК запускает две визуализации бок о бок: холст накопления событий (та же идея, что и Image.draw_event_histogram, но с выбираемыми палитрами и режимами скользящего окна или автоочистки) и карту частот по пикселям, управляемую полосовым IIR-фильтром — полезно для обнаружения периодических сигналов (вращающиеся вентиляторы, мигающие светодиоды и т. д.) прямо в потоке событий.

Он поставляется с двумя скриптами потоковой передачи на камере:

  • Обработанный режим (genx320_event_mode_streaming_on_cam.py) — камера декодирует события с помощью csi.IOCTL_GENX320_READ_EVENTS и передаёт каждую строку как 12 байт по USB ([0] тип, [1] сек, [2] мс, [3] мкс, [4] x, [5] y). Легко обрабатывается на ПК, потому что формат на проводе совпадает с форматом ndarray на камере.

  • Необработанный режим (genx320_raw_event_mode_streaming_on_cam.py) — камера передаёт собственные 32-битные упакованные событийные слова чипа через csi.IOCTL_GENX320_READ_EVENTS_RAW. Это 4 байта на событие против 12 в обработанном режиме (примерно в 3 раза меньше данных по USB), поэтому достижимая частота событий выше в ~3 раза, когда канал является узким местом. ПК декодирует упакованные слова обратно в ту же 6-столбцовую раскладку событий, используя векторизованный numpy, так что код визуализатора на стороне приёмника идентичен.

Необработанный режим используется в GUI по умолчанию, потому что пропускная способность USB является связывающим ограничением при частотах, которые может выдавать GenX320; переключитесь на обработанный режим, если вам нужно встроить логику обработки в скрипт на камере.