OpenCVで画像のエッジ検出をしてみた


はじめに

PythonでOpenCVによる画像のエッジ検出を行ってみました。

微分

微分とは、変化の割合を示すもので、以下の式で表されます。

f'(x) = \lim_{h\rightarrow 0}\frac{f(x+h)-f(x)}{h}

微分により、画像の画素値の変化を調べることができます。
エッジやノイズは、画素値が急激に変化する部分なので、この微分値が大きくなります。

準備

環境はGoogle Colaboratoryを使用します。
Pythonのバージョンは以下です。

import platform
print("python " + platform.python_version())
# python 3.6.9

画像を表示してみよう

では、早速コードを書いていきましょう。
まずは、OpenCVをインポートします。

import cv2

さらに、Colaboratoryで画像を表示するため、以下もインポートします。

from google.colab.patches import cv2_imshow

サンプルの画像も用意しておきましょう。
今回は、Pixabayのフリー画像を使用します。

それでは、用意したサンプル画像を表示してみましょう。

img = cv2.imread(path) # pathは画像を置いている場所を指定
cv2_imshow(img)

また、画像の微分では、グレースケール化した画像を使用しますので、こちらを用意しておきます。

グレースケールの画像は以下で表示できます。

img_gray = cv2.imread(path, 0)
cv2_imshow(img_gray)

画像の微分

それでは、画像を微分する方法について紹介していきます。

Sobelフィルター

まずは、Sobelフィルターを紹介します。
Sobelフィルターは一回微分であり、最も基本的な画像の微分方法といえるかと思います。

Sobelフィルターを使った画像は、以下で表示できます。

img_sobel = cv2.Sobel(img_gray, cv2.CV_32F, 1, 0, ksize=3)
cv2_imshow(img_sobel)

cv2.Sobelの引数は5つです。詳細は、公式のドキュメント等を参照下さい。
第3引数を1とするとx方向の微分、第4引数を1とするとy方向の微分を計算します。上の画像では、x方向(横方向)に微分した画像を表示しています。
第5引数は、カーネルサイズと呼ばれるものです。画像の1点を決めたとき、周囲のどれだけの領域を含むかを表します。「箱の大きさ」だと考えてみて下さい。

それでは、第3、第4引数を変えて、x方向およびy方向にそれぞれ微分した画像を表示してみましょう。

img_sobel_x = cv2.Sobel(img_gray, cv2.CV_32F, 1, 0, ksize=3)
img_sobel_y = cv2.Sobel(img_gray, cv2.CV_32F, 0, 1, ksize=3)
imgs = cv2.hconcat([img_sobel_x, img_sobel_y])
cv2_imshow(imgs)

左がx方向、右がy方向に微分した画像です。
x方向(横方向)の微分は縦方向のエッジが、y方向(縦方向)の微分は横方向のエッジが残ることがわかります。

このように、エッジに方向性がある場合にはSobelフィルターを用いるのが効果的です。

Laplacianフィルター

次は、Laplacianフィルターを紹介します。
Laplacianフィルターは二回微分であり、Sobelフィルターより細かいエッジ検出をしたい場合に効果的です。

念の為、Laplacianの式(二次元)を以下に示します。

\Delta f = \nabla \cdot \nabla f = \frac{\partial^2}{\partial^2 x}f + \frac{\partial^2}{\partial^2 y}f

Sobelフィルターとは異なり、微分する方向を指定する必要はありません。

Laplacianフィルターを使用した画像は以下になります。

img_lap = cv2.Laplacian(img_gray, cv2.CV_32F)
cv2_imshow(img_lap)

Sobelフィルターより、細かいエッジの検出ができているのがわかります。
また、エッジに特定の方向性がなく、Sobelフィルターが有効ではない場合にも使用することができます。

効果的なエッジ検出の手法

ここまで、SobelフィルターとLaplacianフィルターを用いて、エッジを検出してきました。
画像の画素値を微分することで、画素値の急激な変化であるエッジを検出することができます。

しかし、エッジだけでなくノイズも画素値の急激な変化です。
画像には、さまざまなノイズが入っている場合が多くあります。
このような画像に対しては、まず平滑化を行うことでノイズを除去する方法があります。
※平滑化については、以下を参照下さい。
Pythonを使ってOpenCVで画像を『平滑化』してみた

今回は、以下の手順でエッジ検出を行ってみます。

  • グレースケール化
  • 平滑化によりノイズを除去(Gaussianフィルターを使用)
  • 画像の微分によりエッジ検出(Laplacianフィルターを使用)
img_gauss = cv2.GaussianBlur(img_gray, (3, 3), 3)
img_lap = cv2.Laplacian(img_gauss, cv2.CV_32F)
cv2_imshow(img_lap)

Canny

最後に、Cannyの方法を紹介します。
Cannyはエッジ検出のためのアルゴリズムです。
Cannyでは以下の複数のステップを踏んで、エッジ検出を行います。

  • 平滑化によりノイズを除去(Gaussianフィルターを使用)
  • 画像の微分によりエッジ検出(Sobelフィルターを使用)
  • 極大値を検出し、エッジ以外を取り除く
  • 2段階の閾値処理

より詳細は以下を参照下さい。
Canny法によるエッジ検出

それでは、Cannyを用いて画像を表示してみましょう。

img_canny = cv2.Canny(img_gray, 100, 200)
cv2_imshow(img_canny)

第2、第3引数は、2段階の閾値処理で用いる値です。
第2引数は閾値の最小値、第3引数は閾値の最大値を指定します。

まとめ

今回は、Pythonを使ってOpenCVにより画像のエッジ検出を行いました。

画像を微分する方法として、Sobelフィルター、Laplacianフィルター、Cannyの方法を紹介しました。
検出したいエッジの特徴によって、使い分けてみて下さい。
また、微分は画素値の急激な変化を検出する方法ですが、エッジだけでなくノイズにも反応します。
そのため、エッジのみ検出したい場合には、あらかじめノイズ除去を行う必要があります。
ノイズ除去には、平滑化を検討してみて下さい。

画像の微分や平滑化に関する詳細は、以下を参照下さい。