目標
この章では次のことを学ぶ:
理論
OpenCVは3種類の勾配フィルタ(ハイパスフィルタ)、Sobel、Scharr、Laplacianを提供している。それぞれを順に見ていく。
1. SobelおよびScharr微分
Sobelオペレータはガウシアン平滑化と微分を組み合わせた演算であり、ノイズに対してより頑健である。微分を取る方向を縦か横か(それぞれ引数 yorder と xorder で)指定できる。また引数 ksize でカーネルのサイズも指定できる。ksize = -1 の場合、3x3のScharrフィルタが使われ、3x3のSobelフィルタよりも良い結果が得られる。使われるカーネルについてはドキュメントを参照のこと。
2. Laplacian微分
次の関係式で与えられる画像のラプラシアンを計算する。\(\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サイズである。np.uint8型で結果を得るために、出力画像のビット深度には-1を渡している。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img =
cv.imread(
'dave.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
sobelx =
cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
sobely =
cv.Sobel(img,cv.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()
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.
void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT)
Calculates the first, second, third, or mixed image derivatives using an extended Sobel operator.
void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, int borderType=BORDER_DEFAULT)
Calculates the Laplacian of an image.
結果:
image
一つの重要な注意点!
先ほどの例では、出力のデータ型は cv.CV_8U すなわち np.uint8 だった。しかしこれには少し問題がある。黒から白への変化は正の傾き(正の値を持つ)として扱われ、白から黒への変化は負の傾き(負の値を持つ)として扱われる。そのため np.uint8 にデータを変換すると、負の傾きはすべてゼロになる。簡単に言えば、そのエッジを取りこぼしてしまう。
両方のエッジを検出したい場合は、出力のデータ型を cv.CV_16S や cv.CV_64F などのより大きな型に保ち、その絶対値を取ってから cv.CV_8U に戻すのがよい。以下のコードは、水平方向のSobelフィルタについてこの手順と結果の違いを示している。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img =
cv.imread(
'box.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
sobelx8u =
cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
sobelx64f =
cv.Sobel(img,cv.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()
結果を以下に示す。
image