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

记录学习引导滤波的笔记。

一、滤波器的作用
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;

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

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

结果截图如下:
图2

由第一幅图圈出的9个数和第二幅图圈出的一个数,经过计算可以得出,在误差允许范围内,值均值关系。
验证成功。进行下一步,求出原矩阵的平方矩阵,代码如下:

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

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

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

结果截图如下:
图4
图2 和图4是平方关系,验证成功,进行下一步,求方差,方差公式如下:
图5
由这个公式,求平方矩阵的均值矩阵代码如下:

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

结果截图如下:
图6

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

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

结果如下:
图7

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

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

结果如下:

图8

根据上诉公式,求解
求解代码如下:

    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;

结果截图:
图9
图10

有了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;

结果截图如下:
图11

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

具体对于图像的细节增强,需要调整内核大小和ε值,我这里是固定的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边上的像素还是发生了改变?是如何变化的?
需要考究一下。

猜你喜欢

转载自blog.csdn.net/qq_36006553/article/details/78594205