多光譜事件相機

多光譜事件相機模組(Multispectral Event Camera Module)在單一模組上將 GENX320 事件感測器與一顆 1 MP 的 PAG7936 全域快門彩色感測器配對在一起,提供同步的事件加色彩管線,適用於高速物件追蹤、LED 追蹤、流體流動以及其他動態場景。

多光譜事件相機

完整的資料表、照片與訂購資訊請參閱 多光譜事件相機產品頁面

備註

僅 OpenMV N6 支援。

重點特色

  • 320x320 事件感測器,>140 dB 動態範圍,375 Hz 以上的直方圖

  • PAG7936 色彩:1280x800 @ 120 FPS,640x400 @ 240 FPS

  • 具備共用曝光觸發的同步事件時間戳記

  • 在低於 5 lux 的環境下無需自動曝光即可觀察

  • 事件串流的功耗起始於約 3 mW

  • 鎖定高速追蹤、LED 追蹤以及流體/粒子流動等應用

用法

彩色感測器與 GENX320 事件感測器各自取得專屬的 csi.CSI 實例。第一次呼叫預設為主感測器(PAG7936);第二次則透過傳入 cid= csi.GENX320 來繫結 GENX320。以 csi.CSI.reset (hard=True) 對彩色感測器進行硬重置以拉起電源軌,並以 hard=False 設定 GENX320,使其驅動程式只重新編程晶片而不再切換重置。

GENX320 在直方圖模式下輸出 320x320;PAG7936 在 csi.QVGA 下輸出 320x200。下方的基本疊加層會裁掉 GENX320 影格底部的 120 列。若需要貼合的疊加層或更大的 PAG7936 影格大小,請使用單應性變換(見下方)。

兩個暫存緩衝區在整個影格迴圈中保持不變:一個儲存為 image.Image 的 256x1 alpha 調色盤,讓位於中灰基線(128)的直方圖像素變為透明,而 ON 事件高光與 OFF 事件陰影皆變為不透明;以及一個以 image.Image 預先配置的 GENX320 影格緩衝區,讓 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 形 alpha 對應表混合每個像素,使場景中的安靜區域透出彩色影像,而 hint= image.BILINEAR 在彩色感測器以高於 GENX320 的解析度執行時平滑放大效果。

GENX320 的偏壓預設值、AFK 濾波器、熱像素校正以及 STC 濾波器的 ioctl 在這個雙相機設定中的運作方式都相同——在 csi.CSI.reset 之後對 csi1 呼叫它們即可。詳情請參閱下方各節。

GPU 加速對齊

Image.draw_image 接受一個 transform= 引數——一個以 2 維 ulab.numpy 陣列表示的 3x3 單應性矩陣。在 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 韌體透過帶有 cid= csi.GENX320csi.CSI 來公開 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.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)濾波器

週期性光源(螢光燈、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_RISINGEXT_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 位元封裝事件字組。相較於已處理模式的每事件 12 個位元組,這只佔 4 個位元組(USB 上的資料量約少 3 倍),因此當鏈路成為瓶頸時,可達成的事件速率約高 3 倍。PC 使用向量化的 numpy 將封裝字組解碼回相同的 6 欄事件配置,因此下游的視覺化器程式碼完全相同。

原始模式是 GUI 中的預設值,因為在 GenX320 所能產生的速率下,USB 吞吐量是約束性的限制因素;若你需要在相機端指令碼中插入處理邏輯,再切換到已處理模式。