OpenCV实现双边滤波

双边滤波的数学原理在其他博客中很容易找到,再此不在赘述。仅仅记录一下自己对这个滤波器功能的一些总结。

个人觉得这个滤波器十分优美和简洁,其特点:

  • 双边滤波同时考虑了空间(像素距离)和像素值层面的因素。
  • 双边滤波某一点的权重取值 w ( i , j , k , l ) = w d ( i , j , k , l ) × w r ( i , j , k , l ) w(i,j,k,l)=w_d(i,j,k,l)\times w_r(i,j,k,l) w(i,j,k,l)=wd(i,j,k,l)×wr(i,j,k,l)
  • 空间层面等价于高斯滤波,核中权重是由两点间距离来计算,离核中心越远,权重越小。
  • 像素值层面的权重,由两点像素相似程度来计算,两点越相似,权重越大。
  • 在平坦的区域(低频),像素值相差较小, w r w_r wr接近于1,总权重 w w w由空间域 w d w_d wd起主导作用,相当于对平坦区域进行高斯滤波。
  • 在边缘区域(高频),像素值相差较大, w r w_r wr接近于0,总权重 w w w也接近于0,很有效的保留了图像的边缘信息。
  • 具有高斯滤波的去噪性能,更有强于中值滤波的边缘保存能力。

以上总结参考于B站的一位up主,参考链接在文末标出


代码实现如下:

定义权重计算函数: f ( i , j ) f(i,j) f(i,j)表示核中心点, f ( k , l ) f(k,l) f(k,l)表示核中其他待遍历像素

//计算空间当前点空间权重Wd(其实就是高斯核,高斯核与距离挂钩)
double get_space_factor(int i, int j, int k, int l, double sigmaSpace)
{
	double sp1 = std::pow(i * 1.0 - k, 2);
	double sp2 = std::pow(j * 1.0 - l, 2);
	double denom = 2.0 * sigmaSpace * sigmaSpace;

	return std::exp(-(sp1 + sp2) / denom);
}


//计算像素层面权重
double get_color_factor(int fij, int fkl, double sigmaColor)
{
	return std::exp(-std::pow(fij * 1.0 - fkl, 2) / (2.0 * std::pow(sigmaColor, 2)));
}

双边滤波函数简单实现,只针对单通道灰度图像:

//双边滤波
static cv::Mat my_bilateral_filter(const cv::Mat& src, int size, double sigmaColor, double sigmaSpace)
{
	CV_Assert(src.type() == CV_8UC1);
	CV_Assert(size > 0 && size % 2 == 1);

	int ps = size / 2;
	cv::Mat matPadded;
	cv::copyMakeBorder(src, matPadded, ps, ps, ps, ps, cv::BORDER_REFLECT101);

	cv::Mat result(src.size(), CV_8UC1);
	for (int i = 0; i < src.rows; ++i)
	{
		for (int j = 0; j < src.cols; ++j)
		{
			double numerator = 0;						//分子的和
			double denominator = 0;						//分母的和(权重总和)
			double fij = matPadded.ptr<uchar>(i + ps)[j + ps] * 1.0;

			for (int k = i - ps; k <= i + ps; ++k)
			{
				for (int l = j - ps; l <= j + ps; ++l)
				{
					double fkl = matPadded.ptr<uchar>(k + ps)[l + ps] * 1.0;
					double w_ijkl = get_color_factor(fij, fkl, sigmaColor) * get_space_factor(i, j, k, l, sigmaSpace);
					numerator += fkl * w_ijkl;
					denominator += w_ijkl;
				}
			}

			result.ptr<uchar>(i)[j] = cv::saturate_cast<uchar>(numerator / denominator);
		}
	}

	return result;
}

测试和对比:

int main()
{
	cv::Mat mat(16, 16, CV_8UC1);
    //赋随机值
	cv::randu(mat, cv::Scalar::all(0), cv::Scalar::all(255));
	std::cout << "original Mat = \n" << mat << std::endl << std::endl;

    //测试代码,用于对比自定义和OpenCV官方函数的结果
    
	/*cv::Mat matbilateral;
	cv::bilateralFilter(mat, matbilateral, size, sigmaColor, sigmaSpace);
	std::cout << "matbilateral = \n" << cv::format(matbilateral, cv::Formatter::FMT_PYTHON) << std::endl << std::endl;

	cv::Mat my_matbilateral = my_bilateral_filter(mat, size, sigmaColor, sigmaSpace);
	std::cout << "my_matbilateral = \n" << cv::format(my_matbilateral, cv::Formatter::FMT_PYTHON) << std::endl << std::endl;

	cv::Mat bilateralDiff = my_matbilateral - matbilateral;
	std::cout << "bilateralDiff = \n" << cv::format(bilateralDiff, cv::Formatter::FMT_PYTHON) << std::endl;*/


	std::string path = "F:\\NoteImage\\lena.jpg";
	
	cv::Mat src = imread(path, cv::IMREAD_GRAYSCALE);
	if (!src.data) {
		std::cout << "Could not open or find the image" << std::endl;
		return -1;
	}
	
	double sigmaSpace = 20.0, sigmaColor = 20.0;
	int size = 7;

	cv::Mat mat_bilateral;
	cv::bilateralFilter(src, mat_bilateral, size, sigmaColor, sigmaSpace);

	cv::Mat my_bilateral = my_bilateral_filter(src, size, sigmaColor, sigmaSpace);

	cv::Mat mat_gaussian;
	cv::GaussianBlur(src, mat_gaussian, cv::Size(7, 7), 20);

	cv::Mat mat_median;
	cv::medianBlur(src, mat_median, 7);

    cv::imwrite("opencvbil.png", mat_bilateral);
	cv::imwrite("mybil.png", my_bilateral);
	cv::imwrite("opencvGauss.png", mat_gaussian);
	cv::imwrite("opencvmedian.png", mat_median);
	cv::waitKey(0);
	return 0;
}

试验结果如下:

OpenCV双边滤波:
OpenCV实现双边滤波_第1张图片

自定义双边滤波:

OpenCV实现双边滤波_第2张图片

高斯滤波:

OpenCV实现双边滤波_第3张图片
中值滤波:

OpenCV实现双边滤波_第4张图片
源图像:

OpenCV实现双边滤波_第5张图片


参考代码:B站大佬up主

你可能感兴趣的:(OpenCV机器视觉,opencv,计算机视觉,c++)