高斯模糊與圖像卷積濾波一些知識點以及GaussianBlur函數

一.圖像卷積濾波與高斯模糊

1.1 圖像卷積濾波

對於濾波來說,它可以說是圖像處理最基本的方法,可以產生很多不同的效果。以下圖來說

圖中矩陣分別爲二維原圖像素矩陣,二維的圖像濾波矩陣(也叫做卷積核,下面講到濾波器和卷積核都是同個概念),以及最後濾波後的新像素圖。對於原圖像的每一個像素點,計算它的領域像素和濾波器矩陣的對應元素的成績,然後加起來,作爲當前中心像素位置的值,這樣就完成了濾波的過程了。

可以看到,一個原圖像通過一定的卷積核處理後就可以變換爲另一個圖像了。而對於濾波器來說,也是有一定的規則要求的。

  • ① 濾波器的大小應該是奇數,這樣它纔有一箇中心,例如3x3,5x5或者7x7。有中心了,也有了半徑的稱呼,例如5x5大小的核的半徑就是2。
  • ② 濾波器矩陣所有的元素之和應該要等於1,這是爲了保證濾波前後圖像的亮度保持不變。當然了,這不是硬性要求了。
  • ③ 如果濾波器矩陣所有元素之和大於1,那麼濾波後的圖像就會比原圖像更亮,反之,如果小於1,那麼得到的圖像就會變暗。如果和爲0,圖像不會變黑,但也會非常暗。
  • ④ 對於濾波後的結構,可能會出現負數或者大於255的數值。對這種情況,我們將他們直接截斷到0和255之間即可。對於負數,也可以取絕對值。

1.2 卷積核一些用法

既然知道濾波器可以用來對原圖進行操作,那麼,有沒有一些比較具體的例子。文中卷積核相關圖片來源於網絡

1.2.1 空卷積核

可以看到,這個濾波器啥也沒有做,得到的圖像和原圖是一樣的。因爲只有中心點的值是1。鄰域點的權值都是0,對濾波後的取值沒有任何影響。

1.2.2 圖像銳化濾波器

圖像的銳化和邊緣檢測很像,首先找到邊緣,然後把邊緣加到原來的圖像上面,這樣就強化了圖像的邊緣,使圖像看起來更加銳利了。這兩者操作統一起來就是銳化濾波器了,也就是在邊緣檢測濾波器的基礎上,再在中心的位置加1,這樣濾波後的圖像就會和原始的圖像具有同樣的亮度了,但是會更加銳利。

我們把核加大,就可以得到更加精細的銳化效果

1.2.3 浮雕

浮雕濾波器可以給圖像一種3D陰影的效果。只要將中心一邊的像素減去另一邊的像素就可以了。這時候,像素值有可能是負數,我們將負數當成陰影,將正數當成光,然後我們對結果圖像加上128的偏移。這時候,圖像大部分就變成灰色了。
下面是45度的浮雕濾波器

我們只要加大濾波器,就可以得到更加誇張的效果了

1.2.4 均值模糊

我們可以將當前像素和它的四鄰域的像素一起取平均,然後再除以5,或者直接在濾波器的5個地方取0.2的值即可,如下圖:

可以看到,這個模糊還是比較溫柔的,我們可以把濾波器變大,這樣就會變得粗暴了:注意要將和再除以13.

可以看到均值模糊也可以做到讓圖片模糊,但是它的模糊不是很平滑,不平滑主要在於距離中心點很遠的點與距離中心點很近的所帶的權重值相同,產生的模糊效果一樣
而想要做到平滑,讓權重值跟隨中心點位置距離不同而不同,則可以利用正態分佈(中間大,兩端小)這個特點來實現。

1.3 高斯模糊

有了前面的知識,我們知道如果要想實現高斯模糊的特點,則需要通過構建對應的權重矩陣來進行濾波。

1.3.1 正態分佈

正態分佈

正態分佈中,越接近中心點,取值越大,越遠離中心,取值越小。
計算平均值的時候,我們只需要將"中心點"作爲原點,其他點按照其在正態曲線上的位置,分配權重,就可以得到一個加權平均值。正態分佈顯然是一種可取的權重分配模式。

1.3.2 高斯函數

