詳解——導向濾波(Guided Filter)和快速導向濾波

導讀

在圖像濾波算法中,導向濾波、雙邊濾波、最小二乘濾波並稱三大保邊濾波器,他們是各向異性濾波器。相對於常見的均值濾波、高斯濾波等各向同性濾波器,他們最大的特點是在去除噪聲的同時,能最大限度保持邊緣不被平滑。本文講解導向濾波及其應用。
總的來講,導向濾波就是儘可能讓輸出圖像的梯度和導向圖相似,同時讓輸出圖像的灰度(亮度)與輸入圖像相似,以此來保留邊緣並且濾除噪聲。


原理推導

我們先看下圖:
在這裏插入圖片描述
輸入圖像pp,經過引導圖像II, 濾波得到輸出圖像qq, 導向濾波算法中有一個重要假設:即在局部窗口wkw_k上,導向圖II和輸出圖qq存在局部線性關係:
qi=akIi+bk,iwk(1)q_i = a_kI_i + b_k, \forall{i\in{w_k}}\tag{1}
同時在窗口wkw_k上, 濾波後的圖像qq和輸入圖像pp有如下關係:
qi=piniiwk(2)q_i = p_i - n_i,\forall{i\in{w_k}}\tag{2}
這樣公式(1)(1)的線性關係保證瞭如果在每個局部窗口wkw_k中,如果導向圖II中存在一個邊緣,輸出圖像qq將保持邊緣不變。同時,濾波結果圖qq要儘可能與輸入圖像pp相同以此減小濾波帶來的信息損失,該算法的最小二乘表示即:
argminiwk(qipi)2=argminiwk(akIi+bkpi)2argmin\sum_{i\in{w_k}}(q_i-p_i)^2 = argmin\sum_{i\in{w_k}}(a_kI_i+b_k-p_i)^2
這是求解最優值的問題,引入一個正則化參數ϵ\epsilon防止aka_k過大,得到損失函數:
E(ak,bk)=iwk((akIi+bkpi)2+ϵak2)(3) E(a_k,b_k)=\sum_{i\in{w_k}}((a_kI_i+b_k-p_i)^2+\epsilon{a_k^2})\tag{3}
運用最小二乘法求解極小值,利用極小值處導數爲0,求解過程如下:
δEak=iwk(2(akIi+bkpi)Ii+2ϵak)=0iwk(akIi2+bkIipiIi+ϵak)=0δEbk=iwk2(akIi+bkpi)=0akiwkIiiwkpi+iwkbk=0wbk=iwkpiakiwkIi \begin{aligned} \frac{\delta{E}}{a_k} &= \sum_{i\in{w_k}}(2(a_kI_i+b_k-p_i)I_i+2\epsilon{a_k}) = 0 \\ & \Rightarrow \sum_{i\in{w_k}}(a_kI_i^2+b_kI_i-p_iI_i+\epsilon{a_k}) = 0 \\ \frac{\delta{E}}{b_k} & = \sum_{i\in{w_k}}2(a_kI_i+b_k-p_i)= 0 \\ & \Rightarrow a_k\sum_{i\in{w_k}}{I_i}-\sum_{i\in{w_k}}{p_i}+\sum_{i\in{w_k}}{b_k} = 0 \\ & \Rightarrow |w|b_k = \sum_{i\in{w_k}}{p_i}-a_k\sum_{i\in{w_k}}{I_i} \end{aligned}
由上面推到得出:
ak=iwkpiIibkiwkIiiwk(Ii2+ϵ)bk=iwkpiakiwkIiw=pkakμk(4) \begin{aligned} a_k &= \frac{\sum_{i\in{w_k}}p_iI_i-b_k\sum_{i\in{w_k}}I_i}{\sum_{i\in{w_k}}(I_i^2+\epsilon)} \\ b_k &= \frac{\sum_{i\in{w_k}}{p_i}-a_k\sum_{i\in{w_k}}{I_i}}{|w|} \\ & = \overline{p}_k - a_k\mu_k \tag{4} \end{aligned}
其中:μkwkIpkwkp\begin{aligned} & \mu_k——窗口w_k範圍內引導圖I的均值;\\ & \overline{p}_k——窗口w_k範圍內輸入圖p的均值。 \end{aligned}
bkb_k代入aka_k計算可得:
ak=iwkpiIibkiwkIiiwk(Ii2+ϵ)a_k = \frac{\sum_{i\in{w_k}}p_iI_i-b_k\sum_{i\in{w_k}}I_i}{\sum_{i\in{w_k}}(I_i^2+\epsilon)}
ak=iwk(piIipkIi)iwk(Ii2+μkIi+ϵ)\Rightarrow a_k = \frac{\sum_{i\in{w_k}}(p_iI_i-\overline{p}_kI_i)}{\sum_{i\in{w_k}}(I_i^2+\mu_kI_i+\epsilon)}
ak=iwk(piIipkIi+μkpiμkpk)iwk(Ii2μkIiμkIi+μk2+ϵ)\Rightarrow a_k = \frac{\sum_{i\in{w_k}}(p_iI_i-\overline{p}_kI_i {\color{red}{+\mu_kp_i-\mu_k\overline{p}_k}})}{\sum_{i\in{w_k}}(I_i^2-\mu_kI_i{\color{red}{-\mu_kI_i+\mu_k^2}}+\epsilon)}
上下兩邊同除以w|w|,w|w|爲窗口wkw_k像素數量。得到:
ak=1wiwkpiIipkμk+μkpkμkpkiwk(Iiμk)2+ϵa_k = \frac{\frac{1}{|w|}\sum_{i\in{w_k}}p_iI_i-\overline{p}_k\mu_k{\color{red}{+\mu_k\overline{p}_k-\mu_k\overline{p}_k}}}{\sum_{i\in{w_k}}(I_i-\mu_k)^2+\epsilon}
最後:
ak=1wiwkpiIiμkpkσk2+ϵ(5)\color{red}{a_k = \frac{\frac{1}{|w|}\sum_{i\in{w_k}}p_iI_i{ - \mu_k\overline{p}_k}}{\sigma_k^2+\epsilon}} \tag{5}
bk=pk+akμk(6)\color{red}{b_k = \overline{p}_k+a_k\mu_k} \tag{6}
得到上述公式後,可以對每個窗口wkw_k計算一個(ak,bk)(a_k,b_k),但是,每個像素都被包含在多個窗口中,對每個像素,都能計算出多個(ak,bk)(a_k,b_k),我麼將使用多個(ak,bk)(a_k,b_k)計算得到的qiq_i值求平均得到輸出qiq_i值,上述過程描述如下:
qi=1wkk,iwk(akIi+bk)=aiIi+bi \begin{aligned} q_i &= \frac{1}{w_k}\sum_{k,i\in{w_k}}(a_kI_i+b_k) \\ & = \overline{a}_iI_i+\overline{b}_i \end{aligned}
其中:aiwkakbiwkbk\begin{aligned} & \overline{a}_i——窗口w_k範圍內所有像素計算得到的a_k的均值;\\ & \overline{b}_i——窗口w_k範圍內所有像素計算得到的b_k的均值。 \end{aligned}

