部分內容整理自:
http://blog.csdn.net/jianxiong8814/article/details/1562728
http://www.cnblogs.com/pegasus/archive/2011/05/20/2052031.html
http://blog.sina.com.cn/s/blog_640577ed0100yz8v.html
http://blog.csdn.net/pearl333/article/details/8696307
http://www.cnblogs.com/tornadomeet/tag/opencv/
關於濾波
濾波通常是用卷積或者相關來描述,而線性濾波一般是通過卷積來描述的。他們非常類似,但是還是會有不同。下面我們來根據相關和卷積計算過程來體會一下他們的具體區別:
卷積的計算步驟:
1)卷積核繞自己的核心元素順時針旋轉180度
2)移動卷積核的中心元素,使它位於輸入圖像待處理像素的正上方
3)在旋轉後的卷積核中,將輸入圖像的像素值作爲權重相乘
4)第三步各結果的和做爲該輸入像素對應的輸出像素
相關的計算步驟:
1)移動相關核的中心元素,使它位於輸入圖像待處理像素的正上方
2)將輸入圖像的像素值作爲權重,乘以相關核
3)將上面各步得到的結果相加做爲輸出
可以看出他們的主要區別在於計算卷積的時候,卷積核要先做旋轉。而計算相關過程中不需要旋轉相關核。
例如:magic(3) =[8 1 6;3 5 7;4 9 2],旋轉180度後就成了[2 9 4;7 5 3;6 1 8]
高斯濾波
高斯濾波是一種線性平滑濾波,適用於消除高斯噪聲,廣泛應用於圖像處理的減噪過程。通俗的講,高斯濾波就是對整幅圖像進行加權平均的過程,每一個像素點的值,都由其本身和鄰域內的其他像素值經過加權平均後得到。
高斯濾波的具體操作是:用一個模板(或稱卷積、掩模)掃描圖像中的每一個像素,用模板確定的鄰域內像素的加權平均灰度值去替代模板中心像素點的值。
若使用3×3模板,則計算公式如下g(x,y)={f(x-1,y-1)+f(x-1,y+1)+f(x+1,y-1)+f(x+1,y+1)+[f(x-1,y)+f(x,y-1)+f(x+1,y)+f(x,y+1)]*2+f(x,y)*4}/16;
其中,f(x,y)爲圖像中(x,y)點的灰度值,g(x,y)爲該點經過高斯濾波後的值。
1、高斯分佈:
一維高斯分佈:
二維高斯分佈:
2、高斯核
理論上,高斯分佈在所有定義域上都有非負值,這就需要一個無限大的卷積核。實際上,僅需要取均值周圍3倍標準差內的值,以外部份直接去掉即可。如下圖爲一個標準差爲1.0的整數值高斯核。
3、應用:
高斯濾波後圖像被平滑的程度取決於標準差。它的輸出是領域像素的加權平均,同時離中心越近的像素權重越高。因此,相對於均值濾波(mean filter)它的平滑效果更柔和,而且邊緣保留的也更好。
高斯濾波被用作爲平滑濾波器的本質原因是因爲它是一個低通濾波器(讓某一頻率以下的信號分量通過,而對該頻率以上的信號分量大大抑制)
4、特徵:
1):一個高斯函數跟另外一個高斯函數的卷積仍然是一個高斯函數,A*B=C C的標準差的平方是A和B的標準差的平方和,也就是說卷積後的高斯函數更寬,模糊的效果更明顯(直觀上看,連續做高斯模糊運算,圖像會越來越模糊) 。二維高斯函數卷積可以分兩步來進行,首先將圖像與一維高斯函數進行卷積,然後將卷積結果與方向垂直的相同一維高斯函數卷積.因此,二維高斯濾波的計算量隨濾波模板寬度成線性增長而不是成平方增長.
2):高斯函數的傅立葉變換仍然是一個高斯函數,如果原來的高斯函數越寬(標準差越大),變換後的高斯函數就越窄(標準差越小),也就是說一個越寬的高斯函數,低通(高阻)濾波的效果越明顯,處理後的圖像的細節就越不清楚(更模糊)。
要對數字圖像做高斯模糊,就是用一個符合高斯函數分佈的卷積覈對數字圖像做卷積運算。
要確定的有標準差的大小,卷積核的大小,最後的比例係數的大小。
σ越大,高斯濾波器的頻帶就越寬,平滑程度就越好
一個標準差爲1.4的高斯5x5的卷積核:
2 4 5 4 2
4 9 12 9 4
5 12 15 12 5
4 9 12 9 4
2 4 5 4 2
最後乘以比例係數 1/115
在opencv中使用高斯濾波
複合形式函數原型爲:
void cvSmooth(const CvArr* src, CvArr* dst,intsmoothtype=CV_GAUSSIAN,int param1=3, int param2=0,double param3=0, doubleparam4=0 );
該函數前三個參數很容易理解,至於後四個參數以下進行分析。
1) 如果指定param1和param2,則代表核函數的行列,即爲濾波窗口的寬度和高度;
2) Param3:高斯卷積的Sigma值
3) 如果用戶希望採用非對稱的高斯核,則引入param4,最後兩個參數分別代表水平核以及垂直核維數;
4) 如果param3沒有給出,則有前兩個參數param1和param2計算出Sigma。這裏的根據是高斯分佈的特點(如圖所示,數值分佈在(μ—3σ,μ+3σ)中的概率爲0.9974),如果核矩陣更大,那麼相應的Sigma也更大,相反,如果Sigma更大,那麼核矩陣覆蓋範圍也更大。具體到OpenCv下,用如下公式進行計算(根據其源代碼顯示)。
5)同樣的根據這個公式可知,如果param1和param2爲0(或者沒有給出),那麼濾波窗口的尺寸,則有後兩個參數代表的Sigma來確定。
單獨的高斯濾波函數聲明爲
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT ) ;
功能:對輸入的圖像src進行高斯濾波後用dst輸出。
參數:src和dst當然分別是輸入圖像和輸出圖像。
Ksize爲高斯濾波器模板大小
sigmaX和sigmaY分別爲高斯濾波在橫線和豎向的濾波係數。
borderType爲邊緣擴展點插值類型。
接下來的工作就是進入GaussianBlur函數內部,跟蹤其函數代碼,經過分析,在該函數內部調用了很多其他的函數,其調用的函數層次結構如下圖所示:
這裏我們分析源代碼不需要深入到最底層,我們只需分析到函數createSeparableLinearFilter和getGaussianKernel這一層。
從函數調用層次結構圖可以看出,要分析函數GaussianBlur,必須先分析其調用過的內部函數。
因此首先分析函數getGaussianKernel。
功能:返回一個ksize*1的數組,數組元素滿足高斯公式:
其中只有係數alpha和參數sigma未知,sigma的求法爲:
如果輸入sigma爲非正,則計算公式爲:sigma =0.3*((ksize-1)*0.5 - 1) + 0.8 .
如果輸入sigma爲正,則就用該輸入參數sigma。
最後alpha爲歸一化係數,即計算出的ksize個數之和必須爲1,所以後面只需求ksize個數,計算其和並求倒即可。
其源碼與註釋如下:
cv::Mat cv::getGaussianKernel( int n, double sigma, int ktype )
{
const int SMALL_GAUSSIAN_SIZE = 7;
static const float small_gaussian_tab[][SMALL_GAUSSIAN_SIZE] =
{
{1.f},
{0.25f, 0.5f, 0.25f},
{0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f},
{0.03125f, 0.109375f, 0.21875f, 0.28125f, 0.21875f, 0.109375f, 0.03125f}
};
const float* fixed_kernel = n % 2 == 1 && n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 ?
small_gaussian_tab[n>>1] : 0;
CV_Assert( ktype == CV_32F || ktype == CV_64F );//確保核元素爲32位浮點數或者64位浮點數
Mat kernel(n, 1, ktype);//建立一個n*1的數組kernel,一個Mat矩陣包括一個矩陣頭和一個指向矩陣元素的指針
float* cf = (float*)kernel.data;//定義指針cf指向kernel單精度浮點型數據
double* cd = (double*)kernel.data;//定義指針cd指向kernerl雙精度浮點型數據
double sigmaX = sigma > 0 ? sigma : ((n-1)*0.5 - 1)*0.3 + 0.8;//當sigma小於0時,採用公式得到sigma(只與n有關)
double scale2X = -0.5/(sigmaX*sigmaX);//高斯表達式後面要用到
double sum = 0;
int i;
for( i = 0; i < n; i++ )
{
double x = i - (n-1)*0.5;
//如果自己算其核的話,就常用公式exp(scale2X*x*x)計算,否則就用固定係數的核
double t = fixed_kernel ? (double)fixed_kernel[i] : std::exp(scale2X*x*x);
if( ktype == CV_32F )
{
cf[i] = (float)t;//單精度要求時存入cf數組中
sum += cf[i];//進行歸一化時要用到
}
else
{
cd[i] = t;//雙精度時存入cd數組中
sum += cd[i];
}
}
sum = 1./sum;//歸一化時核中各元素之和爲1
for( i = 0; i < n; i++ )
{
if( ktype == CV_32F )
cf[i] = (float)(cf[i]*sum);//歸一化後的單精度核元素
else
cd[i] *= sum;//歸一化後的雙精度核元素
}
return kernel;//返回n*1的數組,其元素或是單精度或是雙精度,且符合高斯分佈
}
下面該分析函數createSeparableLinearFilter了。
功能爲:創建一個圖像濾波其引擎類,其主要處理的是原圖像和目標圖像數據格式的統以及濾波器核的合成。
其源碼及註釋如下:
cv::Ptr<cv::FilterEngine> cv::createGaussianFilter( int type, Size ksize,
double sigma1, double sigma2,
int borderType )
{
int depth = CV_MAT_DEPTH(type);//取數組元素的深度
if( sigma2 <= 0 )
sigma2 = sigma1;//當第3個參數爲非正時,取其與第二個參數相同的值
// automatic detection of kernel size from sigma
if( ksize.width <= 0 && sigma1 > 0 )//當濾波器核的寬非正時,其寬要重新經過計算
ksize.width = cvRound(sigma1*(depth == CV_8U ? 3 : 4)*2 + 1)|1;
if( ksize.height <= 0 && sigma2 > 0 )
ksize.height = cvRound(sigma2*(depth == CV_8U ? 3 : 4)*2 + 1)|1;
CV_Assert( ksize.width > 0 && ksize.width % 2 == 1 &&
ksize.height > 0 && ksize.height % 2 == 1 );//確保核寬和核高爲正奇數
sigma1 = std::max( sigma1, 0. );//sigma最小爲0
sigma2 = std::max( sigma2, 0. );
Mat kx = getGaussianKernel( ksize.width, sigma1, std::max(depth, CV_32F) );//得到x方向一維高斯核
Mat ky;
if( ksize.height == ksize.width && std::abs(sigma1 - sigma2) < DBL_EPSILON )
ky = kx;//如果核寬和核高相等,且兩個sigma相差很小的情況下,y方向的高斯核去與x方向一樣,減少計算量
else
ky = getGaussianKernel( ksize.height, sigma2, std::max(depth, CV_32F) );//否則計算y方向的高斯核係數
return createSeparableLinearFilter( type, type, kx, ky, Point(-1,-1), 0, borderType );//返回2維圖像濾波引擎
}
最後來看真正的高斯濾波函數GaussianBlur:功能:對輸入圖像_src進行濾波得到輸出圖像_dst,濾波核大小爲ksize,濾波參數由sigma1和sigma2計算出,邊緣擴展模式爲borderType.其源代碼和註釋如下: void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize,
double sigma1, double sigma2,
int borderType )
{
Mat src = _src.getMat();//創建一個矩陣src,利用_src的矩陣頭信息
_dst.create( src.size(), src.type() );//構造與輸入矩陣同大小的目標矩陣
Mat dst = _dst.getMat();//創建一個目標矩陣
if( ksize.width == 1 && ksize.height == 1 )
{
src.copyTo(dst);//如果濾波器核的大小爲1的話,則說明根本就不用濾波,輸出矩陣與輸入矩陣完全相同
return;
}
if( borderType != BORDER_CONSTANT )//當邊緣擴展不是常數擴展時
{
if( src.rows == 1 )
ksize.height = 1;//如果輸入矩陣是一個行向量,則濾波核的高強制爲1
if( src.cols == 1 )
ksize.width = 1;//如果輸入矩陣是一個列向量,則濾波核的寬強制爲1
}
Ptr<FilterEngine> f = createGaussianFilter( src.type(), ksize, sigma1, sigma2, borderType );
f->apply( src, dst );//調用引擎函數,完成將輸入矩陣src高斯濾波爲輸出矩陣dst
}
具體的可實現的高斯濾波
源碼和註釋如下:
#include "highgui.h"
#include "cv.h"
#pragma comment(linker, "/subsystem:windows /entry:mainCRTStartup")
void gaussianFilter(uchar* data, int width, int height)
{
int i, j, index, sum;
int templates[9] = { 1, 2, 1,
2, 4, 2,
1, 2, 1 };//模板的值
sum = height * width * sizeof(uchar);//圖像所佔內存的大小
uchar *tmpdata = (uchar*)malloc(sum);
memcpy((char*)tmpdata,(char*)data, sum);
for(i = 1;i < height - 1;i++)
{
for(j = 1;j < width - 1;j++)
{
index = sum = 0;
for(int m = i - 1;m < i + 2;m++)
{
for(int n = j - 1; n < j + 2;n++)
{
sum += tmpdata[m * width + n] * templates[index++]; //處理
}
}
data[i * width + j] = sum / 16;
}
}
free(tmpdata);
}
void imgOperate( IplImage* image )
{
cvNamedWindow( "image-in", CV_WINDOW_AUTOSIZE );
cvNamedWindow( "image-access", CV_WINDOW_AUTOSIZE);
cvNamedWindow( "image-out", CV_WINDOW_AUTOSIZE);
cvShowImage( "image-in", image );
//將色彩圖像強制轉化爲灰度圖像
IplImage* pGrayImg=NULL;
IplImage* pImg=NULL;
pGrayImg=cvCreateImage(cvGetSize(image),8,1);
pImg=cvCreateImage(cvGetSize(image),8,1);
cvCvtColor(image,pGrayImg,CV_RGB2GRAY);
// cvSmooth( image, out, CV_GAUSSIAN, 5,5 );
//添加高斯噪聲
cvZero(pImg);
CvRNG rng = cvRNG(-1); //初始化隨機數發生器
cvRandArr(&rng, pImg, CV_RAND_NORMAL, cvScalarAll(0), cvScalarAll(15));
cvAdd(pGrayImg, pImg,pImg);
cvShowImage("image-access",pImg);
gaussianFilter((unsigned char*)pImg->imageData,pImg->width,pImg->height);
cvShowImage( "image-out", pImg );
cvReleaseImage( &pGrayImg );
cvWaitKey( 0 );
cvDestroyWindow("image-in" );
cvDestroyWindow("image-out" );
}
int main()
{
IplImage* img = cvLoadImage("d:\\demo.jpg");
imgOperate( img );
cvReleaseImage( &img );
return 0;
}