文章目录
一、何为双边滤波?
双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中滤波方法。
线性滤波的含义:
g
(
i
,
j
)
=
T
[
f
(
i
,
j
)
]
g(i,j)=T[f(i,j)]
g(i,j)=T[f(i,j)],其中
T
T
T是线性算子。即输入图像的像素值经过线性变换得到输出图像的像素值,例如:均值滤波、高斯滤波等。线性滤波器的原始数据与滤波结果是一种算数运算,即用加减乘除等运算实现。由于线性滤波器是算数运算,所以有固定的模板。
非线性滤波的含义:非线性滤波的算子中包含了取绝对值、置零等非线性运算。例如:最大值滤波器、中值滤波器等。非线性滤波器的原始数据与滤波结果是一种逻辑关系,是通过比较一定邻域内的灰度值大小来实现的,没有固定的模板。
二、为什么要使用双边滤波?
中值滤波、高斯滤波、维纳滤波等滤波方法容易模糊图片的边缘细节,对高频细节的保护效果并不明显。相较而言,双边滤波器可以很好的在降噪的同时保护边缘。
双边滤波的卷积核是非线性的,因此计算复杂度高。不同位置的卷积核不同,所以不能预先计算或者执行FFT,计算起来比较费时。
三、双边滤波原理
1.空间域核
空间域核模板权重
w
d
w_d
wd如下。
w
d
(
i
,
j
,
k
,
l
)
=
e
x
p
(
−
(
i
−
k
)
2
+
(
j
−
l
)
2
2
σ
d
2
)
w_d(i,j,k,l)=exp(-\frac{(i-k)^2+(j-l)^2}{2\sigma^2_d})
wd(i,j,k,l)=exp(−2σd2(i−k)2+(j−l)2)
w
d
w_d
wd表示了邻域内某点
q
(
k
,
l
)
q(k,l)
q(k,l)与中心点
p
(
i
,
j
)
p(i,j)
p(i,j)的欧氏距离。
σ
d
\sigma_d
σd为高斯函数的标准差。使用该公式生成的滤波器模板和高斯滤波器使用的模板是没有区别的。
2.值域核
值域核的模板权重
w
r
w_r
wr如下。
w
r
(
i
,
j
,
k
,
l
)
=
e
x
p
(
−
∥
f
(
i
,
j
)
−
f
(
k
,
l
)
∥
2
2
σ
r
2
)
w_r(i,j,k,l)=exp(-\frac{\Vert f(i,j)-f(k,l)\Vert^2}{2\sigma^2_r})
wr(i,j,k,l)=exp(−2σr2∥f(i,j)−f(k,l)∥2)
其中,
f
(
i
,
j
)
f(i,j)
f(i,j)表示图像在点
(
i
,
j
)
(i,j)
(i,j)处的像素值;
f
(
k
,
l
)
f(k,l)
f(k,l)为模板窗口中心坐标点的像素值。
σ
r
\sigma_r
σr为高斯函数䣌标准差。
3.模板相乘
将上述两个模板相乘就得到了双边滤波器的模板权值。
w
(
i
,
j
,
k
,
l
)
=
w
d
(
i
,
j
,
k
,
l
)
×
w
r
(
i
,
j
,
k
,
l
)
=
e
x
p
(
−
(
i
−
k
)
2
+
(
j
−
l
)
2
2
σ
d
2
−
∥
f
(
i
,
j
)
−
f
(
k
,
l
)
∥
2
2
σ
r
2
)
w(i,j,k,l)=w_d(i,j,k,l)\times w_r(i,j,k,l)=exp(-\frac{(i-k)^2+(j-l)^2}{2\sigma^2_d}-\frac{\Vert f(i,j)-f(k,l)\Vert^2}{2\sigma^2_r})
w(i,j,k,l)=wd(i,j,k,l)×wr(i,j,k,l)=exp(−2σd2(i−k)2+(j−l)2−2σr2∥f(i,j)−f(k,l)∥2)
滤波后的图像的像素值为: g ( i , j ) = ∑ k l f ( k , l ) w ( i , j , k , l ) ∑ k l w ( i , j , k , l ) g(i,j)=\frac{\sum_{kl}f(k,l)w(i,j,k,l)}{\sum_{kl}w(i,j,k,l)} g(i,j)=∑klw(i,j,k,l)∑klf(k,l)w(i,j,k,l)
四、 w d w_d wd和 w r w_r wr和 σ \sigma σ的理解
空间域权值
w
d
w_d
wd衡量的是
p
,
q
p,q
p,q两点之间的欧式距离,距离越远
w
d
w_d
wd越小。这和高斯滤波器是相同的,高斯滤波器的像素位置越靠近中心点权重越大。
值域权值
w
r
w_r
wr衡量的是
p
,
q
p,q
p,q两点之间的像素值的相似程度,越相似
w
r
w_r
wr越大。
从图像的平坦区域和边缘区域直观分析双边滤波的效果。
- 在平坦区域,临近像素与中心像素的差值较小,对应 w r w_r wr接近1,此时空域权重 w d w_d wd起主要作用,相当于是直接使用高斯滤波器对图像进行滤波。
- 在边缘区域灰度会有较大的变化,与被卷积像素的灰度值类似的区域的 w r w_r wr较大趋近于1,与被卷积像素的灰度值差值较大的区域的 w r w_r wr较小趋于0。这样对被卷积的像素来说,收到的影响较小,从而保持了细节信息。
σ
d
\sigma_d
σd选取:和高斯滤波一样,
σ
d
\sigma_d
σd越大,图像越平滑;
σ
d
\sigma_d
σd越小,中心点权重越大,周围点权重越小,对图像的滤波作用越小,趋于0时,输出等同于原图。
σ
r
\sigma_r
σr选取:
σ
r
\sigma_r
σr越大,边缘越模糊。当
σ
r
\sigma_r
σr无穷大时,值域系数近似相等(
e
x
p
(
0
)
=
1
exp(0)=1
exp(0)=1),与空间域模板相乘后可认为等效于高斯滤波。
σ
r
\sigma_r
σr越小,边缘越清晰。当
σ
r
\sigma_r
σr无限接近0时,值域系数除了中心位置为1,其他近似为0(
e
x
p
(
−
∞
)
=
0
exp(-\infty)=0
exp(−∞)=0),与空间域模板相乘进行滤波的结果等效于原图像。
五、C++代码实现
1.opencv中Mat的一点小知识
在普通的C++编码中,如果我们想要通过函数改变一个类似于int、float这样的变量的值,需要将变量的地址传入到函数中。而在opencv中,如果我们想要改变一个Mat的值,只需要将Mat传入函数即可。具体的介绍请参考这里。
2.关于边界的处理
使用滤波器对边界的像素进行处理时,滤波器会出现越界现象,此时我们用原图中与越界的点相对应的像素点进行替代。
如上图所示,粉色位置为要进行滤波的像素点,滤波器大小为
3
×
3
3\times 3
3×3,滤波器上编号为1的位置明显已经越界(无像素值),此时我们用编号为2的位置的像素值代替1处的像素值。
3.双边滤波代码
double Wd(const int &i, const int &j, const float &sigma_d)
{
return double(exp(-(pow(i, 2) + pow(j, 2)) / (2 * pow(sigma_d, 2))));
}
double Wr(const float &x, const float &sigma_r)
{
return double(exp(-(pow(x, 2) / (2 * pow(sigma_r, 2)))));
}
// 边界处理函数
int reflect(int M, int x)
{
if (x < 0)
return -x;
if (x >= M)
return 2 * M - x-1;
return x;
}
void ValueProcess(const cv::Mat &image, cv::Mat bFilterImage, const int &x, const int &y, const int &d, const float &sigma_d, const float &sigma_r)
{
int k = d / 2;
double iFiltered = 0;
double wP = 0;
int height = image.rows;
int width = image.cols;
int neighbor_x = 0;
int neighbor_y = 0;
for (int i = -k; i <= k; i++)
{
for (int j = -k; j <=k; j++)
{
//构造w_d权重
double wd = Wd(i, j, sigma_d);
//越界处理
neighbor_x = reflect(height, x + i);
neighbor_y = reflect(width, y + j);
//构造w_r权重
double wr = Wr(image.at<uchar>(neighbor_x, neighbor_y) - image.at<uchar>(x, y), sigma_r);
double w = wd * wr;
iFiltered = iFiltered + image.at<uchar>(neighbor_x, neighbor_y)*w;
wP = wP + w;
}
}
iFiltered = iFiltered / wP;
bFilterImage.at<double>(x, y) = iFiltered;
}
cv::Mat BFilter(const cv::Mat &image, const int &d, const float &sigma_d, const float &sigma_r)
{
int height = image.rows;
int width = image.cols;
cv::Mat bFilterImage = cv::Mat::zeros(height, width, CV_64FC1);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
//对每个像素点进行处理
ValueProcess(image, bFilterImage, i, j, d, sigma_d, sigma_r);
}
}
return bFilterImage;
}
int main()
{
cv::Mat image = cv::imread("LenaRGB.bmp");
cv::Mat grayImage;
cv::cvtColor(image, grayImage, CV_BGR2GRAY);
// call b filter
int d = 7;
float sigma_d = 12.0;
float sigma_r = 16.0;
cv::Mat bFilterImage = BFilter(grayImage, d, sigma_d, sigma_r);
cv::convertScaleAbs(bFilterImage, bFilterImage); // 转换到8位uchar类型
return 0;
}
参考链接:
https://www.cnblogs.com/snowxshy/p/3855011.html
https://zhuanlan.zhihu.com/p/127023952
https://blog.csdn.net/leonardohaig/article/details/118058527