2014.12.31.
石立 喬

OpenCVとVisual C++による画像処理と認識(7)

----- いろいろなエッジ検出フィルタを比較する -----

 エッジ検出フィルタは、ノイズを除去しながら物体の輪郭を取り出し、画像認識の前処理に重要な役割を果たす。ここでは、代表的なエッジ検出フィルタであるSobelカーネル、Laplaceカーネルと、Canny法について紹介する。
 カラー画像に対しては、折角のカラー情報を有効に使うため、グレイスケール化をしないでRGB別にエッジを採る方法と比較する。

エッジ検出とは
 画像の中で、ピクセル濃度の変化の多い部分がエッジである。エッジは、物体等の境界部分であることが多いので、物体の検出に有用である。ピクセル濃度差は、一次微分を取るもの(Sobelなど)と二次微分をとるもの(Laplacian)がある。
 いずれも、3 x 3のフィルタリング・カーネルを用い、結果にしきい値を用いて最終的にエッジの判断をする。

エッジ検出フィルタのいろいろ
 OpenCVの関数として用意されているエッジ検出関数には、Sobel、Laplacian、Cannyがある。CannyはSobelの結果にCannyアルゴリズムを適用したもので、フィルタマトリックスとしては、図1のようになっている。


図1 エッジ検出用のカーネル


Sobelフィルタを、グレイスケールとカラーで比較するプログラム
 図2のプログラムは下記から成っている。
1)原画像を、画像src_imageとして読み込んで表示する。
2)原画像をグレイスケール化してから、Sobel処理を行う。X方向とY方向の出力の絶対値を取り、平均を取る。
3)試行錯誤で得た適当なしきい値を与えて、反転二値画像にする。
4)原画像(カラー画像)をチャンネル別に分け、R、G、B各成分の画像を用意する。
5)それぞれの成分についてSobel処理を行う。X方向とY方向の出力の絶対値を取り、平均を取る。
6)各成分のSobel出力から、その最大値を採ってカラー画像のSobel出力とする。
7)試行錯誤で得た適当なしきい値を与えて、反転二値画像にする。

使用したOpenCV関数の説明
◎Sobel関数
 原画像を一次微分でエッジを検出する関数で、平滑化する効果もある。

  Sobel(原画像、X方向結果画像、結果画像のdepth、X方向次数、Y方向次数、カーネルサイズ、倍率、オフセット、周辺処理法);

のように引数を与えるが、カーネルサイズ= 3、倍率 = 1.0、オフセット =0.0、周辺処理法 = BORDER_DEFAULTがデフォルトであるので、
 Sobelカーネルには、X方向の変化を検出するものと、Y方向のものとを使用し、下記で、上がX方向用、下がY方向用である。

  Sobel(原画像、X方向結果画像、CV_32F、1、 0);
  Sobel(原画像、Y方向結果画像、CV_32F、0、 1);

結果画像のdepthを指定する場合、CV_32Fを使うのが好ましい(CV_16Sでは、あとでthreshold関数を使えない)。
 なお、ここでは、X方向とY方向の各絶対値を加算するだけであると、そのままthresholdをかけるには出力が大きすぎて2で割った。平均を採ることに特別の意味はない。
◎threshold関数

  threshold(入力配列、結果配列、しきい値、最大値、結果のタイプ);

となっていて、入力配列はfloat型に限られる(CV_16Sは使えない)。結果のタイプには、THRESH_BINARYとTHRESH_BINARY_INVがあり、後者を指定すると、白黒が反転される。
◎abs関数
 データ値として負数が含まれている配列で、

  結果配列 = abs(原配列);

のようにして使用する。原配列のタイプと結果配列のタイプは同じになる。
 絶対値を採って、結果をucharに治めるには、
 
 convertScaleAbs(原配列、結果配列、倍率、オフセット);

を使用する。デフォルトでは、倍率 = 1.0、オフセット = 0.0である。
◎max関数
  結果配列 = max(入力配列1、入力配列2);

または、

  max(入力配列1、入力配列2、結果配列);

