1. 程式人生 > >影象處理演算法4——Sobel 邊緣檢測運算元

影象處理演算法4——Sobel 邊緣檢測運算元

Sobel 運算元是一個離散微分運算元 (discrete differentiation operator)。 它結合了高斯平滑和微分求導,用來計算影象灰度函式的近似梯度

    影象邊緣,相素值會發生顯著的變化了。表示這一改變的一個方法是使用 導數 。 梯度值的大變預示著影象中內容的顯著變化。用更加形象的影象來解釋,假設我們有一張一維圖形。下圖2中灰度值的”躍升”表示邊緣的存在,圖3中使用一階微分求導我們可以更加清晰的看到邊緣”躍升”的存在。


圖1、lena.jpg                     圖2、畫素一維圖形                       圖3、一階導數

        具體是採用卷積的計算方法實現的。假設被作用的影象為I,在兩個方向上求導:

水平變化求導:將 I 與一個奇數大小的核心 G_{x} 進行卷積。比如,當核心大小為3時, G_{x} 的計算結果為圖4a:

垂直變化求導:將 I 與一個奇數大小的核心 G_{y} 進行卷積。比如,當核心大小為3時, G_{y} 的計算結果為圖4b:

在影象的每一點,結合以上兩個結果求出近似 梯度 ,如圖4c:


圖4a、                          圖4b、                          圖4c、

因為Sobel運算元只是求取了導數的近似值,當核心大小為3時,以上Sobel核心可能產生比較明顯的誤差。為解決這一問題,OpenCV提供了 

Scharr 函式,但該函式僅作用於大小為3的核心,該函式的運算與Sobel函式一樣快,但結果卻更加精確。


兩種實現版本:

C 版本:

cvSobel ( const cvArr* src, CvArr* dst, int xorder, int yorder, int aperture_size =3 )

src,  dst 分別是源影象和目標影象,xorder ,yorder – 分別為x,y方向導數運算引數,可取0,1,2  。aperture_size是方形濾波器的寬,是小於7的奇數。

具體見《Learning OpenCV》那本書,P.170頁

下面是程式碼,比較簡單:

#include <highgui.h>
#include <cv.h>

using namespace cv;
using namespace std;

int main(int argc, char ** argv)
{
	IplImage* src, *dstx,*dsty,*dst;

	src = cvLoadImage( "car.png",0 );
	dst = cvCreateImage( cvGetSize( src ), IPL_DEPTH_16S, 1 );
	dstx = cvCreateImage( cvGetSize( src ), IPL_DEPTH_16S, 1 );
	dsty = cvCreateImage( cvGetSize( src ), IPL_DEPTH_16S, 1 );

	cvNamedWindow( "src" );
	cvNamedWindow( "sobel" );

	cvShowImage( "src", src );

	cvSobel( src, dstx, 1, 0, 7 );  //sobel
	cvSobel( src, dsty, 0, 1, 7 );
	cvAddWeighted(dstx,0.5,dsty,0.5,0,dst);

	cvShowImage( "sobel", dst );

	cvWaitKey(0);
	cvReleaseImage( &src );
	cvReleaseImage( &dst );

	return 0;
}
效果圖:



C++版本:

先來看一下C++下 Sobel 的定義

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 )

各引數的意義如下:

src – 輸入影象。dst – 輸出影象,與輸入影象同樣大小,擁有同樣個數的通道。

ddepth –輸出圖片深度;下面是輸入影象支援深度和輸出影象支援深度的關係:

src.depth() = CV_8Uddepth = -1/CV_16S/CV_32F/CV_64F

src.depth() = CV_16U/CV_16Sddepth = -1/CV_32F/CV_64F

src.depth() = CV_32Fddepth = -1/CV_32F/CV_64F

src.depth() = CV_64Fddepth = -1/CV_64F

當 ddepth為-1時, 輸出影象將和輸入影象有相同的深度。輸入8點陣圖像則會擷取頂端的導數。

xorder – x方向導數運算引數。yorder – y方向導數運算引數。

ksize – Sobel核心的大小,可以是:1,3,5,7。  注意:只可以是小於7 的奇數

scale – 可選的縮放導數的比例常數。delta – 可選的增量常數被疊加到導數中。borderType – 用於判斷影象邊界的模式。

下面是程式:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;
using namespace std;

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;

	src = imread( "car.png" );
	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;
	//x方向梯度計算
	Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
	convertScaleAbs( grad_x, abs_grad_x );
	//y方向梯度計算
	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();
	return 0;
}
如果要用Scharr濾波器的話,把Sobel那行程式碼替換掉就好了:
Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Scharr( src_gray, grad_x, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
效果圖: