Pynote

Python、機械学習、画像処理について

OpenCV - エッジの検出、微分フィルタ

試した環境

エッジ検出とは

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

ipynb は こちら

import cv2
import numpy as np
import matplotlib.pyplot as plt

サンプルとして Lena の画像を使用する。

# 画像をグレースケールで読み込む。
gray = cv2.imread('lena.png', 0)

plt.imshow(gray, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

微分

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


# 画像を離散的な2次元関数と見なして、プロットする。
from mpl_toolkits.mplot3d import Axes3D

xx, yy = np.mgrid[0:gray.shape[0], 0:gray.shape[1]]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(
    xx, yy, gray, rstride=1, cstride=1, cmap=plt.cm.gray)
ax.view_init(80, 20)
plt.show()

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

水平方向の微分フィルタ

x に関する偏微分係数の近似

  • 前進差分近似

 \displaystyle
f_x(x, y) = \frac{f(x + h, y) - f(x, y)}{h}

  • 後退差分近似

 \displaystyle
f_x(x, y) = \frac{f(x, y) - f(x - h, y)}{h}

  • 中心差分近似

 \displaystyle
f_x(x, y) = \frac{f(x + h, y) - f(x - h, y)}{2h}

h = 1 とした場合

  • 前進差分近似

 \displaystyle
f_x(x, y) = f(x + 1, y) - f(x, y)

  • 後退差分近似

 \displaystyle
f_x(x, y) = f(x, y) - f(x - 1, y)

  • 中心差分近似

 \displaystyle
f_x(x, y) = \frac{1}{2}(f(x + 1, y) - f(x - 1, y))

2次元畳み込みで表現した場合

  • 前進差分近似

f_x(x, y) = f(x, y) \cdot (-1) + f(x + 1, y) \cdot 1 なので、

 \displaystyle
f *
\begin{pmatrix}
0 & 0 & 0 \\
0 & -1 & 1 \\
0 & 0 & 0 \\
\end{pmatrix}

  • 後退差分近似

f_x(x, y) = f(x - 1, y) \cdot (-1) + f(x, y) \cdot 1 なので、

 \displaystyle
f *
\begin{pmatrix}
0 & 0 & 0 \\
 -1 & 1 & 0 \\
0 & 0 & 0 \\
\end{pmatrix}

  • 中心差分近似

f_x(x, y) = \frac{1}{2}(f(x - 1, y) \cdot (-1) + f(x + 1, y) \cdot 1) なので、

 \displaystyle
f *
\begin{pmatrix}
0 & 0 & 0 \\
 -1 & 0 & 1 \\
0 & 0 & 0 \\
\end{pmatrix}

※ 計算を簡単にするために、畳み込みのカーネルから係数 \frac{1}{2} は基本的に省略される。

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

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

ax1.imshow(filtered, cmap=plt.cm.gray)
ax1.set_title('Forward Difference')
ax1.axis('off')

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

ax2.imshow(filtered, cmap=plt.cm.gray)
ax2.set_title('Backward Difference')
ax2.axis('off')

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

ax3.imshow(filtered, cmap=plt.cm.gray)
ax3.set_title('Central Difference')
ax3.axis('off')

plt.show()


垂直方向の微分フィルタ

  • 前進差分近似

 \displaystyle
f_x(x, y) = \frac{f(x, y + h) - f(x, y)}{h}

  • 後退差分近似

 \displaystyle
f_x(x, y) = \frac{f(x, y) - f(x, y - h)}{h}

  • 中心差分近似

 \displaystyle
f_x(x, y) = \frac{f(x, y + h) - f(x, y - h)}{2h}

h = 1 とした場合

  • 前進差分近似

 \displaystyle
f_x(x, y) = f(x, y + 1) - f(x, y)

  • 後退差分近似

 \displaystyle
f_x(x, y) = f(x, y) - f(x, y - 1)

  • 中心差分近似

 \displaystyle
f_x(x, y) = \frac{f(x, y + 1) - f(x, y - 1)}{2h}

2次元畳み込みで表現した場合

  • 前進差分近似

f_x(x, y) = f(x, y) \cdot (-1) + f(x, y + 1) \cdot (1) なので、

 \displaystyle
f *
\begin{pmatrix}
0 & 0 & 0 \\
0 & -1 & 0 \\
0 & 1 & 0 \\
\end{pmatrix}

  • 後退差分近似

f_x(x, y) = f(x, y - 1) \cdot (-1) + f(x, y) \cdot (1) なので、

 \displaystyle
f *
\begin{pmatrix}
0 & -1 & 0 \\
0 & 1 & 0 \\
0 & 0 & 0 \\
\end{pmatrix}

  • 中心差分近似

f_x(x, y) = \frac{1}{2}(f(x, y - 1) \cdot (-1) + f(x, y + 1) \cdot (1)) なので、

 \displaystyle
f *
\begin{pmatrix}
0 & -1 & 0 \\
0 & 0 & 0 \\
0 & 1 & 0 \\
\end{pmatrix}

※ 計算を簡単にするために、畳み込みのカーネルから係数 \frac{1}{2} は基本的に省略される。

# 垂直方向の微分フィルタ
fig, [ax1, ax2, ax3] = plt.subplots(1, 3, figsize=(15, 5))
fig.suptitle('Vertical Differential Filter', fontsize=20)

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

ax1.imshow(filtered, cmap=plt.cm.gray)
ax1.set_title('Forward Difference')
ax1.axis('off')

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

ax2.imshow(filtered, cmap=plt.cm.gray)
ax2.set_title('Backward Difference')
ax2.axis('off')

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

ax3.imshow(filtered, cmap=plt.cm.gray)
ax3.set_title('Central Difference')
ax3.axis('off')

plt.show()


Prewitt フィルタ

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

  • 水平方向

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

 \displaystyle
\frac{1}{3}(f_x(x - 1, y) + f_x(x, y) + f_x(x + 1, y))

2次元畳み込みで表現した場合

 \displaystyle
f *
\begin{pmatrix}
 -1 & 0 & 1 \\
 -1 & 0 & 1 \\
 -1 & 0 & 1 \\
\end{pmatrix}

※ 計算を簡単にするために、畳み込みのカーネルから係数 \frac{1}{3} は基本的に省略される。

  • 垂直方向

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

 \displaystyle
\frac{1}{3}(f_x(x, y - 1) + f_x(x, y) + f_x(x, y + 1))

2次元畳み込みで表現した場合

 \displaystyle
f *
\begin{pmatrix}
 -1 & -1 & -1 \\
0 & 0 & 0 \\
1 & 1 & 1 \\
\end{pmatrix}

※ 計算を簡単にするために、畳み込みのカーネルから係数 \frac{1}{3} は基本的に省略される。

fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 5))
fig.suptitle('Prewitt Filter', fontsize=20)

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

