您的位置:首页 > 运维架构

引导滤波的opencv实现以及解释

2017-11-21 16:38 513 查看
记录学习引导滤波的笔记。

一、滤波器的作用

1、数字图像信号的频率分布?

回答:信号或者图像的能量大部分在中低频,少部分有用信号在高频段被噪声淹没(噪声都是高频信号)。因此设计滤波器能降低高频成分幅度就能减弱噪声影响。

2、为什么进行图像滤波?

回答:适应图像处理要求,消除图像数字化时所混入的噪声;提取对象特征作为图像识别的特征模式。

3、如何理解滤波器?

回答:把滤波器想象成一个包含加权系数的窗口,当时用滤波器平滑图像时,相当于把窗口放大图像上,透过窗口看图像。

4、滤波器实现的结果是怎样的?

回答:对图像做平滑或者滤波后图像变得更模糊

二、滤波器分类

2.1、线性滤波器

低通滤波器:允许低频通过

高通滤波器:允许高频通过

带通滤波器:允许一定频率通过

带阻滤波器:阻止一定频率通过

全通滤波器:允许所有频率通过,只改变相位

2.2、非线性滤波器

中值滤波器:像素点邻域灰度值的中值代替该像素点的灰度值。

双边滤波器:基于空间分布的高斯滤波函数,比高斯滤波多一个sigma-d的高斯方差。

三、OpenCV提供的滤波器

3.1、线性滤波器

方框滤波:boxFilter

C++: void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), bool normalize=true, int borderType=BORDER_DEFAULT )


参数解释:
src – 输入图像.
dst – 输出图像(和输入图像有着相同的类型和尺寸).
ddepth – 输出图像的深度 (-1代表使用原图深度).
ksize – 内核大小.
anchor – 被模糊的点; 默认值Point(-1,-1) 表示内核中心.
normalize – 内核是否归一化,默认true.
borderType – 默认值BORDER_DEFAULT,推断图像外部像素的某种边界模式.


这个函数平滑图像使用如下内核:



其中:



也就是说:选择了归一化,输出结果就是求均值。

均值滤波:blur

void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )


参数解释:
src – 输入图像 图像深度应该是:CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
dst – 输出图像
ksize – 内核尺寸
anchor – 被模糊的点; 默认值Point(-1,-1) 表示内核中心.
borderType – 默认值BORDER_DEFAULT,推断图像外部像素的某种边界模式.


这个函数使用如下内核:



注意:调用函数 blur(src, dst, ksize, anchor, borderType) 相当于调用 boxFilter(src, dst, src.type(), anchor, true, borderType) .

高斯滤波:GaussianBlur

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )


参数解释:
src – 输入图像 图像深度应该是:CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
dst – 输出图像.
ksize – 内核尺寸.
sigmaX – 高斯核函数在X方向上的标准偏差.
sigmaY – 高斯核函数在Y方向上的标准偏差.
borderType – 默认值BORDER_DEFAULT,推断图像外部像素的某种边界模式.


注意:对于sigmaX和sigmaY的具体怎么设置,需要查阅相关资料。官方api推荐。

3.2、非线性滤波器

中值滤波:medianBlur

void medianBlur(InputArray src, OutputArray dst, int ksize)


参数解释:
src – 输入1-,3-,或者4-通道图像;当内核尺寸是3或者5,图像深度必须是:CV_8U,CV_16U,或者CV_32F,对于更大      的内核尺寸,深度只能是CV_8U.
dst – 输出图像
ksize – 内核尺寸,必须是大于1的奇数


双边滤波:bilateralFilter

void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )


参数解释:
src – 输入8位或者浮点类型的1-channel或者3-channel图像.
dst – 输出图像.
d – 过滤过程每个像素邻域的直径.
sigmaColor – 颜色滤波器的sigma值.
sigmaSpace – 坐标空间的sigma值.


四、引导滤波算法原理(公式罗列更加贴切)

假设滤波器输出q(i,j)与引导图I(i,j)之间存在如下关系:



其中:



位对于固定窗口

内的线性因子。然后通过输入图像p(i,j)和输出图像q(i,j)之间的差异最小化来确定这两个线性因子,成本函数如下:



