【計算攝影學】高斯濾波的簡單實現


概念

通常我們認為圖像像素之間的相關性隨着距離增加應該不斷減弱,但是均值濾波並沒有體現這一性質。在對圖像進行均值濾波時,如果圖像中有一些很顯著的亮點,濾波后它的周圍會形成光斑。這正是因為均值濾波無視了距離,對很遠處的像素依舊采用同樣的權重導致的。一些場合,我們為了美感會需要這種效果。另一些場合,這種結果是不利的,特別是在做圖像處理時。因此我們引入了具有距離權重的高斯濾波。

通常而言,高斯函數可以分為一維和二維,一維高斯函數更廣為人知,其就是正態分布的表達式。而二維高斯濾波則具有和一維相似的性質:

由二維高斯函數的圖形,我們很容易看出,若用其作為卷積核,那么越靠近中心的像素相關性越大。在進行高斯濾波時,就是將二維高斯函數作為卷積核,對圖像進行滑窗卷積。二維高斯函數的表達式如下:

毫無疑問,卷積核通常是奇數長寬的窗口,以便於確定中心。高斯濾波的卷積核一般采用正方形,3 × 3或5 × 5甚至更大。由於高斯函數的高度對稱性,取正方形作為卷積核是不難理解的。在本實驗中,我們將高斯函數的卷積核大小設置為:

下面我們以3 × 3大小的卷積核為例,介紹具體如何求取卷積核的數值。在模板的各個位置的坐標如下圖所示(x軸水平向右,y軸豎直向上):

這樣,將各個位置的坐標(x,y)代入到高斯函數G中,得到的每個值按照位置排列,就得到了模板。

例如:生成高斯核為3 × 3,σ = 0.8的模板:

0.057118 0.12476 0.057118
0.12476 0.2725 0.12476
0.057118 0.12476 0.057118

 

 

 

 


從以上描述中我們可以看出,高斯濾波模板中最重要的參數就是高斯分布的標准差σ。它代表着數據的離散程度,如果σ較小,那么生成的模板中心系數越大,而周圍的系數越小,這樣對圖像的平滑效果就不是很明顯;相反,σ較大時,則生成的模板的各個系數相差就不是很大,比較類似於均值模板,對圖像的平滑效果就比較明顯。

 

理論分析

 求解圖像的高斯濾波與均值濾波的步驟相似,可以分為如下幾步:

  •  讀取原始圖像
  •  求出高斯濾波的卷積核
  •  利用 filter2D 函數進行卷積操作
  •  寫入高斯濾波后的圖像

高斯濾波與均值濾波對比,主要的不同在於第二步求解卷積核,相比而言高斯濾波卷積核的求解稍微復雜一些。

 

實驗細節

1、卷積核的求取

// 初始化高斯卷積核
kernel = Mat::ones(kHeight, kWidth, CV_32F);

// 找到高斯核的中心點
// 注意該中心點是以0開頭,10σ結尾的中心點
int center = floor(5 * sigma);

// 遍歷Mat,求值
for (int i = 0; i < kHeight; i++) {
    for (int j = 0; j < kWidth; j++) {
        // 坐標(i,j)對應的高斯坐標系中的坐標為(i - center, center - j)
        double x = i - center;
        double y = center - j;
        kernel.at<float>(i, j) = 1.0f / (2.0f * pi*sigma*sigma)*exp(-(1.0f)* ((x * x + y * y) / (2.0f*sigma*sigma)));
        cout << kernel.at<float>(i, j) << "\t\t";
    }
    cout << endl;
}

代碼如上,由用戶輸入的σ,上面又規定了卷積核的大小,因此很容易確定卷積核的中心為:

在真實的代碼環境中,由於下標默認為0開始,因此卷積核的中心可以將最后的+1去掉。此時,將該中心設置為原點(0,0),對整個Mat進行遍歷,對於掩膜矩陣中的每一個點(i,j),其在高斯坐標系中對應的(x,y)坐標為(i - center,center - j),直接套用高斯函數的公式即可求解出掩膜中每一個點的數值。這樣就得到了我們想要的高斯卷積核。

2、利用 filter2D 函數進行卷積操作

與均值濾波類似,直接用filter2D進行卷積操作即可:

Point anchor(-1, -1); //中間的點
int depth = -1;       //深度和輸入一樣
int delta = 0;        //在處理之后 再加上delta,默認0

filter2D(src, dst, depth, kernel, anchor, delta);

 

結果展示

                                                                (原始圖像)

                                               (高斯濾波輸出圖像,取σ = 0.8)

 

最后附上完整代碼:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
#define pi 3.1415926535

int main()
{
    Mat src, dst;
    Mat kernel;
    string image_name;
    string output_name;
    int kHeight, kWidth, img_height, img_width;
    double sigma;

    cout << "請輸入需要讀取的圖像名稱:";
    cin >> image_name;

    cout << "請輸入輸出的圖像名稱:";
    cin >> output_name;

    cout << "請輸入需要的sigma:";
    cin >> sigma;

    // 進行初始判斷
    src = imread(image_name, IMREAD_COLOR);
    if (src.empty()) {
        cout << "圖像讀取失敗!" << endl;
        return 0;
    }
    else {
        // 若讀取成功,判斷卷積核的大小與圖像大小的關系
        kHeight = 2 * floor(5 * sigma) + 1;
        kWidth = 2 * floor(5 * sigma) + 1;
        img_height = src.rows;
        img_width = src.cols;
        if (img_height < kHeight || img_width < kWidth) {
            cout << "卷積核大小超過圖像大小!" << endl;
            return 0;
        }
    }

    // 初始化高斯卷積核
    kernel = Mat::ones(kHeight, kWidth, CV_32F);

    // 找到高斯核的中心點
    // 注意該中心點是以0開頭,10σ結尾的中心點
    int center = floor(5 * sigma);

    // 遍歷Mat,求值
    for (int i = 0; i < kHeight; i++) {
        for (int j = 0; j < kWidth; j++) {
            // 坐標(i,j)對應的高斯坐標系中的坐標為(i - center, center - j)
            double x = i - center;
            double y = center - j;
            kernel.at<float>(i, j) = 1.0f / (2.0f * pi*sigma*sigma)*exp(-(1.0f)* ((x * x + y * y) / (2.0f*sigma*sigma)));
            cout << kernel.at<float>(i, j) << "\t\t";
        }
        cout << endl;
    }

    Point anchor(-1, -1); //中間的點
    int depth = -1;//深度和輸入一樣
    int delta = 0; //在處理之后 再加上delta,默認0

    filter2D(src, dst, depth, kernel, anchor, delta);
    imshow("高斯濾波", dst);
    imwrite(output_name, dst);
    waitKey(0);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM