本文整理自以下博客:
https://blog.csdn.net/u011285477/article/details/52077199
https://blog.csdn.net/cyh706510441/article/details/46581417
https://blog.csdn.net/linj_m/article/details/21892471
https://blog.csdn.net/mumusan2016/article/details/54578038
双边滤波器
双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。双边滤波器的好处是可以做边缘保存(edge preserving),一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。
原理示意图如下:
双边滤波在处理相邻各像素值的灰度值或彩色信息时,不仅考虑到几何上的邻近关系,也考虑到了亮度上的相似性,通过对二者的非线性组合,自适应滤波后得到平滑图像。这样处理过的图像在滤除噪声的同时还能够很好地保持图像的边缘信息。
简单地讲:双边滤波器类似于高斯滤波器,它也是给每一个邻域像素分配一个加权系数。不过,这些加权系数包含两个部分, 第一部分加权方式与高斯滤波一样,第二部分的权重则取决于该邻域像素与当前像素的灰度差值。
算法:
双边滤波器不像普通的高斯滤波器/卷积低通滤波器,只考虑了位置对中心像素的影响,它还考虑了卷积核中像素与中心像素之间相似程度的影响。
看如下公式:
上式中f是经过双边滤波器的输出图像,g是原始输入图像,w是权重系数。到这里为止,其实跟高斯滤波器的思路一样,就是通过加权平均求值。
双边滤波器的主要区别在于它的权重系数,它的权重系数由两部分组成。一个是空间邻近度因子ws、一个是亮度相似度因子wr。它是ws和wr的乘积。
即w的公式如下:
在这个权重系数中同时考虑了空间域和值域的差别。
ws随着像素点与中心点之间欧几里德距离的增大而减小,wr随着两像素亮度值之差的增大而减小。
在图像平滑的区域,邻域内像素亮度值相差不大,双边滤波器转化为高斯低通滤波器;
在图像变化剧烈的区域,滤波器利用边缘点附近亮度值相近的像素点的亮度值平均代替原亮度值。
一个简单的示例如下:
上图中(a)表示一个边缘,(b)表示权重系数,(c)是滤波后的结果。
代码:
第一种:
#include
#include
#include
#include
using namespace std;
using namespace cv;
/* 计算空间权值 */
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;
}
/* 计算相似度权值 */
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;
}
/* 双边 扫描计算 */
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(i, j)[0] + (*_src).at(i, j)[1] + (*_src).at(i, j)[2]
- (*_src).at(x, y)[0] - (*_src).at(x, y)[1] - (*_src).at(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(i, j)[0] + (*_src).at(i, j)[1] + (*_src).at(i, j)[2]
- (*_src).at(x, y)[0] - (*_src).at(x, y)[1] - (*_src).at(x, y)[2]);
for (int c = 0; c < 3; c++) {
sum[c] += ((*_src).at(x, y)[c]
* _colorArray[values]
* _spaceArray[k][l])
/ space_color_sum;
}
}
}
for (int c = 0; c < 3; c++) {
temp.at(i, j)[c] = sum[c];
}
}
}
}
// 放入原图
(*_src) = temp.clone();
return ;
}
/* 双边滤波函数 */
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;
}
int main(void)
{
// [1] src读入图片
cv::Mat src = cv::imread("JFKreg.jpg");
cv::imshow("src", src);
cv::waitKey(0);
// [2] dst目标图片
cv::Mat dst;
// [3] 滤波 N越大越平越模糊(2*N+1) sigmas空间越大越模糊sigmar相似因子
myBialteralFilter(&src, &dst, 25, 12.5, 50);
// [4] 窗体显示
cv::imshow("dst", dst);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
第二种:
//*功能 -- 双边滤波
//*input_img -- 【输入】
//*output_img -- 【输出】
//*sigmaR -- 【输入】拉普拉斯方差
//*sigmaS -- 【输入】高斯方差
//*d -- 【输入】半径
//***********************************************/
void bilateralBlur(Mat &input_img, Mat &output_img, float sigmaS, float sigmaR,int length)
{
// Create Gaussian/Bilateral filter --- mask ---
int i, j, x, y;
int radius = (int)length/2;//半径
int m_width = input_img.rows ;
int m_height= input_img.cols ;
std::vector mask(length*length);
//定义域核
for(i = 0; i < length; i++)
{
for (j = 0; j < length; j++)
{
mask[i*length + j] = exp(-(i*i + j*j)/(2 * sigmaS*sigmaS));
}
}
float sum = 0.0f, k = 0.0f;
for(x = 0; x < m_width; x++)
{
unsigned char *pin = input_img.ptr(x);
unsigned char *pout = output_img.ptr(x);
for(y = 0; y < m_height; y++)
{
int centerPix = y;
for(i = -radius; i <= radius; i++)
{
for(j = -radius; j <= radius; j++)
{
int m = x+i, n = y+j;
if(x+i > -1&& y+j > -1 && x+i < m_width && y+j < m_height)
{
unsigned char value = input_img.at(m, n);
//spatial diff
float euklidDiff = mask[(i+radius)*length + (j + radius)];
float intens = pin[centerPix]-value;//值域核
float factor = (float)exp(-0.5 * intens/(2*sigmaR*sigmaR)) * euklidDiff;
sum += factor * value;
k += factor;
}
}
}
pout[y] = sum/k;
sum=0.0f;
k=0.0f;
}
}
}
int main(void)
{
// [1] src读入图片
cv::Mat src = cv::imread("JFKreg.jpg");
// [2] dst目标图片
cv::Mat dst;
// [3] 滤波 N越大越平越模糊(2*N+1) sigmas空间越大越模糊sigmar相似因子
bilateralBlur(src, dst, 12.5, 50, 25);
// [4] 窗体显示
cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey(0);
return 0;
}