2021年4月2日金曜日

【C#,OpenCV】エッジ抽出フィルタを使ってみる

前回はぼかし関連のフィルタを使ったのでした。
今回はエッジ抽出のフィルタを試してみたいと思います。

エッジ抽出は下記のようなフィルタがあるみたいですね

・Sobel(ソーベル)法
・Laplacian(ラプラシアン)法
・Canny(キャニー)法
・LoG法


輪郭や境界を抽出するために使用するみたいですね。
ガウシアンフィルタなどで元画像のノイズを除去してから使用することが
多いみたいです。



元画像のノイズを除去する?

元画像にノイズが存在してしまうと、
それ自体がエッジとして検出されてしまうことがあるので
ガウシアンフィルタなどでノイズをある程度除いてから使用するみたいですね。

ガウシアンフィルタは前回やった…ような気がします。

ただ、これも、むやみやたらと行ってよいものでもないみたいで
カーネル(フィルタする範囲)が適正でないと、逆にノイズがのってしまうことも
あるみたいです。なんか難しいな?

Sobel法

Sobel法は1次微分フィルタというそうです
代表的なエッジ検出フィルタで、輝度差の少ないエッジでも抽出できるため、
人が見たときにエッジと感じる部分が抽出されるようです。

カーネルを指定する際に、
「横方向で行うか、縦方向で行うかというのを」
決めるみたいです。
この方向によっても、出てくるエッジが変わってくるということですね。

使う場合は下記のような形になります。
imageは読み込んだ画像、dstは変換用のMat型変数です。
sobel_xはX方向、sobel_yはY方向にエッジ抽出をしています。

一旦グレイスケールに変換してから行っています。
ImShowで表示する際には、表示可能なようにBGR形式に戻しています。

フィルタをかけた後はthresholdで画像を2値化しています。
ThresholdTypes.Otsuを指定すると、自動でしきい値を決めてくれるみたいですね。

Cv2.CvtColor(image, dst, ColorConversionCodes.BGR2GRAY);
Cv2.Sobel(dst, dst, MatType.CV_8UC1, 1, 0, 1, 5);
Cv2.Threshold(dst, dst, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
Cv2.CvtColor(dst, dst, ColorConversionCodes.GRAY2BGR);
Cv2.ImShow("sobel_x", dst);

Cv2.CvtColor(image, dst, ColorConversionCodes.BGR2GRAY);
Cv2.Sobel(dst, dst, MatType.CV_8UC1, 0, 1, 1, 5);
Cv2.Threshold(dst, dst, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
Cv2.CvtColor(dst, dst, ColorConversionCodes.GRAY2BGR);
Cv2.ImShow("sobel_x", dst);

オリジナル画像

 
ソーベルフィルタ後の画像

抽出方向によって若干違いが出ていますね。
このあたりも用途に応じて変更したりする…という感じなのでしょう。
人工知能とかだったりすると、このフィルタのパラメータとかを
「いい感じ」の状態に持っていくんでしょうね。

Laplacian法

Laplacian法は2次微分フィルタだそうです。
画像の濃淡に応じてエッジ部分を抽出するようなので、
対象となる画像にホワイトノイズが含まれていると、影響してしまうようです。
こういった場合に、前回のガウシアンフィルタなどでノイズを緩和してから
エッジ検出を行うようですね。

ラプラシアンフィルタは「Cv2.Laplacian」を使用します。
下記のような形で使用します。

ソーベルフィルタと同様に、一回グレイスケールに変換してから
エッジ抽出を行っています。

Cv2.CvtColor(image, dst, ColorConversionCodes.BGR2GRAY);
Cv2.Laplacian(dst, dst, MatType.CV_8UC1, 1, 5);
Cv2.ConvertScaleAbs(dst, dst, 1, 0);
Cv2.Threshold(dst, dst, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
Cv2.CvtColor(dst, dst, ColorConversionCodes.GRAY2BGR);
Cv2.ImShow("laplacian", dst);

 

オリジナル画像

ラプラシアンフィルタ後の画像

これもまた、ソーベルフィルタとは違う結果ができますね。
ソーベルフィルタに比べるとエッジがシャープに出てきているイメージです。

Canny法

Canny法は一個の方法ではなく、
複数のステップによってエッジを検出しているらしいです。
下記のようなことを行っているらしいです。

①ガウシアンフィルタで画像を平滑化する
②平滑化された画像の微分を計算する(Sobel法の適用)
③微分した結果から勾配の大きさと方向の計算をする
④NonMaximumSuppression処理をする
⑤HysteresisThreshold処理をする

なんか複雑ですね。
①や②、③はSobel法までの抽出処理ですね
④のNonMaximumSuppression処理で、③の結果から輪郭を細線化するそうです。
最後に⑤のHysteresisThreshold処理で、信頼性の高い輪郭を残すようです。

これによって下記のような特徴があるのだとか
「輪郭の検出もれや誤検出が少ない」
「各店に一本の輪郭を検出する」
「真なエッジ部分を検出できる」

下記のような形で使用します。

Cv2.CvtColor(image, dst, ColorConversionCodes.BGR2GRAY);
Cv2.Canny(dst, dst, 50, 100);
Cv2.ConvertScaleAbs(dst, dst, 1, 0);
Cv2.Threshold(dst, dst, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
Cv2.CvtColor(dst, dst, ColorConversionCodes.GRAY2BGR);
Cv2.ImShow("canny", dst);
オリジナル画像

キャニーフィルタ後の画像

ラブラシアンフィルタで出ていた、ホワイトノイズのような部分が
なくなったような結果になっていますね。
背景の柱や人の輪郭、陰影部分などのエッジが
結構ちゃんと出ているのではないでしょうか?

ちなみに、パラメータを変えるとそれに応じて出力は変わります。
上のは50~100で出力しましたが、128~255で出すとこのようになります。

128~255で出力した画像

こっちの方が輪郭線っぽく出ていますね。

LoG法

LoG法フィルタは「Laplacian Of Gaussian フィルタ」の略称らしいです。
ガウシアンフィルタとラプラシアンフィルタを組合わせたもので、
ガウシアンフィルタで画像の平滑化を行った後、
ラプラシアンフィルタでエッジを抽出するフィルタです。

ラプラシアンフィルタがノイズに影響されやすいフィルタのため、
事前にガウシアンフィルタを用いることで、誤検出を防いでいるのですね。

方法としてはわかりやすいですね。
ガウシアンフィルタを適用した後のデータに
ラプラシアンフィルタを適用することで実現できそうです。

他にもいろいろ

他にもDoG法とかエッジ抽出の方法はいろいろあるようです。
現在はCanny法がスタンダードなんですかね?
でも、処理工程が多いので、早い処理には向かないのかな?

平滑化の時もそうでしたけど、
その時に応じた、適切なフィルタを使用するのが重要なのでしょうね。

また、フィルタのカーネル値やしきい値といったパラメータも
その時に応じた設定が必要そうです。
このあたりは人の手で逐次やっていくのが厳しい部分だと思いますね。
こういった部分は人工知能AIの出番なのかな、と。

んではでは。

0 件のコメント:

コメントを投稿