如何反映出正態分佈?則需要使用高函數來實現。
上面的正態分佈是一維的,而對於圖像都是二維的,所以我們需要二維的正態分佈。

正態分佈的密度函數叫做"高斯函數"(Gaussian function)。它的一維形式是:

其中,μ是x的均值,σ是x的方差。因爲計算平均值的時候,中心點就是原點,所以μ等於0。

 

根據一維高斯函數,可以推導得到二維高斯函數:

有了這個函數 ,就可以計算每個點的權重了。

1.3.3 獲取權重矩陣

假定中心點的座標是(0,0),那麼距離它最近的8個點的座標如下:

x正方向水平向右,y正方向
更遠的點以此類推。
爲了計算權重矩陣,需要設定σ的值。假定σ=1.5,則模糊半徑爲1的權重矩陣如下:



這9個點的權重總和等於0.4787147,如果只計算這9個點的加權平均,還必須讓它們的權重之和等於1,因此上面9個值還要分別除以0.4787147,得到最終的權重矩陣。


除以總值這個過程也叫做”歸一問題“
目的是讓濾鏡的權重總值等於1。否則的話,使用總值大於1的濾鏡會讓圖像偏亮,小於1的濾鏡會讓圖像偏暗。

1.3.4 計算模糊值

有了權重矩陣,就可以計算高斯模糊的值了。假設現有9個像素點,灰度值(0-255)如下:


每個點乘以自己的權重值:
 

得到



將這9個值加起來,就是中心點的高斯模糊的值。
對所有點重複這個過程,就得到了高斯模糊後的圖像。對於彩色圖片來說,則需要對RGB三個通道分別做高斯模糊。

 

1.3.5 邊界值問題

 

既然是根據權重矩陣來進行處理的,如果一個點處於邊界,周邊沒有足夠的點,怎麼辦?

  • ① 對稱處理,就是把已有的點拷貝到另一面的對應位置,模擬出完整的矩陣。
  • ② 賦0,想象圖像是無限長的圖像的一部分,除了我們給定值的部分,其他部分的像素值都是0
  • ③ 賦邊界值,想象圖像是無限制長,但是默認賦值的不是0而是對應邊界點的值

 以上參考https://www.jianshu.com/p/8d2d93c4229b

高斯濾波:其函數聲明爲:

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT ) ;

功能:對輸入的圖像src進行高斯濾波後用dst輸出。

參數:src和dst當然分別是輸入圖像和輸出圖像。Ksize爲高斯濾波器模板大小,sigmaX和sigmaY分別爲高斯濾波在橫線和豎向的濾波係數(有點晦澀,等下解釋)。borderType爲邊緣點插值類型。

理解:數字圖像的濾波可以簡單的這麼理解,就是對原圖像的每一個像素濾波,那麼對應這個像素濾波後的值是根據其相鄰像素(包括自己那個點)與一個濾波模板進行相乘即可。所以具體到高斯濾波,我們只要知道這個高斯濾波的模板即可。

那怎麼確定這個模板呢?首先這個模板的大小爲ksize,其每個數字的計算是這樣的:看成2維的高斯函數

 

其中 是歸一化係數,因爲其和要爲1. μ是均值,σ是方差

爲了簡化,一般在二維圖像處理中,ui和uj取0,sigma1和sigma2取相等。所以公式就簡化爲 :

因此很容易就計算出模板每個位置的數字了,簡單吧!

但是要注意2點:

                           1、ksize的寬和高必須是奇數;

                           2、如果參數sigmaX=sigmaY=0, 則實際用的是公式sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 .

接下來的工作就是進入GaussianBlur函數內部,跟蹤其函數代碼,經過分析,在該函數內部調用了很多其他的函數,其調用的函數層次結構如下圖所示:

     這裏我們分析源代碼不需要深入到最底層,我們只需分析到函數createSeparableLinearFilter和getGaussianKernel這一層。

     那就開始我們的源碼分析工作吧!

     從函數調用層次結構圖可以看出,要分析函數GaussianBlur,必須先分析其調用過的內部函數。

     因此首先分析函數getGaussianKernel。

     功能:返回一個ksize*1的數組,數組元素滿足高斯公式:

 

     其中只有係數alpha和參數sigma未知,sigma的求法爲:

     如果輸入sigma爲非正,則計算公式爲:sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 .

     如果輸入sigma爲正,則就用該輸入參數sigma。

     最後alpha爲歸一化係數,即計算出的ksize個數之和必須爲1,所以後面只需求ksize個數,計算其和並求倒即可。

