GENX320 イベントカメラ

GENX320 イベントカメラモジュールは、320x320 の解像度とマイクロ秒単位の時間精度を備えた Prophesee 製のイベントベースビジョンセンサーです。

GENX320 イベントカメラ

完全なデータシート、写真、注文方法については、GENX320 イベントカメラ製品ページ を参照してください。

注釈

OpenMV H7 Plus、RT1062、N6 でサポートされています。

主な特長

  • 320x320 のイベントベースビジョンセンサー

  • 140 dB のダイナミックレンジ、モーションブラーなし

  • 375 Hz 以上のイベントヒストグラム出力レート

  • 消費電力はシーンのアクティビティに応じて変化し、最小で約 3 mW から始まります

  • 自動露出なしで 5 ルクス未満から明るい日光まで動作します

  • グレースケールフレームまたは生のイベントストリームを出力します

使い方

GENX320 はイベントベースのビジョンセンサーです。固定のフレームクロックで 320x320 アレイ全体を読み出す代わりに、各ピクセルが輝度変化を検出した瞬間に非同期の「イベント」を報告します。各イベントには X/Y 座標、ON/OFF の極性(明→暗または暗→明)、マイクロ秒単位のタイムスタンプが含まれます。これがこのセンサーのマイクロ秒単位の時間精度、モーションブラーのなさ、非常に高いダイナミックレンジ、アクティビティに応じてスケールする消費電力の源です。静的なシーンではデータは生成されません。

OpenMV ファームウェアは、csi.CSIcid= csi.GENX320 を指定して GENX320 を公開します。2 つの動作モードが利用できます。

  • ヒストグラムモード(デフォルト)— イベントはチップ上でピクセルごとのビンに蓄積され、設定可能なレート(約 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 がヒストグラム出力を形作る 3 つのつまみです。

カラー化出力

明るい背景には csi.CSI.color_paletteimage.PALETTE_EVT_LIGHT を、暗い背景には image.PALETTE_EVT_DARK を設定します。ドライバはパレットを直接使用して RGB565 フレームを出力します:

csi0.color_palette(image.PALETTE_EVT_LIGHT)

ホットピクセルのキャリブレーション

イベントセンサーは、誤って発火する「ホットピクセル」を蓄積します。静的なシーンに対して csi.IOCTL_GENX320_CALIBRATE を実行してそれらを無効化します。ドライバはピクセルごとの 320x320 のヒットカウントを構築し、平均と標準偏差を計算して、カウントが mean + sigma * stddev を超えるピクセルを無効化します。無効化されたピクセルは、センサーレベルでイベントの出力を停止します。

キャリブレーションを制御するパラメータは 2 つあります。

  • 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_DEFAULT ではなく csi.GENX320_BIASES_LOW_NOISE を適用します。データシートのデフォルトははるかに高い背景イベントレートを出力するため、ストリームを静かに保つ出発点として LOW_NOISE が使用されます。アプリケーションがより高い感度や帯域幅を必要とする場合は、別のプリセットを指定して csi.IOCTL_GENX320_SET_BIASES を呼び出してください。

csi.IOCTL_GENX320_SET_BIASES は 5 つのプリセットのいずれかを適用します。

  • 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_BIAScsi.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 にストリーミングします。各イベントは 6 つの uint16 列からなる 1 行です。

  • [0] イベントタイプ — 下記参照

  • [1] 秒のタイムスタンプ

  • [2] ミリ秒のタイムスタンプ

  • [3] マイクロ秒のタイムスタンプ

  • [4] X 座標、0-319

  • [5] Y 座標、0-319

ドライバは列 [0] に 6 つのイベントタイプを出力します。

  • 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_MODEcsi.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 フィルタは、バーストごとに 1 つ(または数個)のイベントだけを残して残りを破棄するチップ上の後処理です。

これは 3 つの戦略を実装しており、csi.IOCTL_GENX320_SET_STCGENX320_STC_* 定数で選択します。各モードは、バーストからどのイベントを転送するかで定義されます。

モード

残す

破棄する

csi.GENX320_STC_DISABLE

すべてのイベント

なし

csi.GENX320_STC_ONLY

バーストの 2 番目のイベント

最初のイベントとそれ以降のイベント

csi.GENX320_STC_TRAIL_ONLY

バーストの最初のイベント

後続のイベント

csi.GENX320_STC_TRAIL

最初のエッジと後続のエッジ

冗長なノイズのみ

詳細は次のとおりです。

  • csi.GENX320_STC_DISABLE — フィルタはオフで、すべてのイベントが通過します(デフォルト)。

  • csi.GENX320_STC_ONLY — バーストの 2 番目 のイベントを残します。パラメータ: stc_threshold(ms)。あるピクセルへの新しいイベントが直前のイベントから stc_threshold 以内に到着した場合、それはバーストの「2 番目」とみなされて転送されます。最初のイベントと同じバースト内の後続のイベントはフィルタで除去されます。最初のヒットそのものではなく、ノイズで確認された遷移が欲しい場合に最適です。

  • csi.GENX320_STC_TRAIL_ONLY — バーストの 最初 のイベントを残します。パラメータ: trail_threshold(ms)。ピクセルが発火した後、trail_threshold が経過するまで同じピクセルへの後続のイベントは破棄されます。先頭エッジの正確なタイミングを保持します。バーストの確認よりも極性が切り替わる瞬間が重要な場合に役立ちます。

  • csi.GENX320_STC_TRAIL — 両方を組み合わせます。パラメータ: stc_thresholdtrail_threshold(どちらも ms)。Trail モードに従って先頭エッジを残し、さらに STC モードに従って後続のエッジも残すため、バーストから複数のイベントが通過します。単一モードのフィルタよりもイベントスループットは高くなりますが、最も豊富な信号が得られます。

2 つのしきい値はおおよそ 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 は 2 つの可視化を並べて実行します。1 つはイベント蓄積キャンバス(Image.draw_event_histogram と同じ考え方ですが、選択可能なパレットとスライディングウィンドウ/自動クリアモードを備えています)、もう 1 つは IIR バンドパスフィルタで駆動されるピクセルごとの周波数マップです。後者はイベントストリーム内で周期的な信号(回転するファン、点滅する LED など)を直接見つけるのに役立ちます。

これには 2 つのオンカメラストリーミングスクリプトが付属します。

  • 処理済みモードgenx320_event_mode_streaming_on_cam.py)— カメラは csi.IOCTL_GENX320_READ_EVENTS でイベントをデコードし、各行を 12 バイトとして USB 経由でストリーミングします([0] タイプ、[1] 秒、[2] ミリ秒、[3] マイクロ秒、[4] x、[5] y)。ワイヤフォーマットがオンカメラの ndarray フォーマットと一致するため、PC 側で扱いやすくなっています。

  • 生モードgenx320_raw_event_mode_streaming_on_cam.py)— カメラはチップネイティブの 32 ビットパック済みイベントワードを csi.IOCTL_GENX320_READ_EVENTS_RAW 経由でストリーミングします。これはイベントあたり 4 バイトで、処理済みモードの 12 バイトに対して(USB 経由のデータ量が約 3 分の 1)、リンクがボトルネックの場合に約 3 倍高いイベントレートを達成できます。PC はパック済みワードをベクトル化された numpy を使って同じ 6 列のイベントレイアウトにデコードし直すため、下流の可視化コードは同一です。

GenX320 が生成できるレートでは USB スループットが制約となるため、GUI では生モードがデフォルトです。オンカメラスクリプトに処理ロジックを組み込む必要がある場合は処理済みモードに切り替えてください。