今回は、前回の基本概念をふまえて、Cannyエッジ検出器の3段階の処理について順番にみていきたいと思います。
処理1:入力画像のDoG画像(Difference of Gaussian)を生成
まずは入力画像にガウシアンフィルタを適用して、平滑化を行った画像を作成します。平滑化する理由は、ノイズが多い元の入力画像のまま微分画像を作成すると、ノイズの影響でエッジ部分だけうまく残すことができないからです。
そこで、ガウシアンフィルタなどでまず滑らかな画像を作成すると、上図の右側3つ目のグラフのように、エッジ付近の領域の画素の値が、(1)「平坦なところのノイズがなくなる」、(2)「元画像で急激な変化であった境界付近が、なだらかな変化になる」という2つのメリットが生まれます。この平滑化が終わった画像を微分すると、なだらかになった境界付近のみに綺麗にピークが立つので、そのピークが立った場所のみをそれぞれ「エッジ」として検出できるようになるというわけです。
次に、その平滑化した画像に対して、横方向の微分画像と、縦方向の微分画像の2枚の画像を作成します。作成したそれら2枚の微分画像の差分を取ることでDifference of Gaussian(DoG)画像が生成されます。ちなみにこのDoG画像は、第29回の「キーポイントの検出とSIFT記述子の計算」でも登場した、キーポイントの検出のために使用した画像と同じものです。DoG画像は、2次微分であるLaplacian画像を作成する代わりに、近似的に1次微分の差分計算で(少し高速に)Laplacian画像を近似したものである2次微分画像(つまりはエッジ画像)に相当する画像を(直接2階微分するよりも)高速に作成できるというものです。
ここで、エッジ検出前の前処理に用いられる「ガウシアンフィルタ」について、パラメータを変えるとどのような処理結果になるかについてだけ、個別に大きな図で見ておきましょう(フィルタ処理の仕組み自体については、画像処理やコンピュータビジョンについて解説されている各種書籍等を参照ください)。それでは、以下の図の各結果画像を見てみましょう。
ガウシアンフィルタは2次元のフィルタを用いて2次元ガウシアン関数を各画素に対して畳み込む処理です。その際、2次元ガウシアン関数の分散σの値を大きくしていくと、この図の各結果画像のようにボケ具合が強くなっていきます。Cannyエッジ検出器では微分画像を計算したあとの元画像のノイズの影響を減らすためにガウシアンフィルタを用いるわけですが、あまりに強くガウシアンフィルタをかけてしまうと、この図の一番右図のσ=4の結果画像のように、検出したいエッジそのものが消えてしまっていくことがわかります。境界部分がなだらかになりすぎて検出できなくなるわけです。従ってエッジ検出の前処理としてのガウシアンフィルタでは、σは小さめの値に抑えておくことで、狭い局所領域ごとでのホワイトノイズを少し取り除くくらいの程度で用いるのが、妥当だと言えます。
処理2:Non-maximal suppression(局所的に最大値以外を0に抑える)
DoG処理によりエッジ画像を取得したら、次はNon-Maximum suppressionという処理を行います。これは日本語で言うと「最大でない値は(すべて)値を抑える(=値をゼロにする)」という意味の処理です。つまりは、DoG画像で既に大体のところ浮かび上がっているエッジ候補の白色領域について、エッジとみなせる所以外をすべて黒(0)に抑えてしまう処理です。この処理の目的は「細線化を行い、DoG画像の時点よりもエッジ部分だけを残す画像をつくる」という点にあります。
処理の手順ですが、まずはDoG画像の各ピクセルにおいて、エッジの回転方向θの値を求めます。次に、各注目画素において、その法線方向に沿った直線上で隣り合う2つの画素での値と比較し、その注目画素が3つの中で最大値を取っている注目画素のみを画素値255(白)とし、それ以外の画素値を0(黒)に変更します。白になった画素は法線方向に隣の2つの画素の勾配値より自身の勾配値が高い場所ですので、山の頂点か谷の底である境界を示しており、細線化後に残すべき画素だと言えるでしょう。
こうしてDoG画像上の各画素において、その法線方向で最大の値を持つ画素のみが境界の端点として残るので、エッジが細線化されて細い輪郭に変わるわけです。
処理3:ヒステリシスしきい値処理
最後に、ヒステリシスしきい値処理という処理を行う事により、現時点の各エッジをまとめられるものを1つに連結していきます。これにより、2の段階ではまだ間に溝があるエッジが、出来る限り閉じた長い輪郭に変わります。
ヒステリシスしきい値処理では、処理2で細くなったエッジをたどって行きながら、各点での輝度値を、2つのしきい値より大きいか小さいかをすでにしきい値を超えたかどうかを「履歴的に(ヒステリシスに)」判定してくことにより、間のギャップを埋めてつなげてよいかどうかを決定し、エッジの間の溝を埋めてつなげていく、もしくは切り離したままにするという処理を行います。ここからは実際の処理手順を説明して行きましょう。
まず処理を始める前に、2つのしきい値として「最大しきい値」と「最小しきい値」を用意します。そして、処理を始めるとNon-maximum suppression後の画像に対して、エッジに沿って輝度値がしき値より大きいか小さいかを判定していきます。このとき「最小しきい値」を下回ると、エッジ部分候補とは考えません。これが、最小しいき値を上回りはじめると次のエッジの断片が始まったとみなし、画素ごとに最小しきい値を下回らないかどうかを判定しながら、下回らなければ現在のエッジ候補部分にその画素を追加していきます。このとき、最小しきい値を下回るまでのあいだ、前回最小しきい値以上になってから現時点まで、「最大しきい値を超えたかどうか」の履歴も保持しておきます。そして、最小しきい値を下回り始めた時に、その保持していた履歴にそって、以下の決定を下します。
- 最大しきい値を超える座標が存在していた場合:前回最小しきい値を上回り始めてから現時点までを「エッジ」であると決定して、結果画像では保存しておきます
- 最大しきい値を超える座標が存在していない場合:最大しきい値にみたない輝度の座標しか存在しなかったので、非エッジであると決定して、結果画像では破棄します
この判定処理により、各断片的に存在しているエッジが、1の条件が満たされるごとに1つのエッジとして連結されるという処理が実現されるわけです。これをエッジ画像中のエッジ候補部分すべてについて行えば、ヒステリシスしきい値処理は完了です。
以上の3段階の処理により、Cannyエッジ検出器は細い輪郭としてエッジ部分を入力画像から自動的に検出することができます。