概念
通常我們認為圖像像素之間的相關性隨着距離增加應該不斷減弱,但是均值濾波並沒有體現這一性質。在對圖像進行均值濾波時,如果圖像中有一些很顯著的亮點,濾波后它的周圍會形成光斑。這正是因為均值濾波無視了距離,對很遠處的像素依舊采用同樣的權重導致的。一些場合,我們為了美感會需要這種效果。另一些場合,這種結果是不利的,特別是在做圖像處理時。因此我們引入了具有距離權重的高斯濾波。
通常而言,高斯函數可以分為一維和二維,一維高斯函數更廣為人知,其就是正態分布的表達式。而二維高斯濾波則具有和一維相似的性質:
由二維高斯函數的圖形,我們很容易看出,若用其作為卷積核,那么越靠近中心的像素相關性越大。在進行高斯濾波時,就是將二維高斯函數作為卷積核,對圖像進行滑窗卷積。二維高斯函數的表達式如下:
毫無疑問,卷積核通常是奇數長寬的窗口,以便於確定中心。高斯濾波的卷積核一般采用正方形,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); }