導向濾波的應用

  • 保邊濾波
    I=pI=p時,該算法成爲一個保邊濾波器。上述(ak,bk)(a_k,b_k)計算公式變化爲:
    ak=σk2σk2+ϵbk=(1ak)pka_k = \frac{\sigma_k^2}{\sigma_k^2+\epsilon} \\ b_k = (1-a_k)\overline{p}_k
    考慮以下兩種情況:
    • Case 1:平坦區域。如果在某個濾波窗口內,該區域是相對平滑的,方差σk2\sigma_k^2將遠遠小於ϵ\epsilon。從而ak0,bkpka_k≈0,b_k≈\overline{p}_k。相當於對該區域作均值濾波。
    • Case 2:高方差區域。相反,如果該區域是邊緣區域,方差很大,σk2\sigma_k^2將遠遠大於ϵ\epsilon。從而ak1,bk0a_k≈1,b_k≈0。相當於在區域保持原有梯度。
      以上可以出:ϵ\epsilon爲界定平滑區域和邊緣區域的閾值。
  • 圖像去霧
    在圖像去霧中,導向濾波一般用來細化透射率圖像,以原圖的灰度圖爲導向圖,以粗投射率圖爲輸入圖,能得到非常精細的透射率圖像。

當然,導向濾波的應用不止以上兩種,網上還有圖像融合等應用,本人沒有去了解。

導向濾波的實現

導向濾波的代碼實現較爲簡單,我們直接貼出計算流程在這裏插入圖片描述

快速導向濾波的實現

