【C#】Bitmapクラスへのポインタ渡し

画像処理のプログラムを作成するときは、GUIはC#、画像処理はC、C++で書かれたライブラリで作成するというのが、私の定番となっているのですが、C言語で処理された画像データをC#へ渡す場合は、画像データのコピーや画像ファイルを介せずとも、画像データのポインタをC#側へ渡す事で画像データを扱うことが可能となります。

 

これを可能にするのには、Bitmapクラスのコンストラクタに

public Bitmap(
    int width,
    int height,
    int stride,
    PixelFormat format,
    IntPtr scan0
)

というのがあるので、scan0の部分にC言語で扱っている画像データのポインタを渡す事で、C#のビットマップオブジェクトとして扱うことが可能となります。

ただし、画像データのメモリ構造はトップダウンで画像1行あたりのサイズが4バイト単位となります。

(参考)

【C#】Bitmap画像データのメモリ構造

 

scan0に渡されたポインタはBitmapクラス内ではメモリを参照するだけ(Bitmapクラス内でメモリを確保していない)なので、BitmapオブジェクトをDisposeするまでscan0で示されたメモリは解放しないように注意が必要です。

逆にBitmapオブジェクトをDisposeしてもメモリは解放されないので、メモリの解放も忘れずに。

 

メモリが参照されているだけかどうか?を評価するのに、下記のようなプログラムを作成してみました。

// 元のBitmap
var bmp1 = new Bitmap(@"C:\Temp\Lenna.bmp");

// ロックしてポインタ参照できるようにする
var bmpData1 = bmp1.LockBits(new Rectangle(0, 0, bmp1.Width, bmp1.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp1.PixelFormat);

// ポインタを渡してBitmapクラスを作成
var bmp2 = new Bitmap(
    bmp1.Width,
    bmp1.Height,
    bmpData1.Stride,
    bmp1.PixelFormat,
    bmpData1.Scan0 // bmp1の画像データのポインタ
);

// アンロック
bmp1.UnlockBits(bmpData1);

// ロックしてポインタ参照できるようにする
var bmpData2 = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp2.PixelFormat);

var data2 = new byte[Math.Abs(bmpData2.Stride) * bmp2.Height];

// bmpData2の輝度値を配列へコピーする
System.Runtime.InteropServices.Marshal.Copy(
    bmpData2.Scan0,
    data2,
    0,
    data2.Length
);

// 画像の輝度値を+50する
for (int i = 0; i < data2.Length; i++)
{
    data2[i] = (byte)Math.Min(data2[i] + 50, 255);
}

// 元のポインタへコピーして、データを戻す
System.Runtime.InteropServices.Marshal.Copy(
    data2,
    0,
    bmpData2.Scan0,
    data2.Length
);

// アンロック
bmp2.UnlockBits(bmpData2);
//
bmp2.Dispose();

