高斯濾波(Gauss Filter)是線性濾波中的一種。在OpenCV圖像濾波處理中,高斯濾波用於平滑圖像,或者說是圖像模糊處理,因此高斯濾波是低通的。其廣泛的應用在圖像處理的減噪過程中,尤其是被高斯噪聲所污染的圖像上。
高斯濾波的基本思想是: 圖像上的每一個像素點的值,都由其本身和鄰域內其他像素點的值經過加權平均後得到。其具體操作是,用一個核(又稱爲卷積核、掩模、矩陣)掃描圖像中每一個像素點,將鄰域內各個像素值與對應位置的權值相稱並求和。從數學的角度來看,高斯濾波的過程是圖像與高斯正態分佈做卷積操作。
注意: 高斯濾波是將二維高斯正態分佈放在圖像矩陣上做卷積運算。考慮的是鄰域內像素值的空間距離關係,因此對彩色圖像處理時應分通道進行操作,也就是說操作的圖像原矩陣時用單通道數據,最後合併爲彩色圖像。
本章節僅討論快速高斯濾波的實現,如對高斯濾波的基本原理和實現不理解的,可以先看之前的一篇
OpenCV高斯濾波器詳解及代碼實現
一、高斯函數分離特性
可以看到,高斯二維公式可以推導爲X軸與Y軸上的一維高斯公式。而圖形矩陣是二維的,高斯濾波就是將核範圍中的各個點的座標帶入高斯二維公式,得出在覈矩陣上的空間分佈特性,這些特性將作爲權值反應在覈矩陣的各個點上。最終使用核與圖像矩陣作卷積運算得到處理圖像。
在之前的那篇高斯濾波文章上,採用的二維方式實現的。假設一張單通道圖片大小(M*N),核大小(size*size),核上的(size*size)個點都將被計算權值。最終實現的複雜度爲 (M*N*size*size)。
而如果將二維分步成X軸Y軸的一維處理。在X軸上計算size個點,Y軸上size個點。其複雜度將優化到 (M*N*size*2).
注意:先使用X軸方向(Y軸方向)對整個圖像矩陣作卷積,再在Y軸方向(X軸方向)對整個圖像矩陣作卷積。
二、高斯二維的空間分佈
二維高斯是構建高斯濾波器的基礎。可以看到,G(x,y)在x軸y軸上的分佈是一個突起的帽子的形狀。這裏的sigma可以看作兩個值,一個是x軸上的分量sigmaX,另一個是y軸上的分量sigmaY。對圖像處理可以直接使用sigma並對圖像的行列操作,也可以用sigmaX對圖像的行操作,再用sigmaY對圖像的列操作。它們是等價的。
當sigmaX和sigmaY取值越大,整個形狀趨近於扁平;當sigmaX和sigmaY取值越小,整個形狀越突起。
假設核大小爲(size*size),那麼核上(size*size)個點都將計算權值。
三、高斯二維分步爲X軸Y軸的高斯一維
假設一個(3*3)的核,在X軸(k方向)上
在Y軸(l方向)上
可以看到,實際上(size*size)個點中,最後僅以(size/2, size/2)點爲中心,計算了(size*2)個點的權值。
四、二維與一維時間比較
同樣對一張高斯噪聲圖處理,核大小取(53*53)。
上方時二維處理所用時間,下方是分步一維處理所用時間。當圖像越大,或者核大小越大時,兩者的差異將更加明顯。
五、代碼實現
(1)main函數
int main(void)
{
// [1] src讀入圖片
cv::Mat src = cv::imread("Gaussian_pic.jpg");
// [2] dst目標圖片
cv::Mat dst;
cv::Mat dst2 = src.clone();
// [3] 高斯濾波 sigma越大越平越模糊
myGaussianFilterFast(&src, &dst, 53, 2.0f, 2.0f);
// [4] 窗體顯示
cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
(2)彩色圖像通道分離以及X,Y分別確定權值矩陣
void myGaussianFilterFast(cv::Mat *src, cv::Mat *dst, int n, double sigmaX, double sigmaY)
{
// [1] 初始化
*dst = (*src).clone();
// [2] 彩色圖片通道分離
std::vector<cv::Mat> channels;
cv::split(*src, channels);
// [3] 濾波
// [3-1] 分別確定高斯正態矩陣(X,Y)
double *arrayX = getGaussianArray(n, sigmaX);
double *arrayY = getGaussianArray(n, sigmaY);
for (int i = 0; i < 3; i++) {
gaussian(&channels[i], arrayX, arrayY, n);
}
// [4] 合併返回
cv::merge(channels, *dst);
return;
}
(3)高斯一維計算
/* 獲取高斯分佈數組 (核大小, sigma值) */
double *getGaussianArray(int arr_size, double sigma)
{
int i;
// [1] 初始化數組
double *array = new double[arr_size];
// [2] 高斯分佈計算
int center_i = arr_size / 2;
double sum = 0.0f;
// [2-1] 高斯函數
for (i = 0; i < arr_size; i++) {
array[i] =
exp(-(1.0f)* (((i - center_i)*(i - center_i)) /
(2.0f*sigma*sigma)));
sum += array[i];
}
// [2-2] 歸一化求權值
for (i = 0; i < arr_size; i++) {
array[i] /= sum;
//printf(" [%.15f] ", array[i]);
}
return array;
}
(4)濾波處理,請注意,在X方向卷積完整個圖像後,再在Y方向上卷積,不要一邊X卷積一邊Y卷積,此時計算中包含X卷積過和沒卷積過的值,因此此時不能進行Y卷積。
/* 高斯濾波 (待處理單通道圖片, 高斯分佈數組, 高斯數組大小(核大小) ) */
void gaussian(cv::Mat *_src, double *_arrayX, double *_arrayY, int _size)
{
int center = _size / 2;
cv::Mat temp = (*_src).clone();
// [1] 掃描 X方向
for (int i = 0; i < (*_src).rows; i++) {
for (int j = 0; j < (*_src).cols; j++) {
// [2] 忽略邊緣
if (i >center - 1 && j >center - 1 &&
i < (*_src).rows - center && j < (*_src).cols - center) {
// [3] 找到圖像輸入點,以輸入點爲中心與核中心對齊
// 核心爲中心參考點 卷積算子=>高斯矩陣180度轉向計算
// x y 代表卷積核的權值座標 i j 代表圖像輸入點座標
// 卷積算子 (f*g)(j) = f(j-l)g(l) f代表圖像輸入 g代表核
// 帶入核參考點 (f*g)(j) = f(j-(l-aj))g(l) ai,aj 核參考點
// 加權求和 注意:核的座標以左上0,0起點
double sum = 0.0;
for (int l = 0; l < _size; l++) {
sum += (*_src).ptr<uchar>(i)[j - l + center] * _arrayX[l];
}
// 放入中間結果
temp.ptr<uchar>(i)[j] = MAX(MIN(sum, 255), 0);
}
}
}
// [1] 掃描 Y方向
for (int i = 0; i < (*_src).rows; i++) {
for (int j = 0; j < (*_src).cols; j++) {
// [2] 忽略邊緣
if (i >center - 1 && j >center - 1 &&
i < (*_src).rows - center && j < (*_src).cols - center) {
double sum = 0.0;
for (int k = 0; k < _size; k++) {
// 從中間結果取
sum += temp.ptr<uchar>(i - k + center)[j] * _arrayY[k];
}
// 放入原圖像
(*_src).ptr<uchar>(i)[j] = MAX(MIN(sum, 255), 0);
}
}
}
}