画像の勾配

目的

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

  • 画像の勾配,エッジなどを検出する方法を学びます.
  • 以下の関数について学びます : cv2.Sobel(), cv2.Scharr(), cv2.Laplacian() など

理論

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

1. Sobel と Scharr 微分

Sobel演算子はGaussianによる平滑化と微分演算子を組み合わせた演算子であり,ノイズに対する耐性があります.勾配を計算する方向はそれぞれ引数 yorderxorder で指定します.第3引数( yorder )を1とするとx方向の勾配,第4引数( xorder )を1とするとy方向の勾配を計算します.また,第5引数( ksize )によって勾配を計算するカーネルのサイズを指定できます.もしもksize = -1 とすると,3x3のSobelフィルタより良いと言われている3x3のScharrフィルタを使います.詳細については各カーネルに関するドキュメントを参照してください.

2. Laplacian微分

以下の式に基づいて画像のLaplacian(2次微分)を計算します \Delta src = \frac{\partial ^2{src}}{\partial x^2} + \frac{\partial ^2{src}}{\partial y^2} ここで各微分は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

補足資料

課題