// bmp1を保存(※bmp1は直接輝度値の変更はしていない)
bmp1.Save(@"C:\Temp\Lenna1.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
// 解放
bmp1.Dispose();

上記のプログラムではbmp1でBitmapオブジェクトを作成(画像のメモリを確保)し、bmp1の画像データのポインタをbmp2へ渡しています。

この時点でbmp1とbmp2の画像データのメモリは同じをメモリを参照しているはずなので、bmp2の画像の輝度値を変更すると、bmp1の画像も変更されているか?を確認しています。

 

元の画像(bmp1の画像)

 

bmp2を処理した後のbmp1の画像

 

以上のようになり、思惑通りでした!

 

画像処理のためのC#テクニックへ戻る

Intel RealSense D435を購入

以前から気にはなっていたIntel RealSense D435ですが、品薄状態が続いており、ようやくスイッチサイエンスのサイトから購入することができました。

 

ここのサイトで入荷通知機能を登録していたので、メールが来てから速攻で注文したのですが、50個あった在庫が1~2時間ぐらいで在庫切れになったんではないでしょうか?

購入を検討されている方は、まずはこの入荷通知機能を登録しておく事をおススメします。

 

以下、Intel RealSense D435の開封の儀

 

これ、写真でも見ると大きく見えるのですが、長手方向の長さがiPhoneXの長手方向とほぼ同じというぐらい、すごく小さいです。

 

フタを開けたところ。本体の背面側

 

正面側は保護テープが貼ってあります。

 

本体背面側。M3ぐらいのネジ穴が切ってあって、何かに取り付け易そう。

 

本体底面。カメラねじが切ってあります。

 

本体の正面から見て左側にUSB3.0(Type-C)のコネクタがあります。

最初はフタが付いていますが、無くしそうなので、箱の中にしまいました。。

 

そして正面。

 

写真で見るとなんとなく大きく見えるのですが、手の上に載せると、その小ささを実感して頂けると思います。

 

同梱されていた物、一式。

 

USBケーブルは本体側がType-C、PC側がType-Aでケーブル長は1mぐらいでした。

(ちょっと短い。。)

 

そして動作させるソフトはこちら↓

https://github.com/IntelRealSense/librealsense/releases/latest

 

とりあえず動作を見るだけならIntel.RealSense.Viewer.exeをダウンロードすれは動作すると思います。(これが分からなくて、CMakeからやってしまった。。)

 

実行結果はこちら↓

 

思っていたよりもキレイかも??

これから色々触ってみようと思います。

【C#】Bitmap画像データのメモリ構造

Bitmap画像の輝度値を参照するには、こちらのページ↓

【C#】Bitmap画像の輝度値の参照設定

 

で、Bitmap画像のメモリの値を参照するにはBitmapオブジェクトをLockBitsでロックし、BitmapDataScan0プロパティを参照すれば、良いと書きましたが、このScan0で示されたメモリの構造のお話です。

 

C言語で画像の表示を行った事がある人には、WindowsのDIB(Device Independent Bitmaps)の画像データの構造とほぼ同じで、データの並びがトップダウンになります。

で済むのですが、私自身もC言語で画像の表示をすることは少なくなり、これで通じる人は少なくなった気がします。。

 

簡単に画像データのメモリ構造を説明すると

 

●画像の左上から順にメモリに配置されている(トップダウン)

●画像1行あたりのメモリは4の倍数のバイトになるように調整されている

●カラーの場合、B,G,R,B,G,R,・・・もしくは B,G,R,A,B,G,R,A,・・・の順で並んでいる

 

より具体的に3x3画素の画像を例にとって説明します。

 

上図のように3x3の画像であってもメモリ上は下図のように一列にデータが並んでおり、BitmapDataのSacn0がメモリの先頭アドレスを差しています。

 

上図は8ビットのモノクロ画像の場合ですが、1行あたりのメモリサイズが4の倍数のバイトになるように余計にメモリが追加(上図のグレーの部分)されいます。

 

この1行あたりのメモリのサイズはBitmapDataStrideプロパティで取得できますが、自分で計算すると

 

stride = (width * bitCount + 31) / 32 * 4;

 

という計算で求めることができ、幅が3画素、8ビットの場合は

 

24ビットカラーの場合は、1画素あたり3バイト、メモリを使用し、画像の左上からB,G,R,B,G,R,・・・の順番でメモリに画像の輝度値が格納され

 

のようになっています。

同様に32ビットの場合は、1画素あたり4バイト、メモリを使用し、画像の左上からB,G,R,A,B,G,R,A,・・・の順番でメモリに画像の輝度値が格納され

 

となり、32ビットの場合は1画素あたり4バイトなので、画像の幅の画素数に関係なく余計なメモリが追加されることはありません。

 

画像を表示して斜めに表示される場合は、このStrideあたり(メモリの確保や輝度値の参照方法)がおかしい場合が多いので、確認してみて下さい。

 

参考までに、ビットマップファイル(*.bmp)の画像データ部分は、幅方向については、上記の通りですが、画像の左下から上へ(ボトムアップ)メモリに格納されています。

 

画像処理のためのC#テクニックへ戻る