OpenCV 2.4+ C++ 邊緣梯度計算

圖像的邊緣 html

圖像的邊緣從數學上是如何表示的呢? 數組

How intensity changes in an edge

圖像的邊緣上,鄰近的像素值應當顯著地改變了。而在數學上,導數是表示改變快慢的一種方法。梯度值的大變預示着圖像中內容的顯著變化了。 app

用更加形象的圖像來解釋,假設咱們有一張一維圖形。下圖中灰度值的「躍升」表示邊緣的存在: ide

    Intensity Plot for an edge

使用一階微分求導咱們能夠更加清晰的看到邊緣「躍升」的存在(這裏顯示爲高峯值): 函數

    First derivative of Intensity - Plot for an edge

由此咱們能夠得出:邊緣能夠經過定位梯度值大於鄰域的相素的方法找到。 ui

 

卷積 spa

卷積能夠近似地表示求導運算。 .net

那麼卷積是什麼呢? code

卷積是在每個圖像塊與某個算子(核)之間進行的運算。 htm

核?!

核就是一個固定大小的數值數組。該數組帶有一個錨點 ,通常位於數組中央。

kernel example

 但是這怎麼運算啊?

假如你想獲得圖像的某個特定位置的卷積值,可用下列方法計算:

  1. 將核的錨點放在該特定位置的像素上,同時,核內的其餘值與該像素鄰域的各像素重合;
  2. 將核內各值與相應像素值相乘,並將乘積相加;
  3. 將所得結果放到與錨點對應的像素上;
  4. 對圖像全部像素重複上述過程。

用公式表示上述過程以下:

    H(x,y) = \sum_{i=0}^{M_{i} - 1} \sum_{j=0}^{M_{j}-1} I(x+i - a_{i}, y + j - a_{j})K(i,j)

在圖像邊緣的卷積怎麼辦呢?

計算卷積前,OpenCV經過複製源圖像的邊界建立虛擬像素,這樣邊緣的地方也有足夠像素計算卷積了。

 

近似梯度

好比內核爲3時。

首先對x方向計算近似導數:

G_{x} = \begin{bmatrix}
-1 & 0 & +1  \\
-2 & 0 & +2  \\
-1 & 0 & +1
\end{bmatrix} * I

而後對y方向計算近似導數:

G_{y} = \begin{bmatrix}
-1 & -2 & -1  \\
0 & 0 & 0  \\
+1 & +2 & +1
\end{bmatrix} * I

而後計算梯度:

G = \sqrt{ G_{x}^{2} + G_{y}^{2} }

固然你也能夠寫成:

G = |G_{x}| + |G_{y}|

 

開始求梯度

複製代碼
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace cv; int main( int argc, char** argv ){ Mat src, src_gray; Mat grad; char* window_name = "求解梯度"; int scale = 1; int delta = 0; int ddepth = CV_16S; int c; src = imread( argv[1] ); if( !src.data ){ return -1; } //高斯模糊 GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT ); //轉成灰度圖  cvtColor( src, src_gray, CV_RGB2GRAY ); namedWindow( window_name, CV_WINDOW_AUTOSIZE ); Mat grad_x, grad_y; Mat abs_grad_x, abs_grad_y; Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_x, abs_grad_x ); Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_y, abs_grad_y ); addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad ); imshow( window_name, grad ); waitKey(0); return 0; }
複製代碼

 

Sobel函數

索貝爾算子(Sobel operator)計算。

C++: 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 )
參數
  • src – 輸入圖像。
  • dst – 輸出圖像,與輸入圖像一樣大小,擁有一樣個數的通道。
  • ddepth –輸出圖片深度;下面是輸入圖像支持深度和輸出圖像支持深度的關係:
    • src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F
    • src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F
    • src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F
    • src.depth() = CV_64F, ddepth = -1/CV_64F

    當 ddepth爲-1時, 輸出圖像將和輸入圖像有相同的深度。輸入8位圖像則會截取頂端的導數。

  • xorder – x方向導數運算參數。
  • yorder – y方向導數運算參數。
  • ksize – Sobel內核的大小,能夠是:1,3,5,7。
  • scale – 可選的縮放導數的比例常數。
  • delta – 可選的增量常數被疊加到導數中。
  • borderType – 用於判斷圖像邊界的模式。

代碼註釋:

//在x方向求圖像近似導數 Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT ); //在y方向求圖像近似導數 Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );

若是咱們打印上面兩個輸出矩陣,能夠看到grad_x和grad_y中的元素有正有負。

固然,正方向遞增就是正的,正方向遞減則是負值。

這很重要,咱們能夠用來判斷梯度方向。

 

convertScaleAbs函數

線性變換轉換輸入數組元素成8位無符號整型。

C++: void convertScaleAbs(InputArray  src, OutputArray  dst, double  alpha=1, double  beta=0)
參數
  • src – 輸入數組。
  • dst – 輸出數組。
  • alpha – 可選縮放比例常數。
  • beta – 可選疊加到結果的常數。

對於每一個輸入數組的元素函數convertScaleAbs 進行三次操做依次是:縮放,獲得一個絕對值,轉換成無符號8位類型。