のようにして使う。


図2 グレイスケール画像とカラー画像に対するSobel処理プログラム


 図3は得られた結果で、左上の原画像を、左下のGray画像に変換してからSobel処理を行うと、右下の結果が得られる。これによると、駐車禁止の斜めの線が出ていない。これは、グレイスケールでは赤と青の濃度差が小さいからである。これをエッジとして検出できるようにしきい値を下げると、ノイズが多量に出てしまう。
 原画像をR、G、Bに分解し、それぞれにSobel処理をして、出力の最大値を採った結果が図2の右上に示してある。これによれば、駐車禁止の斜めの線がはっきりと出ている。


図3 Sobel処理の結果


Laplacianフィルタを、グレイスケールとカラーで比較するプログラム
 図4のプログラムは下記から成っている。
1)原画像を、画像src_imageとして読み込む。
2)原画像にGaussianBlurを使用して、ノイズを除去し、表示する。
3)ノイズを除去した原画像src_imageをグレイスケール化してgray_imageとする。
4)gray_imageにLaplacianを実施し、試行錯誤で得たしきい値で反転画像lap_imageをを生成し、表示する。
5)ノイズを除去した原画像(カラー画像)src_imageをチャンネル別に分け、R、G、B各成分の画像を用意する。
6)それぞれの成分についてLaplacian処理を行う。
7)各成分のLaplacian出力の絶対値を取り、その最大値を求めてlap_imageとする。
8)試行錯誤で得た適当なしきい値を与えて、反転二値画像にして表示する。

使用したOpenCV関数の説明
◎Laplacian関数
 二次微分を用いて画像ピクセル値の変化を検出する関数で、僅かな変化にも敏感に反応し、ノイズに弱い。GaussianBlurなどでノイズを除去してから使用するのが原則である。Sobelのように方向性を持たないので、簡単に使用できる。
 関数の形式は

  Laplacian(原画像、結果画像、結果画像のdepth、アパーチャ(カーネル)サイズ、倍率、オフセット、周辺処理法);

であるが、カーネルサイズ = 1(3 x 3の標準カーネルを指す)、倍率 = 1.0、オフセット = 0.0、周辺処理法 = BORDER_DEFAULTがデフォルトなので、結局一般的には、
  
  Laplace(原画像、結果画像、CV_32F);

で良い。
 ただし、ここではカーネルサイズ = 3を使ったので、最後に3と指定した。3の方が出力が大で、5ではノイズを拾いすぎた。




図4 グレイスケール画像とカラー画像に対するLaplacian処理プログラム


 図5は得られた結果で、左上のノイズ除去原画像を、左下のノイズ除去Gray画像に変換してからLaplacian処理を行うと、右下の結果が得られる。これによると、やはり、駐車禁止の斜めの線が出ていない。
 原画像をR、G、Bに分解し、それぞれにLaplacian処理をして、出力の最大値を採った結果が図5の右上に示してある。これによれば、駐車禁止の斜めの線が不完全ながら出ている。
 Laplacian処理は、あらかじめノイズを除去しておかないと、使い物にならないほどの偽エッジが出る。ここでは、5 x 5のカーネルを使ってGaussianBlurを行い、ようやく実用可能になるほどであった。その影響で、肝心の欲しいエッジの出力が小さく、しきい値の設定に困る面もあった。
 Laplacianは二次微分のため、真のエッジ部を挟んで、正負二つの出力が出る。これの絶対値を取ると、エッジが急峻な部分では一本の線になるが、傾斜が緩いエッジでは、線が二本に分かれる。真のエッジは正負の出力の間のゼロクロス点にあるが、これを求めるのは面倒で、エッジの強度にかかわりなく検出されるの欠点がある。なお、OpenCVにはゼロクロスを求める関数は用意されていない。



図5 Laplacian処理の結果


Cannyアルゴリズムを、グレイスケールとカラーで比較するプログラム
 図6は、変更部分のみを示したプログラムで、下記から成っている。カラー原画像を、GaussianBlurでノイズ除去したものを、画像src_imageとして、あらかじめ準備してある。
