5.3. ピクセルフォーマット

エッジを検出するアルゴリズムは、各ピクセルが明るさの値を保持していることを前提とします。色のついた物体を追跡するアルゴリズムは、各ピクセルがカラー情報を持っていることを前提とします。モルフォロジーのクロージング処理を実行するアルゴリズムは、各ピクセルがオンかオフのいずれかであることを前提とします。Image が持つピクセルフォーマット(カタログの Vision Sensors に列挙されているもののいずれか)は、こうした前提を事前にチェック可能にするものです。フォーマットは、ピクセルがどのような形式であるか、したがってどのアルゴリズムが変換ステップなしでそれらに対して実行できるかを、あらかじめ示しているのです。

このページでは、その制約が実際にどのように働くかについて説明します。どのフォーマットが正しい選択かは、パイプラインが何をしようとしているかによって決まり、フォーマット間の変換メソッドは、複数のフォーマットを必要とするパイプラインが各ステージをつなぎ合わせる手段となります。

ラベル付きのバイトレイアウトの帯が5本、縦に積み重なっている図。BINARY は1バイトが8つの単一ビットのセルに分割され、「8 pixels per byte」と表示されている。GRAYSCALE はそれぞれ「1 pixel」と表示された3つのラベル付き1バイトセルを示している。RGB565 はビットフィールド RRRRR GGGGGG BBBBB を持つ隣接した2バイトを示し、「1 pixel」とラベル付けされている。YUV422 は Y0, U, Y1, V とラベル付けされた4つのバイトセルを示し、「2 pixels」と表示されている。BAYER はラベル付きの4バイトセルが2行示されており、上の行は R G R G、下の行は G B G B となっている。

5つの非圧縮ピクセルフォーマットと、それぞれのバイトがどのようにパックされるか。JPEG と PNG は固定サイズのピクセルグリッドではなく可変長の圧縮ストリームであるため、ここには描かれていません。

5.3.1. グレースケールという働き者

古典的なマシンビジョンの大部分は、明るさの値を扱うことに帰着します。エッジ検出、テンプレートマッチング、AprilTag のデコード、オプティカルフロー推定、モルフォロジー演算子、ブロブ解析――これらすべては、アルゴリズムが動作するレベルでは、各ピクセルがどれだけ明るいか、そしてその明るさが近傍のピクセルの明るさとどう比較されるかを見ています。シーンのカラー情報は、それらを呼び出すアプリケーションにとってはしばしば 有用 ですが、アルゴリズム自体はそれを必要としません。

グレースケールフォーマットは、オーバーヘッドなしに、まさにそれをアルゴリズムに渡します。1ピクセルあたり1バイトが、0(黒)から 255(白)までの明るさの値を保持します。このフォーマットは RGB565 や YUV422 の半分、RGB888 の3分の1のサイズなので、すべての演算がより少ないデータを処理することになり、より高速で、バッファへの負荷も小さくなります。フレームバッファが残りのスクリプトと RAM を奪い合う小型のカメラでは、このフットプリントの差がパイプラインが収まるかどうかを左右することがあります。アルゴリズムが必要とする手がかりがカラーでないなら、グレースケールが正しい答えです。

5.3.2. RGB565 によるカラー

カラーが 手がかりである 場合――色のついたマーカーを追跡する、赤いリンゴと緑のリンゴを区別する、UI 要素を色合いで選び出す――1ピクセルあたり2バイトで、アルゴリズムが行う種類の分類に十分なカラー情報が得られます。RGB565 はカメラのデフォルトのカラーフォーマットであり、表層のカラー対応メソッドが前提とするフォーマットです。

注釈付きのフレームをレンダリングすること――検出ボックスを描画する、診断テキストを書き込む、フレームを画面に表示したりリモートビューアに送り出したりすること――もまた、自然と RGB565 を必要とします。IDE のプレビュー、オンボードのディスプレイコントローラ、そしてほとんどのネットワーク送信先は、このフォーマットを直接消費するか、安価に変換します。

5.3.3. ストレージフォーマットとしての Bayer

