OpenCV – 微分フィルタ、Prewitt フィルタ、Sobel フィルタについて

概要

画像処理におけるエッジ検出について解説し、微分フィルタ、Prewitt フィルタ、Sobel フィルタの OpenCV での実装例を紹介します。

エッジ検出

画像 (以下、グレースケール画像とする) の輝度が鋭敏に変化している箇所をエッジ (edge) といい、エッジを特定する手法をエッジ検出 といいます。

画像の微分

画像 (以下、グレースケールとする) は各画素 (x,y)(x, y) が輝度値 f(x,y)f(x, y) を持つ離散的な2次元関数とみることができます。 各画素における微分係数を求めることで、その値が大きい画素は輝度が鋭敏に変化している箇所とわかります。

ただし、関数 ff は離散型であり、微分は定義されないため、差分法により近似的に微分係数を求めます。 以下では基本的な前進差分近似、後退差分近似、中心差分近似の3つを紹介します。

微分フィルタ

xx 方向

xx に関する偏微分係数の近似 (h=1h = 1 とした場合)

前進差分近似

fx(x,y)=f(x+h,y)f(x,y)h=f(x+1,y)f(x,y)=f(x,y)(1)+f(x+1,y)1 \begin{aligned} f_x(x, y) &= \frac{f(x + h, y) – f(x, y)}{h} \\ &= f(x + 1, y) – f(x, y) \\ &= f(x, y) \cdot (-1) + f(x + 1, y) \cdot 1 \end{aligned}

後退差分近似

fx(x,y)=f(x,y)f(xh,y)h=f(x,y)f(x1,y)=f(x1,y)(1)+f(x,y)1 \begin{aligned} f_x(x, y) &= \frac{f(x, y) – f(x – h, y)}{h} \\ &= f(x, y) – f(x – 1, y) \\ &= f(x – 1, y) \cdot (-1) + f(x, y) \cdot 1 \end{aligned}

中心差分近似

fx(x,y)=f(x+h,y)f(xh,y)2h=12(f(x+1,y)f(x1,y))=12(f(x1,y)(1)+f(x+1,y)1) \begin{aligned} f_x(x, y) &= \frac{f(x + h, y) – f(x – h, y)}{2h} \\ &= \frac{1}{2}(f(x + 1, y) – f(x – 1, y)) \\ &= \frac{1}{2}(f(x – 1, y) \cdot (-1) + f(x + 1, y) \cdot 1) \end{aligned}

(3, 3) の2次元畳み込みで表すと

前進差分近似 後退差分近似 中心差分近似
f(000011000)f * \begin{pmatrix} 0 & 0 & 0 \\ 0 & -1 & 1 \\ 0 & 0 & 0 \\ \end{pmatrix} f(000110000)f * \begin{pmatrix} 0 & 0 & 0 \\ -1 & 1 & 0 \\ 0 & 0 & 0 \\ \end{pmatrix} f12(000101000)f * \frac{1}{2} \begin{pmatrix} 0 & 0 & 0 \\ -1 & 0 & 1 \\ 0 & 0 & 0 \\ \end{pmatrix}
In [1]:
import cv2
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import Image, display


def imshow(img):
    """ndarray 配列をインラインで Notebook 上に表示する。
    """
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))
Python
In [2]:
# 画像を読み込む。
gray = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)

# 水平方向の微分フィルタ
fig, [ax1, ax2, ax3] = plt.subplots(1, 3, figsize=(15, 5))
fig.suptitle("Horizonal Differential Filter", fontsize=15)

# 前進差分近似
kernel = np.array([[0, 0, 0], [0, -1, 1], [0, 0, 0]])
dst = cv2.filter2D(gray, -1, kernel)

ax1.imshow(dst, cmap="gray")
ax1.set_title("Forward Difference", fontsize=15)
ax1.set_axis_off()

# 後退差分近似
kernel = np.array([[0, 0, 0], [-1, 1, 0], [0, 0, 0]])
dst = cv2.filter2D(gray, -1, kernel)

ax2.imshow(dst, cmap="gray")
ax2.set_title("Backward Difference", fontsize=15)
ax2.set_axis_off()

# 中心差分近似
kernel = 0.5 * np.array([[0, 0, 0], [-1, 0, 1], [0, 0, 0]])
dst = cv2.filter2D(gray, -1, kernel)

ax3.imshow(dst, cmap="gray")
ax3.set_title("Central Difference", fontsize=15)
ax3.set_axis_off()

plt.show()
Python

yy 方向

yy に関する偏微分係数の近似 (h=1h = 1 とした場合)

前進差分近似

fy(x,y)=f(x,y+h)f(x,y)h=f(x,y+1)f(x,y)=f(x,y)(1)+f(x,y+1)1 \begin{aligned} f_y(x, y) &= \frac{f(x, y + h) – f(x, y)}{h} \\ &= f(x, y + 1) – f(x, y) \\ &= f(x, y) \cdot (-1) + f(x, y + 1) \cdot 1 \end{aligned}

後退差分近似

fy(x,y)=f(x,y)f(x,yh)h=f(x,y)f(x,y1)=f(x,y1)(1)+f(x,y)1 \begin{aligned} f_y(x, y) &= \frac{f(x, y) – f(x, y – h)}{h} \\ &= f(x, y) – f(x, y – 1) \\ &= f(x, y – 1) \cdot (-1) + f(x, y) \cdot 1 \end{aligned}

