最近在看淺墨前輩的OpenCV教程來做一次復(fù)習(xí),其中發(fā)現(xiàn)了一個挺有趣的之前沒見過的算法,叫雙邊濾波算法。這個算法可以對圖像進(jìn)行平滑的同時(shí)盡量對高頻信息進(jìn)行保留(比如邊緣和邊角),而相對低頻的區(qū)域則會被平滑。感覺淺墨一書中講的還是有點(diǎn)不直觀,這里給出我自己的一些理解。
直觀理解
雙邊濾波算法本質(zhì)是基于高斯濾波,目的是解決高斯濾波造成的邊緣模糊。那么算法的做法就是想辦法去“推斷”出當(dāng)前像素是否是邊緣點(diǎn)或者接近邊緣的點(diǎn)。
我們都知道,對圖像進(jìn)行空間域?yàn)V波的方法是使用一個結(jié)構(gòu)元素(核)來對原圖像進(jìn)行卷積。比如說高斯核像是這樣的:

而這個結(jié)構(gòu)元素就會對原圖像進(jìn)行卷積操作,從而得到一個新的圖像,即輸出圖像。我們知道,這個結(jié)構(gòu)元素是不會變的。但是!但是!但是!在雙邊濾波算法中就不是如此了。
為了使圖像的邊緣得到保留,就要根據(jù)當(dāng)前被卷積像素的鄰域進(jìn)行觀察,“推斷”是否是邊緣點(diǎn)和接近邊緣的點(diǎn)。因此,結(jié)構(gòu)元素就會改變,從而保留邊緣點(diǎn)。
下面的一組圖中,圖a是原圖像,圖c是輸出。而中間的圖像是什么呢?顯然,這是原圖中根據(jù)某個點(diǎn)的鄰域生成的,專屬于這個點(diǎn)的結(jié)構(gòu)元素!

可以看到,原圖中顯然有一個灰度的突變,這就表示是邊緣?;叶戎蹈叩牡胤讲粦?yīng)該和灰度低的區(qū)域進(jìn)行混合,所以,圖像中接近邊緣的一個點(diǎn)就會生成圖b這樣的結(jié)構(gòu)元素。那么這個接近邊緣的點(diǎn)在哪里呢?大概就在我標(biāo)出的這個區(qū)域。

而生成這樣的結(jié)構(gòu)元素的方法,是將我們原本的高斯核,與一個能“推斷”出是否在邊緣點(diǎn)的結(jié)構(gòu)元素相乘,如下圖中間的結(jié)構(gòu)元素

數(shù)學(xué)定義
雙邊濾波器的輸出像素依賴于當(dāng)前被卷積像素的鄰域。i和j是當(dāng)前被卷積像素的坐標(biāo)點(diǎn),k和l是鄰域像素的坐標(biāo)點(diǎn)。

加權(quán)系數(shù)ω由定義域核和值域核決定,是它們的乘積。定義域核就是高斯核,不解釋

而值域核就是用于“推斷”是否是邊緣點(diǎn)的方法,公式如下

可以看到,它取決于被卷積像素的灰度值和鄰域像素的灰度值的差。我們知道,邊緣會有較大的灰度變化,而這個公式就會使邊緣和邊緣另一邊的區(qū)域生成比較小的權(quán)值,與被卷積像素的灰度值類似的區(qū)域會生成比較大的權(quán)值,就像之前圖中的一個“斷崖”。

相乘就得到加權(quán)系數(shù)ω

編程實(shí)現(xiàn)
這里使用滑動條來觀察一下參數(shù)對輸出的變化。
/********************************************************************
* 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 華南農(nóng)業(yè)大學(xué)
********************************************************************/
#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("坐標(biāo)空間方差","output", &g_sigmaSpace, 100, on_Trackbar);
imshow("input", image1);
imshow("output", image2);
waitKey();
return 0;
}


明顯,粗糙的皮膚得到了平滑。這個算法對皮膚不好的妹子有奇效→_→!
References:
Bilateral Filtering for Gray and Color Images
OpenCV學(xué)習(xí)筆記(七)中值、雙邊濾波