滤波
滤波(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方向上有
y方向所以拉普拉斯算子在离散的情况下,可以近似为
离散拉普拉斯
那么可以得矩阵的模板系数
拉普拉斯模板系数还有一般常用的拉普拉斯模板
拉普拉斯模板Roberts算子
Roberts算子比较简单,使用2x2的领域
Roberts算子
计算响应值的方式如下
Roberts算子计算sobel算子
sobel算子Robinson算子
Robinson算子Kirsch算子
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;
}
}
网友评论