ax1.imshow(filtered, cmap=plt.cm.gray)
ax1.set_title('Hrizonal')
ax1.axis('off')

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

ax2.imshow(filtered, cmap=plt.cm.gray)
ax2.set_title('Vertical')
ax2.axis('off')

plt.show()


Sobel フィルタ

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

  • 水平方向

 \displaystyle
\frac{1}{4}(f_x(x - 1, y) + 2 f_x(x, y) + f_x(x + 1, y))

2次元畳み込みで表現した場合

 \displaystyle
f *
\begin{pmatrix}
 -1 & 0 & 1 \\
 -2 & 0 & 2 \\
 -1 & 0 & 1 \\
\end{pmatrix}

※ 計算を簡単にするために、畳み込みのカーネルから係数 \frac{1}{4} は基本的に省略される。

  • 垂直方向

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

 \displaystyle
\frac{1}{4}(f_x(x, y - 1) + 2 f_x(x, y) + f_x(x, y + 1))

2次元畳み込みで表現した場合

 \displaystyle
f *
\begin{pmatrix}
 -1 & -2 & -1 \\
0 & 0 & 0 \\
1 & 2 & 1 \\
\end{pmatrix}

※ 計算を簡単にするために、畳み込みのカーネルから係数 \frac{1}{4} は基本的に省略される。

fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 5))
fig.suptitle('Sobel Filter', fontsize=20)

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

ax1.imshow(filtered, cmap=plt.cm.gray)
ax1.set_title('Hrizonal')
ax1.axis('off')

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

ax2.imshow(filtered, cmap=plt.cm.gray)
ax2.set_title('Vertical')
ax2.axis('off')

plt.show()