イメージバッファにアクセスする - RGB555 -

RGB555 は16 ビットのカラーフォーマットです。すべてのピクセルが2バイトで表されます。555というのは各色で使用されるビット数を表しています。この場合、15だけが必要となり、最後の最上位ビット(most significant bit)は使用されません。イメージバッファのピクセルは左から右へ、下から上へと組織されます。

メモリ配置

上記の図で表されている通り、WORDの0~4の下位5ビットがBlueの値に、5~9のビットがGreenに、10~14のビットがRedに対応しています。15のビットはここでは使用されません。x86 アーキテクチャではWORDはリトルエンディアンで格納されるため、LOW BYTE(下位バイト)から保存されます。このことは1バイトのポインタでイメージバッファにアクセスする上で重要なことです。

ピクセルデータの読み込み、書き込みの方法

画像取り込みデバイス、ビデオフォーマット、 画像データのカラーフォーマットを定義するMemBufferCollectionクラスライブラリリファレンス>クラス>MemBufferCollectionを持つFrameHandlerSinkクラスライブラリリファレンス>クラス>FrameHandlerSinkがセットアップされている必要があります。下記ではRGB555のピクセルデータにどようにアクセスし、操作するかを順番に説明しています。
まず最初に、イメージバッファ内には何もありませんので、画像をキャプチャしなければなりません。そのためにライブ表示を開始しGrabber::snapImagesクラスライブラリリファレンス>クラス>Grabber>Grabber::snapImages Methodをコールします。

バッファにアクセスする

次のコードは画像データへの1バイトのポインタを取得します。getPtr()はWORD型ポインターに型キャストされる1バイトのポインタを返します。RGB555は16ビットのカラーフォーマットのため、これによってアクセスが格段に容易になります。

WORD* pbImgData = (WORD*)pActiveBuf->getPtr();

今回の例では、画像中の(左上から)最初の2ピクセル分の読み出した後、3ピクセル分を操作します。先ほど説明したようにRGB 画像は下から上に保存されるため、pbImgDataはイメージの最後の行の最初のピクセルをポイントします。1行目の最初のピクセルにアクセスしたい場合には下記のようにして算出する必要があります。

// 左上のピクセルのインデックスを算出
// 画像はイメージバッファ内に上下逆に保存されます
// * 1: ピクセルは2バイトだが(同じく2バイトの)WORDポインタがあるのでピクセル単位でなくバイト単位で計算する
SIZE dim = pActiveBuf->getFrameType().dim;
int iOffsUpperLeft = (dim.cy-1) * dim.cx * 1;

まず最初に、画像の高さと幅をピクセル数で取得します。それから左上のピクセルへのオフセットが算出されます。

Width * 2ではなくWidth * 1として計算することに注意してください。これは画像データにアクセスするのにWORD 型ポインタを使用するためです。もちろん×1というのは分かりやすくするためのものですので省略可能です。

(Height-1) * Width

最初のピクセルのオフセットを取得したので読み出します。

// 注: RGB の各値はWORD型の中に次の順番で格納されます。(R,G,B)
// 特定の色を抽出するためにカラーマスクを使ってバイナリのAND(論理積)演算を行う
// AND演算後、右シフトが行われることでアウトプットが正しく表示される
printf( "\nImage buffer pixel format is eRGB555\n" );
printf( "Pixel 1(RGB): %d %d %d\n", ( pwImgData[iOffsUpperLeft] & eRGB555_R ) >> 10 ,
   ( pwImgData[iOffsUpperLeft] & eRGB555_G ) >> 5,
   ( pwImgData[iOffsUpperLeft] & eRGB555_B ) );
printf( "Pixel 2(RGB): %d %d %d\n", ( pwImgData[iOffsUpperLeft+1] & eRGB555_R ) >> 10 ,
   ( pwImgData[iOffsUpperLeft+1] & eRGB555_G ) >> 5,
   ( pwImgData[iOffsUpperLeft+1] & eRGB555_B ) );

上記のコードにあるように、現在のピクセルに使われているピクセルマスクでAND演算を行い色値を抽出します。その後RedとGreenの値は右シフトすることによって正しい値を得る必要があります。(でないと値が1024となり実際の32倍になってしまいます )

画像データを操作する

値の割り当てにおいてシフトも重要になってきます。例えば、Redの値に7を割り当てたとすると、左に10回シフトさせる必要があります。下記のように記述しないように気を付けてください。

// Redの値に7を割り当てる
pwImgData[iOffsUpperLeft] = 7;  // これは間違いです

これではBlueの値に7を割り当てることになります。正しくは次のように記述します。

// Redの値に7を割り当てる
pwImgData[iOffsUpperLeft] = 7 << 10;

もう一つ重要なことは上記の割り当てはGreenとBlueの値を上書きするということです。これを防ぐためには次のコードのように値をピクセルデータにOR演算する必要があります。

// Redの値をクリア(全てのビットを0に設定)
pwImgData[iOffsUpperLeft] &= ~eRGB555_R;

// Redの値に7を割り当てる
pwImgData[iOffsUpperLeft] = 7 << 10;

上記で行っているように、全ピットの値を0に設定するという作業を忘れないでください。例えば前のRedの値は16(2進法では10000)になっていた可能性があります。7(2進法では111)を使ったOR演算を行うとその結果は23(2進法で10111)となります。こういった理由から特定の色の全ビットの値を0に設定するようにしています。

では一番左上のピクセルから順にRed, Green, Blueと色を設定します。

// 最初の3つのピクセルを上書きしてディスクに画像を保存する
// 最初のピクセルをREDに設定
ppwImgData[iOffsUpperLeft] = 0;             // ピクセルのクリア
pwImgData[iOffsUpperLeft] |= 31 << 10;    // Redの値を割り当て

// 最初のピクセルをGREENに設定
pwImgData[iOffsUpperLeft+1] = 0;             // ピクセルのクリア
pwImgData[iOffsUpperLeft+1] |= 31 << 5;    // Greenの値を割り当て

// 最初のピクセルをBLUEに設定
pwImgData[iOffsUpperLeft+2] = 0;             // ピクセルのクリア
pwImgData[iOffsUpperLeft+2] |= 31;    // Blueの値を割り当て

pActiveBuf->save( "RGB555.bmp" );

確認のために保存されたか画像の左上のピクセルを見てみましょう。このようになっているはずです。