影象處理中的濾波
濾波(Wave filtering)是將訊號中特定波段頻率濾除的操作,是抑制和防止干擾的一項重要措施。在影象處理中,濾波是影象預處理的一種。影象處理中濾波將訊號中特定的波段頻率濾除,從而保留所需要的波段頻率訊號。根據選擇保留的不同頻段可以體現這麼兩個作用
消除影象中混入的噪聲
對應的是低通濾波,噪聲在影象中一般是高頻訊號。
為影象識別抽取出影象特徵
這裡的特徵一般為邊緣紋理的特徵,對應的是高通濾波,影象中邊緣和紋理細節是高頻訊號。
濾波的分類
影象中濾波演算法的分類有很多,可以分為線性濾波和非線性濾波,可以分為相關濾波和卷積濾波,還可以分為高通濾波和低通濾波,空間濾波和頻域濾波。
線性濾波和非線性濾波
- 線性濾波
維基百科解釋為:用於時變輸入訊號的線性運算,在影象處理中可以這麼理解,對於輸入的訊號(即要處理的影象),進行的是線性的運算,得出的結果作為輸出影象。可以參考下圖

首先有一個濾波器的模板(這裡是3x3大小),模板裡有係數,f(x,y)的值等於模板係數與f(x,y)周圍一的畫素點相乘求和,這個運算是線性的。線上性的濾波器中,運算的不同即為濾波器的係數模板不同。
線性濾波的包含方框濾波、均值濾波、高斯濾波、拉普拉斯濾波、sobel運算元等。
- 非線性濾波
輸出的訊號響應是由輸入經過非線性的運算得到的。比如典型的中值濾波,就是取畫素點鄰域的中值作為畫素的的響應輸出。
非線性濾波包含中值濾波和雙邊濾波。
卷積濾波和相關濾波
首先要注意的是卷積濾波和相關濾波都屬於線性濾波,兩者的區別是加權係數的對應相乘順序有所不同。
高通濾波和低通濾波
高通濾波與低通濾波之分是相對於濾波的目的而言的,簡而言之,高通濾波器就是去除影象中的低頻部分,保留高頻。表現就是經高通濾波後,保留了影象的高頻邊緣和紋理細節,所以高通濾波對應的是影象的銳化。低通濾波則是相反的,處理的結果是保留低頻部分去除高頻部分,在影象上的表現是紋理細節都被模糊了,所以低通濾波對應的是影象的平滑模糊。
空間濾波和頻域濾波
空間濾波即直接在畫素座標上對影象資料進行處理濾波,頻域濾波則是先把影象由空間域變換到頻域,在頻域進行處理,結束以後再由頻域變換會空間域。
下面是幾個具體的濾波演算法的例子
方框濾波
方框濾波,線性濾波里面最簡單的一個。即用一個已確定的模板係數去與畫素點領域相乘,所得結果即為畫素點的響應。
均值濾波
均值濾波則是方框濾波的特例,將方框濾波的係數模板歸一化之後便是均值濾波
可以看到,均值濾波的結果是取畫素點周圍領域的平均值作為響應輸出。
高斯濾波
高斯濾波的模板係數就稍顯複雜,其模板係數是服從高斯分佈的。
先看看一維的高斯分佈
二維的高斯分佈

所以高斯濾波的模板係數,應該服從上圖中的錐形山峰。那如何計算高斯分佈的模板係數呢,我們取均值為模板的中心點,計算公式如下

這裡附上程式碼
std::vector<std::vector<double> >getModel(const int& n, const double& sigmma)//求解nxn的模板係數 { const double pi = 3.1415926; const double weight = 1.0 /(2.0 * pi * sigmma * sigmma);//gaussion公式中的係數 double sum = 0.0; std::vector<std::vector<double>> res(n, std::vector<double>(n, 0.0)); for(int i = 0;i < n; ++i) for (int j = 0; j < n; ++j) { res[i][j] = weight * std::exp(-((i - n / 2) * (i - n / 2) + (j - n / 2)*(j - n / 2)) / (2.0*sigmma*sigmma));//n/2為公式中都均值,即原點在影象中心 } return res; }
假如不想手動計算高斯濾波的模板係數,也可以取模板的近似值作為模板。如3x3和5x5的模板可以取
拉普拉斯運算元
拉普拉斯濾波屬於高通濾波,運算元定義為
下面由運算元推導計算模板係數
在x方向上,二階導數的微分可以由差分近似表示,有
類似地,在y方向上有
所以拉普拉斯運算元在離散的情況下,可以近似為

