高斯滤波器是一种线性滤波器,能够有用的按捺噪声,滑润图画。其作用原理和均值滤波器相似,都是取滤波器窗口内的像素的均值作为输出。其窗口模板的系数和均值滤波器不同,均值滤波器的模板系数都是相同的为1;而高斯滤波器的模板系数,则跟着间隔模板中心的增大而系数减小。所以,高斯滤波器比较于均值滤波器对图画个含糊程度较小。
什么是高斯滤波器?
已然名称为高斯滤波器,那么其和高斯散布(正态散布)是有必定的联系的。一个二维的高斯函数如下:
其间(x,y)(x,y)为点坐标,在图画处理中可认为是整数;σσ是标准差。要想得到一个高斯滤波器的模板,能够对高斯函数进行离散化,得到的高斯函数值作为模板的系数。例如:要发生一个3×33×3的高斯滤波器模板,以模板的中心方位为坐标原点进行取样。模板在各个方位的坐标,如下所示(x轴水平向右,y轴竖直向下)
这样,将各个方位的坐标带入到高斯函数中,得到的值便是模板的系数。
关于窗口模板的巨细为(2k+1)×(2k+1),模板中各个元素值的核算公式如下:
这样核算出来的模板有两种方式:小数和整数。
小数方式的模板,便是直接核算得到的值,没有经过任何的处理;
整数方式的,则需求进行归一化处理,将模板左上角的值归一化为1,下面会详细介绍。运用整数的模板时,需求在模板的前面加一个系数,系数为也便是模板系数和的倒数。
高斯模板的生成
知道模板生成的原理,完成起来也就不困难了
void generateGaussianTemplate(double window[][11], int ksize, double sigma)
{
static const double pi = 3.1415926;
int center = ksize / 2; // 模板的中心方位,也便是坐标的原点
double x2, y2;
for (int i = 0; i < ksize; i++)
{
x2 = pow(i – center, 2);
for (int j = 0; j < ksize; j++)
{
y2 = pow(j – center, 2);
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
g /= 2 * pi * sigma;
window[i][j] = g;
}
}
double k = 1 / window[0][0]; // 将左上角的系数归一化为1
for (int i = 0; i < ksize; i++)
{
for (int j = 0; j < ksize; j++)
{
window[i][j] *= k;
}
}
}
需求一个二维数组,寄存生成的系数(这儿假定模板的最大尺度不会超越11);第二个参数是模板的巨细(不要超越11);第三个参数就比较重要了,是高斯散布的标准差。
生成的进程,首要依据模板的巨细,找到模板的中心方位ksize/2。然后便是遍历,依据高斯散布的函数,核算模板中每个系数的值。
需求留意的是,最终归一化的进程,运用模板左上角的系数的倒数作为归一化的系数(左上角的系数值被归一化为1),模板中的每个系数都乘以该值(左上角系数的倒数),然后将得到的值取整,就得到了整数型的高斯滤波器模板。
下面截图生成的是,巨细为3×3,σ=0.83×3,σ=0.8的模板。
对上述解成果取整后得到如下模板:
这个模板就比较了解了,其便是依据σ=0.8的高斯函数生成的模板。
至于小数方式的生成也比较简略,去掉归一化的进程,并且在求解进程后,模板的每个系数要除以一切系数的和。详细代码如下:
void generateGaussianTemplate(double window[][11], int ksize, double sigma)
{
static const double pi = 3.1415926;
int center = ksize / 2; // 模板的中心方位,也便是坐标的原点
double x2, y2;
double sum = 0;
for (int i = 0; i < ksize; i++)
{
x2 = pow(i – center, 2);
for (int j = 0; j < ksize; j++)
{
y2 = pow(j – center, 2);
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
g /= 2 * pi * sigma;
sum += g;
window[i][j] = g;
}
}
//double k = 1 / window[0][0]; // 将左上角的系数归一化为1
for (int i = 0; i < ksize; i++)
{
for (int j = 0; j < ksize; j++)
{
window[i][j] /= sum;
}
}
}
3×3,σ=0.8的小数型模板。
σσ值的含义及选取
经过上述的完成进程,不难发现,高斯滤波器模板的生成最重要的参数便是高斯散布的标准差σσ。标准差代表着数据的离散程度,假如σσ较小,那么生成的模板的中心系数较大,而周围的系数较小,这样对图画的滑润作用就不是很显着;反之,σσ较大,则生成的模板的各个系数相差就不是很大,比较相似均值模板,对图画的滑润作用比较显着。
来看下一维高斯散布的概率散布密度图:
横轴表明或许得取值x,竖轴表明概率散布密度F(x),那么不难了解这样一个曲线与x轴围成的图形面积为1。σσ(标准差)决议了这个图形的宽度,能够得出这样的定论:σσ越大,则图形越宽,尖峰越小,图形较为陡峭;σσ越小,则图形越窄,越会集,中心部分也就越尖,图形改变比较剧烈。这其实很好了解,假如sigma也便是标准差越大,则表明该密度散布必定比较涣散,因为面积为1,所以尖峰部分减小,宽度越宽(散布越涣散);同理,当σσ越小时,阐明密度散布较为会集,所以尖峰越尖,宽度越窄!
所以能够得到如下定论:
σσ越大,散布越涣散,各部分比重不同不大,所以生成的模板各元素值不同不大,相似于均匀模板;
σσ越小,散布越会集,中心部分所占比重远远高于其他部分,反映到高斯模板上便是中心元素值远远大于其他元素值,所以自然而然就相当于中心值得点运算。
依据OpenCV的完成
在生成高斯模板好,其简略的完成和其他的空间滤波器没有差异,详细代码如下:
void GaussianFilter(const Mat &src, Mat &dst, int ksize, double sigma)
{
CV_Assert(src.channels() || src.channels() == 3); // 只处理单通道或许三通道图画
const static double pi = 3.1415926;
// 依据窗口巨细和sigma生成高斯滤波器模板
// 请求一个二维数组,寄存生成的高斯模板矩阵
double **templatematrix = new double*[ksize];
for (int i = 0; i < ksize; i++)
templateMatrix[i] = new double[ksize];
int origin = ksize / 2; // 以模板的中心为原点
double x2, y2;
double sum = 0;
for (int i = 0; i < ksize; i++)
{
x2 = pow(i – origin, 2);
for (int j = 0; j < ksize; j++)
{
y2 = pow(j – origin, 2);
// 高斯函数前的常数能够不必核算,会在归一化的进程中给消去
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
sum += g;
templateMatrix[i][j] = g;
}
}
for (int i = 0; i < ksize; i++)
{
for (int j = 0; j < ksize; j++)
{
templateMatrix[i][j] /= sum;
cout << templateMatrix[i][j] << " ";
}
cout << endl;
}
// 将模板应用到图画中
int border = ksize / 2;
copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
int channels = dst.channels();
int rows = dst.rows – border;
int cols = dst.cols – border;
for (int i = border; i < rows; i++)
{
for (int j = border; j < cols; j++)
{
double sum[3] = { 0 };
for (int a = -border; a <= border; a++)
{
for (int b = -border; b <= border; b++)
{
if (channels == 1)
{
sum[0] += templateMatrix[border + a][border + b] * dst.at<uchar>(i + a, j + b);
}
else if (channels == 3)
{
Vec3b rgb = dst.at<Vec3b>(i + a, j + b);
auto k = templateMatrix[border + a][border + b];
sum[0] += k * rgb[0];
sum[1] += k * rgb[1];
sum[2] += k * rgb[2];
}
}
}
for (int k = 0; k < channels; k++)
{
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
else if (channels == 3)
{
Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i, j) = rgb;
}
}
}
// 开释模板数组
for (int i = 0; i < ksize; i++)
delete[] templateMatrix[i];
delete[] templateMatrix;
}
只处理单通道或许三通道图画,模板生成后,其滤波(卷积进程)就比较简略了。不过,这样的高斯滤波进程,其循环运算次数为m×n×ksize2,其间m,n为图画的尺度;ksize为高斯滤波器的尺度。这样其时刻复杂度为O(ksize2),随滤波器的模板的尺度呈平方增加,当高斯滤波器的尺度较大时,其运算功率是极低的。为了,进步滤波的运算速度,能够将二维的高斯滤波进程分化开来。
别离完成高斯滤波
因为高斯函数的可别离性,尺度较大的高斯滤波器能够分红两步进行:首要将图画在水平(竖直)方向与一维高斯函数进行卷积;然后将卷积后的成果在竖直(水平)方向运用相同的一维高斯函数得到的模板进行卷积运算。详细完成代码如下:
// 别离的核算
void separateGaussianFilter(const Mat &src, Mat &dst, int ksize, double sigma)
{
CV_Assert(src.channels()==1 || src.channels() == 3); // 只处理单通道或许三通道图画
// 生成一维的高斯滤波模板
double *matrix = new double[ksize];
double sum = 0;
int origin = ksize / 2;
for (int i = 0; i < ksize; i++)
{
// 高斯函数前的常数能够不必核算,会在归一化的进程中给消去
double g = exp(-(i – origin) * (i – origin) / (2 * sigma * sigma));
sum += g;
matrix[i] = g;
}
// 归一化
for (int i = 0; i < ksize; i++)
matrix[i] /= sum;
// 将模板应用到图画中
int border = ksize / 2;
copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
int channels = dst.channels();
int rows = dst.rows – border;
int cols = dst.cols – border;
// 水平方向
for (int i = border; i < rows; i++)
{
for (int j = border; j < cols; j++)
{
double sum[3] = { 0 };
for (int k = -border; k <= border; k++)
{
if (channels == 1)
{
sum[0] += matrix[border + k] * dst.at<uchar>(i, j + k); // 行不变,列改变;先做水平方向的卷积
}
else if (channels == 3)
{
Vec3b rgb = dst.at<Vec3b>(i, j + k);
sum[0] += matrix[border + k] * rgb[0];
sum[1] += matrix[border + k] * rgb[1];
sum[2] += matrix[border + k] * rgb[2];
}
}
for (int k = 0; k < channels; k++)
{
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
else if (channels == 3)
{
Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i, j) = rgb;
}
}
}
// 竖直方向
for (int i = border; i < rows; i++)
{
for (int j = border; j < cols; j++)
{
double sum[3] = { 0 };
for (int k = -border; k <= border; k++)
{
if (channels == 1)
{
sum[0] += matrix[border + k] * dst.at<uchar>(i + k, j); // 列不变,行改变;竖直方向的卷积
}
else if (channels == 3)
{
Vec3b rgb = dst.at<Vec3b>(i + k, j);
sum[0] += matrix[border + k] * rgb[0];
sum[1] += matrix[border + k] * rgb[1];
sum[2] += matrix[border + k] * rgb[2];
}
}
for (int k = 0; k < channels; k++)
{
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
else if (channels == 3)
{
Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i, j) = rgb;
}
}
}
delete[] matrix;
}
代码没有重构较长,不过其完成原理是比较简略的。首要得到一维高斯函数的模板,在卷积(滤波)的进程中,坚持行不变,列改变,在水平方向上做卷积运算;接着在上述得到的成果上,坚持列不边,行改变,在竖直方向上做卷积运算。这样分化开来,算法的时刻复杂度为O(ksize)O(ksize),运算量和滤波器的模板尺度呈线性增加。
在OpenCV也有对高斯滤波器的封装GaussianBlur,其声明如下:
CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT );
二维高斯函数的标准差在x和y方向上应该别离有一个标准差,在上面的代码中一向设其在x和y方向的标准是持平的,在OpenCV中的高斯滤波器中,能够在x和y方向上设置不同的标准差。
下图是自己完成的高斯滤波器和OpenCV中的GaussianBlur的成果比照
上图是5×5,σ=0.8的高斯滤波器,能够看出两个完成得到的成果没有很大的差异。
总结
高斯滤波器是一种线性滑润滤波器,其滤波器的模板是对二维高斯函数离散得到。因为高斯模板的中心值最大,四周逐步减小,其滤波后的成果相关于均值滤波器来说更好。
高斯滤波器最重要的参数便是高斯散布的标准差σσ,标准差和高斯滤波器的滑润才能有很大的才能,σσ越大,高斯滤波器的频带就较宽,对图画的滑润程度就越好。经过调理σσ参数,能够平衡对图画的噪声的按捺和对图画的含糊。