1. 程式人生 > >引導濾波器(原理及opencv程式碼)

引導濾波器(原理及opencv程式碼)

這篇部落格整理自以下部落格內容:https://blog.csdn.net/sinat_36264666/article/details/77990790引導濾波定義:

即需要引導圖的濾波器,引導圖可以是單獨的影象或者是輸入影象,當引導圖為輸入影象時,引導濾波就成為一個保持邊緣的濾波操作,可以用於影象重建的濾波。

引導濾波與其他方向相比優勢所在:

像高斯濾波等線性濾波演算法所用的核函式相對於待處理的影象是獨立無關的,也就意味著,對任意影象都是採用相同的操作。但是,有時候我們是希望在濾波過程中可以加入引導影象中的資訊的,例如,在上色處理過程中,結果影象的色度通道需要包含跟給定亮度通道一致的連續邊緣。

引導濾波的主要特點及應用

1、引導濾波(導向濾波)是一種影象濾波技術,通過一張引導圖I,對初始影象p(輸入影象)進行濾波處理,使得最後的輸出影象大體上與初始影象P相似,但是紋理部分與引導圖I相似。其典型應用有兩個:保邊影象平滑,摳圖

2、 引導濾波(導向濾波)的目的是,保持雙邊濾波的優勢(有效保持邊緣,非迭代計算),而克服雙邊濾波的缺點(設計一種時間複雜度為 O(1) 的快速濾波器,而且在主要邊緣附近沒有梯度的變形)。

3、引導濾波(導向濾波)不僅能實現雙邊濾波的邊緣平滑,而且在檢測到邊緣附近有很好的表現,可應用在影象增強、HDR壓縮、影象摳圖及影象去霧等場景。

引導濾波的演算法原理

第一步:

假設該引導濾波函式的輸出與輸入在一個二維視窗內滿足線性關係,如下:

其中,q是輸出畫素的值,I是輸入影象的值,i和k是畫素索引,a和b是當視窗中心位於k時該線性函式的係數。(當引導圖為輸入影象時,引導濾波就成為一個保持邊緣的濾波操作,即 I= p)

對上式兩邊取梯度,可以得到:

即當輸入圖I有梯度時,輸出 q 也有類似的梯度,現在可以解釋為什麼引導濾波有邊緣保持特性了。

q 即 p去除噪聲或者紋理之後的影象:

其中ni表示噪聲。

第二步:

求出線性函式的係數,也就是線性迴歸,即希望擬合函式的輸出值 q與真實值 p 之間的差距最小,轉化為最優化問題,也就是讓下式最小:

這裡p只能是待濾波影象,並不像I那樣可以是其它影象。同時,a之前的係數是用於防止求得的a過大,也是調節濾波器濾波效果的重要引數。

通過最小二乘法,可以得到:

其中, 是影象  在視窗 中的平均值, 是待濾波影象p在視窗  中的均值, 是 在視窗 中的方差, 是視窗 中畫素的數量

引數的詳解:

1、如果=0,顯然a=1, b=0是E(a,b)為最小值的解,從上式可以看出,這時的濾波器沒有任何作用,將輸入原封不動的輸出。

2、如果>0,在畫素強度變化小的區域(或單色區域),有a近似於(或等於)0,而b近似於(或等於),即做了一個加權均值濾波;

3、 而在變化大的區域,a近似於1,b近似於0,對影象的濾波效果很弱,有助於保持邊緣。

4、 而的作用就是界定什麼是變化大,什麼是變化小。在視窗大小不變的情況下,隨著的增大,濾波效果越明顯。

第三步:

在計算每個視窗的線性係數時,我們可以發現一個畫素會被多個視窗包含,也就是說,每個畫素都由多個線性函式所描述。因此,如之前所說,要具體求某一點的輸出值時,只需將所有包含該點的線性函式值平均即可,如下:

這裡,是所有包含畫素i的視窗,k是其中心位置

引導濾波的優勢:在濾波效果上,引導濾波和雙邊濾波差不多,在一些細節上,引導濾波較好。引導濾波最大的優勢在於,可以寫出時間複雜度與視窗大小無關的演算法,因此在使用大視窗處理圖片時,其效率更高。

整體實現步驟如下

  • 1.利用boxFilter濾波器完成相關係數引數,其中均值包括引導影象均值、原始待濾波影象均值、互相關均值及自相關均值。
  • 2.根據均值計算相關係數引數,包括自相關方差var,互相關協方差cov。
  • 3.計算視窗線性變換引數係數a、b。
  • 4.根據公式計算引數a、b的均值。
  • 5.利用引數得到引導濾波輸出影象q

 

演算法解釋:

1、

計算的分子部分

  • 分子第一項, Ip是在視窗中的和,再除以視窗中畫素的個數,剛好就是盒式濾波,因此我們可以將輸入的引導影象I和待濾波影象 p 相乘,並對相乘後的影象做box filtering,即得第一項的結果。
  • 分子第二項 ,分別為I 和 p 在視窗中均值,因此分別對I和p進行box filtering,再將box filtering之後的結果相乘即可。

實際上,的分子就是Ip在視窗中的協方差。

2、是引導圖I在視窗中的方差,方差和期望(均值)之間是有關係的,如下式:     
  • 因此在計算I的方差時,我們可以先計算|w|的均值,再減去I均值的平方即的平方。在方法上,計算的|w|均值和計算Ip的均值是一樣的。
  • 最後,對計算出來的方差影象,加上常量(每個元素都加),分母就計算完了,自然,在所有視窗中的值也就得到了。