那麼可以得矩陣的模板係數
還有一般常用的拉普拉斯模板
Roberts運算元
Roberts運算元比較簡單,使用2x2的領域

計算響應值的方式如下
sobel運算元
Robinson運算元
Kirsch運算元
以上所有有的運算元都屬於線性濾波,在得到離散情況下的運算元模板係數之後,就可以用這個模板去對影象進行操作,使用模板係數去遍歷影象,操作可以選擇卷積或者相關。這些運算元也稱卷積掩模。
這裡先給出二維卷積的程式碼實現
//二維卷積的實現 #include<cassert> #include<vector> void conv2(int** filter, int **mat, int** res, const int filter_rows, const int filter_cols, const int mat_rows, const int mat_cols);//指標陣列版本 std::vector<std::vector<int> > conv2(std::vector<std::vector<int> > filter, std::vector<std::vector<int> > mat);//向量版本 int main(void) { return 0; }//main void conv2(int** filter, int **mat, int** res, const int filter_rows, const int filter_cols, const int mat_rows, const int mat_cols) { assert(filter_cols < mat_cols && filter_rows < mat_rows); for(int i = 0; i < mat_rows - 1; ++i) for (int j = 0; j < mat_cols - 1; ++j) { int tmp = 0; for (int m = 0; m < filter_rows; ++m) for (int n = 0; n < filter_cols; ++n) if(0 <= i -m&& i - m < mat_rows && 0 <= j - n && j - n < mat_cols) tmp += filter[m][n] * mat[i - m][j - n];//卷積公式 res[i][j] = tmp; } } std::vector<std::vector<int> > conv2(std::vector<std::vector<int> > filter, std::vector<std::vector<int> > mat )//向量版本 { const int filter_rows = filter.size(); const int filter_cols = filter[0].size(); const int mat_rows = mat.size(); const int mat_cols = mat[0].size(); assert(filter_cols < mat_cols && filter_rows < mat_rows); std::vector<std::vector<int> > res(mat_rows, std::vector<int>(mat_cols, 0)); for (int i = 0; i < mat_rows - 1; ++i) for (int j = 0; j < mat_cols - 1; ++j) { int tmp = 0; for (int m = 0; m < filter_rows; ++m) for (int n = 0; n < filter_cols; ++n) if (0 <= i - m && i - m < mat_rows && 0 <= j - n && j - n < mat_cols) tmp += filter[m][n] * mat[i - m][j - n];//卷積公式 res[i][j] = tmp; } return res; }
然後以高斯濾波為例,給出這些模板係數卷積濾波的實現
void mycv::gaussianFilter(cv::Mat& src, cv::Mat& dst) { const int rows = src.rows; const int cols = src.cols; dst = cv::Mat(src.size(), src.type(), cv::Scalar::all(0)); std::vector<std::vector<double>> gauss = getModel(3);//3x3模板係數 switch (src.channels()) { case 1://灰度圖 for(int i = 0; i < rows -1; ++i) for (int j = 0; j < cols - 1; ++j) { double tmp = 0.0; for(int m = 0; m < gauss.size(); ++m) for (int n = 0; n < gauss[0].size(); ++n) { if (i - m >= 0 & i - m < rows && j - n >= 0 && j - n < cols) tmp += gauss[m][n] * static_cast<double>(src.at<uchar>(i - m, j - n));//卷積公式 } dst.at<uchar>(i, j) = static_cast<int>(tmp); } break; case 3://彩色 for (int i = 0; i < rows - 1; ++i) for (int j = 0; j < cols - 1; ++j) for (int k = 0; k < 3; ++k) { double tmp = 0.0; for (int m = 0; m < gauss.size(); ++m) for (int n = 0; n < gauss[0].size(); ++n) if (i - m >= 0 & i - m < rows && j - n >= 0 && j - n < cols) tmp += gauss[m][n] * static_cast<double>(src.at<cv::Vec3b>(i - m, j - n)[k]);//卷積公式 dst.at<cv::Vec3b>(i, j)[k] = static_cast<int>(tmp); } break; default: break; } }