2013. 8.20.
石立 喬

Visual Studio 2012 Expressの易しい使い方(7)

――― Win32 で、ファイルから画像を読み込み、コンボリューションによる画像処理を行う ――

 Win32 での画像処理の次のステップとして、コンボリューションによる、画像の平滑化(ぼかす)、鮮鋭化(シャープにする)、エッジ検出を試みる。原画像は、ファイルから読み込むようにし、メニューとダイアログボックスを用いる。
 コンボリューションについては、「Visual C++ 2010 Expressを用いた易しい画像処理(5)」に詳しく述べてあり、特に、エッジ検出については、同じく、「易しい画像処理(7)」を参照されたい。
 また、Java APIのコンボリューション関連標準メソッドを用いた例は、CodeZine「コンボリューションを用いた画像の平滑化、鮮鋭化とエッジ検出」にある。

画像読み込みのための準備
 メニューから、「ファイル」→「開く(0)...」をクリックすると、「画像ファイルの読込み」ダイアログボックスが開き、ファイル名を選択すると、その画像ファイルが読み込まれて画面に表示されるようにする。なお、プロジェクト名はWin32Project5である。
 これを実現するためには、
 1)メニューの作成と識別子IDM_OPENの設定 --- Win32Project5.rcへの記述
 2)IDM_OPEN識別子の定義 --- Resource.hへの記述
 3)「画像ファイルの読込み」ダイアログボックスの設定 --- obtainOpenFileName関数の作成
 4)メニューがクリックされた時の処理の指定 --- WndProc関数のcase IDM_OPENとcase WM_PAINTへの記述
が必要である。
◎メニューの作成
 図1に示すように、Win32Project5.rcへ、"開く(&O)..."を追加する(エディタの使い方は、「易しい使い方(5)」参照)。


図1 Win32P.roject5.rcに新しくメニュー項目を記述する


◎識別子IDM_OPENの定義
 図2に示すように、Resource.hに、「開く」のメニューをクリックしたことを示す識別子IDM_OPENを定義しておく。WIDTHとHEIGHTを、プログラム中で定数として使用するので、これらもここで定義しておく。


図2 Resource.hで、IDM_OPENなどを定義する。


◎「画像ファイルの読込み」ダイアログボックスを用意し、画像が選択された時の処理を関数化する
 図3に示すobtainOpenFileName関数を作成し、「開く」のダイアログボックスを開き、画像ファイルを選択して「OK」をクリックすると、そのファイル名が得られるようにする。
  画像の読み込みは、標準のGetOpenFileName関数を用い、選択したファイル名を取得し、これをLoadImage関数で読み込む。GetOpenFileName関数は、OPENFILENAME構造体のポインタを引数として動作するので、OPENFILENAME ofnの記述が必要である。この詳細については、ネット上に多くの解説があるので、省略する。


図3 用意したobtainOpenFileName関数


◎メニューがクリックされた時の処理
 図4と図5は、WndProc関数の一部であり、下記を記述する。
 1)IDM_OPENで、作成した関数obrainOpenFileNameを呼び出し、ファイル名を得る(146行)。
 2)ウインドウを再描画し(147行)、図5に示すように、WM_PAINTにより、UserProgramを呼び出す(ファイル名を渡して)。


図4 WndProcの一部、IDM_OPENで、obtainOpenFileNameを呼び出す



図5 WndProcの一部、WM_PAINTで、lptstrFileNameを渡して、UserProgramを呼び出す。


プログラムの最初の部分
 図6に示すように、GetOpenFileNameを使用するためのcommdlg.hと、sqrt(平方根)の数学関数のためのcmathをインクルードし(6行〜7行)、自作の関数を宣言する(21行〜24行)。


図6 プログラムの最初の部分


UserProgramの内容
 図7に、UserProgramの最初の部分を示す。平滑化と鮮鋭化の二種類のカーネルを定義してある。それ以外の部分は、「易しい使い方(6)」の後半に示したものと、ほぼ同じである。カーネルは、画像に対して、二次元的に作用するが、ここでは、一次元配列にしてある。


図7 UserProgramの最初の部分


 図8は、UserProgramの主要部分で、このプロジェクトが起動して、最初に呼び出される時には、まだ画像を読み込んでいないので、画像の表示も処理も出来ず、ウインドウの表示のみにしないといけない。そのため、フラグbFirstを用いて、これをコントロールする。
 メニューで画像を選択して、再びUserProgramが呼び出された場合には(その時は、bFirstがFALSEになっている)、その画像ファイルを読み込み、画面左上に表示する。
 次いで、原画像をDIB(ここでは、lpcrPixelDataの一次元配列)に変換し、平滑化、鮮鋭化、エッジ検出のために、関連する関数を呼び出し、画像処理をして、それぞれ、画面に表示する。SetDIBitsToDevice関数については、「易しい使い方(6)」に解説してある。


図8 UserProgramの主要部分


 最後に、図9のように、確保したメモリ領域を、念のために解放しておく。


図9 UserProgramの最後の部分


コンボリューションによる画像処理
 これについては、「Visual C++ 2010 Expressを用いた易しい画像処理(5)」に詳しく述べてあるので、詳細は省略する。
 簡単なコンボリューションは、3x3のマトリックス(これを、カーネルと呼ぶ)を、画像上の注目するピクセルの周辺の3x3のピクセルデータと掛け合わせ、その合計値を新しい画像のピクセル値とする。
 カーネルを変えることにより、平滑化(ぼかす)や鮮鋭化(シャープにする)ができる。エッジ検出は、鮮鋭化の極端なケースで、コンボリューションの結果(複数個使用する)に非線形的な処理を行う。

makeConvolution関数の作成(平滑化と鮮鋭化に使用)
 コンボリューションは、画面を3x3の範囲で演算を行うので、原画像の周辺1ピクセル分は処理に工夫が必要になる。ここでは、原画像の周辺は処理を行わず、処理後の画像は、原画像のコピーで済ます簡便な方法をとった。
 図10に示すのは、makeConvolution関数の前半部分で、まず、周辺部のコピーを行っている。


図10 makeConvolutionの前半部、コンボリューションに先立ち、周辺部をコピーする


 図11に示すように、makeConvolution関数の主要部であるコンボリューションでは、UserProgramから受け取った引数のpfKernelを用いて、積和演算を行う。R、G、Bの各成分について行われ、値の範囲を補正したのちに、DIB(lpcrPixelData)に書き込んで、UserProgramに返す。


図11 makeConvolutionの主要部


detecteEdges関数の作成(エッジ検出専用)
 エッジ検出も、コンボリューション処理の一種であるが、コンボリューションのカーネルを2個(垂直方向エッジ用と水平方向エッジ用)使用し、処理後に、やや複雑な処理を行うので、専用の関数を作成した。ここでも、原画像の周辺1ピクセル分は処理に工夫が必要になる。ここでは、原画像の周辺は処理を行わず、この部分の処理後の画像は、強制的に白(エッジが検出されない)にする。
 図12に示すのは、detectEdges関数の前半部分で、カーネルの定義と、周辺部の白データ化を行っている。カーネルは、Prewittとして知られるものを用い、垂直方向の検出用と水平方向の検出用の2種類を用意した。


図12 detectEdges関数の前半部、二種のカーネルを用意し、周辺に白を書き込む


 続いてのコンボリューションでは、図13に示すように、上で設定した2種類のカーネルを用いて、積和演算を行う。R、G、Bの各成分について行われ、エッジ強度の計算を行った後に、DIB(lpcrPixelData)に書き込んで、UserProgramに返す。エッジ検出は、1か0か、すなわち、ピクセル濃度では、0か255かの二値で表すが、ここでは、その判定のしきい値に128を選んだ(403行)。


図13 detectEdges関数の主要部、エッジ強度の計算方法は一例


得られた結果
 図14は、メニューの「開く」をクリックして、「画像ファイルの読込み」ダイアログボックスが開いたところで、ビットマップ画像を選択して、「開く(0)」をクリックする直前を示す。


図14 読み込む画像を選択するダイアログボックス


 画像ファイルを開くと、図15のような結果が得られる。左上は原画像、右上は平滑化(ぼかし)画像、左下は鮮鋭化(シャープ化)画像、右下はエッジ検出結果である。
 一般的に、自然画像からエッジを検出するのは、むつかしい。同一画像内で、エッジ強度に大きな差があり、エッジ判定の閾値を高くすると、検出されるエッジが少なくなり、閾値を下げると、多すぎてノイズになる。ここでは、しきい値を128に固定した結果を示す。
 原画像として、自然画像ではなく、白黒のパターンを用いると、図16に示すように、エッジ検出がきれいにできていることが分かる。


図15 得られた結果



図16 白黒パターンに対するエッジ検出の例

エッジ検出の場合の、しきい値の効果
 図15では、エッジ強度のしきい値を128に固定して示した。しかし、実際的には、原画像によって、最適のしきい値がある。
 図17はしきい値を変えてエッジを検出したもので、左上の図は、エッジ強度が160以上のもののみをエッジと判定した場合、右上の図は、128以上のものを判定した場合を示す。しきい値を下げて行くと、次第にエッジが増え、真っ黒になってしまう。

しきい値を160とした場合 しきい値を128とした場合(図15と同じ)
   
しきい値を96とした場合 しきい値を64とした場合
   

図17 しきい値を変えてエッジを判定した結果