由於導向濾波效率問題,何凱明博士在2015年,對其做了優化,基本原理是將導向圖,輸入圖都進行下采樣計算(ak,bk)(a_k,b_k),然後對(ak,bk)(a_k,b_k)進行上採樣恢復原始大小,整個算法流程如下:在這裏插入圖片描述

算法效果

我們演示一下保邊濾波效果:在這裏插入圖片描述

代碼

接下來,廢話不多說,我們上代碼:https://github.com/EthanAndEvan/ImageAlgorithmDraft,爲防止github無法訪問,我們直接貼上代碼:

  • 導向濾波
#include <opencv2/opencv.hpp>
//導向濾波
void GuidedFilter(cv::Mat& srcImage, cv::Mat& guidedImage, cv::Mat& outputImage, int filterSize, double eps)
{
	try
	{
		if (srcImage.empty() || guidedImage.empty() || filterSize <= 0 || eps < 0 ||
			srcImage.channels() != 1 || guidedImage.channels() != 1)
		{
			throw "params input error";
		}
		cv::Mat srcImageP, srcImageI, meanP, meanI, meanIP, meanII, varII, alfa, beta;
		srcImage.convertTo(srcImageP, CV_32FC1);
		guidedImage.convertTo(srcImageI, CV_32FC1);
		cv::boxFilter(srcImageP, meanP, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(srcImageI, meanI, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(srcImageI.mul(srcImageP), meanIP, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(srcImageI.mul(srcImageI), meanII, CV_32FC1, cv::Size(filterSize, filterSize));
		varII = meanII - meanI.mul(meanI); 
		alfa = (meanIP - meanI.mul(meanP)) / (varII + eps);
		beta = meanP - alfa.mul(meanI);
		cv::boxFilter(alfa, alfa, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(beta, beta, CV_32FC1, cv::Size(filterSize, filterSize));
		outputImage = (alfa.mul(srcImageI) + beta);
	}
	catch (cv::Exception& e)
	{
		throw e;
	}
	catch (std::exception& e)
	{
		throw e;
	}
}
  • 快速導向濾波
#include <opencv2/opencv.hpp>
//快速導向濾波
void FastGuidedFilter(cv::Mat& srcImage, cv::Mat& guidedImage, cv::Mat& outputImage, int filterSize, double eps, int samplingRate)
{
	try
	{
		if (srcImage.empty() || guidedImage.empty() || filterSize <= 0 || eps < 0 ||
			srcImage.channels() != 1 || guidedImage.channels() != 1 || samplingRate < 1)
		{
			throw "params input error";
		}
		cv::Mat srcImageP, srcImageSubI, srcImageI, meanP, meanI, meanIP, meanII, var, alfa, beta;
		
		cv::resize(srcImage, srcImageP, cv::Size(srcImage.cols / samplingRate, srcImage.rows / samplingRate));
		cv::resize(guidedImage, srcImageSubI, cv::Size(srcImage.cols / samplingRate, srcImage.rows / samplingRate));

		filterSize = filterSize / samplingRate;

		srcImageP.convertTo(srcImageP, CV_32FC1);
		guidedImage.convertTo(srcImageI, CV_32FC1);
		srcImageSubI.convertTo(srcImageSubI, CV_32FC1);
		cv::boxFilter(srcImageP, meanP, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(srcImageSubI, meanI, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(srcImageSubI.mul(srcImageP), meanIP, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(srcImageSubI.mul(srcImageSubI), meanII, CV_32FC1, cv::Size(filterSize, filterSize));
		var = meanII - meanI.mul(meanI);
		alfa = (meanIP - meanI.mul(meanP)) / (var + eps);
		beta = meanP - alfa.mul(meanI);
		cv::boxFilter(alfa, alfa, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::boxFilter(beta, beta, CV_32FC1, cv::Size(filterSize, filterSize));
		cv::resize(alfa, alfa, cv::Size(srcImage.cols, srcImage.rows));
		cv::resize(beta, beta, cv::Size(srcImage.cols, srcImage.rows));
		outputImage = alfa.mul(srcImageI) + beta;
	}
	catch (cv::Exception& e)
	{
		throw e;
	}
	catch (std::exception& e)
	{
		throw e;
	}
}

參考

[1] 視覺一隻白 .《導向濾波的原理及實現》[DB/OL].
[2] lsflll.《導向濾波(Guided Filter)公式詳解》[DB/OL]
[3] SongpingWang.《OpenCV—Python 導向濾波》[DB/OL]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章