Bilateral Filtering(雙邊濾波) for SSAO
1. 簡介
影象平滑是一個重要的操作,而且有多種成熟的演算法。這裡主要簡單介紹一下Bilateral方法(雙邊濾波),這主要是由於前段時間做了SSAO,需要用bilateral blur 演算法進行降噪。Bilateral blur相對於傳統的高斯blur來說很重要的一個特性即可可以保持邊緣(Edge Perseving),這個特點對於一些影象模糊來說很有用。一般的高斯模糊在進行取樣時主要考慮了畫素間的空間距離關係,但是卻並沒有考慮畫素值之間的相似程度,因此這樣我們得到的模糊結果通常是整張圖片一團模糊。Bilateral blur的改進就在於在取樣時不僅考慮畫素在空間距離上的關係,同時加入了畫素間的相似程度考慮,因而可以保持原始影象的大體分塊進而保持邊緣。在於遊戲引擎的post blur演算法中,bilateral blur常常被用到,比如對SSAO的降噪。
2. 原理
濾波演算法中,目標點上的畫素值通常是由其所在位置上的周圍的一個小區域性鄰居畫素的值所決定。在2D高斯濾波中的具體實現就是對周圍的一定範圍內的畫素值分別賦以不同的高斯權重值,並在加權平均後得到當前點的最終結果。而這裡的高斯權重因子是利用兩個畫素之間的空間距離(在影象中為2D)關係來生成。通過高斯分佈的曲線可以發現,離目標畫素越近的點對最終結果的貢獻越大,反之則越小。其公式化的描述一般如下所述:
其中的c即為基於空間距離的高斯權重,而用來對結果進行單位化。
高斯濾波在低通濾波演算法中有不錯的表現,但是其卻有另外一個問題,那就是隻考慮了畫素間的空間位置上的關係,因此濾波的結果會丟失邊緣的資訊。這裡的邊緣主要是指影象中主要的不同顏色區域(比如藍色的天空,黑色的頭髮等),而Bilateral就是在Gaussian blur中加入了另外的一個權重分部來解決這一問題。Bilateral濾波中對於邊緣的保持通過下述表示式來實現:
其中的s為基於畫素間相似程度的高斯權重,同樣用來對結果進行單位化。對兩者進行結合即可以得到基於空間距離、相似程度綜合考量的Bilateral濾波:
上式中的單位化分部綜合了兩種高斯權重於一起而得到,其中的c與s計算可以詳細描述如下:
且有
且有
上述給出的表示式均是在空間上的無限積分,而在畫素化的影象中當然無法這麼做,而且也沒必要如此做,因而在使用前需要對其進行離散化。而且也不需要對於每個區域性畫素從整張影象上進行加權操作,距離超過一定程度的畫素實際上對當前的目標畫素影響很小,可以忽略的。限定區域性子區域後的離散化公就可以簡化為如下形式:
上述理論公式就構成了Bilateral濾波實現的基礎。為了直觀地瞭解高斯濾波與雙邊濾波的區別,我們可以從下列圖示中看出依據。假設目標源影象為下述左右區域分明的帶有噪聲的影象(由程式自動生成),藍色框的中心即為目標畫素所在的位置,那麼當前畫素處所對應的高斯權重與雙邊權重因子3D視覺化後的形狀如後邊兩圖所示:
左圖為原始的噪聲影象;中間為高斯取樣的權重;右圖為Bilateral取樣的權重。從圖中可以看出Bilateral加入了相似程度分部以後可以將源影象左側那些跟當前畫素差值過大的點給濾去,這樣就很好地保持了邊緣。為了更加形象地觀察兩者間的區別,使用Matlab將該圖在兩種不同方式下的高度圖3D繪製出來,如下:
上述三圖從左到右依次為:雙邊濾波,原始影象,高斯濾波。從高度圖中可以明顯看出Bilateral和Gaussian兩種方法的區別,前者較好地保持了邊緣處的梯度,而在高斯濾波中,由於其在邊緣處的變化是線性的,因而就使用連累的梯度呈現出漸變的狀態,而這表現在影象中的話就是邊界的丟失(影象的示例可見於後述)。
3. 程式碼實現
有了上述理論以後實現Bilateral Filter就比較簡單了,其實它也與普通的Gaussian Blur沒有太大的區別。這裡主要包括3部分的操作: 基於空間距離的權重因子生成;基於相似度的權重因子的生成;最終filter顏色的計算。
3.1 Spatial Weight
這就是通常的Gaussian Blur中使用的計算高斯權重的方法,其主要通過兩個pixel之間的距離並使用如下公式計算而來:
其中的就表示兩個畫素間的距離,比如當前畫素與其右邊緊鄰的一個畫素之間的距離我們就可以用來計算,也即兩個二維向量{0 , 0}以及{0 , 1}之間的歐氏距離。直接計算一個區域上的高斯權重並單位化後就可以進行高斯模糊了。
3.2 Similarity Weight
與基於距離的高斯權重計算類似,只不過此處不再根據兩個pixel之間的空間距離,而是根據其相似程度(或者兩個pixel的值之間的距離)。
其中的表示兩個畫素值之間的距離,可以直接使用其灰度值之間的差值或者RGB向量之間的歐氏距離。
3.3 Color Filtering
有了上述兩部分所必需的權重因子之後,那麼具體的雙邊濾波的實現即與普通的高斯濾波無異。主要部分程式碼如下述:
UCHAR3 BBColor(int posX , int posY)
{
int centerItemIndex = posY * picWidth4 + posX * 3 , neighbourItemIndex;
int weightIndex;
double gsAccumWeight = 0;
double accumColor = 0;
// 計算各個取樣點處的Gaussian權重,包括closeness,similarity
for(int i = -number ; i <= number ; ++i)
{
for(int j = -number ; j <= number ; ++j)
{
weightIndex = (i + number) * (number * 2 + 1) + (j + number);
neighbourItemIndex = min(noiseImageHeight - 1 , max(0 , posY + j * radius)) * picWidth4 +
min(noiseImageWidth - 1 , max(0 , posX + i * radius)) * 3;
pCSWeight[weightIndex] = LookupGSWeightTable(pSrcDataBuffer[neighbourItemIndex] , pSrcDataBuffer[centerItemIndex]);
pCSWeight[weightIndex] = pGSWeight[weightIndex] * pGCWeight[weightIndex];
gsAccumWeight += pCSWeight[weightIndex];
}
}
// 單位化權重因子
gsAccumWeight = 1 / gsAccumWeight;
for(int i = -number ; i <= number ; ++i)
{
for(int j = -number ; j <= number ; ++j)
{
weightIndex = (i + number) * (number * 2 + 1) + (j + number);
pCSWeight[weightIndex] *= gsAccumWeight;
}
}
// 計算最終的顏色並返回
for(int i = -number ; i <= number ; ++i)
{
for(int j = -number ; j <= number ; ++j)
{
weightIndex = (i + number) * (number * 2 + 1) + (j + number);
neighbourItemIndex = min(noiseImageHeight - 1 , max(0 , posY + j * radius)) * picWidth4 +
min(noiseImageWidth - 1 , max(0 , posX + i * radius)) * 3;
accumColor += pSrcDataBuffer[neighbourItemIndex + 0] * pCSWeight[weightIndex];
}
}
return UCHAR3(accumColor , accumColor , accumColor);
}
其中的相似度分部的權重s主要根據兩個Pixel之間的顏色差值計算面來。對於灰度圖而言,這個差值的範圍是可以預知的,即[-255, 255],因而為了提高計算的效率我們可以將該部分權重因子預計算生成並存表,在使用時快速查詢即可。使用上述實現的演算法對幾張帶有噪聲的影象進行濾波後的結果如下所示:
上圖從左到右分別為:雙邊濾波;原始影象;高斯濾波。從圖片中可以較為明顯地看出兩種演算法的區別,最直觀的感受差別就是使用高斯演算法後整張圖片都是一團模糊的狀態;而雙邊濾波則可以較好地保持原始影象中的區域資訊,看起來仍然嘴是嘴、眼是眼(特別是在第一張美女影象上的效果!看來PS是灰常重要啊~~^o^)。
4. 在SSAO中的使用
在上述實現中的邊緣判定函式主要是通過兩個畫素值之間的差異來決定,這也是我們觀察普通圖片的一種普遍感知方式。當然,也可以根據使用的需求情況來使用其它的方式判斷其它定義下的邊緣,比如使用場景的depth或是normal。比如在對SSAO進行濾波時可以直接使用Depth值來行邊緣判斷。首先,設定一個深度的閾值,在作邊緣檢測時比較兩點間的depth差值,如果差值大於閾值,則認為其屬於不同的區域,則此處就應為邊界。使用此方法得到的效果可見於下圖所示:
高斯濾波
雙邊濾波
在得到濾波之後的SSAO影象之後,與原始影象進行直接的整合就可以得到最終的渲染效果,如下圖所示:
SSAO關閉
SSAO開啟
後記: 嶄新的2012年自己以一篇博文開始,感覺也不錯,加油~!~!