OpenCV 2.4+ C++ 邊緣梯度計(jì)算
2012-11-23 09:11 Justany_WhiteSnow 閱讀(28202) 評(píng)論(7) 收藏 舉報(bào)圖像的邊緣
圖像的邊緣從數(shù)學(xué)上是如何表示的呢?

圖像的邊緣上,鄰近的像素值應(yīng)當(dāng)顯著地改變了。而在數(shù)學(xué)上,導(dǎo)數(shù)是表示改變快慢的一種方法。梯度值的大變預(yù)示著圖像中內(nèi)容的顯著變化了。
用更加形象的圖像來(lái)解釋,假設(shè)我們有一張一維圖形。下圖中灰度值的“躍升”表示邊緣的存在:

使用一階微分求導(dǎo)我們可以更加清晰的看到邊緣“躍升”的存在(這里顯示為高峰值):

由此我們可以得出:邊緣可以通過(guò)定位梯度值大于鄰域的相素的方法找到。
卷積
卷積可以近似地表示求導(dǎo)運(yùn)算。
那么卷積是什么呢?
卷積是在每一個(gè)圖像塊與某個(gè)算子(核)之間進(jìn)行的運(yùn)算。
核?!
核就是一個(gè)固定大小的數(shù)值數(shù)組。該數(shù)組帶有一個(gè)錨點(diǎn) ,一般位于數(shù)組中央。

可是這怎么運(yùn)算啊?
假如你想得到圖像的某個(gè)特定位置的卷積值,可用下列方法計(jì)算:
- 將核的錨點(diǎn)放在該特定位置的像素上,同時(shí),核內(nèi)的其他值與該像素鄰域的各像素重合;
- 將核內(nèi)各值與相應(yīng)像素值相乘,并將乘積相加;
- 將所得結(jié)果放到與錨點(diǎn)對(duì)應(yīng)的像素上;
- 對(duì)圖像所有像素重復(fù)上述過(guò)程。
用公式表示上述過(guò)程如下:
在圖像邊緣的卷積怎么辦呢?
計(jì)算卷積前,OpenCV通過(guò)復(fù)制源圖像的邊界創(chuàng)建虛擬像素,這樣邊緣的地方也有足夠像素計(jì)算卷積了。
近似梯度
比如內(nèi)核為3時(shí)。
首先對(duì)x方向計(jì)算近似導(dǎo)數(shù):

然后對(duì)y方向計(jì)算近似導(dǎo)數(shù):

然后計(jì)算梯度:

當(dāng)然你也可以寫(xiě)成:

