C#で画像の輝度値を取得/設定を行う場合は、GetPixel、SetPixelメソッドを使うと遅いのでLockBits~UnlockBitsでポインタをむき出して輝度値の取得/設定を行うのが定番となっていますが、自分自身でGetPixel、SetPixelメソッドを使った事が無かったので、いくつかの方法で処理時間を比較してみました。
【評価環境】
OS | Windows10 64bit ver.1809 |
CPU | Intel Core i7-7700K 4.2GHz |
メモリ | 32GB |
.NET Framework | 4.5.2 |
プラットフォーム | Any CPU(32ビットを優先) |
評価画像 | jpeg 6000×4000画素 24bitカラー |
です。
評価プログラムとしては、カラー画像の輝度値をR,G,Bそれぞれ取得し、値を明暗反転して輝度値を入れなおすプログラムを作成しています。
処理結果は、以下のようにポジ→ネガ→ポジ→ネガと繰り返すようになっています。
処理時間は処理を5回行い平均した時間となります。
GetPixel、SetPixelを使った方法
平均処理時間は27439msec
本当に遅かった。。
しかもGetPixel、SetPixelではモノクロ画像(Format8bppIndexed)に対応していないので、モノクロ画像も多く扱う画像処理には不向きとなります。
LockBits~UnlockBitsでポインタ(Scan0)を取得し、データを配列にコピーしてから処理を行い、結果を元にコピーしなおす方法
平均処理時間は101msec
さすがにGetPixel、SetPixelよりはぜんぜん速いです。
MarshalクラスのReadByte、WriteByteメソッドでポインタ(Scan0)に直接、値を読み書きする方法
平均処理時間は273.6msec
この方法に少し期待していたのですが、配列にコピーした方が速かった。。
unsafeを使ったポインタ(Scan0)を直接読み書きする方法
平均処理時間は25.4msec
やっぱりunsafeはできれば使いたくないのですが、ここまで速いと使うのもあり??
ポインタを使うなら、C言語のライブラリにしておきたい気もしますが、C#だけで完結できるのもちょっと惹かれます。
ポインタで読み書きし、Parallel.Forをつかって処理を並列化する方法
平均処理時間は11.4msec
もう、unsafeは使うしかないでしょ!というレベルですが、やっぱり抵抗がある。。
処理を呼び出す側のプログラム
まとめ
それぞれの処理時間をまとめると以下の通りでした。
方法 | 処理時間(msec) |
GetPixel、SetPixel | 27439 |
LockBits、UnlockBitsで配列を介して処理 | 101 |
MarshalクラスのReadByte、WriteByte | 273.6 |
unsafeのポインタで参照 | 25.4 |
unsafeのポインタの並列処理 | 11.4 |
結局GetPixel、SetPixelは論外でした。
あとは好みというか、ポリシーというか、意見の分かれるところだと思いますが、ポインタで処理するのは、やはり魅力です。
(2019.11.8追記)
.NET Framework4.5.2の32ビットを優先で評価を行っていたので、この部分を変えると処理速度に差が出るか?確認しました。
.NET Framework 4.7.2 32ビットを優先
方法 | 処理時間(msec) |
GetPixel、SetPixel | 27293.4 |
LockBits、UnlockBitsで配列を介して処理 | 101.8 |
MarshalクラスのReadByte、WriteByte | 290.8 |
unsafeのポインタで参照 | 24.4 |
unsafeのポインタの並列処理 | 9.8 |
.NET Framework 4.7.2 32ビットを優先なし
方法 | 処理時間(msec) |
GetPixel、SetPixel | 21281.2 |
LockBits、UnlockBitsで配列を介して処理 | 85.4 |
MarshalクラスのReadByte、WriteByte | 213.6 |
unsafeのポインタで参照 | 27.2 |
unsafeのポインタの並列処理 | 9.2 |
となりました。
ということで、.NET Frameworkの4.5.2と4.7.2の差はほとんどありませんでしたが、「32ビットを優先」のあるなしでは優先しない(64bitで動作)方が少し速い結果となりました。
関連記事
←画像処理のためのC#テクニックへ戻る