画像の勾配

目的

このチュートリアルでは:

理論

OpenCVはSobel, Scharr, Laplacianという3種類の勾配検出フィルタ(もしくはハイパスフィルタ)を提供している.それぞれ見ていこう.

1. Sobel と Scharr 微分

Sobel演算子は、Gaussianによる平滑化と微分演算子を組み合わせた演算子で,ノイズに対する耐性がある.

cv2.Sobel(src, ddepth, dx, dy[, dst, [ksize[, scale[, delta[, borderType]]]]])で計算する。第1引数srcは対象画像で、第2引数は出力の深さ(-1なら入力と同じ)、第3引数dxと第4引数dyの値で勾配を計算する方向と階数が決まる。つまり第3引数dxを1とすればx方向(水平方向)の1次の微分係数、第4引数dyを1とするとy方向(垂直方向)の1次の微分係数を計算する(多くの場合、dx=1, dy=0か、dx=0, dy=1)。また,オプションの引数( ksize )によって勾配を計算するカーネルのサイズを指定できるが、-1から7までの奇数に限る。ただし ksize = cv2.CV_SCHARR (これは-1と同じ) ならば 3x3のSobelフィルタより良いと言われている3x3のScharrフィルタが使われる.

なおcv2.Scharr(src, ddepth, dx, dy [, dst, scale, delta, borderType])cv2.Sobel(src, ddepth, dx, dy, ksize=cv2.CV_SCHARR [, dst, scale, delta, borderType])と同じである。

詳細については各カーネルに関するドキュメントを参照すること.

2. Laplacian微分

次の式に基づいて画像のLaplacian(2次微分)を計算する: \Delta src = \frac{\partial ^2{src}}{\partial x^2} + \frac{\partial ^2{src}}{\partial y^2}

cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) 関数で計算する。なおそれぞれの微分はSobelフィルタを使って計算される. ksize = 1 と指定すると以下のカーネルを使う.:

kernel = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0  \end{bmatrix}

実行例

以下に上述した全演算子の結果を見せるためのコードを示する.全てのカーネルを5x5のサイズにしている.出力画像のdepthは -1 を指定して, np.uint8 型としている. (コード, 対象画像)

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

img = cv2.imread('dave.jpg',0)

laplacian = cv2.Laplacian(img,cv2.CV_64F)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)

plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

plt.show()

実行結果:

Image Gradients

重要ポイント

上のコードでは出力画像の型を cv2.CV_8U もしくは np.uint8 としているが,実はちょっとした問題がある.黒から白への変化(画素値の低い値から高い値への変化)は正方向の傾きとして計算されるが,白から黒への変化(画素値の高い値から低い値への変化)は負の傾きとして計算される.そのため,勾配を np.uint8 へ変換すると,負の値は全て0になってしまう、つまり,負の勾配を失ってしまうという問題がある.

正負両方のエッジを検出したいのであれば,画素値の型を cv2.CV_16Scv2.CV_64F といった、より高次のものに変更する.次のコードは横方向のSobelフィルタを例に,画素値の型の違いが結果に及ぼす影響を示す. (コード)

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

img = cv2.imread('box.png',0)

# Output dtype = cv2.CV_8U
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)

# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx8u,cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobel_8u,cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])

plt.show()

以下の結果を比べてみよ:

Double Edges

補足資料

課題