GENX320 事件相机

GENX320 事件相机模块是一款 Prophesee 基于事件的视觉传感器,分辨率为 320x320,具有微秒级的时间精度。

GENX320 事件相机

完整的数据手册、照片和订购信息请参阅 GENX320 事件相机产品页面

备注

支持 OpenMV H7 Plus、RT1062 和 N6。

亮点

  • 320x320 基于事件的视觉传感器

  • 140 dB 动态范围,无运动模糊

  • 375 Hz 以上的事件直方图输出速率

  • 功耗随场景活动量变化——最低约 3 mW 起

  • 在 <5 lux 到强烈阳光下均可工作,无需自动曝光

  • 输出灰度帧或原始事件流

用法

GENX320 是一款基于事件的视觉传感器——它不会按固定的帧时钟读出整个 320x320 阵列,而是每个像素在检测到亮度变化的瞬间报告异步“事件”。每个事件携带一个 X/Y 坐标、一个 ON/OFF 极性(由亮变暗或由暗变亮)以及一个微秒级时间戳。这正是该传感器具备微秒级时间精度、无运动模糊、极高动态范围以及功耗随活动量变化等特性的来源。静态场景不会产生任何数据。

OpenMV 固件通过 csi.CSIcid= csi.GENX320 暴露 GENX320。可用两种工作模式:

  • 直方图模式(默认)——事件在芯片上累积到每像素的分箱中,并以可配置的速率(约 20-350 FPS)报告为 320x320 的灰度帧。传感器表现得像一台普通相机,因此所有标准图像处理例程(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.brightnesscsi.CSI.contrastcsi.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)滤波器

周期性光源(荧光灯、LED 驱动的显示屏)会产生大量冗余事件。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 —— 针对跟踪高对比度闪烁 LED 进行了调优。对比度阈值被提高,只有锐利的过渡才会触发;FO 和 HPF 被调高以最大化像素带宽并拒绝任何缓慢的环境漂移;REFR 被拉到 0,以便每个闪烁边沿都能连续捕获。其结果是:一条几乎全是 LED 边沿、易于跟踪的事件流。

  • csi.GENX320_BIASES_LOW_NOISE —— 驱动程序默认值。相对于 DEFAULT,两个对比度阈值都被提高(灵敏度更低),FO 被降低(像素更慢 = 像素更安静)。最适合静态或缓慢的场景,否则虚假事件会占据主导。

  • csi.GENX320_BIASES_HIGH_SPEED —— FO 被调高,使每个像素都能更快响应;HPF 被提高以拒绝缓慢的亮度漂移;REFR 被提高,以免单个快速移动的边沿淹没读出——更长的死区时间可在剧烈运动下将事件量保持在有限范围内。

可使用 csi.IOCTL_GENX320_SET_BIAS 加上 csi.GENX320_BIAS_DIFF_ONcsi.GENX320_BIAS_DIFF_OFFcsi.GENX320_BIAS_FOcsi.GENX320_BIAS_HPFcsi.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)

跟踪

由于直方图模式的输出只是一张灰度图像,常规的色块跟踪可直接使用。要跟踪一个有源标记 LED,请加载有源标记偏置预设,并在直方图的明亮端查找色块:

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_EVENTPIX_ON_EVENT;触发类型让你能够将事件与外部定时信号关联起来。

分配事件缓冲区时使用形状 (EVT_res, 6),其中 EVT_res 是 1024 到 65536 之间的 2 的幂,然后通过 csi.IOCTL_GENX320_SET_MODE 并配合 csi.GENX320_MODE_EVENT 和缓冲区大小进入事件模式。使用 csi.IOCTL_GENX320_READ_EVENTS 读取事件,它会将缓冲区填充至其容量上限,并返回有效行数。

Image.draw_event_histogram 会将事件光栅化为一张灰度图像——对于每个 ON 事件,它会将 contrast 加到分箱上;对于每个 OFF 事件则减去。clear=True 会先将图像重置为 brightnessclear=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 次迭代打印一次统计信息——在高事件速率下,每次迭代都推送一行打印输出会成为瓶颈:

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

缓冲区深度

当事件速率激增时,默认的三缓冲流水线会优先保留最新的帧并丢弃旧帧。可通过 csi.CSI.framebuffers 增加 FIFO 深度以改为对事件排队——代价是当主机落后时会处理稍旧的数据:

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

桌面端流式传输与可视化

若需在主机 PC 上进行实时 GUI 可视化,openmv-projects 仓库中的 GenX320 事件流式传输工具 将相机与 DearPyGui 前端配对。PC GUI 并排运行两种可视化:一个事件累积画布(与 Image.draw_event_histogram 思路相同,但带有可选的调色板以及滑动窗口与自动清除两种模式)和一个由 IIR 带通滤波器驱动的每像素频率图——可用于直接在事件流中发现周期性信号(旋转的风扇、闪烁的 LED 等)。

它随附两个在相机上运行的流式传输脚本:

  • 处理模式genx320_event_mode_streaming_on_cam.py)—— 相机用 csi.IOCTL_GENX320_READ_EVENTS 解码事件,并通过 USB 将每一行以 12 字节流式传输([0] 类型、[1] 秒、[2] 毫秒、[3] 微秒、[4] x、[5] y)。由于线缆格式与相机上的 ndarray 格式一致,在 PC 上易于消费。

  • 原始模式genx320_raw_event_mode_streaming_on_cam.py)—— 相机通过 csi.IOCTL_GENX320_READ_EVENTS_RAW 流式传输芯片原生的 32 位打包事件字。每个事件 4 字节,而处理模式中为 12 字节(USB 上的数据约少 3 倍),因此当链路成为瓶颈时可实现约 3 倍的事件速率。PC 使用向量化的 numpy 将打包字解码回相同的六列事件布局,因此下游可视化代码完全相同。

在 GUI 中原始模式是默认值,因为在 GenX320 能产生的速率下,USB 吞吐量是制约因素;如果你需要将处理逻辑接入相机上的脚本,则切换到处理模式。