快速高斯濾波、高斯模糊、高斯平滑(二維卷積分步爲一維卷積)

高斯濾波(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);
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章