Bayer 画像は、ISP がデベイヤー処理して完成したカラー表現にする前の、生のセンサー出力です。各ピクセルは1バイトで、単一のカラーチャンネル――モザイク内のその位置にあるカラーフィルタが透過させたチャンネル――を保持します。これにより Bayer 画像はグレースケール画像と同じサイズ、RGB888 の3分の1のサイズとなり、Bayer が実際に役立つ用途と一致します。すなわち、RAM が制約となる場合に多くのフレームを一度に保存することです。

問題は、image モジュールのアルゴリズムが Bayer 画像を直接扱わないことです。デベイヤー処理なしでは、どのピクセルも単独でカラー判断を下すのに十分な情報を持たず、アルゴリズムが探しているパターン――エッジ、コーナー、ブロブ――はモザイクによって歪んでしまいます。Bayer 画像を読み書きする唯一の方法は get_pixel()set_pixel() であり、それ以外はすべて完成した表現を前提とします。

そこから導かれるパターンは、フレームがキューに留まる必要がある間は Bayer として保存し、実際に処理が始まる時点で各フレームをグレースケールまたは RGB565 に変換することです。この変換は CPU サイクルを消費しますが、そうしなければアプリケーションの存続期間中、完成したフレームを保持するために占有されてしまう RAM を節約できます。

注釈

image モジュールが Bayer ピクセルを直接扱う唯一の操作は、get_pixel()set_pixel()、および IDE プレビューやリモートビューアに供給する JPEG エンコードのパスです。描画、解析、フィルタリングはいずれも、まずグレースケール、RGB565、またはバイナリへの変換を必要とします。

5.3.4. 両方を求めるパイプラインのための YUV422

YUV422 は各ピクセルの情報を、輝度チャンネル(Y)と2つの色差チャンネル(U と V)に分離し、色差をサブサンプリングして隣接するピクセルのペアが単一の U と単一の V を共有するようにします。1ピクセルあたりのバイト数は平均すると2バイト――RGB565 と同じ――ですが、Y チャンネルがバッファ内の既知のオフセットに位置する連続した8ビットのグレースケール画像となるようにレイアウトされています。

そのレイアウトは、パイプラインの一部のステージがグレースケール処理で、一部がカラーを必要とする場合に、まさに求められるものです。グレースケールのステージで Y 値を直接読み取れば、明示的な変換のコストを省けます。U と V のチャンネルは、後のステージが実際にカラーを必要とするときにそこにあります。その特定のパターン以外では、カラーには通常 RGB565 の方が単純な選択であり、明るさのみの処理にはグレースケールの方が単純な選択です――YUV422 の価値は、両方を同時にうまくこなせる点にあります。

注釈

image モジュールは、グレースケール、RGB565、バイナリに対するよりも限定的な方法で YUV422 を扱います――グレースケール処理のための Y チャンネルの直接読み取りと、IDE プレビューやリモートビューアに供給する JPEG エンコードのパスです。カラー対応メソッドは RGB565 を前提とします。YUV422 のフレームは、カラー解析や描画の前に明示的な変換が必要です。

5.3.5. バイナリ、マスク、しきい値処理された出力

バイナリ画像は1ピクセルあたり1ビットで、各ピクセルは 01 のいずれかです。このフォーマットがセンサーのキャプチャとして現れることはまれです。代わりに、しきい値処理(カラーまたは明るさのテストが各ピクセルを「はい、一致する」か「いいえ、一致しない」に分類する)の自然な出力として、またモルフォロジー演算や多くのメソッドが受け取る mask 引数への自然な入力として現れます。

このフォーマットの実用上の利点はそのサイズです。バイナリ画像はグレースケール画像のフットプリントの8分の1なので、大きなマスク――下流のある操作がどの位置に触れるべきかをピクセル単位で選択したもの――を持ち回るのが安価です。多くの操作がバイナリ画像を mask= キーワード引数として受け取るという事実は、同じ点の裏返しです。このフォーマットは小さく、あるステージのバイナリ出力を別のステージのマスク入力につなぐことは、一般的なパイプラインのパターンなのです。

5.3.6. 境界における JPEG と PNG

