見出し画像

画像処理100本ノックをC#でおこなってみる

こんにちは。
久しぶりの投稿になります。

GitHubには「○○100本ノック」というのが、ちらほらあります。
その中でも、今回は画像処理100本ノックというのに着目しました。
本家の画像処理100本ノックは以下になります。

本家ではPythonとC++を使って画像処理に関する問題100問に解答するという流れになっていますが、今回はC#を使ってこの画像処理100本ノックを問題を解いていこうというものになっています。

現在、35問目あたりまで解いておりますが、ソースコードは以下にまとめてあります。

せっかくなので、UIも用意しました。

今回、問題を解くにあたり、OpenCVやOpenCVSharp、さらには有償のライブラリなどは一切使用しておりません。
また、処理高速化のために並列処理やアンセーフコードを許可したりしておりますが、C++で画像処理を実行するといった他の言語の使用も禁止しながら行ってみました(それでは、他の言語で問題を解いているのと変わりないので)。

画像を扱う上での注意点

ここでは、C#(特にWPF)で画像を扱う上での注意点をまとめていこうかと思います。
まず、WinFormsに慣れている方でしたら、画像を扱う型といえば「System.Drawing.Bitmap」が一番よく使うものとして知られているかと思いますが、WPFでは「System.Windows.Media.Imaging.BitmapSource」や「System.Windows.Media.Imaging.BitmapImage」など他の型を使うことが必要になる場面も存在します。
そして、それぞれの型変換も結構厄介であったりと個人的には使いづらい印象があります。
これらの型については、他の方が記事にまとめているので参考にしてみてください。

今回は、基本的に内部では「System.Drawing.Bitmap」で画像を扱うことにし、UIに画像を表示させるときにだけ「System.Windows.Media.Imaging.BitmapSource」に変換することにしました。
理由としては、この変換が一番簡単だったからです。
変換処理はソースコードを見ていただけるとわかりますが「CreateBitmapSourceFromHBitmap」という関数を使用してます。

また、画像データのメモリ構造については、こちらの記事が参考になりました。

特に、画素値を取得する際に必要になる情報として

・画像1行あたりのメモリは4の倍数のバイトになるように調整されている
・カラーの場合、B,G,R,B,G,R,・・・もしくは B,G,R,A,B,G,R,A,・・・の順で並んでいる

というのが重要になるポイントだと思います。
これがわかれば、あとはBitmapデータをLockBitsして、先頭のポインタ(Scan0)を取得すれば、欲しい画素値は先頭ポインタから何バイト先にあるかで参照できるようになります。

実はこんなことをしなくても、Bitmapデータには、予め「SetPixel」や「GetPixel」というような画素値にアクセスするような関数が用意されているのですが、これが途轍もなく遅いのです。
単純に画像の高さ方向と幅方向に、二重のforループを回してすべての画素値にアクセスするだけでも上の関数を使用すると数秒かかってしまいます(PCのスペックにもよりますが、一般的に上記の関数は遅いと言われています)。
問題が解けたとしても、実運用で許容されないような実行時間では、やってもあんまり意味がないなと感じたので、今回は「SetPixel」や「GetPixel」を使わない方法を採用しました。
他の言語とも引けを取らない速度になったかなと感じています。
余裕があれば、アルゴリズムの改善はしていきたいです。
特に、メディアンフィルタやフーリエ変換は遅いので。。。
また、GPUの利用も可能にしていければ尚良いなと思っています。

まとめ

今回は画像処理100本ノックに挑戦したということで記事を書いてみましたが、まだまだ問題は解けていないものも多いので、今後も挑戦を続けていこうと思っています。
また、アルゴリズムについてもまだまだ改善の余地があると思うので、精進していかねばと感じています。
高速化については、何か良いアドバイス等ありましたら、コメント残してくれれば幸いです。

最後までお読みいただきありがとうございました。

この記事が気に入ったらサポートをしてみませんか?