開(kāi)始求梯度
#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 ); //轉(zhuǎn)成灰度圖 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函數(shù)
索貝爾算子(Sobel operator)計(jì)算。
- C++: void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, intborderType=BORDER_DEFAULT )
參數(shù)
- src – 輸入圖像。
- dst – 輸出圖像,與輸入圖像同樣大小,擁有同樣個(gè)數(shù)的通道。
- ddepth –
- 輸出圖片深度;下面是輸入圖像支持深度和輸出圖像支持深度的關(guān)系:
- 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
當(dāng) ddepth為-1時(shí), 輸出圖像將和輸入圖像有相同的深度。輸入8位圖像則會(huì)截取頂端的導(dǎo)數(shù)。
- xorder – x方向?qū)?shù)運(yùn)算參數(shù)。
- yorder – y方向?qū)?shù)運(yùn)算參數(shù)。
- ksize – Sobel內(nèi)核的大小,可以是:1,3,5,7。
- scale – 可選的縮放導(dǎo)數(shù)的比例常數(shù)。
- delta – 可選的增量常數(shù)被疊加到導(dǎo)數(shù)中。
- borderType – 用于判斷圖像邊界的模式。
代碼注釋:
//在x方向求圖像近似導(dǎo)數(shù) Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT ); //在y方向求圖像近似導(dǎo)數(shù) Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
如果我們打印上面兩個(gè)輸出矩陣,可以看到grad_x和grad_y中的元素有正有負(fù)。
當(dāng)然,正方向遞增就是正的,正方向遞減則是負(fù)值。
這很重要,我們可以用來(lái)判斷梯度方向。
convertScaleAbs函數(shù)
線性變換轉(zhuǎn)換輸入數(shù)組元素成8位無(wú)符號(hào)整型。
- C++: void convertScaleAbs(InputArray src, OutputArray dst, double alpha=1, double beta=0)
參數(shù)
- src – 輸入數(shù)組。
- dst – 輸出數(shù)組。
- alpha – 可選縮放比例常數(shù)。
- beta – 可選疊加到結(jié)果的常數(shù)。
對(duì)于每個(gè)輸入數(shù)組的元素函數(shù)convertScaleAbs 進(jìn)行三次操作依次是:縮放,得到一個(gè)絕對(duì)值,轉(zhuǎn)換成無(wú)符號(hào)8位類型。
對(duì)于多通道矩陣,該函數(shù)對(duì)各通道獨(dú)立處理。如果輸出不是8位,將調(diào)用Mat::convertTo 方法并計(jì)算結(jié)果的絕對(duì)值,例如:
Mat_<float> A(30,30); randu(A, Scalar(-100), Scalar(100)); Mat_<float> B = A*5 + 3; B = abs(B);
為了能夠用圖像顯示,提供一個(gè)直觀的圖形,我們利用該方法,將-256 — 255的導(dǎo)數(shù)值,轉(zhuǎn)成0 — 255的無(wú)符號(hào)8位類型。
addWeighted函數(shù)
計(jì)算兩個(gè)矩陣的加權(quán)和。
- C++: void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, intdtype=-1)
參數(shù)
- src1 – 第一個(gè)輸入數(shù)組。
- alpha – 第一個(gè)數(shù)組的加權(quán)系數(shù)。
- src2 – 第二個(gè)輸入數(shù)組,必須和第一個(gè)數(shù)組擁有相同的大小和通道。
- beta – 第二個(gè)數(shù)組的加權(quán)系數(shù)。
- dst – 輸出數(shù)組,和第一個(gè)數(shù)組擁有相同的大小和通道。
- gamma – 對(duì)所有和的疊加的常量。
- dtype – 輸出數(shù)組中的可選的深度,當(dāng)兩個(gè)數(shù)組具有相同的深度,此系數(shù)可設(shè)為-1,意義等同于選擇與第一個(gè)數(shù)組相同的深度。
函數(shù)addWeighted 兩個(gè)數(shù)組的加權(quán)和公式如下:
在多通道情況下,每個(gè)通道是獨(dú)立處理的,該函數(shù)可以被替換成一個(gè)函數(shù)表達(dá)式:
dst = src1*alpha + src2*beta + gamma;
利用convertScaleAbs和addWeighted,我們可以對(duì)梯度進(jìn)行一個(gè)可以用圖像顯示的近似表達(dá)。
這樣我們就可以得到下面的效果:

梯度方向
但有時(shí)候邊界還不夠,我們希望得到圖片色塊之間的關(guān)系,或者研究樣本的梯度特征來(lái)對(duì)機(jī)器訓(xùn)練識(shí)別物體時(shí)候,我們還需要梯度的方向。
二維平面的梯度定義為:

這很好理解,其表明顏色增長(zhǎng)的方向與x軸的夾角。
但Sobel算子對(duì)于沿x軸和y軸的排列表示的較好,但是對(duì)于其他角度表示卻不夠精確。這時(shí)候我們可以使用Scharr濾波器。
Scharr濾波器的內(nèi)核為:

這樣能提供更好的角度信息,現(xiàn)在我們修改原程序,改為使用Scharr濾波器進(jìn)行計(jì)算:
#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 = "梯度計(jì)算"; 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濾波器計(jì)算x軸導(dǎo)數(shù) Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_x, abs_grad_x ); //改為Scharr濾波器計(jì)算y軸導(dǎo)數(shù) 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函數(shù)接受參數(shù)與Sobel函數(shù)相似,這里就不敘述了。
下面我們通過(guò)divide函數(shù)就能得到一個(gè)x/y的矩陣。
對(duì)兩個(gè)輸入數(shù)組的每個(gè)元素執(zhí)行除操作。
- 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)
參數(shù)
- src1 – 第一個(gè)輸入數(shù)組。
- src2 – 第二個(gè)輸入數(shù)組,必須和第一個(gè)數(shù)組擁有相同的大小和通道。
- scale – 縮放系數(shù)。
- dst – 輸出數(shù)組,和第二個(gè)數(shù)組擁有相同的大小和通道。
- dtype – 輸出數(shù)組中的可選的深度,當(dāng)兩個(gè)數(shù)組具有相同的深度,此系數(shù)可設(shè)為-1,意義等同于選擇與第一個(gè)數(shù)組相同的深度。
該函數(shù)對(duì)兩個(gè)數(shù)組進(jìn)行除法:
或則只是縮放系數(shù)除以一個(gè)數(shù)組:
這種情況如果src2是0,那么dst也是0。不同的通道是獨(dú)立處理的。
被山寨的原文
Sobel Derivatives . OpenCV.org
Image Filtering . OpenCV.org





浙公網(wǎng)安備 33010602011771號(hào)