cv::Mat cv::getGaussianKernel( int n, double sigma, int ktype )
{
    const int SMALL_GAUSSIAN_SIZE = 7;
    static const float small_gaussian_tab[][SMALL_GAUSSIAN_SIZE] =
    {
        {1.f},
        {0.25f, 0.5f, 0.25f},
        {0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f},
        {0.03125f, 0.109375f, 0.21875f, 0.28125f, 0.21875f, 0.109375f, 0.03125f}
    };
    
      /*如果sigma小於0,且n爲不大於7的奇整數,則核的濾波係數固定了,其固定在數組

        small_gaussian_tab中,根據其n的長度來選擇具體的值 ,如果不滿足上面的,則固定核爲0
        固定核爲0表示自己計算其核*/ 
        
    const float* fixed_kernel = n % 2 == 1 && n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 ?
        small_gaussian_tab[n>>1] : 0;

    CV_Assert( ktype == CV_32F || ktype == CV_64F );//確保核元素爲32位浮點數或者64位浮點數
    Mat kernel(n, 1, ktype);//建立一個n*1的數組kernel,一個Mat矩陣包括一個矩陣頭和一個指向矩陣元素的指針
    float* cf = (float*)kernel.data;//定義指針cf指向kernel單精度浮點型數據
    double* cd = (double*)kernel.data;//定義指針cd指向kernerl雙精度浮點型數據

    double sigmaX = sigma > 0 ? sigma : ((n-1)*0.5 - 1)*0.3 + 0.8;//當sigma小於0時,採用公式得到sigma(只與n有關)
    double scale2X = -0.5/(sigmaX*sigmaX);//高斯表達式後面要用到
    double sum = 0;

    int i;
    for( i = 0; i < n; i++ )
    {
        double x = i - (n-1)*0.5;
        //如果自己算其核的話,就常用公式exp(scale2X*x*x)計算,否則就用固定係數的核
        double t = fixed_kernel ? (double)fixed_kernel[i] : std::exp(scale2X*x*x);
        if( ktype == CV_32F )
        {
            cf[i] = (float)t;//單精度要求時存入cf數組中
            sum += cf[i];//進行歸一化時要用到
        }
        else
        {
            cd[i] = t;//雙精度時存入cd數組中
            sum += cd[i];
        }
    }

    sum = 1./sum;//歸一化時核中各元素之和爲1
    for( i = 0; i < n; i++ )
    {
        if( ktype == CV_32F )
            cf[i] = (float)(cf[i]*sum);//歸一化後的單精度核元素
        else
            cd[i] *= sum;//歸一化後的雙精度核元素
    }

    return kernel;//返回n*1的數組,其元素或是單精度或是雙精度,且符合高斯分佈
}

高斯函數的使用方法:

-參數 InputArray表示輸入圖像的Mat對象。

-參數OutputArray表示模糊之後輸出Mat對象

-參數Size表示卷積和大小,此參數決定模糊程度,Size(x,y)中x,y取值越大表現模糊程度越深,而且x,y必須爲奇數。

-參數SigmaX表示高斯方程中x方向的標準方差

-參數SigmaY表示高斯方程中y方向的標準方差

-最後一個參數表示對邊緣的處理方法,一般取默認值

使用方法一:Size(x,y)中x,y取正奇數,SigmaX,SigmaY取0,此時SigmaX,SigmaY會自動計算,公式爲sigma = ((n-1)*0.5 - 1)*0.3 + 0.8,xy方向上都計算。

使用方法二:Size(x,y) 中的xy取0,SigmaX,SigmaY都不去0,此時xy會自動計算,公式爲sigma1*(depth == CV_8U ? 3 : 4)*2 + 1)|1。xy都計算。

使用方法三:Size(x,y)中xy,SigmaX,SigmaY都不爲0.

前兩中方法總之x是SigmaX,y是SigmaY的6-9倍。

以上參考https://www.cnblogs.com/invisible2/p/9177018.html

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