4.18. 複数のセンサー

ごく一部のOpenMV Camは、同じボード上に2つのイメージセンサーを組み合わせています -- 最も一般的なのはカラーカメラとFLIR® Lepton®サーマルセンサーの組み合わせですが、同じ構成はカラー+イベントのボードや、将来のあらゆるデュアルセンサーハードウェアにも当てはまります。各センサーは独自のピクセルアレイと独自の制御バスを持ち、独自のフレームレートで独自のパイプラインを動かします。CSI APIは、物理センサーごとに1つの CSI オブジェクトをアプリケーションがインスタンス化できるようにすることで、これらに対応するよう拡張されています。

4.18.1. どのセンサーを選択するか

CSI コンストラクタは、ボード上の特定のセンサーを指定する cid 引数を取ります。cid=-1(デフォルト)はプライマリセンサーを選択します。名前付きの cid 定数は、チップIDによってセカンダリを選択します:

import csi

csi_rgb     = csi.CSI()                    # primary colour sensor
csi_thermal = csi.CSI(cid=csi.LEPTON)      # FLIR® Lepton®

各インスタンスは独自の構成 -- ピクセルフォーマット、フレームサイズ、露出/ゲインの調整、フレームバッファプール -- を所有し、他方とは独立してリセット、構成、読み取りが行われます。サポートされるセカンダリセンサー用の定数(LEPTONGENX320、および CSI リファレンスに列挙されているその他)は、アプリケーションがセカンダリポートに期待するチップを指定します。実際のチップが一致しない場合、ドライバは構築を失敗させます。

4.18.2. 両方のセンサーからのキャプチャ

各センサーは互いに独立してキャプチャパイプラインを動かします -- カラーセンサーが毎秒30フレームを供給する一方で、Lepton®は9フレームを供給するかもしれません。このミスマッチを扱う最も直接的な方法は、より高速なセンサーにループを駆動させ、より低速なセンサーをノンブロッキングで読み取り、準備ができているものは何でも受け取り、何もないときはその反復をスキップすることです:

import csi

csi_rgb     = csi.CSI()
csi_thermal = csi.CSI(cid=csi.LEPTON)

csi_rgb.reset()                        # powers the rail, pulses RESET
csi_rgb.pixformat(csi.RGB565)
csi_rgb.framesize(csi.QVGA)

csi_thermal.reset(hard=False)          # I2C reconfigure only
csi_thermal.pixformat(csi.GRAYSCALE)
csi_thermal.framesize(csi.QQVGA)

while True:
    rgb_img     = csi_rgb.snapshot()                  # blocks for next colour frame
    thermal_img = csi_thermal.snapshot(blocking=False)  # returns None if not ready
    if thermal_img is not None:
        # process aligned colour + thermal pair
        pass
    else:
        # process colour only on this iteration
        pass

ブロッキングの snapshot() がループのペースを決めます。ノンブロッキングのものは、前回の呼び出し以降に新しいサーマルフレームが到着していればその最新のものを返し、そうでなければ None を返します。アプリケーションはカラーセンサーのフレームレートで動き続け、Lepton®がサーマルフレームを生成するたびにそれを取得します。

逆のパターン -- 2つのブロッキングスナップショットを連続して -- も機能しますが、その場合ループは2つのセンサーのうちより低速な方のレートで動作し、より高速なセンサーのパイプラインは反復の間に停止します。アプリケーションの下流の処理が実際に駆動したいレートはどちらなのかを選んでください。

4.18.3. 共有電源レール上でのリセット

一部のデュアルセンサーボードは、両方のチップを単一の電源レールで動かすか、リセットラインを共有します。そうしたボードでは、最初の reset() がレールを立ち上げ、共有信号をパルスします。他の CSI インスタンスでのそれ以降のリセットは hard=False を渡し、隣接チップをリセットに引きずり込むことなく自身のチップだけを再プログラムするべきです:

csi_rgb.reset()                        # primary -- powers the rail, pulses RESET
csi_thermal.reset(hard=False)          # secondary -- I2C reconfigure only

この構成でセカンダリに hard=True を指定すると、副作用としてプライマリが再リセットされ、アプリケーションがすでに反映していたセットアップが取り消されてしまいます。各デュアルセンサーボードのリファレンスページには、レールが共有されているかどうかが明記されています。

4.18.4. ストリームソースの選択

2つのセンサーを持つカメラは2つの CSI インスタンスを持ちますが、それらの間でストリームフレームバッファは依然として1つだけです。コンストラクタ引数で、どのセンサーのフレームがプレビューに供給されるかを選びます:

csi_rgb     = csi.CSI()                    # primary
csi_thermal = csi.CSI(cid=csi.LEPTON,
                      stream=True)         # preview source

stream=True は、名前付きのインスタンスをソースにします。stream= 引数がない場合、プライマリセンサー(cid=-1、デフォルト)がソースになります。セカンダリセンサーの cid= を指定して構築されたインスタンスは、stream=True が明示的に渡されない限りプレビュー上では無音のままです。選択されていないセンサーでの snapshot() の呼び出しは、依然として通常通りそのセンサーのフレームバッファにフレームをキャプチャします -- ただプレビューを更新しないだけです。