部分内容整理自:
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)为该点经过高斯滤波后的值。
一维高斯分布:
二维高斯分布:
理论上,高斯分布在所有定义域上都有非负值,这就需要一个无限大的卷积核。实际上,仅需要取均值周围3倍标准差内的值,以外部份直接去掉即可。如下图为一个标准差为1.0的整数值高斯核。
高斯滤波后图像被平滑的程度取决于标准差。它的输出是领域像素的加权平均,同时离中心越近的像素权重越高。因此,相对于均值滤波(mean filter)它的平滑效果更柔和,而且边缘保留的也更好。
高斯滤波被用作为平滑滤波器的本质原因是因为它是一个低通滤波器(让某一频率以下的信号分量通过,而对该频率以上的信号分量大大抑制)
二维高斯函数卷积可以分两步来进行,首先将图像与一维高斯函数进行卷积,然后将卷积结果与方向垂直的相同一维高斯函数卷积.因此,二维高斯滤波的计算量随滤波模板宽度成线性增长而不是成平方增长.
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
复合形式函数原型为:
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::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 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;
}