3、的計算類似:

  • 第一項:為 p 在視窗中均值,因此對 p 進行box filtering。
  • 第二項:為I在視窗中均值,因此對進行box filtering,並與相乘。

第一項減去第二項即為的值。

4、第二個公式,即我們最後要得到的濾波後圖像 q :

其中,輸出值q又與兩個均值有關,分別為a和b在視窗中的均值(不是),所以還是box filtering,我們將上一步得到兩個影象都進行盒式濾波,得到兩個新圖:。然後用乘以引導影象Ii,再加上,即得最終濾波之後的輸出影象q。

結論:當視窗處於平坦區域時,影象的區域性方差小,則趨於0,趨於均值,相當於對影象進行了均值濾波;當視窗處於邊緣時,區域性方差較大,則趨於1,趨於0,濾波器輸出相當於原圖。這樣就可以很好的保護邊緣。一般地,影象中間,畫素跳躍變化不大,趨於穩定,方差小;影象邊緣,畫素跳躍變化大,不穩定,方差大。

程式碼解釋:

Mat.mul(Mat):

兩個矩陣維數必須相同,對應位置元素相乘。

[0, 1, 2, 3;           [ 0, 1, 2, 3;       [0, 1, 4, 9;

0, 1, 2, 3;   .mul         0, 1, 2, 3;   =      0, 1, 4, 9;

 0, 1, 2, 3]            0, 1, 2, 3]         0, 1, 4, 9;]

Mat型別轉換 ConvertTo:讀取一個圖片內容後要把圖片內容的畫素資訊轉為浮點並把當前的mat作為矩形進行矩陣計算。程式碼:
#include <opencv2/core/core.hpp>                    
#include <opencv2/highgui/highgui.hpp>        
#include <opencv2/imgproc/imgproc.hpp>    
#include <iostream>       
using namespace std;  
using namespace cv; 

Mat guidedFilter(Mat &srcMat, Mat &guidedMat, int radius, double eps);//引導濾波器  
  
int main()  
{  
    //------------【0】定義相關變數-------------  
    Mat resultMat;  //最後結果影象  
    vector<Mat> vSrcImage, vResultImage;  
    //------------【1】讀取源影象並檢查影象是否讀取成功------------       
    Mat srcImage = imread("image//left1.png");  
    if (!srcImage.data)  
    {  
        cout << "讀取圖片錯誤,請重新輸入正確路徑!\n";  
        system("pause");  
        return -1;  
    }  
    imshow("【源影象】", srcImage);  
    //-------【2】對源影象進行通道分離,並對每個分通道進行導向濾波操------  
    split(srcImage, vSrcImage);  
    for (int i = 0; i < 3; i++)  
    {  
        Mat tempImage;  
        vSrcImage[i].convertTo(tempImage, CV_64FC1, 1.0 / 255.0);//將分通道轉換成浮點型資料  
        Mat cloneImage = tempImage.clone(); //將tempImage複製一份到cloneImage  
        Mat resultImage = guidedFilter(tempImage, cloneImage, 9, 0.01);//對分通道分別進行導向濾波,半徑為1、3、5...等奇數  
        vResultImage.push_back(resultImage);//將分通道導向濾波後的結果存放到vResultImage中  
    }  
    //----------【3】將分通道導向濾波後結果合併-----------------------  
    merge(vResultImage, resultMat);  
    imshow("【引導濾波/導向濾波】", resultMat);  
    waitKey(0);  
    return 0;  
}  
  
//導向濾波器  
Mat guidedFilter(Mat &srcMat, Mat &guidedMat, int radius, double eps)  
{  
    //------------【0】轉換源影象資訊,將輸入擴充套件為64位浮點型,以便以後做乘法------------  
    srcMat.convertTo(srcMat, CV_64FC1);  
    guidedMat.convertTo(guidedMat, CV_64FC1);  
    //--------------【1】各種均值計算----------------------------------  
    Mat mean_p, mean_I, mean_Ip, mean_II;  
    boxFilter(srcMat, mean_p, CV_64FC1, Size(radius, radius));//生成待濾波影象均值mean_p   
    boxFilter(guidedMat, mean_I, CV_64FC1, Size(radius, radius));//生成引導影象均值mean_I     
    boxFilter(srcMat.mul(guidedMat), mean_Ip, CV_64FC1, Size(radius, radius));//生成互相關均值mean_Ip  
    boxFilter(guidedMat.mul(guidedMat), mean_II, CV_64FC1, Size(radius, radius));//生成引導影象自相關均值mean_II  
    //--------------【2】計算相關係數,計算Ip的協方差cov和I的方差var------------------  
    Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);  
    Mat var_I = mean_II - mean_I.mul(mean_I);  
    //---------------【3】計算引數係數a、b-------------------  
    Mat a = cov_Ip / (var_I + eps);  
    Mat b = mean_p - a.mul(mean_I);  
    //--------------【4】計算係數a、b的均值-----------------  
    Mat mean_a, mean_b;  
    boxFilter(a, mean_a, CV_64FC1, Size(radius, radius));  
    boxFilter(b, mean_b, CV_64FC1, Size(radius, radius));  
    //---------------【5】生成輸出矩陣------------------  
    Mat dstImage = mean_a.mul(srcMat) + mean_b;  
    return dstImage;  
}