.. _Gradients: 画像の勾配 ********************** 目的 ====== このチュートリアルでは: * 画像の勾配,エッジなどを検出する方法を学びます. * 以下の関数について学びます : **cv2.Sobel()**, **cv2.Scharr()**, **cv2.Laplacian()** など 理論 ======= OpenCVはSobel, Scharr, Laplacianという3種類の勾配検出フィルタ(もしくはハイパスフィルタ)を提供しています.それぞれ見ていきましょう. 1. Sobel と Scharr 微分 --------------------------------- Sobel演算子はGaussianによる平滑化と微分演算子を組み合わせた演算子であり,ノイズに対する耐性があります.勾配を計算する方向はそれぞれ引数 ``yorder`` と ``xorder`` で指定します.第3引数( ``yorder`` )を1とするとx方向の勾配,第4引数( ``xorder`` )を1とするとy方向の勾配を計算します.また,第5引数( ``ksize`` )によって勾配を計算するカーネルのサイズを指定できます.もしもksize = -1 とすると,3x3のSobelフィルタより良いと言われている3x3のScharrフィルタを使います.詳細については各カーネルに関するドキュメントを参照してください. 2. Laplacian微分 -------------------------- 以下の式に基づいて画像のLaplacian(2次微分)を計算します :math:`\Delta src = \frac{\partial ^2{src}}{\partial x^2} + \frac{\partial ^2{src}}{\partial y^2}` ここで各微分はSobelフィルタを使って計算されます. ``ksize = 1`` と指定すると以下のカーネルを使います.: .. math:: 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:: images/gradients.jpg :alt: Image Gradients :align: center 重要な点! ======================= 上記のコードでは出力画像の型を cv2.CV_8U もしくは np.uint8 としていますが,実はちょっとした問題があります.黒から白への変化(画素値の低い値から高い値への変化)は正方向の傾きとして計算されますが,白から黒への変化(画素値の高い値から低い値への変化)は負の傾きとして計算されます.そのため,勾配を np.uint8 へ変換すると,負の値は全て0になってしまいます.一言で言えば,負の勾配を失ってしまいます. 正負両方のエッジを検出したいのであれば,画素値の型を ``cv2.CV_16S`` や ``cv2.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() 以下の結果を比較してください: .. image:: images/double_edge.jpg :alt: Double Edges :align: center 補足資料 ====================== 課題 ===========