マルチスペクトラルイベントカメラ

マルチスペクトラルイベントカメラモジュールは、GENX320イベントセンサーと1 MPのPAG7936グローバルシャッターカラーセンサーを単一のモジュール上に組み合わせたもので、高速な物体トラッキング、LEDトラッキング、流体の流れ、その他の動的なシーンに向けた、同期されたイベント+カラーパイプラインを実現します。

マルチスペクトラルイベントカメラ

完全なデータシート、写真、注文情報については、マルチスペクトラルイベントカメラ製品ページ を参照してください。

注釈

OpenMV N6でのみサポートされます。

ハイライト

  • 320x320イベントセンサー、140 dB超のダイナミックレンジ、375 Hz以上のヒストグラム

  • PAG7936カラー:1280x800 @ 120 FPS、640x400 @ 240 FPS

  • 共有露出トリガーを用いた同期イベントタイムスタンプ

  • オートエクスポージャーなしで5ルクス未満でも視認可能

  • イベントストリーミング時の消費電力は約3 mWから

  • 高速トラッキング、LEDトラッキング、流体/粒子の流れを対象

使用方法

カラーセンサーとGENX320イベントセンサーは、それぞれ独自の csi.CSI インスタンスを取得します。最初の呼び出しはデフォルトでプライマリセンサー(PAG7936)を指します。2回目は cid= csi.GENX320 を渡すことでGENX320にバインドされます。カラーセンサーは csi.CSI.reset (hard=True) でハードリセットして電源レールを立ち上げ、GENX320は hard=False で設定して、そのドライバがリセットを再度トグルせずにチップを再プログラムするだけにします。

GENX320はヒストグラムモードで320x320を出力し、PAG7936は csi.QVGA で320x200を出力します。以下の基本的なオーバーレイは、GENX320フレームの下端120行をクリップします。フィットしたオーバーレイやより大きなPAG7936のフレームサイズには、ホモグラフィ変換(後述)を使用してください。

フレームループ全体を通じて2つのスクラッチバッファが一定に保たれます。1つは image.Image として保存された256x1のアルファパレットで、中間グレーのベースライン(128)にあるヒストグラムピクセルを透明にし、ONイベントのハイライトとOFFイベントのシャドウの両方を不透明にします。もう1つは 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 がその2つを合成します:color_palette= image.PALETTE_EVT_LIGHT(暗い背景の場合は image.PALETTE_EVT_DARK)はGENX320のグレースケールヒストグラムをカラーランプにマッピングし、alpha_palette= はV字型のアルファマップを用いて各ピクセルをブレンドするため、シーンの静かな領域はカラー画像へと透過し、hint= image.BILINEAR はカラーセンサーがGENX320より高い解像度で動作する際のアップスケールを滑らかにします。

GENX320のバイアスプリセット、AFKフィルタ、ホットピクセルキャリブレーション、STCフィルタの各ioctlは、このデュアルカメラ構成でも同じように動作します。csi.CSI.reset の後に csi1 上で呼び出してください。詳細は以下の各セクションを参照してください。

GPUアクセラレーションによる位置合わせ

Image.draw_imagetransform= 引数を受け付けます。これは2次元のulab.numpy配列としての3x3ホモグラフィ行列です。OpenMV N6ではGPUが同じ描画中にピクセルごとの投影を実行するため、GENX320フレームを別途のワープパスなしでカラーカメラの視点に再整列できます。これは2つのセンサーの光学系や視野がわずかに異なる場合や、カラーカメラがより高い解像度で動作する場合に役立ちます。行列はカメラごとに 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.GENX320 を指定して csi.CSI を通じて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_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 を超える任意のピクセルを無効にします。その後、無効化されたピクセルはセンサーレベルでイベントの出力を停止します。

キャリブレーションは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つのプリセットのうちの1つを適用します:

  • 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.GENX320_MODE_EVENT とバッファサイズを指定して csi.IOCTL_GENX320_SET_MODE でイベントモードに入ります。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回ごとの反復でのみ統計を出力してください。毎回の反復で出力行をプッシュすると、高いイベントレートではそれがボトルネックになります:

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つ(または少数)のイベントだけを保持し、残りを破棄するオンチップの後処理です。

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

モード

保持

破棄

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

2つのオンカメラストリーミングスクリプトが付属しています:

  • 処理済みモードgenx320_event_mode_streaming_on_cam.py)— カメラは csi.IOCTL_GENX320_READ_EVENTS でイベントをデコードし、各行を12バイトとしてUSB経由でストリーミングします([0] タイプ、[1] 秒、[2] ms、[3] us、[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分の1)であるため、リンクがボトルネックの場合に約3倍高いイベントレートが達成可能です。PCはベクトル化されたnumpyを使ってパックワードを同じ6列のイベントレイアウトにデコードし直すため、下流の可視化コードは同一です。

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