OpenCV双边滤波详解及实代码实现

双边滤波(Bilateral Filter)是非线性滤波中的一种。这是一种结合图像的空间邻近度与像素值相似度的处理办法。在滤波时,该滤波方法同时考虑空间临近信息与颜色相似信息,在滤除噪声、平滑图像的同时,又做到边缘保存。
双边滤波采用了两个高斯滤波的结合。一个负责计算空间邻近度的权值,也就是常用的高斯滤波器原理。而另一个负责计算像素值相似度的权值。在两个高斯滤波的同时作用下,就是双边滤波。

如果对于高斯滤波(空间邻近)原理不清晰的,建议先阅读这两篇文章
OpenCV高斯滤波器详解及代码实现
快速高斯滤波、高斯模糊、高斯平滑(二维卷积分步为一维卷积)

看到这里,博主认为你对高斯滤波(空间临近)已经理解并掌握了,那么我们开始对双边滤波进行讲解。
在上面两篇文章中提到,高斯滤波(空间临近)是将二维高斯正态分布放在图像矩阵上做卷积运算。考虑的是邻域内像素值的空间距离关系。通过在核大小范围内,各个点到中心点的空间临近度计算出对应的权值,并将计算好的核与图像矩阵作卷积。最后,图像经过滤波后达到平滑的效果,而图像上的边缘也会有一定程度的平滑,使得整个图像变得模糊,边缘得不到保存。

双边滤波的基本思想是:将高斯滤波(空间临近)的原理中,通过各个点到中心点的空间临近度计算的各个权值进行优化,将其优化为空间临近度计算的权值像素值相似度计算的权值的乘积,优化后的权值再与图像作卷积运算。从而达到保边去噪的效果。

一、双边滤波的公式

这里写图片描述
g(i, j)代表输出点;
S(i, j)的是指以(i,j)为中心的(2N+1)(2N+1)的大小的范围;
f(k, l)代表(多个)输入点;
w(i, j, k, l)代表经过两个高斯函数计算出的值(这里还不是权值)

上述公式我们进行转化,假设公式中w(i,j,k,l)为m,则有
这里写图片描述

设 m1+m2+m3 … +mn = M,则有
这里写图片描述
此时可以看到,这明显是图像矩阵与核的卷积运算了。其中m1/M代表的第一个点(或最后一个点,看后面如何实现)的权值,而图像矩阵与核通过卷积算子作加权和,最终得到输出值。

接下来我们来讨论最关键的w(i, j, k, l)
ws为空间临近高斯函数,wr为像素值相似度高斯函数
这里写图片描述
这里写图片描述
这里写图片描述
可以看到,w是ws和wr的乘积。对于ws来说,这就是普通的高斯滤波函数,其代入的坐标,sigmas是程序输入值,该函数是在空间临近度上计算的。
而wr是计算像素值相似度(颜色空间),注意,这就是高斯函数代入坐标值,2sigmar^2的上方是范数,在这里的值为 |f(i,j)-f(k,l)|^2。也就是两个点像素值差值的绝对值的平方。其中,彩色图片计算差值时应将(i,j)点的RGB三通道值之和减去(k,l)点的RGB三通道值之和。这里是颜色空间计算,不能当成单通道,但是在最后矩阵卷积时,是单通道与权值相乘而不是三个通道之和。

二、双边滤波权值分布(图片来源于网络)

(1)当图像在变化程度平缓的区域时,邻域中的像素值(RGB值)差距相差不大。此时wr无限接近于1,因此此时的双边就是普通的高斯滤波,达到对图像平滑的效果。
这里写图片描述
(2) 当图像在变化程度剧烈的区域,比如在边缘区域时,邻域中的像素值(RGB值)差距相差很大。此时wr朝0值趋近,颜色差值越大,wr越逼近0,最终整个式子的值逼近于0。最终的结果是权值为0。因此在最终计算时,该处将不影响输出值。
这里写图片描述

通过此种方式,其既能平滑图像,又保持了图像的边缘。

三、双边滤波实现(图片来源于网络)

首先来看下双边滤波的效果
这里写图片描述
这里写图片描述

该函数有三个参数,滤波板(核)半径N、参数sigmas和sigmar.
(1) main函数实现

