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

画像の輝度値(画素値)を取得/設定するのに、.NETでは SetPixel と GetPixel というメソッドが用意されていますが、処理が遅かったり、モノクロの8ビット画像でSetPixelを実行しようとすると

型 ‘System.InvalidOperationException’ のハンドルされていない例外が System.Drawing.dll で発生しました

追加情報:SetPixel は、インデックス付きピクセル形式のイメージに対してサポートされていません。

というエラーメッセージが出て、実質的に使い物になりません。

そこで、LockBits~UnlockBitsというメソッドを使い、ビットマップのポインタ(Scan0)から輝度値の値を参照/設定するのが定番となっています。

(unsafeコードではScan0をポインタにキャストして使う事もできます。)

以下に3x3の移動平均フィルタ処理を行った例を示します。

var bmp = new Bitmap(@"C:\Temp\Mandrill.bmp");

// Bitmapをロック
System.Drawing.Imaging.BitmapData bmpData =
    bmp.LockBits(
        new Rectangle(0, 0, bmp.Width, bmp.Height),
        System.Drawing.Imaging.ImageLockMode.ReadWrite,
        bmp.PixelFormat
    );

// メモリの幅のバイト数を取得
var stride = Math.Abs(bmpData.Stride);
// チャンネル数を取得(8, 24, 32bitのBitmapのみを想定)
var channel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;

// 元の画像データ用配列
var srcData = new byte[stride * bmpData.Height];
// 処理後の画像データ用配列
var dstData = new byte[stride * bmpData.Height];

// Bitmapデータを配列へコピー
System.Runtime.InteropServices.Marshal.Copy(
    bmpData.Scan0,
    srcData,
    0,
    srcData.Length
    );

int index;

// 移動平均処理
for (int j = 1; j < bmpData.Height - 1; j++)
{
    for (int i = channel; i < (bmpData.Width - 1) * channel; i++)
    {
        index = i + j * stride;
        dstData[index]
            = (byte)((
                srcData[index - channel - stride] + srcData[index - stride] + srcData[index + channel - stride]
              + srcData[index - channel ] + srcData[index ] + srcData[index + channel ]
              + srcData[index - channel + stride] + srcData[index + stride] + srcData[index + channel + stride]
              ) / 9);
    }
}

// 配列をBitmapデータへコピー
System.Runtime.InteropServices.Marshal.Copy(
    dstData,
    0,
    bmpData.Scan0,
    dstData.Length
);

// アンロック
bmp.UnlockBits(bmpData);

bmp.Save(@"C:\Temp\Mandrill_Smooth.bmp", System.Drawing.Imaging.ImageFormat.Bmp);

(処理結果)

C# SetPixel SetPixel C# SetPixel SetPixel
処理前画像 処理後画像

上記のコードはBitmapのフォーマットが8ビット(1チャンネル、Format8bppIndexed)、24ビット(3チャンネル、Format24bppRgb)、32ビット(4チャンネル、Format32bppArgb)のみを対象としていますが、OpenCVとかで使われるチャンネルという概念を持ち込む事で、上記のコードだけでモノクロとカラーの両対応となっています。

Scan0で示されたメモリに画像データがどのように格納されているか?についでは下記ページを合わせて参照下さい。

【C#】Bitmap画像データのメモリ構造
Bitmap画像の輝度値を参照するには、こちらのページ↓ で、Bitmap画像のメモリの値を参照するにはBitmapオ...

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