[C#] ビットマップにピクセル単位で高速にアクセスするには (GetPixel/SetPixel vs BitmapData 速度比較)

Bitmapクラスにはピクセル単位のアクセス関数 SetPixel/GetPixel が用意されているけど、ネットを見るとこれら関数はあまり速くないらしい。これら関数を使う代わりに、ビットマップデータをアンマネージ配列にコピーした 上で処理する方法が推奨されているみたい。画像処理ソフトを書く上で処理速度は重要だ。両者でどれぐらいの速度差があるのか調べてみた。

フルカラー画像を読み込んだ後、ピクセル単位にグレイスケールに変換するのに要した時間をミリ秒単位で計測。グレイスケールへの変換には、YCrCb変換のY値の計算式を使った。

方法1: SetPixel/GetPixelを使う

方法2: ビットマップデータをアンマネージ配列にコピーしてから処理する

方法3: ビットマップをシステムメモリにロックして直アクセスする (バイト単位)

方法2のMarshal.Copyのコストが掛かっているかも?という懸念から。

方法4: ビットマップをシステムメモリにロックして直アクセスする (ピクセル単位)

方法3では1ピクセルごとにMarshal.ReadByte/WriteByteが3回ずつ呼ばれるので、これら関数のオーバーヘッドが掛かっているかも?という懸念から。

テストに使ったデータ

画像1 lena.jpg (400×400ピクセル)
画像2 ff_x_e1_004.JPG (4896×3264ピクセル) – 富士フィルムサイトより拝借

フジノンレンズ XF18-55mmF2.8-4 R LM OIS : サンプル画像 | 富士フイルム
http://fujifilm.jp/personal/digitalcamera/x/fujinon_lens_xf18_55mmf28_4_r_lm_ois/sample_images/

テスト結果

処理時間は以下のようになった。方法3と4は、アンマネージ配列をアロケートしてデータをコピーするコストを考えた代替方法だったのだけど、画像が大きくなってもそのコストは大したことはなかったので気にする必要はなさそう。

# 処理内容 画像1(400×400ピクセル) 画像2(4896×3264ピクセル)
方法1 GetPixel/SetPixel関数を使用 597ms 28,187ms
方法2 アンマネージ配列にコピーした上で処理 10ms 268ms
方法3 システムメモリにロックして直アクセス (バイト単位) 19ms 523ms
方法4 システムメモリにロックして直アクセス (ピクセル単位) 15ms 400ms

環境: Windows 7 Ultimate SP1 (64bit), Intel Core i7 3.4GHz, RAM 16GB, Visual Studio 2012