最近在看浅墨前辈的OpenCV教程来做一次复习,其中发现了一个挺有趣的之前没见过的算法,叫双边滤波算法。这个算法可以对图像进行平滑的同时尽量对高频信息进行保留(比如边缘和边角),而相对低频的区域则会被平滑。感觉浅墨一书中讲的还是有点不直观,这里给出我自己的一些理解。
直观理解
双边滤波算法本质是基于高斯滤波,目的是解决高斯滤波造成的边缘模糊。那么算法的做法就是想办法去“推断”出当前像素是否是边缘点或者接近边缘的点。
我们都知道,对图像进行空间域滤波的方法是使用一个结构元素(核)来对原图像进行卷积。比如说高斯核像是这样的:
而这个结构元素就会对原图像进行卷积操作,从而得到一个新的图像,即输出图像。我们知道,这个结构元素是不会变的。但是!但是!但是!在双边滤波算法中就不是如此了。
为了使图像的边缘得到保留,就要根据当前被卷积像素的邻域进行观察,“推断”是否是边缘点和接近边缘的点。因此,结构元素就会改变,从而保留边缘点。
下面的一组图中,图a是原图像,图c是输出。而中间的图像是什么呢?显然,这是原图中根据某个点的邻域生成的,专属于这个点的结构元素!
可以看到,原图中显然有一个灰度的突变,这就表示是边缘。灰度值高的地方不应该和灰度低的区域进行混合,所以,图像中接近边缘的一个点就会生成图b这样的结构元素。那么这个接近边缘的点在哪里呢?大概就在我标出的这个区域。
而生成这样的结构元素的方法,是将我们原本的高斯核,与一个能“推断”出是否在边缘点的结构元素相乘,如下图中间的结构元素
数学定义
双边滤波器的输出像素依赖于当前被卷积像素的邻域。i和j是当前被卷积像素的坐标点,k和l是邻域像素的坐标点。
加权系数ω由定义域核和值域核决定,是它们的乘积。定义域核就是高斯核,不解释
而值域核就是用于“推断”是否是边缘点的方法,公式如下
可以看到,它取决于被卷积像素的灰度值和邻域像素的灰度值的差。我们知道,边缘会有较大的灰度变化,而这个公式就会使边缘和边缘另一边的区域生成比较小的权值,与被卷积像素的灰度值类似的区域会生成比较大的权值,就像之前图中的一个“断崖”。
相乘就得到加权系数ω
编程实现
这里使用滑动条来观察一下参数对输出的变化。
/********************************************************************
* Created by 杨帮杰 on 1/25/2019
* Right to use this code in any way you want without
* warranty, support or any guarantee of it working
* E-mail: yangbangjie1998@qq.com
* Association: SCAU 华南农业大学
********************************************************************/
#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#define IMG1 "/home/jacob/图片/77.jpg"
using namespace cv;
using namespace std;
int g_d = 15;
int g_sigmaColor = 20;
int g_sigmaSpace = 50;
Mat image1;
Mat image2;
void on_Trackbar(int, void*)
{
bilateralFilter(image1, image2, g_d, g_sigmaColor, g_sigmaSpace);
imshow("output", image2);
}
int main()
{
image1= imread(IMG1);
//resize(image1,image1,Size(image1.cols/2, image1.rows/2));
if (!image1.data)
{
cout << "img1 没读到" <<endl;
return 0;
}
image2 = Mat::zeros(image1.rows, image1.cols, image1.type());
bilateralFilter(image1, image2, g_d, g_sigmaColor, g_sigmaSpace);
namedWindow("output");
createTrackbar("核直径","output", &g_d, 50, on_Trackbar);
createTrackbar("颜色空间方差","output", &g_sigmaColor, 100, on_Trackbar);
createTrackbar("坐标空间方差","output", &g_sigmaSpace, 100, on_Trackbar);
imshow("input", image1);
imshow("output", image2);
waitKey();
return 0;
}
明显,粗糙的皮肤得到了平滑。这个算法对皮肤不好的妹子有奇效→_→!
References:
Bilateral Filtering for Gray and Color Images
OpenCV学习笔记(七)中值、双边滤波