午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

BitmapSourceでの画像処理速度、unsafe、ポインタ、ビルドモード

アプリダウンロード先

WPFのBitmapSourceの画像処理速度向上するためにいろいろ試してみた

初めて使ったunsafeでポインタが一番効果があった
 
画像処理の内容は色の反転、RGBそれぞれの値を逆にするだけのもので
50だったら255-50=205にする
単純な軽い処理
 
 

f:id:gogowaten:20191212154608p:plain

15種類、基準になるのは処理1で
 
結果
イメージ 2
ポインタを使ったら1.926秒が0.874秒
約2倍速くなった
 
イメージ 4
 
使った画像は

f:id:gogowaten:20191212154654j:plain

1024x768ピクセル
なぜこの画像を選んだのかコレガワカラナイ
 
PC環境は
OS Windows 10 Home 64bit
CPU AMD PhenomⅡ X3 720 @3.0GHz
メモリ DDR2 PC-800 8GB
っていう組んでから10年目になるパソコン
 
ビルドの設定?
イメージ 7
Releaseでx64
 
 
デザイン画面

f:id:gogowaten:20191212154717p:plain

 
基準になるコードはこれ
private void ColorReverce1()
{
    var wb = new WriteableBitmap(OriginBitmap);
    int w = wb.PixelWidth;
    int h = wb.PixelHeight;
    int stride = wb.BackBufferStride;
    byte[] pixels = new byte[h * stride];
    wb.CopyPixels(pixels, stride, 0);
    long p;
    for (int y = 0; y < h; ++y)
    {
        for (int x = 0; x < w; ++x)
        {
            p = y * stride + x * 4;
            //RGBを反転、アルファ値はそのまま
            pixels[p] = (byte)(255 - pixels[p]);
            pixels[p + 1] = (byte)(255 - pixels[p + 1]);
            pixels[p + 2] = (byte)(255 - pixels[p + 2]);
        }
    }

    wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
    MyImage.Source = wb;
}
 
 
OriginBitmapが元になる画像でBitmapSource
PixelFormatはPbgra32
これからWriteableBitmapを作ってCopyPixelsからbyte配列を作って
画像処理でRGBを反転、最後に確認用にImageコントロール
MyImageに表示して完了
これを100回繰り返した時間を計測
なので実際の処理時間は1/100になる
 
時間計測はStopwatch
イメージ 6
 
 
イメージ 11
↑普通↓ポインタ使用
イメージ 12
見た感じだとほとんど一緒だねえ、赤いところがbyte型ポインタ?の変数を使っているところ
 
イメージ 13
WriteableBitmapのBackBufferってのからポインタを取得できる
バックバッファーってのは後の画面、処理用の画面ってとこかなあ
対するのはフロント、メインとかで前の画面で実際に見えている画面
処理の途中は見えない後で行って結果だけを表示する感じ
 
ポインタってのはメモリのアドレスみたいな感じ
そのデータの先頭のメモリのアドレス
今回だとbyte型のポインタを使うからbyte* ptrってすると
ptrがbyte型のポインタになるみたいで
配列のインデックスみたいにptr[0]ってすると先頭のアドレスから1byte分のデータになって、ptr[1]だと先頭から1byte分進んだアドレスにある1byte分のデータ
ptr[2]だと先頭から2byte分進んだアドレスにある1byte分のデータ
かなあ
 
WriteableBitmap.Lockメソッドでメモリのロック
OSはメモリに入っているデータを最適な状態にしようと移動しているらしい
ハードディスクのデフラグみたいな感じかしら
処理中に勝手に移動されたら困るから、その対象から一時的に外して移動されないようにするのがWriteableBitmapのLockメソッドで、これを使った後は必ずUnlockメソッドを実行してもとに戻す
 
 
 
ポインタを使う前準備
 
プロジェクトのビルドのモードをunsafeってのにする必要があるみたいで

f:id:gogowaten:20191212154843p:plain

ソリューションエクスプローラーで
アプリの名前あたりを右クリックのメニューでプロパティ
 
プロパティ画面

f:id:gogowaten:20191212154856p:plain

ビルドタブにあるアンセーフコードの許可にチェックを入れる
これでコードでunsafeってのが使えるようになって
unsafeが使えるとポインタが使えるようになる
もしこうしないでunsafeって書いても
イメージ 10
って使えない
最初はこれがなんのことかわからなかった

 
 
 
処理2
イメージ 14
Pixelsのインデックス計算を少し変更
530行目でp = y * stride + x * 4;
ってしてたのを
527行目とで分けた、少し計算量が減ったはずなんだけどタイムは変わらずだった
1.926秒が1.882秒
 