通过使得成本最小来确定



,ε为防止

过大的正则化参数。上述方程解为:



结论如下:当窗口处于平坦区域时,图像的局部方差小,则

趋于0,

趋于均值,相当于对图像进行了均值滤波;当窗口处于边缘时,局部方差较大,则则

趋于1,

趋于0,滤波器输出相当于原图。这样就可以很好的保护边缘。

注意:一般地,图像中间,像素跳跃变化不大,趋于稳定,方差小;图像边缘,像素跳跃变化大,不稳定,方差大。

五、OpenCV完成引导滤波编写

本案例是可以通过人工手算验证OpenCV的算法是否达到我们所需要的要求,所以直接处理图像数据是不合适的。但是只是简单的处理一个很小的矩阵也是不合适的。通过网络资源的搜寻,采用下面二维数组:

float matrix[8][8] = {
{ 45.0f, 60.0f, 98.0f, 127.0f, 132.0f, 133.0f, 137.0f, 133.0f },
{ 46.0f, 65.0f, 98.0f, 123.0f, 126.0f, 128.0f, 131.0f, 133.0f },
{ 47.0f, 65.0f, 96.0f, 115.0f, 119.0f, 123.0f, 135.0f, 137.0f },
{ 47.0f, 63.0f, 91.0f, 107.0f, 113.0f, 122.0f, 138.0f, 134.0f },
{ 50.0f, 59.0f, 80.0f, 97.0f,  110.0f, 123.0f, 133.0f, 134.0f },
{ 49.0f, 53.0f, 68.0f, 83.0f,  97.0f,  113.0f, 128.0f, 133.0f },
{ 50.0f, 50.0f, 58.0f, 70.0f,  84.0f,  102.0f, 116.0f, 126.0f },
{ 50.0f, 50.0f, 52.0f, 58.0f,  69.0f,  86.0f,  101.0f, 120.0f }
};


并且,我还为了方便验证和计算,把引导图和输入图像都用这个数据代替。float数组转Mat代码和显示代码如下:

//原矩阵
Mat mat(Size(8, 8), CV_32F, matrix);
cout <<"mat:\n"<< mat << endl;


结果截图如下:



证明Mat数据构造没有问题,可以进行下一步,求解这个矩阵的均值,代码如下:

//原矩阵的均值矩阵
Mat mean;
boxFilter(mat, mean, -1, Size(3, 3));
cout << "mean:\n" << mean << endl;


结果截图如下:



由第一幅图圈出的9个数和第二幅图圈出的一个数,经过计算可以得出,在误差允许范围内,值均值关系。

验证成功。进行下一步,求出原矩阵的平方矩阵,代码如下:

//原矩阵的平方矩阵
Mat mat2 = mat.mul(mat);
cout << "mat2:\n" << mat2 << endl;


结果截图如下:



对比图1和图3的圈住的9个数,确定是平方关系。验证成功,进行下一步,求均值矩阵的平方矩阵,代码如下:

//均值矩阵的平方矩阵
Mat mean2 = mean.mul(mean);
cout << "mean2:\n" << mean2 << endl;


结果截图如下:



图2 和图4是平方关系,验证成功,进行下一步,求方差,方差公式如下:



由这个公式,求平方矩阵的均值矩阵代码如下:

//平方矩阵的均值矩阵
Mat mat2mean;
boxFilter(mat2, mat2mean, -1, Size(3, 3));
cout << "mat2mean:\n" << mat2mean << endl;


结果截图如下:



现在得到了平方均值和均值平方,求方差,代码如下:

//平方均值减去均值平方等于方差
Mat variance = mat2mean - mean2;
cout << "variance:\n" << variance << endl;


结果如下:



在这里,我们去ε=500,求得到分母,代码如下:

Mat variance_epislon = variance + 500;
cout << "\nvariance_epislon:\n" << variance_epislon << endl;


结果如下:



根据上诉公式,求解





求解代码如下:

Mat a;
divide(variance, variance_epislon, a);
cout << "\na:\n" << a << endl;

Mat b = (1 - a)*mean;
cout << "\nb:\n" << b << endl;

