自用备忘,若侵则删。
利用二维高斯函数生成空间域核,一维高斯函数生成颜色域核。
- 空间域核:
其中, ( k , l ) (k,l) (k,l)为核中心坐标, ( i , j ) (i,j) (i,j)为核内邻域坐标。 σ d \sigma_d σd为高斯函数的标准差,我个人感性上认为就是 σ \sigma σ小的时候,高斯函数瘦瘦高高的,反之,矮矮胖胖的。
- 色彩域核(下式仅为灰度值的表示):
f ( i , j ) f(i,j) f(i,j)代表图像在 ( i , j ) (i,j) (i,j)处的灰度值,其他标识与空间域一致。
- 双边滤波器模板: 空间域核 色彩域核
- 思考一下,为什么可以保边?
虽然说在边缘处,空间域的权重很大,但是色彩变化大,这就导致了色彩域的权重很小,空间域的大权重优势被色彩域的小权重拉了下来,因此边界邻域的像素作用不大,保住了边缘自身。而对于平坦区域来说,色彩域的作用不大,几乎纯靠空间域核来撑,也就近似于普通的高斯滤波了。
/*
双边滤波器思想:
1. 在高斯滤波器基础上增加了图像像素之间的相似度的考虑,进而达到保边的效果。
双边滤波器的实现:
1. 分别求空间域以及颜色域的权重系数
2. 根据双边公式,移动窗口,处理整张影像
*/
#include "opencv2/opencv.hpp"
#include"opencv2/highgui/highgui.hpp"
#include
using namespace cv;
using namespace std;
const int kernel_size = 7;
const int sigma_distance = 3;
const int sigma_color = 20;
#define DEBUG 0
// 空间域权重确定
// @ distance_kernel 空间域核,1D
void GetDistanceWeight(double* distance_kernel)
{
int k = kernel_size / 2;
double distance_kernel_2D[kernel_size][kernel_size];
double delta_square = 2* sigma_distance * sigma_distance; //分母
for (int i = -k; i <= k; i++) {
for (int j = -k; j <= k; j++) {
double distance_numerator = i * i + j * j;
distance_kernel_2D[i + k][j + k] = exp(-1.0 * distance_numerator / delta_square);
#ifdef DEBUG
{
cout << i+k << " " <<j+k <<" "<< distance_kernel_2D[i + k][j + k] << endl;
}
#endif
}
}
// 将2D kernel 转换为 1D kernel
for (int i = 0; i < kernel_size; i++) {
for (int j = 0; j < kernel_size; j++) {
distance_kernel[kernel_size * i + j] = distance_kernel_2D[i][j];
#ifdef DEBUG
{
cout << kernel_size * i + j << " " << distance_kernel[kernel_size * i + j] << endl;
}
#endif
}
}
}
// 颜色域权重确定
// @ color_kernel 颜色域核,1D,长度为256
void GetColorWeight(double* color_kernel) {
for (int i = 0; i < 256; i++) {
color_kernel[i] = exp(-1.0 * (i * i) / (2 * sigma_color * sigma_color));
#ifdef DEBUG
cout<< i <<" "<< color_kernel[i] << endl;
#endif // DEBUG
}
}
// 双边滤波
// @ src 待滤波的影像
// @ distance_kernel 空间域核
// @ color_kernel 颜色域核
// @ dst 输出的影像
void BilateralFilter(Mat& src, double* distance_kernel, double* color_kernel, Mat& dst) {
dst = src.clone();
int n_rows = dst.rows;
int n_cols = dst.cols;
int n_channels = dst.channels();
int n_cols_with_channels = n_cols * n_channels;
int half_kernel_size = kernel_size / 2;
int index;
double pixel_sum;
double weight_sum = 0;
double temp_bilateral_weight = 0;
// 边界不做处理
for (int i = half_kernel_size; i < (n_rows - half_kernel_size); i++) {
uchar* pt_dst = dst.ptr<uchar>(i);
uchar* pt_src = src.ptr<uchar>(i);
for (int j = n_channels * half_kernel_size; j < (n_cols_with_channels - n_channels * half_kernel_size); j++) {
index = 0;
pixel_sum = weight_sum = 0;
// 内层kx,ky循环,空间域内滤波
for (int kx = i - half_kernel_size; kx <= i + half_kernel_size; kx++) {
uchar* pt_k_src = src.ptr<uchar>(kx);
for (int ky = j - n_channels * half_kernel_size; ky <= (j + n_channels * half_kernel_size); ky += n_channels) {
temp_bilateral_weight = distance_kernel[index++] * color_kernel[(int)abs(pt_src[j] - pt_k_src[ky])];
weight_sum += temp_bilateral_weight;
pixel_sum += (pt_k_src[ky] * temp_bilateral_weight); // 邻域某像素与中心点的双边权重乘积
}
}
pixel_sum /= weight_sum; // 归一化
pt_dst[j] = saturate_cast<uchar>(pixel_sum); //加权赋值
}
}
}
int main() {
Mat src, dst, dst_cv;
src = imread("test.png");
if (!src.data) {
cout << "loading failed" << endl;
system("pause");
return -1;
}
double distance_kernel[kernel_size * kernel_size];
double color_kernel[256];
GetDistanceWeight(distance_kernel);
GetColorWeight(color_kernel);
// 测试自己实现的双边滤波函数
BilateralFilter(src, distance_kernel, color_kernel, dst);
namedWindow("src");
imshow("src", src);
namedWindow("my bf");
imshow("my bf", dst);
// 测试opencv中的bilateralFilter, 观察与自实现的函数的异同
bilateralFilter(src, dst_cv, 7, 50, 3);
namedWindow("cv bf");
imshow("cv bf", dst_cv);
waitKey(0);
return 0;
}
高斯函数的 σ \sigma σ设置越小则越窄。
σ \sigma σ太小的时候平滑效果不明显。
联合双边滤波相对于双边滤波来说,最大的特点就是引入了一幅引导影像。
具体可使用以下数学表达:
J p = 1 k p ∑ q ∈ Ω I q f ( ∥ p − q ∥ ) g ( ∥ I ~ p − I ~ q ∥ ) J_{p}=\frac{1}{k_{p}} \sum_{q \in \Omega} I_{q} f(\|p-q\|) g\left(\left\|\tilde{I}_{p}-\tilde{I}_{q}\right\|\right) Jp=kp1q∈Ω∑Iqf(∥p−q∥)g(∥∥∥I~p−I~q∥∥∥)
其中 I ~ p \tilde I_{p} I~p以及 I ~ q \tilde I_{q} I~q就是引导影像上的像素灰度值。
在opencv的contrib模块中,也提供了联合双边滤波的API:jointBilateralFilter(引导图,待滤波的图,滤波后的图,像素邻域直径,灰度域sigma,空间域sigma)。
简单的代码实现思路可以为将双边滤波中灰度域权重的计算过程进行修改,取引导影像上的灰度而非待处理影像的灰度,在此不赘述。至于opencv中API调用的代码则为:
#include
#include
int main()
{
cv::Mat src = cv::imread("data/dp.png", 1); // 原始带噪声的深度图
cv::Mat joint = cv::imread("data/teddy.png", 0);
cv::Mat dst;
cv::ximgproc::jointBilateralFilter(joint, src, dst, -1, 3, 9);
imshow("src", src);
imshow("joint", joint);
imshow("jointBilateralFilter", dst);
cv::waitKey(0);
return 0;
}
https://blog.csdn.net/panda1234lee/article/details/52839205