中心差分近似

fy(x,y)=f(x,y+h)f(x,yh)2h=f(x,y+1)f(x,y1)2h=12(f(x,y1)(1)+f(x,y+1)1) \begin{aligned} f_y(x, y) &= \frac{f(x, y + h) – f(x, y – h)}{2h} \\ &= \frac{f(x, y + 1) – f(x, y – 1)}{2h} \\ &= \frac{1}{2}(f(x, y – 1) \cdot (-1) + f(x, y + 1) \cdot 1) \end{aligned}

(3, 3) の2次元畳み込みで表すと

前進差分近似 後退差分近似 中心差分近似
f(000010010)f * \begin{pmatrix} 0 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 1 & 0 \\ \end{pmatrix} f(010010000)f * \begin{pmatrix} 0 & -1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 0 \\ \end{pmatrix} f12(010000010)f * \frac{1}{2} \begin{pmatrix} 0 & -1 & 0 \\ 0 & 0 & 0 \\ 0 & 1 & 0 \\ \end{pmatrix}
In [3]:
# 垂直方向の微分フィルタ
fig, [ax1, ax2, ax3] = plt.subplots(1, 3, figsize=(15, 5))
fig.suptitle("Vertical Differential Filter", fontsize=15)

# 前進差分近似
kernel = np.array([[0, 0, 0], [0, -1, 0], [0, 1, 0]])
dst = cv2.filter2D(gray, -1, kernel)

ax1.imshow(dst, cmap="gray")
ax1.set_title("Forward Difference", fontsize=15)
ax1.set_axis_off()

# 後退差分近似
kernel = np.array([[0, -1, 0], [0, 1, 0], [0, 0, 0]])
dst = cv2.filter2D(gray, -1, kernel)

ax2.imshow(dst, cmap="gray")
ax2.set_title("Backward Difference", fontsize=15)
ax2.set_axis_off()

# 前進差分近似
kernel = 0.5 * np.array([[0, -1, 0], [0, 0, 0], [0, 1, 0]])
dst = cv2.filter2D(gray, -1, kernel)

ax3.imshow(dst, cmap="gray")
ax3.set_title("Central Difference", fontsize=15)
ax3.set_axis_off()

plt.show()
Python

Prewitt フィルタ

微分フィルタはノイズの影響を受けやすいので、周囲の微分係数も計算し、平均をとるように改良した次のフィルタを Prewitt フィルタという。

xx 方向

対象の画素及びその上下の画素の微分係数の平均をとります。

13(fx(x1,y)+fx(x,y)+fx(x+1,y)) \frac{1}{3}(f_x(x – 1, y) + f_x(x, y) + f_x(x + 1, y))

yy 方向

対象の画素及びその左右の画素の微分係数の平均をとります。

13(fy(x,y1)+fy(x,y)+fy(x,y+1)) \frac{1}{3}(f_y(x, y – 1) + f_y(x, y) + f_y(x, y + 1))
水平方向 垂直方向
f13(101101101)f * \frac{1}{3} \begin{pmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \\ \end{pmatrix} f13(111000111)f * \frac{1}{3} \begin{pmatrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \\ \end{pmatrix}
In [4]:
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 5))
fig.suptitle("Prewitt Filter", fontsize=15)

# 水平方向の Prewitt フィルタ
kernel = 1 / 3 * np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
dst = cv2.filter2D(gray, -1, kernel)

ax1.imshow(dst, cmap="gray")
ax1.set_title("Hrizonal", fontsize=15)
ax2.set_axis_off()

# 垂直方向の Prewitt フィルタ
kernel = 1 / 3 * np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]])
dst = cv2.filter2D(gray, -1, kernel)

ax2.imshow(dst, cmap="gray")
ax2.set_title("Vertical", fontsize=15)
ax2.set_axis_off()

plt.show()
Python

Sobel フィルタ

対象の画素と周囲の画素の重み付けを同じにすることで、対象の画素をより重視する次のフィルタを Sobel フィルタという。

xx 方向

対象の画素及びその上下の画素の微分係数の平均をとる。

14(fx(x1,y)+2fx(x,y)+fx(x+1,y)) \frac{1}{4}(f_x(x – 1, y) + 2 f_x(x, y) + f_x(x + 1, y))

yy 方向

対象の画素及びその左右の画素の微分係数の平均をとる。

14(fxy(x,y1)+2fy(x,y)+fy(x,y+1)) \frac{1}{4}(f_xy(x, y – 1) + 2 f_y(x, y) + f_y(x, y + 1))
水平方向 垂直方向
f14(101202101)f * \frac{1}{4} \begin{pmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \\ \end{pmatrix} f14(121000121)f * \frac{1}{4} \begin{pmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \\ \end{pmatrix}
In [5]:
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 5))
fig.suptitle("Sobel Filter", fontsize=15)

# 水平方向の Sobel フィルタ
kernel = 0.25 * np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
dst = cv2.filter2D(gray, -1, kernel)

ax1.imshow(dst, cmap="gray")
ax1.set_title("Hrizonal", fontsize=15)
ax1.set_axis_off()

# 垂直方向の Sobel フィルタ
kernel = 0.25 * np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
dst = cv2.filter2D(gray, -1, kernel)

ax2.imshow(dst, cmap="gray")
ax2.set_title("Vertical", fontsize=15)
ax2.set_axis_off()

plt.show()
Python

参考文献

コメント

コメントする