Mat mean_a;
boxFilter(a, mean_a, CV_32F, Size(3, 3));
cout << "\nmean_a:\n" << mean_a << endl;

Mat  mean_b;
boxFilter(b, mean_b, CV_32F, Size(3, 3));
cout << "\nmean_b:\n" << mean_b << endl;


结果截图:





有了mean_a和mean_b,求出输出图像,代码如下:

Mat tmp = mean_a * mat + mean_b;
cout << "\ntmp:\n" << tmp << endl;

Mat output;
//归一化
normalize(tmp, output, 1.0, 0.0, NORM_MINMAX);
//转成8位
output.convertTo(output, CV_8UC1, 255);
cout << "\noutput:\n" << output << endl;


结果截图如下:



可以看出,经过这一番变化,这个矩阵的内容发生变化,也是按照我们需要的方向变化的。每一步都不能错,每一步都是验证成功才进行下一步。

具体对于图像的细节增强,需要调整内核大小和ε值,我这里是固定的3*3和500,是为了方便计算。

整体代码如下:

int main()
{

float matrix[8][8] = {
{ 45.0f, 60.0f, 98.0f, 127.0f, 132.0f, 133.0f, 137.0f, 133.0f },
{ 46.0f, 65.0f, 98.0f, 123.0f, 126.0f, 128.0f, 131.0f, 133.0f },
{ 47.0f, 65.0f, 96.0f, 115.0f, 119.0f, 123.0f, 135.0f, 137.0f },
{ 47.0f, 63.0f, 91.0f, 107.0f, 113.0f, 122.0f, 138.0f, 134.0f },
{ 50.0f, 59.0f, 80.0f, 97.0f, 110.0f, 123.0f, 133.0f, 134.0f },
{ 49.0f, 53.0f, 68.0f, 83.0f, 97.0f, 113.0f, 128.0f, 133.0f },
{ 50.0f, 50.0f, 58.0f, 70.0f, 84.0f, 102.0f, 116.0f, 126.0f },
{ 50.0f, 50.0f, 52.0f, 58.0f, 69.0f, 86.0f, 101.0f, 120.0f }
};

//原矩阵
Mat mat(Size(8, 8), CV_32F, matrix);
cout <<"mat:\n"<< mat << endl;

//原矩阵的均值矩阵
Mat mean;
boxFilter(mat, mean, -1, Size(3, 3));
cout << "mean:\n" << mean << endl;

//均值矩阵的平方矩阵
Mat mean2 = mean.mul(mean);
cout << "mean2:\n" << mean2 << endl;

//原矩阵的平方矩阵
Mat mat2 = mat.mul(mat);
cout << "mat2:\n" << mat2 << endl;

//平方矩阵的均值矩阵
Mat mat2mean;
boxFilter(mat2, mat2mean, -1, Size(3, 3));
cout << "mat2mean:\n" << mat2mean << endl;

//平方均值减去均值平方等于方差
Mat variance = mat2mean - mean2;
cout << "variance:\n" << variance << endl;

Mat variance_epislon = variance + 500;
cout << "\nvariance_epislon:\n" << variance_epislon << endl;
Mat a;
divide(variance, variance_epislon, a);
cout << "\na:\n" << a << endl;

Mat b = (1 - a)*mean;
cout << "\nb:\n" << b << endl;

Mat mean_a;
boxFilter(a, mean_a, CV_32F, Size(3, 3));
cout << "\nmean_a:\n" << mean_a << endl;
Mat  mean_b;
boxFilter(b, mean_b, CV_32F, Size(3, 3));
cout << "\nmean_b:\n" << mean_b << endl;

Mat tmp = mean_a * mat + mean_b;
cout << "\ntmp:\n" << tmp << endl;
Mat output;
normalize(tmp, output, 1.0, 0.0, NORM_MINMAX);
output.convertTo(output, CV_8UC1, 255);

cout << "\noutput:\n" << output << endl;
cin.get();
return 0;
}


总算是把这个过程搞明白了,引导滤波也算是弯沉了第一步。

问题:

为什么在OpenCV里面号称保边的boxFilter滤波器,在3*3的内核尺寸下,4边上的像素还是发生了改变?是如何变化的?

需要考究一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