JPEG と PNG の Image オブジェクトは、カタログ内の他のものとは異なります。これらはピクセルグリッドではなく、ピクセルレベルの操作が読み取れない形でピクセルデータを エンコードする 圧縮されたバイトストリームです。JPEG に対して get_pixel() を呼び出しても、ある位置のピクセルは返されません。そのピクセルは、メソッドが取得できる形でバッファのどこにも展開されていないのです。

JPEG と PNG は、ピクセルデータが圧縮された形でカメラから出ていく、あるいは入ってくる、画像処理の境界において現れます。フレームを JPEG としてディスクに保存すればファイルは小さく保たれます。フレームを JPEG としてネットワーク経由で送信すれば伝送が安価に保たれます。JPEG ファイルから参照フレームを読み込めば、生のピクセルよりもはるかに小さい形でディスク上に置いておけます。これらのいずれの用途においても、圧縮された表現が正しい答えです。しかし JPEG に対して実際の処理を行うには、アプリケーションはまずそれを扱えるフォーマットに変換します――そしてその変換こそが、圧縮されたバイトがピクセルに展開され、バッファが膨れ上がる(30 KB の JPEG が 600 KB の RGB565 になりうる)場所なのです。

5.3.7. フォーマット間の変換

変換パスは、異なるフォーマットを単一のパイプラインに縫い合わせるものです。Image クラスの5つのメソッドは、既存の画像を受け取り、異なるフォーマットの新しい画像を返します。

  • to_grayscale() は、古典的なアルゴリズムが求める1ピクセルあたり1バイトの画像を生成します。

  • to_rgb565() は、カラー対応メソッドと IDE プレビューの双方が扱う1ピクセルあたり2バイトのカラーフォーマットを生成します。

  • to_bitmap() は、モルフォロジーや mask 引数が受け取る1ビットのバイナリ画像を生成します。

  • to_jpeg() は、保存や伝送に適した JPEG 圧縮画像を生成します。

  • to_png() は、JPEG のより小さなファイルよりも可逆エンコードが好まれる場合に、PNG 圧縮画像を生成します。

各変換はデフォルトで インプレース で実行されます。元画像のバッファは変換結果で上書きされ、呼び出しが戻った後には元画像の元のピクセルは失われます。これは CPU にとってもメモリにとっても最も安価な選択肢であり、元のフレームが他の何にも必要とされない場合に正しい答えです。

元画像が まだ必要 な場合――パイプラインの後のステージが元のフレームを見なければならない場合――2つのキーワード引数がインプレースのデフォルトを上書きします。copy=True は変換後の画像のために Python ヒープ上に別個のバッファを割り当て、元画像をそのまま残します。copy_to_fb=True は同じ割り当てを行いますが、ヒープではなくフレームバッファに配置します――これは、IDE がフレームバッファから読み取るため、変換後の画像を IDE プレビューに表示する必要がある場合にアプリケーションが用いるものです。

さらに2つのメソッドが、単純な変換ではなく パレット を通じて色付けされた RGB565 画像を生成します。to_rainbow() は、各単一チャンネルの入力値を、可視スペクトルを通る滑らかなグラデーション上の色にマッピングします。to_ironbow() は、各入力値を、黒から暗い赤やオレンジを経て白へと至る非線形のサーマルイメージャーパレットにマッピングします。どちらも測定ツールではなく 可視化 ツールです。要点は、生の値ではそのままでは目に見えない単一チャンネル画像を、一目で読み取れるようにすることです。

5.3.8. バッファサイズ

フォーマットについて明示しておく価値のある最後の詳細です。size() は常に、ピクセル数ではなく バイトバッファ のサイズを報告します。非圧縮フォーマットの場合、これは寸法と1ピクセルあたりのバイト数から直接導かれます。width * height * bytes_per_pixel です。JPEG と PNG の場合は圧縮ストリームのサイズであり、シーンに何が含まれるかに応じてフレームごとに変動します。バイト予算からバッファを割り当てるコードは前者のケースで size() を使います。圧縮されたフレームをカメラからストリーミングするコードは、ストリームが実際に何バイトを含むかを知るために、各圧縮の後にそれを読み取ります。