【從零學習OpenCV 4】可分離濾波
- 2019 年 12 月 30 日
- 筆記
前面介紹的濾波函數使用的濾波器都是固定形式的濾波器,有時我們需要根據實際需求調整濾波模板,例如在濾波計算過程中濾波器中心位置的像素值不參與計算,濾波器中參與計算的像素值不是一個矩形區域等。OpenCV 4無法根據每種需求單獨編寫濾波函數,因此OpenCV 4提供了根據自定義濾波器實現影像濾波的函數,就是我們本章最開始介紹的卷積函數filter2D(),不過根據函數的名稱,這裡稱呼為濾波函數更為準確一些,輸入的卷積模板也應該稱為濾波器或者濾波模板。該函數的使用方式我們在一開始已經介紹,只需要根據需求定義一個卷積模板或者濾波器,便可以實現自定義濾波。
無論是影像卷積還是濾波,在原影像上移動濾波器的過程中每一次的計算結果都不會影響到後面過程的計算結果,因此影像濾波是一個並行的演算法,在可以提供並行計算的處理器中可以極大的加快影像濾波的處理速度。除此之外,影像濾波還具有可分離行,這個性質我們在高斯濾波中有簡單的接觸,可分離性指的是先對X(Y)方向濾波,再對Y(X)方向濾波的結果與將兩個方向的濾波器聯合後整體濾波的結果相同。兩個方向的濾波器的聯合就是將兩個方向的濾波器相乘,得到一個矩形的濾波器,例如X方向的濾波器為 ,Y方向的濾波器為 ,兩個方向聯合濾波器可以用式(5.7)計算,無論先進行X方向濾波還是Y方向濾波,兩個方向聯合濾波器都是相同的。

因此在高斯濾波中,我們利用getGaussianKernel()函數分別得到X方向和Y方向的濾波器,之後通過生成聯合濾波器或者分別用兩個方向的濾波器進行濾波的計算結果相同。但是兩個方向聯合濾波需要在使用filter2D()函數濾波之前計算聯合濾波器,而兩個方向分別濾波需要調用兩次filter2D()函數,增加了通過程式碼實現的複雜性,因此OpenCV 4提供了可以輸入兩個方向濾波器實現濾波的濾波函數sepFilter2D(),該函數的函數原型在程式碼清單5-16中給出。
程式碼清單5-16 sepFilter2D()函數原型 void cv::sepFilter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernelX, InputArray kernelY, Point anchor = Point(-1,-1), double delta = 0, int borderType = BORDER_DEFAULT )
- src:待濾波影像
- dst:輸出影像,與輸入影像src具有相同的尺寸、通道數和數據類型。
- ddepth:輸出影像的數據類型(深度),根據輸入影像的數據類型不同擁有不同的取值範圍,具體的取值範圍在表5-1給出,當賦值為-1時,輸出影像的數據類型自動選擇。
- kernelX:X方向的濾波器,
- kernelY:Y方向的濾波器。
- anchor:內核的基準點(錨點),其默認值為(-1,-1)代表內核基準點位於kernel的中心位置。基準點即卷積核中與進行處理的像素點重合的點,其位置必須在卷積核的內部。
- delta:偏值,在計算結果中加上偏值。
- borderType:像素外推法選擇標誌,取值範圍在表3-5中給出。默認參數為BORDER_DEFAULT,表示不包含邊界值倒序填充。
該函數將可分離的線性濾波器分離成X方向和Y方向進行處理,與filter2D()函數不同之處在於,filter2D()函數需要通過濾波器的尺寸區分濾波操作是作用在X方向還是Y方向,例如濾波器尺寸為K×1時是Y方向濾波,1×K尺寸的濾波器是X方向濾波。而sepFilter2D()函數通過不同參數區分濾波器是作用在X方向還是Y方向,無論輸入濾波器的尺寸是K×1還是1×K,都不會影響濾波結果。
為了更加了解線性濾波的可分離性,在程式碼清單5-17中給出了利用filter2D()函數和sepFilter2D()函數實現濾波的示常式序。程式中利用filter2D()函數依次進行Y方向和X方向濾波,將結果與兩個方向聯合濾波器濾波結果相比較,驗證兩種方式計算結果的一致性。同時將兩個方向的濾波器輸入sepFilter2D()函數中,驗證該函數計算結果是否與前面的計算結果一致。最後利用自定義的濾波器,對影像依次進行X方向濾波和Y方向濾波,查看濾波結果是否與使用聯合濾波器的濾波結果一致。程式的計算結果依次在圖5-19、圖5-20給出。
程式碼清單 5-17 myselfFilter.cpp可分離影像濾波 #include <opencv2opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main() { system("color F0"); //更改輸出介面顏色 float points[25] = { 1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15, 16,17,18,19,20, 21,22,23,24,25 }; Mat data(5, 5, CV_32FC1, points); //X方向、Y方向和聯合濾波器的構建 Mat a = (Mat_<float>(3, 1) << -1, 3, -1); Mat b = a.reshape(1, 1); Mat ab = a*b; //驗證高斯濾波的可分離性 Mat gaussX = getGaussianKernel(3, 1); Mat gaussData, gaussDataXY; GaussianBlur(data, gaussData, Size(3, 3), 1, 1, BORDER_CONSTANT); sepFilter2D(data,gaussDataXY,-1, gaussX, gaussX, Point(-1,-1),0,BORDER_CONSTANT); //輸入兩種高斯濾波的計算結果 cout << "gaussData=" << endl << gaussData << endl; cout << "gaussDataXY=" << endl << gaussDataXY << endl; //線性濾波的可分離性 Mat dataYX, dataY, dataXY, dataXY_sep; filter2D(data, dataY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT); filter2D(dataY, dataYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT); filter2D(data, dataXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT); sepFilter2D(data, dataXY_sep, -1, b, b, Point(-1, -1), 0, BORDER_CONSTANT); //輸出分離濾波和聯合濾波的計算結果 cout << "dataY=" << endl << dataY << endl; cout << "dataYX=" << endl << dataYX << endl; cout << "dataXY=" << endl << dataXY << endl; cout << "dataXY_sep=" << endl << dataXY_sep << endl; //對影像的分離操作 Mat img = imread("lena.png"); if (img.empty()) { cout << "請確認影像文件名稱是否正確" << endl; return -1; } Mat imgYX, imgY, imgXY; filter2D(img, imgY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT); filter2D(imgY, imgYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT); filter2D(img, imgXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT); imshow("img", img); imshow("imgY", imgY); imshow("imgYX", imgYX); imshow("imgXY", imgXY); waitKey(0); return 0; }

圖5-19 myselfFilter.cpp程式中數據矩陣濾波結果

圖5-20 myselfFilter.cpp程式中影像濾波結果