1)原画像src_imageをグレイスケール化してgray_imageとする。
2)gray_imageにCannyを実施し、試行錯誤で得た二つのしきい値を用いて、結果画像canny_imageを得る。
3)canny_imageを白黒反転させて、表示する。
4)カラー原画像src_imageをチャンネル別に分け、R、G、B各成分の画像を用意する。
5)それぞれの成分についてCanny処理を行う。
6)各成分のCanny出力の最大値(bitwise_orと同じ)を採り、canny_imageとして、表示する。

使用したOpenCV関数の説明
◎Canny関数
 Cannyアルゴリズムを用いて、エッジを検出する。ガウスぼかしの後、縦横二つのSobelカーネルを用いてエッジを検出するが、それらの結果は、二乗和の平方根を取るL2 norm、または、絶対値の和を用いるL1 norm(計算コストが安い)から選んでエッジ強度とする。デフォルトでは、後者になっている。良好な結果が得られるが、処理時間は遅い。
  外から与える二つのしきい値は、以下のように用いられる。
      エッジ強度<しきい値1(低い方) -------- エッジとして認めない
      エッジ強度>しきい値2(高い方) -------- 必ずエッジと判断する
      上記以外の場合(中間値)-------------- すでにエッジと判定されたものと連結している場合はエッジとする
 エッジ部分の最大値近辺の出力を抑制して細線化を行っているので、判定されたエッジの幅が細い。しきい値2は、しきい値1の2倍から3倍の間が推奨されている。
 関数の引数は、

  Canny(原画像、結果エッジマップ、しきい値1(低)、しきい値2(高)、アパーチャ(カーネル)サイズ、傾斜の計算方法);

であるが、アパーチャサイズ = 3、傾斜の計算方法はL2 norm = falseがデフォルト(高速なL2 normを使用する)なので、結局一般的には、
  
  Canny(原画像、結果エッジマップ、しきい値1、しきい値2);

で良い。
 実際に使用した結果では、ノイズ除去効果はあるものの、あらかじめノイズを除去しておいたた方が、さらに良い結果が得られた。
◎bitwise_or関数
 二つの原画像の配列の各要素ごとに、論理和を採る。Canny出力のように、濃度0 (=00000000)、濃度255 (=11111111)の場合には、最大値を採ることと同じである。多チャンネルに対応している。

  bitwise_or(原配列1、原配列2、結果配列、マスク);

 マスクは、noArray()がデフォルトなので、省略できる。


図6 Canny処理プログラム(主要部のみ)


 実行結果を図7に示す。Cannyは、グレイスケール画像にしか対応していないので、カラー画像は、やはり、RGBに分解した方が良い結果が得られた。原画像は、図4に示すものと同じなので、省略する。


図7 Canny処理の結果


結 論
 エッジ検出フィルタは、原画像から、認識の対象を見つけやすくする目的で使用する。比較的簡単で、処理速度も速いのがSobelカーネルを使うもので、縦横双方向の処理が必要であるが、一次微分のために、ノイズの影響を受けにくい。Laplacianカーネルは、二次微分のため、Gaussianフィルタと共に使用しないと、ノイズが出る。ただし、縦横の区別がないので、プログラム的には使いやすい。
 Cannyは、素晴らしい。当然のことながら、処理時間はかかる。
 OpenCVのエッジ検出フィルタは、グレイスケール画像しか考えていない。カラー情報を有効に利用するには、RGBに分解して、それぞれの成分ごとにエッジ検出を行い、それらの最大値(または合計値など)をエッジとする必要がある。HSV表色系への分解も試みたが、使用したサンプル画像に限っては、良い結果が得られなかった。
 エッジ検出は、OpenCVの関数を使う限り、完全な自動化はできない。しきい値を外部から、試行錯誤で与える必要がある。他の物体識別・認識方法も活用して、エッジ検出に適した原画像を得る必要がある。


「Visual C++の勉強部屋」(目次)へ