画像処理におけるエッジ検出とは、画像中の物体の輪郭(境界)を抽出する技術です。物体のエッジを検出することで、画像中のオブジェクトをより鮮明に把握できます。
この記事では、1次微分フィルタ、Prewitt、Sobel、Canny、Laplacianの5種類のエッジ検出手法を解説します。
エッジ検出の原理
エッジの検出は、明るさ(輝度)が大きく変化する部分を抽出することで実現します。例えば、白い背景に黒い物体がある場合、白から黒に変わる境界が物体の輪郭です。
具体的には、各ピクセルの輝度を微分し、微分値が大きくなる部分をエッジとして検出します。画像のピクセルは離散値なので、隣り合う画素間の差を取ることで近似的な微分として扱います。
1次微分フィルタ(cv2.filter2D())
最もシンプルなエッジ検出手法です。以下のカーネルを使用し、注目画素と隣接ピクセルとの差でエッジを検出します。

Kxでは水平方向、Kyでは垂直方向の微分値が得られます。
Pythonで実装
OpenCVには1次微分フィルタ専用の関数がないため、カーネルを定義してcv2.filter2D()でフィルタリングします。
import cv2
import matplotlib.pyplot as plt
import numpy as np
import japanize_matplotlib
# 画像の読み込み
image = cv2.imread("input/flowers.png", cv2.IMREAD_GRAYSCALE)
# 水平方向微分のカーネル
kernel_x = np.array([[0, -1, 0], [0, 0, 0], [0, 1, 0]])
# 垂直方向微分のカーネル
kernel_y = np.array([[0, 0, 0], [-1, 0, 1], [0, 0, 0]])
# フィルタ2Dを適用
first_derivative_x = cv2.filter2D(image, cv2.CV_64F, kernel_x)
first_derivative_y = cv2.filter2D(image, cv2.CV_64F, kernel_y)
# エッジの強度を計算
first_derivative = np.sqrt(first_derivative_x**2 + first_derivative_y**2)
# 画像を表示
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.imshow(image, cmap='gray')
plt.title('Original')
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(first_derivative, cmap='gray')
plt.title('一次微分フィルタ')
plt.axis("off")
plt.show()
Prewittフィルタ(cv2.filter2D())
1次微分フィルタはノイズにも反応しやすいという問題があります。Prewittフィルタは、対象を斜めを含む周辺ピクセルに拡大することで平滑化処理を加え、ノイズの影響を低減します。

Pythonで実装
import cv2
import matplotlib.pyplot as plt
import numpy as np
import japanize_matplotlib
image = cv2.imread("input/flowers.png", 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]])
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.figure(figsize=(10, 6))
plt.subplot(2, 2, 1)
plt.imshow(image, cmap='gray')
plt.title('Original')
plt.axis("off")
plt.subplot(2, 2, 2)
plt.imshow(prewitt, cmap='gray')
plt.title('Prewitt Filter')
plt.axis("off")
plt.subplot(2, 2, 3)
plt.imshow(prewitt_x, cmap='gray')
plt.title('水平方向')
plt.axis("off")
plt.subplot(2, 2, 4)
plt.imshow(prewitt_y, cmap='gray')
plt.title('垂直方向')
plt.axis("off")
plt.show()
Sobelフィルタ(cv2.Sobel())
Sobelフィルタも1次微分フィルタに分類されますが、中心に隣接するピクセルが強調されるカーネルを使用します。