処理3
イメージ 15
処理1のx,yの2重になっていたループを1重ループに変更
1.926秒が1.784秒
1割弱速くなった
ループ処理の合計は同じなんだけど速くなったから
forは減らしたほうがいいみたい
 
 
処理4
イメージ 16
処理3のRGBの各値変更のところをforにしたら遅くなった
1.784秒が1.927秒
 
 
処理5
イメージ 17
ここからポインタを使用した処理になる
処理1のポインタ版+ビット反転版
1.926秒が0.786秒
2倍以上速くなった
ビット反転
~を付けるとビット反転
p[0]が0000 0000だったときに~を付けて~p[0]にすると
~p[0]は1111 1111になる
ちょうど色の反転に使えたのでこれも初めて使ってみた
 
処理6
イメージ 18
処理5をビット反転じゃなくて普通の引き算版
つまり処理1のポインタ版
1.926秒が0.874秒
画像処理でポインタを使うときはこれが基本になりそう
ビット反転版より遅いけど何回か計測すると逆転することもあったので
普通の引き算もビット反転も変わらないかもと、このときは思っていた
 
処理7
イメージ 19
処理6のLockしない版
0.874秒→0.889秒
ほとんど変わらないのでこれは使う意味がない
 
処理8
イメージ 20
処理6のパラレル版、yループをパラレルに変更で
0.874秒→0.973秒
なぜか遅くなった
でもこれはもっと重たい画像処理なら処理6より速くなるはず
…だと思う
 
処理9
イメージ 21
処理6のx,yでの2重ループを1重ループにしたもの
0.874秒→0.898秒
速くなると思ったけど変わらず、意外
 
処理10

f:id:gogowaten:20191212154919p:plain

処理9の改変
0.878秒→1.405秒
思った以上に遅くなった
PixelFormat.Pbgra32はアルファ値もあるけど
色反転の処理ではアルファ値は変更したくないのでifで判定して弾いている
その処理が重たいみたい、回数が多いからねえ
でもこれでパラレル版にできる、パラレルなら速くなるはず
 
処理11

f:id:gogowaten:20191212154931p:plain

処理10のパラレル版
1.405秒→2.209秒
速くなると思ったのに遅くなった、しかもかなり遅い
アルファ値がじゃま、ifをなくしたい
 
処理12

f:id:gogowaten:20191212154942p:plain

処理11を改変
アルファ値がじゃまならPixelFormatを変更すればいいじゃない
FormatConvertedBitmapを使ってBgr24に変更して
色変反転処理を軽くしたら
2.209秒→1.938秒
速くなったけど…遅い
PixelFormat変換部分が重たいみたい
 
処理13

f:id:gogowaten:20191212154954p:plain

処理6の改変
0.874秒→1.264秒と遅くなった
処理6ではWriteableBitmapのBackBufferからのポインタだったのを
もとのBitmapSourceからCopyPixelsで得たbyte配列のポインタに変更しただけ
なんだけど遅くなったねえ
CopyPixelsと最後のBitmapSource.Createが重たいのかなあ
 
処理14
イメージ 26
処理9のビット反転版
0.898秒→0.723秒
今回試した中で最速になった!
普通の引き算よりビット反転のほうが速いのかなあ
 
処理15
イメージ 27
処理8のビット反転版
0.973秒→0.849秒
やっぱりビット反転のほうが速いみたい
 
DebugとReleaseと32bitと64bit
 
 
イメージ 29
いつもはDebug+AnyCPUっていうモードでコードを書いて
 
イメージ 30
できたなあってときだけRelease+x64にしているんだけど
このモードの違いで処理時間がかなり変わるのに気づいた
多少は違うだろうなあと思っていたけど多少じゃなくて
イメージ 28
5倍以上差が出るのもあった
できたときはRelease+x64でいいんだけど、作っている途中はどんな設定にしたらいいのかなあ、いままでビルドの設定を気にしたことなかったからさっぱりわからん
けどCPUもOSも64bitだから32bit優先はないなあ
 

f:id:gogowaten:20191212155020p:plain

完走した感想
  • ポインタ使ったほうが速い
  • ビット反転のほうが引き算より速い
  • forでのループ総数がおなじなら2重より1重のほうが速い
  • 今回みたいに軽い画像処理ならパラレルは使わなくてもいい
 
コード
 
 
 
 
参照したところ
WriteableBitmapの画素をポインタから操作する - schima.hatenablog.com
http://schima.hatenablog.com/entry/20100918/1284817562
今回のコードはこちらを見よう見まねで書いた
 
.NETによる画像処理の高速化Tips:unsafe編(改稿:2015/11/08)
https://qiita.com/Nuits/items/da8c11e5b284ad6cb90a
WPFじゃないけどbyte配列のポインタの使い方はこちらから
 
 
 
関連記事
2018/3/26
処理速度比較、画像の使用色数を数える、重複なしのリストのHashSetも速いけど配列+ifも速かった ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15430276.html
 
2018/3/15
Parallelクラスを使ってもっと速く減色 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15413665.html