圖像的邊緣 html
圖像的邊緣從數學上是如何表示的呢? 數組
圖像的邊緣上,鄰近的像素值應當顯著地改變了。而在數學上,導數是表示改變快慢的一種方法。梯度值的大變預示着圖像中內容的顯著變化了。 app
用更加形象的圖像來解釋,假設咱們有一張一維圖形。下圖中灰度值的「躍升」表示邊緣的存在: ide
使用一階微分求導咱們能夠更加清晰的看到邊緣「躍升」的存在(這裏顯示爲高峯值): 函數
由此咱們能夠得出:邊緣能夠經過定位梯度值大於鄰域的相素的方法找到。 ui
卷積 spa
卷積能夠近似地表示求導運算。 .net
那麼卷積是什麼呢? code
卷積是在每個圖像塊與某個算子(核)之間進行的運算。 htm
核?!
核就是一個固定大小的數值數組。該數組帶有一個錨點 ,通常位於數組中央。
但是這怎麼運算啊?
假如你想獲得圖像的某個特定位置的卷積值,可用下列方法計算:
- 將核的錨點放在該特定位置的像素上,同時,核內的其餘值與該像素鄰域的各像素重合;
- 將核內各值與相應像素值相乘,並將乘積相加;
- 將所得結果放到與錨點對應的像素上;
- 對圖像全部像素重複上述過程。
用公式表示上述過程以下:
![]()
在圖像邊緣的卷積怎麼辦呢?
計算卷積前,OpenCV經過複製源圖像的邊界建立虛擬像素,這樣邊緣的地方也有足夠像素計算卷積了。
近似梯度
好比內核爲3時。
首先對x方向計算近似導數:
而後對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位類型。
![]()
對於多通道矩陣,該函數對各通道獨立處理。若是輸出不是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 兩個數組的加權和公式以下:
![]()
在多通道狀況下,每一個通道是獨立處理的,該函數能夠被替換成一個函數表達式:
dst = src1*alpha + src2*beta + gamma;
利用convertScaleAbs和addWeighted,咱們能夠對梯度進行一個能夠用圖像顯示的近似表達。
這樣咱們就能夠獲得下面的效果:
梯度方向
但有時候邊界還不夠,咱們但願獲得圖片色塊之間的關係,或者研究樣本的梯度特徵來對機器訓練識別物體時候,咱們還須要梯度的方向。
二維平面的梯度定義爲:
這很好理解,其代表顏色增加的方向與x軸的夾角。
但Sobel算子對於沿x軸和y軸的排列表示的較好,可是對於其餘角度表示卻不夠精確。這時候咱們可使用Scharr濾波器。
Scharr濾波器的內核爲:
這樣能提供更好的角度信息,如今咱們修改原程序,改成使用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,意義等同於選擇與第一個數組相同的深度。
該函數對兩個數組進行除法:
![]()
或則只是縮放係數除以一個數組:
![]()
這種狀況若是src2是0,那麼dst也是0。不一樣的通道是獨立處理的。
被山寨的原文
Sobel Derivatives . OpenCV.org
Image Filtering . OpenCV.org