4.14. CSIの基本

csi モジュールは、Pythonコードがカメラセンサーを駆動する手段です。フレームをキャプチャするすべてのスクリプトは、同じ3部構成の形に従います。上部にインポート、中央に1回限りの設定、そして下部にカメラから1フレームずつ取り出す while True ループです。

4.14.1. 典型的なループ

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. 各呼び出しの動作

import csi, image, time

3つのモジュールを取り込みます。csi はセンサーを制御し、imagesnapshot() が返す Image クラスを定義し、time は1秒あたりのフレーム数を測定するために使う time.clock() ヘルパーを提供します。

csi.CSI()

1つの物理カメラセンサーをラップする CSI インスタンスを構築します。コンストラクタはカメラペリフェラルを確保し、センサーごとの設定を記録します。単一のセンサーを持つカメラには1つの CSI インスタンスがあり、2つのセンサー(カラーとサーマル、カラーとイベント)を持つカメラには2つあり、それぞれコンストラクタへの cid 引数で選択されます。

csi0.reset()

センサーに電源を入れて設定します。デフォルトではセンサーのリセットピンをパルスし、その後センサーのI2Cレジスタを既知の初期状態に書き込みます。後続の設定呼び出し(pixformatframesize、オート制御の各設定)は、同じI2C制御バスを通じてさらにレジスタ書き込みを送ります。

csi0.pixformat(csi.RGB565)

出力ピクセルフォーマットを選択するセンサーレジスタに書き込みます。選択肢は ピクセルフォーマット ページで紹介したフォーマット、すなわち RGB565GRAYSCALEBAYERYUV422、そしてサポートするセンサーでは JPEG です。

csi0.framesize(csi.QVGA)

出力解像度を選択するレジスタに書き込みます。QVGA は320 × 240です。名前付きサイズは、サポートするセンサーでは WQXGA2(2592 × 1944、約5 MP)まであります。カスタムの (width, height) タプルも、センサーの出力能力に合致していれば使えます。

clock = time.clock()

クロックヘルパーを作成します。ループ内の clock.tick() の各呼び出しは反復の開始時刻を記録し、time.clock.fps() は最近のループレートを1秒あたりのフレーム数で報告します。

img = csi0.snapshot()

センサーから1フレームをキャプチャし、Image として返します。そのフレームがメモリに収まる仕組みは、より詳しく見る価値があります。

4.14.3. snapshotがメモリを埋める仕組み

センサーは、センサーバス で説明されているピクセルデータバス上で、毎秒数百メガバイトの速度でピクセルを供給します。これはCPUがソフトウェアでピクセルごとにコピーするには速すぎます。

代わりに、MCUは転送を ダイレクトメモリアクセス(DMA)にオフロードします。これはCPUとは別のハードウェアエンジンで、CPUを一切介さずにMCU内のある場所から別の場所へバイトをコピーします。カメラ入力ペリフェラルは、入ってくる各ピクセルバイトを小さなオンチップFIFOに取り込み、MCU側で実行されるISP段階が通過中にデータを処理し、DMAエンジンが完成したピクセルをRAM内のフレームバッファの対応するピクセルオフセットに書き込みます。DMAチャネルがプログラムされた後は、この連鎖のどの部分もCPUを必要としません。

snapshot() が呼び出されると:

  1. CSIドライバは、フレームバッファのアドレス、転送長(1フレーム分のピクセル)、およびDMA完了割り込み用のコールバックをDMAエンジンにプログラムします。

  2. ドライバはカメラ入力ペリフェラルを有効にし、センサーが次のフレームの開始を通知するのを待ちます。

  3. センサーがフレームをストリーム出力すると、ペリフェラルは各ピクセルバイトをISPを通じてDMAエンジンに渡し、DMAエンジンが結果をRAM内の次のフレームバッファオフセットに書き込みます。転送中、CPUは他のコードを自由に実行できます。

  4. フレームの最後のピクセルが到着すると、DMAは完了割り込みを発生させ、ドライバはフレームバッファを Image でラップし、snapshot() がそれをユーザーコードに返します。

返される Image はピクセルデータのコピーを所有しません。それはRAM内のカメラのフレームバッファの1つを指しています。カメラがいくつのフレームバッファを保持するか、そしてそれらが snapshot() の各呼び出しでDMAとユーザーコードの間でどのように受け渡されるかは、アプリケーションが framebuffers() を通じて選択したバッファリングモードに依存します。