Мультиспектральная событийная камера

Модуль мультиспектральной событийной камеры объединяет событийный датчик GENX320 с 1-мегапиксельным цветным датчиком PAG7936 с глобальным затвором на одном модуле — синхронизированный конвейер событий и цвета для высокоскоростного отслеживания объектов, отслеживания светодиодов, потоков жидкости и других динамических сцен.

Мультиспектральная событийная камера

Полную техническую документацию, фотографии и информацию для заказа см. на странице продукта Multispectral Event Camera.

Примечание

Поддерживается только на OpenMV N6.

Основные особенности

  • Событийный датчик 320x320, динамический диапазон >140 дБ, гистограммы от 375 Гц

  • Цвет PAG7936: 1280x800 @ 120 FPS, 640x400 @ 240 FPS

  • Синхронизированные временные метки событий с общим триггером экспозиции

  • Видит при освещённости ниже 5 люкс без автоэкспозиции

  • Энергопотребление начинается от ~3 мВт при потоковой передаче событий

  • Ориентирован на высокоскоростное отслеживание, отслеживание светодиодов и потоки жидкостей/частиц

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

Цветной датчик и событийный датчик GENX320 каждый получают свой собственный экземпляр csi.CSI. Первый вызов по умолчанию обращается к основному датчику (PAG7936); второй привязывается к GENX320 путём передачи cid= csi.GENX320. Выполните аппаратный сброс цветного датчика с помощью csi.CSI.reset (hard=True), чтобы подать питание, и настройте GENX320 с hard=False, чтобы его драйвер только перепрограммировал чип без повторного переключения сброса.

GENX320 выдаёт 320x320 в режиме гистограммы; PAG7936 при csi.QVGA выдаёт 320x200. Базовое наложение ниже обрезает нижние 120 строк кадра GENX320. Используйте гомографическое преобразование (ниже) для подогнанного наложения или большего размера кадра PAG7936.

Два рабочих буфера остаются постоянными в течение цикла кадра — альфа-палитра 256x1, хранящаяся как image.Image, чтобы пиксели гистограммы на базовой линии средне-серого (128) становились прозрачными, а как подсветки ON-событий, так и тени OFF-событий становились непрозрачными, и буфер кадра GENX320, предварительно выделенный с помощью image.Image, чтобы csi.CSI.snapshot (blocking=False, image=...) мог заполнять его на месте на каждой итерации без повторного выделения:

import time
import csi
import image
import math

# V-shaped alpha: pixels far from the baseline 128 become opaque.
alpha_pal = image.Image(256, 1, image.GRAYSCALE)
for i in range(256):
    alpha_pal[i] = int(math.pow(abs(i - 128) / 128.0, 2) * 255)

# Setup the color camera sensor.
csi0 = csi.CSI()
csi0.reset(hard=True)  # force hardware reset.
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

csi1 = csi.CSI(cid=csi.GENX320)
csi1.reset(hard=False)  # no hardware reset - just configure GENX320
csi1.pixformat(csi.GRAYSCALE)
csi1.framesize((320, 320))
csi1.brightness(128)  # histogram baseline (default)
csi1.contrast(64)     # per-event step

clock = time.clock()

img1 = image.Image(csi1.width(), csi1.height(), csi1.pixformat())

while True:
    clock.tick()
    img0 = csi0.snapshot()
    csi1.snapshot(blocking=False, image=img1)
    img0.draw_image(img1, 0, 0, color_palette=image.PALETTE_EVT_LIGHT,
                    alpha_palette=alpha_pal,
                    hint=image.BILINEAR)
    print(clock.fps())

Каждая итерация делает блокирующий цветной снимок и неблокирующий снимок GENX320. Затем Image.draw_image объединяет их: color_palette= image.PALETTE_EVT_LIGHT (или image.PALETTE_EVT_DARK для тёмного фона) отображает гистограмму GENX320 в оттенках серого в цветовую шкалу, alpha_palette= смешивает каждый пиксель с помощью V-образной альфа-карты, чтобы тихие области сцены пропускали цветное изображение, а hint= image.BILINEAR сглаживает увеличение масштаба, когда цветной датчик работает с более высоким разрешением, чем GENX320.

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

Выравнивание с GPU-ускорением

Image.draw_image принимает аргумент transform= — матрицу гомографии 3x3 в виде двумерного массива ulab.numpy. На OpenMV N6 GPU выполняет попиксельную проекцию во время того же рисования, поэтому кадр GENX320 можно повторно выровнять относительно перспективы цветной камеры без отдельного прохода деформации — полезно, когда два датчика имеют немного разную оптику или поля зрения, либо когда цветная камера работает с более высоким разрешением. Откалибруйте матрицу для каждой камеры с помощью инструмента калибровки наложения GenX320, который отображает мерцающую шахматную доску, чтобы событийный датчик создавал угловые события без какого-либо физического движения:

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

# Calibration matrix from the GenX320 Overlay Calibration tool.
m = np.array([
    [2.000000, 0.000000,   0.000000],
    [0.000000, 2.000000,  80.000000],
    [0.000000, 0.000000,   1.000000],
])

alpha_pal = image.Image(256, 1, image.GRAYSCALE)
for i in range(256):
    alpha_pal[i] = int(math.pow(abs(i - 128) / 128.0, 2) * 255)

# Setup the color camera sensor.
csi0 = csi.CSI()
csi0.reset(hard=True)
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)

csi1 = csi.CSI(cid=csi.GENX320)
csi1.reset(hard=False)
csi1.pixformat(csi.GRAYSCALE)
csi1.framesize((320, 320))
csi1.brightness(128)
csi1.contrast(64)

clock = time.clock()

img1 = image.Image(csi1.width(), csi1.height(), csi1.pixformat())

while True:
    clock.tick()
    img0 = csi0.snapshot()
    csi1.snapshot(blocking=False, image=img1)
    img0.draw_image(img1, 0, 0, color_palette=image.PALETTE_EVT_LIGHT,
                    alpha_palette=alpha_pal,
                    hint=image.BILINEAR,
                    transform=m)
    print(clock.fps())

Этот вариант запускает цветную камеру в csi.VGA (640x480), а GENX320 в его собственном разрешении 320x320 — гомография проецирует меньший кадр GENX320 в больший цветной кадр как часть рисования, поэтому коэффициент масштабирования встроен в саму матрицу, а не применяется отдельно.

Подробности о событийной камере

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 — значения по умолчанию из технической документации генерируют гораздо более высокую фоновую частоту событий, поэтому LOW_NOISE используется как отправная точка, чтобы поток оставался тихим. Вызовите csi.IOCTL_GENX320_SET_BIASES с другой предустановкой, когда приложению нужна большая чувствительность или полоса пропускания.

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

  • csi.GENX320_BIASES_DEFAULT — значения по умолчанию из технической документации 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

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

Откажитесь от визуализации, чтобы освободить CPU для обработки событий. Печатайте статистику только на каждой 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

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

Для визуализации в графическом интерфейсе в реальном времени на хост-ПК инструмент потоковой передачи событий GenX320 в репозитории openmv-projects объединяет камеру с фронтендом DearPyGui. Графический интерфейс ПК запускает две визуализации бок о бок: холст накопления событий (та же идея, что и 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, поэтому код визуализатора ниже по конвейеру идентичен.

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