\texttt{dst} (I)= \texttt{saturate\_cast<uchar>} (| \texttt{src} (I)* \texttt{alpha} +  \texttt{beta} |)

對於多通道矩陣,該函數對各通道獨立處理。若是輸出不是8位,將調用Mat::convertTo 方法並計算結果的絕對值,例如:

Mat_<float> A(30,30); randu(A, Scalar(-100), Scalar(100)); Mat_<float> B = A*5 + 3; B = abs(B); 

爲了可以用圖像顯示,提供一個直觀的圖形,咱們利用該方法,將-256 — 255的導數值,轉成0 — 255的無符號8位類型。

 

addWeighted函數

計算兩個矩陣的加權和。

C++: void addWeighted(InputArray  src1, double  alpha, InputArray  src2, double  beta, double  gamma, OutputArray  dst, int dtype=-1)
參數
  • src1 – 第一個輸入數組。
  • alpha – 第一個數組的加權係數。
  • src2 – 第二個輸入數組,必須和第一個數組擁有相同的大小和通道。
  • beta – 第二個數組的加權係數。
  • dst – 輸出數組,和第一個數組擁有相同的大小和通道。
  • gamma – 對全部和的疊加的常量。
  • dtype – 輸出數組中的可選的深度,當兩個數組具備相同的深度,此係數可設爲-1,意義等同於選擇與第一個數組相同的深度。

函數addWeighted 兩個數組的加權和公式以下:

    \texttt{dst} (I)= \texttt{saturate} ( \texttt{src1} (I)* \texttt{alpha} +  \texttt{src2} (I)* \texttt{beta} +  \texttt{gamma} )

在多通道狀況下,每一個通道是獨立處理的,該函數能夠被替換成一個函數表達式:

    dst = src1*alpha + src2*beta + gamma;

利用convertScaleAbs和addWeighted,咱們能夠對梯度進行一個能夠用圖像顯示的近似表達。

這樣咱們就能夠獲得下面的效果:

Result of applying Sobel operator to lena.jpg

 

梯度方向

但有時候邊界還不夠,咱們但願獲得圖片色塊之間的關係,或者研究樣本的梯度特徵來對機器訓練識別物體時候,咱們還須要梯度的方向。

二維平面的梯度定義爲:

    

這很好理解,其代表顏色增加的方向與x軸的夾角。

但Sobel算子對於沿x軸和y軸的排列表示的較好,可是對於其餘角度表示卻不夠精確。這時候咱們可使用Scharr濾波器。

Scharr濾波器的內核爲:

    G_{x} = \begin{bmatrix}
-3 & 0 & +3  \\
-10 & 0 & +10  \\
-3 & 0 & +3
\end{bmatrix}
G_{y} = \begin{bmatrix}
-3 & -10 & -3  \\
0 & 0 & 0  \\
+3 & +10 & +3
\end{bmatrix}

這樣能提供更好的角度信息,如今咱們修改原程序,改成使用Scharr濾波器進行計算:

複製代碼
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace cv; int main( int argc, char** argv ){ Mat src, src_gray; Mat grad; char* window_name = "梯度計算"; int scale = 1; int delta = 0; int ddepth = CV_16S; int c; src = imread( argv[1] ); if( !src.data ){ return -1; } GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT ); cvtColor( src, src_gray, CV_RGB2GRAY ); namedWindow( window_name, CV_WINDOW_AUTOSIZE ); Mat grad_x, grad_y; Mat abs_grad_x, abs_grad_y; //改成Scharr濾波器計算x軸導數 Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_x, abs_grad_x ); //改成Scharr濾波器計算y軸導數 Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_y, abs_grad_y ); addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad ); imshow( window_name, grad ); waitKey(0); return 0; }
複製代碼

Scharr函數接受參數與Sobel函數類似,這裏就不敘述了。

下面咱們經過divide函數就能獲得一個x/y的矩陣。

對兩個輸入數組的每一個元素執行除操做。

C++: void divide(InputArray  src1, InputArray  src2, OutputArray  dst, double  scale=1, int  dtype=-1) C++: void divide(double  scale, InputArray  src2, OutputArray  dst, int  dtype=-1)
參數
  • src1 – 第一個輸入數組。
  • src2 – 第二個輸入數組,必須和第一個數組擁有相同的大小和通道。
  • scale – 縮放係數。
  • dst – 輸出數組,和第二個數組擁有相同的大小和通道。
  • dtype – 輸出數組中的可選的深度,當兩個數組具備相同的深度,此係數可設爲-1,意義等同於選擇與第一個數組相同的深度。

該函數對兩個數組進行除法:

  \texttt{dst(I) = saturate(src1(I)*scale/src2(I))}

或則只是縮放係數除以一個數組:

  \texttt{dst(I) = saturate(scale/src2(I))}

這種狀況若是src2是0,那麼dst也是0。不一樣的通道是獨立處理的。

 

被山寨的原文

Sobel Derivatives . OpenCV.org

Image Filtering . OpenCV.org

相關文章
相關標籤/搜索