int main(void)
{
    // [1] src读入图片
    cv::Mat src = cv::imread("pic3.jpg");
    // [2] dst目标图片
    cv::Mat dst;
    // [3] 滤波 N越大越平越模糊(2*N+1) sigmas空间越大越模糊sigmar相似因子
    myBialteralFilter(&src, &dst, 25, 12.5, 50);
    // [4] 窗体显示
    cv::imshow("src 1006534767", src);
    cv::imshow("dst 1006534767", dst);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

(2)双边滤波主函数,注意,现在的权值并没有进行归一化,归一化需要在计算时,根据第一大点中讲解的公式进行计算。

/* 双边滤波函数 */
void myBialteralFilter(cv::Mat *src, cv::Mat *dst, int N, double sigmas, double sigmar)
{
    // [1] 初始化
    *dst = (*src).clone();
    int _size = 2 * N + 1;
    // [2] 分别计算空间权值和相似度权值
    int channels = (*dst).channels();
    double *_colorArray = NULL;
    double **_spaceArray = NULL;
    _colorArray = get_color_Array( _size, channels, sigmar);
    _spaceArray = get_space_Array(_size, channels, sigmas);
    // [3] 滤波
    doBialteral(dst, N, _colorArray, _spaceArray);

    return;
}

(3)高斯函数带入坐标(空间临近)

/* 计算空间权值 */
double **get_space_Array( int _size, int channels, double sigmas)
{
    // [1] 空间权值
    int i, j;
    // [1-1] 初始化数组
    double **_spaceArray = new double*[_size+1];   //多一行,最后一行的第一个数据放总值
    for (i = 0; i < _size+1; i++) {
        _spaceArray[i] = new double[_size+1];
    }
    // [1-2] 高斯分布计算
    int center_i, center_j;
    center_i = center_j = _size / 2;
    _spaceArray[_size][0] = 0.0f;
    // [1-3] 高斯函数
    for (i = 0; i < _size; i++) {
        for (j = 0; j < _size; j++) {
            _spaceArray[i][j] =
                exp(-(1.0f)* (((i - center_i)*(i - center_i) + (j - center_j)*(j - center_j)) /
                (2.0f*sigmas*sigmas)));
            _spaceArray[_size][0] += _spaceArray[i][j];
        }
    }
    return _spaceArray;
}

(4)高斯函数代入坐标值(颜色空间)

/* 计算相似度权值 */
double *get_color_Array(int _size, int channels, double sigmar)
{
    // [2] 相似度权值
    int n;
    double *_colorArray = new double[255 * channels + 2];   //最后一位放总值
    double wr = 0.0f;
    _colorArray[255 * channels + 1] = 0.0f;
    for (n = 0; n < 255 * channels + 1; n++) {
        _colorArray[n] = exp((-1.0f*(n*n)) / (2.0f*sigmar*sigmar));
        _colorArray[255 * channels + 1] += _colorArray[n];
    }
    return _colorArray;
}

(5)滤波

/* 双边 扫描计算 */
void doBialteral(cv::Mat *_src, int N, double *_colorArray, double **_spaceArray)
{
    int _size = (2 * N + 1);
    cv::Mat temp = (*_src).clone();
    // [1] 扫描
    for (int i = 0; i < (*_src).rows; i++) {
        for (int j = 0; j < (*_src).cols; j++) {
            // [2] 忽略边缘
            if (i > (_size / 2) - 1 && j > (_size / 2) - 1 &&
                i < (*_src).rows - (_size / 2) && j < (*_src).cols - (_size / 2)) {

                // [3] 找到图像输入点,以输入点为中心与核中心对齐
                //     核心为中心参考点 卷积算子=>高斯矩阵180度转向计算
                //     x y 代表卷积核的权值坐标   i j 代表图像输入点坐标
                //     卷积算子     (f*g)(i,j) = f(i-k,j-l)g(k,l)          f代表图像输入 g代表核
                //     带入核参考点 (f*g)(i,j) = f(i-(k-ai), j-(l-aj))g(k,l)   ai,aj 核参考点
                //     加权求和  注意:核的坐标以左上0,0起点 
                double sum[3] = { 0.0,0.0,0.0 };
                int x, y, values;
                double space_color_sum = 0.0f;
                // 注意: 公式后面的点都在核大小的范围里
                // 双边公式 g(ij) =  (f1*m1 + f2*m2 + ... + fn*mn) / (m1 + m2 + ... + mn)
                // space_color_sum = (m1 + m12 + ... + mn)
                for (int k = 0; k < _size; k++) {
                    for (int l = 0; l < _size; l++) {
                        x = i - k + (_size / 2);   // 原图x  (x,y)是输入点
                        y = j - l + (_size / 2);   // 原图y  (i,j)是当前输出点 
                        values = abs((*_src).at<cv::Vec3b>(i, j)[0] + (*_src).at<cv::Vec3b>(i, j)[1] + (*_src).at<cv::Vec3b>(i, j)[2]
                            - (*_src).at<cv::Vec3b>(x, y)[0] - (*_src).at<cv::Vec3b>(x, y)[1] - (*_src).at<cv::Vec3b>(x, y)[2]);
                        space_color_sum += (_colorArray[values] * _spaceArray[k][l]);
                    }
                }
                // 计算过程
                for (int k = 0; k < _size; k++) {
                    for (int l = 0; l < _size; l++) {
                        x = i - k + (_size / 2);   // 原图x  (x,y)是输入点
                        y = j - l + (_size / 2);   // 原图y  (i,j)是当前输出点 
                        values = abs((*_src).at<cv::Vec3b>(i, j)[0] + (*_src).at<cv::Vec3b>(i, j)[1] + (*_src).at<cv::Vec3b>(i, j)[2]
                            - (*_src).at<cv::Vec3b>(x, y)[0] - (*_src).at<cv::Vec3b>(x, y)[1] - (*_src).at<cv::Vec3b>(x, y)[2]);
                        for (int c = 0; c < 3; c++) {
                            sum[c] += ((*_src).at<cv::Vec3b>(x, y)[c]
                                * _colorArray[values]
                                * _spaceArray[k][l])
                                / space_color_sum;
                        }
                    }
                }
                for (int c = 0; c < 3; c++) {
                    temp.at<cv::Vec3b>(i, j)[c] = sum[c];   
                }
            }
        }
    }
    // 放入原图
    (*_src) = temp.clone();

    return ;
}

猜你喜欢

转载自blog.csdn.net/qq_36359022/article/details/80198890