メリット: コントラストの小さいエッジもしっかりと強調できる デメリット: ノイズにも反応しやすい
Pythonで実装
OpenCVにはcv2.Sobel()関数が用意されています。
cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])| 引数 | 説明 |
|---|---|
| ddepth | 出力画像の深度。-1で入力画像と同じ型 |
| dx | 1でX軸方向の微分を実施。0で微分しない |
| dy | 1でY軸方向の微分を実施。0で微分しない |
| ksize | カーネルのサイズ |
| scale | カーネル要素にかかる倍率 |
| delta | 計算結果に加算される定数値 |
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("input/flowers.png", cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(src=image, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=1)
sobely = cv2.Sobel(src=image, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=1)
sobelxy = cv2.Sobel(src=image, ddepth=cv2.CV_32F, dx=1, dy=1, ksize=1)
plt.figure(figsize=(10, 6))
plt.subplot(2, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
plt.title('Original')
plt.axis("off")
plt.subplot(2, 2, 2)
plt.imshow(cv2.cvtColor(sobelx, cv2.COLOR_RGB2BGR))
plt.title('Sobel X')
plt.axis("off")
plt.subplot(2, 2, 3)
plt.imshow(cv2.cvtColor(sobely, cv2.COLOR_RGB2BGR))
plt.title('Sobel Y')
plt.axis("off")
plt.subplot(2, 2, 4)
plt.imshow(cv2.cvtColor(sobelxy, cv2.COLOR_RGB2BGR))
plt.title('Sobel XY')
plt.axis("off")
plt.show()
Cannyフィルタ(cv2.Canny())
Cannyフィルタは、ガウシアンぼかしとSobelフィルタを組み合わせた高精度なエッジ検出手法です。ノイズを除去しつつ、コントラストの弱いエッジも的確に検出できます。
Cannyフィルタの処理は以下の4ステップで構成されます。
- ガウシアンぼかし: ノイズを除去する
- Sobelフィルタ: エッジの勾配を計算する
- 非極大値抑制処理: エッジ以外のピクセルを除去し、1ピクセル幅のシャープなエッジを抽出する
- ヒステリシス閾値処理: 断片的なエッジを連結し、1本のエッジとして形成する
処理の詳細
非極大値抑制処理では、注目画素の画素値と勾配方向に隣り合う2つの画素の値を比較します。注目画素が最大でなければ、その値を0に置き換えます。
ヒステリシス閾値処理では、2つの閾値(最大閾値・最小閾値)を使用します。
- 最大閾値より高い → 信頼性の高い輪郭として残す
- 最小閾値より低い → 信頼性の低い輪郭として消去する
- 2つの閾値の間 → 隣接ピクセルが高信頼なら残す、そうでなければ消去する
Pythonで実装
cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])| 引数 | 説明 |
|---|---|
| threshold1 | ヒステリシス閾値の最小閾値 |
| threshold2 | ヒステリシス閾値の最大閾値 |
| apertureSize | Sobel演算子に使用するカーネルサイズ |
| L2gradient | Trueでユークリッド距離、False(デフォルト)でL1ノルムを使用 |
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("input/flowers.png")
img_canny = cv2.Canny(image=image, threshold1=100, threshold2=200)
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
plt.title('Original')
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(img_canny, cv2.COLOR_RGB2BGR))
plt.title('Canny')
plt.axis("off")
plt.show()
エッジが1本の明確な線として抽出されていることが確認できます。
Laplacianフィルタ(cv2.Laplacian())
Laplacianフィルタは二次微分を利用したエッジ検出手法です。これまでのフィルタが一次微分だったのに対し、異なるアプローチでエッジを検出します。
4近傍と8近傍のカーネルがあります。


メリット: 直線的なエッジだけでなく、複雑な曲線も強調できる デメリット: わずかなノイズにも反応しやすく、不要なエッジが強調される場合がある。特定方向のみのエッジ検出には不向き
Pythonで実装
cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])| 引数 | 説明 |
|---|---|
| ddepth | 出力画像の深度。-1で入力画像と同じ型 |
| ksize | カーネルのサイズ |
| scale | カーネル要素にかかる倍率 |
| delta | 計算結果に追加するオフセット値 |
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("input/flowers.png")
img_lap = cv2.Laplacian(image, cv2.CV_32F, ksize=1)
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
plt.title('Original')
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(img_lap, cv2.COLOR_RGB2BGR))
plt.title('Laplacian')
plt.axis("off")
plt.show()
まとめ
今回は、5種類のエッジ検出フィルタを紹介しました。
| フィルタ | 微分の種類 | OpenCV関数 | 特徴 |
|---|---|---|---|
| 1次微分 | 一次微分 | cv2.filter2D() | シンプルだがノイズに弱い |
| Prewitt | 一次微分 | cv2.filter2D() | 平滑化でノイズ耐性向上 |
| Sobel | 一次微分 | cv2.Sobel() | コントラストの弱いエッジにも有効 |
| Canny | 一次微分 | cv2.Canny() | 高精度な1ピクセル幅のエッジ検出 |
| Laplacian | 二次微分 | cv2.Laplacian() | 複雑な曲線のエッジにも対応 |
処理の複雑さと精度はトレードオフの関係にあるため、画像の特性や用途に応じて適切なフィルタを選択してください。