本文整理自以下博客:
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 <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
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<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 ;
}
/* 雙邊濾波函數 */
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<float> 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<unsigned char>(x);
unsigned char *pout = output_img.ptr<unsigned char>(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<unsigned char>(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;
}