はじめに
エッジ検出のアルゴリズムであるプレヴィット フィルター(Prewitt Filter)について紹介します。
エッジ検出アルゴリズムはOpenCVではいくつかのアルゴリズムが実装されています。本サイトでもCannyエッジ検出器、ソーベルフィルター、ラプラシアン フィルターによるエッジ検出を紹介しています。
これらに加えて、OpenCVの汎用的なフィルター関数であるcv2.filter2D
関数にカーネルを設定して実現する、プレヴィット フィルターの実装方法を紹介します。
プレヴィット フィルターとは
プレヴィット フィルターは、エッジ検出に広く使用される一つの手法です。
このフィルタは、画像内の各ピクセルに対して、その周囲のピクセルの輝度差を計算し、エッジを強調します。
特徴
- 計算が比較的高速
プレヴィット フィルターは水平方向と垂直方向の勾配を計算する際に単純な微分フィルター([-1, 0, 1])を使用しています。このシンプルな構造により、計算が効率的かつ容易に行えます。 - ノイズへの影響
プレヴィット フィルターは、ノイズに対して比較的敏感です。ノイズが強い場合、エッジ検出の結果に不要な影響を与える可能性があります。そのため、前処理や他のフィルタとの組み合わせが必要なことがあります。
他のエッジ検出アルゴリズムとの比較
当サイトでも紹介した、いくつかの代表的なアルゴリズムと比較しながら、それらの特徴を紹介します。
ソーベル フィルター(Sobel Filter)
ソーベルフィルタは、プレヴィット フィルターと同様に水平方向と垂直方向の勾配を計算しますが、プレヴィット フィルターよりも滑らかなエッジを生成することがあります。
Cannyエッジ検出器
Cannyエッジ検出器は、エッジ検出のための包括的な手法で、エッジの検出とエッジの細線化、しきい値処理を組み合わせて行います。高品質なエッジ検出結果を得ることができますが、プレヴィット フィルターに比べ計算コストが高いことが欠点です。
ラプラシアン フィルター(Laplacian Filter)
プレヴィット フィルターはエッジの方向と強度を同時に抽出できるため、一般的なエッジ検出に使用されることが多く、ラプラシアン フィルターはエッジの位置を特に強調するため、特定のエッジの位置を抽出したい場合に使用される場合が多い様です。
プレヴィット フィルターの計算
エッジは通常、物体や領域の境界を示し、その境界においては輝度が急激に変化します。
その画素値の変化に対して微分を行うことで、エッジを検出することができます。
微分は、関数の変化率を表すものであり、画像においては画素値の変化に相当します。エッジがある領域では、画素値が急激に変化するため、その位置で微分をとると大きな値が得られます。この大きな値がエッジの存在を示します。
プレヴィット フィルターは水平方向と垂直方向の微分を同時に計算するため、エッジの方向も同時に検出できます。これにより、エッジの向きや角度に関する情報も得られるため、エッジの特徴をより詳細に捉えることができます。
プレヴィット フィルターの様な微分フィルタを実装するために、cv2.filter2D
関数を使用します。まず、cv2.filter2D
関数について解説します。
cv2.filter2D関数
cv2.filter2D
関数は、OpenCVで畳み込み演算を行うための汎用的な関数です。
畳み込みは、画像フィルタリングやエッジ検出など、画像処理で広く使用される操作です
この関数は、十分に大きなカーネル (~11 x 11 以上) の場合は DFT ベースのアルゴリズムを使用し、小さなカーネルの場合は直接アルゴリズムを使用します。
引数
名称 | 説明 |
入力画像(必須) | 入力画像 |
ddepth(必須) | 出力画像のデータ型を指定します。出力画像のデータ型を指定することで、演算の精度を調整できます。指定できるものはこちらで確認できます。-1の場合、入力画像と同じ深度になります。 |
kernel(必須) | 畳み込み演算に使用されるカーネル。通常はNumPyの行列(2次元配列)として指定します。この行列が畳み込みのフィルタになります。 |
anchor(オプション) | カーネルの中心の相対的な位置を示す点。デフォルトでは、カーネルの中心が使用されます。 |
delta(オプション) | み込みの結果に加算されるオプションの値。(デフォルト:0) |
borderType(オプション) | 像の境界条件を指定します。(デフォルト:cv2.BORDER_DEFAULT ) |
戻り値
戻り値は検出されたエッジの強度を表すマップ(画像)。
■(広告)OpenCVの参考書としてどうぞ!■
プレヴィット フィルターを実装する
cv2.filter2D
関数でプレヴィット フィルターの演算を行うには以下のカーネルを引数のkernel
に渡します。
Pythonでは下の様に、Numpyの2次元配列として作成します。
# 水平方向微分のカーネル kernel_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]]) # 垂直方向微分のカーネル kernel_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]])
また、水平方向微分により縦方向の輪郭を検出し、垂直方向微分により横方向の輪郭を検出となり、微分の方向と検出されるエッジで作られる輪郭が逆の関係になります。
水平方向、垂直方向をそれぞれエッジ検出して、その結果を二乗和平方根の演算で合成することでエッジの検出結果となります。
水平方向のエッジ画像を \(I_{x}(x,y)\)、垂直方向のエッジ画像を \(I_{y}(x,y)\)とすると、エッジの検出結果 \(I(x,y)\)は時式の様に表されます。
$$I(x,y) = \sqrt{\mathstrut I_{x}(x,y)^2+I_{y}(x,y)^2}$$
これらをソースコードで表現すると下の様になります。
# 水平方向微分カーネルでエッジ検出 prewitt_x = cv2.filter2D(image, -1, kernel_x) # 垂直方向微分カーネルでエッジ検出 prewitt_y = cv2.filter2D(image, -1, kernel_y) # 水平方向、垂直方向の結果を二乗和平方根でエッジ画像を生成 prewitt = np.sqrt(prewitt_x**2 + prewitt_y**2)
全体のサンプルコードと入力画像は以下の通りです。
入力画像として、この画像を使用しています。
cv2.imread
関数でグレースケールに変換しました。引用元
https://pixabay.com/ja/photos/%E5%BB%BA%E7%89%A9-%E6%A9%8B-%E5%B7%A5%E4%BA%8B-%E5%AF%BE%E7%A7%B0-5506574/
import cv2 import matplotlib.pyplot as plt import numpy as np # 画像の読み込み image = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE) # 水平方向微分のカーネル kernel_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]]) # 垂直方向微分のカーネル kernel_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]) # フィルタ2Dを適用 prewitt_x = cv2.filter2D(image, -1, kernel_x) prewitt_y = cv2.filter2D(image, -1, kernel_y) # エッジの強度を計算 prewitt = np.sqrt(prewitt_x**2 + prewitt_y**2) # 画像とエッジ画像を表示 plt.rcParams["figure.figsize"] = [12,7.5] # ウィンドウサイズを設定 title = "Prewitt Filter: codevace.com" plt.figure(title) # ウィンドウタイトルを設定 plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95) # 余白を設定 plt.subplot(221) # 2行2列の1番目の領域にプロットを設定 plt.imshow(image, cmap='gray') # 入力画像をグレースケールで表示 plt.title('Original Image') # 画像タイトル設定 plt.axis("off") # 軸目盛、軸ラベルを消す plt.subplot(222) # 2行2列の2番目の領域にプロットを設定 plt.imshow(prewitt, cmap='gray') # プレヴィット フィルタの結果 plt.title('Prewitt Filter') # 画像タイトル設定 plt.axis("off") # 軸目盛、軸ラベルを消す plt.subplot(223) # 3行2列の3番目の領域にプロットを設定 plt.imshow(prewitt_x, cmap='gray') # 水平方向微分の結果 plt.title('Horizontal Kernel') # 画像タイトル設定 plt.axis("off") # 軸目盛、軸ラベルを消す plt.subplot(224) # 2行2列の4番目の領域にプロットを設定 plt.imshow(prewitt_y, cmap='gray') # 垂直方向微分の結果 plt.title('Vertical Kernel') # 画像タイトル設定 plt.axis("off") # 軸目盛、軸ラベルを消す plt.show()
この例では、グレースケールに変換した入力画像、プレヴィット フィルター、水平方向微分カーネルで検出したエッジ画像と垂直方向微分カーネルで検出したエッジ画像を下の様に出力します。
右上の二乗和平方根で合成した、プレヴィット フィルターの結果は微細なエッジも感度良く検出できていることが確認できます。
下段の左側は垂直方向のエッジを検出する水平方向微分の結果、右側は水平方向のエッジを検出する垂直方向微分の結果です。水平、垂直それぞれのエッジをよく捉えていることが確認できます。
また、斜め45°に傾いたエッジは水平方向微分、垂直方向微分の両方で検出されることも確認できます。
これらの結果より、エッジの角度、つまりは画像中の被写体の角度の情報が得られることがわかりました。
おわりに
プレヴィット フィルターは水平方向、垂直方向のエッジを個別に抽出するため、必要な方向のエッジだけを検出することができることが大きな特徴と言えます。
計算コストも比較的低いため、場合によっては非常に有用なエッジ検出アルゴリズムです。
当サイトでも、様々なエッジ検出方法を紹介しました。これらの記事が、それぞれの特徴を理解や使いこなしにお役に立てて頂けると嬉しいです。
本ブログでは他のエッジ検出の方法も紹介しています。ご参考にして頂けると幸いです。
ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。
参考リンク
Image Filtering
Convolves an image with the kernel.
(広告)おすすめ転職エージェント
転職はあなたの可能性を広げる鍵です。その鍵を手に入れるため、おすすめ転職エージェントを紹介します。
新しいキャリアの扉を開くために、転職エージェント活用して、妥協のない転職活動を行ってください。