一.图像卷积滤波与高斯模糊
1.1 图像卷积滤波
对于滤波来说,它可以说是图像处理最基本的方法,可以产生很多不同的效果。以下图来说
图中矩阵分别为二维原图像素矩阵,二维的图像滤波矩阵(也叫做卷积核,下面讲到滤波器和卷积核都是同个概念),以及最后滤波后的新像素图。对于原图像的每一个像素点,计算它的领域像素和滤波器矩阵的对应元素的成绩,然后加起来,作为当前中心像素位置的值,这样就完成了滤波的过程了。
可以看到,一个原图像通过一定的卷积核处理后就可以变换为另一个图像了。而对于滤波器来说,也是有一定的规则要求的。
- ① 滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2。
- ② 滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。当然了,这不是硬性要求了。
- ③ 如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
- ④ 对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。
1.2 卷积核一些用法
既然知道滤波器可以用来对原图进行操作,那么,有没有一些比较具体的例子。文中卷积核相关图片来源于网络
1.2.1 空卷积核
可以看到,这个滤波器啥也没有做,得到的图像和原图是一样的。因为只有中心点的值是1。邻域点的权值都是0,对滤波后的取值没有任何影响。
1.2.2 图像锐化滤波器
图像的锐化和边缘检测很像,首先找到边缘,然后把边缘加到原来的图像上面,这样就强化了图像的边缘,使图像看起来更加锐利了。这两者操作统一起来就是锐化滤波器了,也就是在边缘检测滤波器的基础上,再在中心的位置加1,这样滤波后的图像就会和原始的图像具有同样的亮度了,但是会更加锐利。
我们把核加大,就可以得到更加精细的锐化效果
1.2.3 浮雕
浮雕滤波器可以给图像一种3D阴影的效果。只要将中心一边的像素减去另一边的像素就可以了。这时候,像素值有可能是负数,我们将负数当成阴影,将正数当成光,然后我们对结果图像加上128的偏移。这时候,图像大部分就变成灰色了。
下面是45度的浮雕滤波器
我们只要加大滤波器,就可以得到更加夸张的效果了
1.2.4 均值模糊
我们可以将当前像素和它的四邻域的像素一起取平均,然后再除以5,或者直接在滤波器的5个地方取0.2的值即可,如下图:
可以看到,这个模糊还是比较温柔的,我们可以把滤波器变大,这样就会变得粗暴了:注意要将和再除以13.
可以看到均值模糊也可以做到让图片模糊,但是它的模糊不是很平滑,不平滑主要在于距离中心点很远的点与距离中心点很近的所带的权重值相同,产生的模糊效果一样
而想要做到平滑,让权重值跟随中心点位置距离不同而不同,则可以利用正态分布(中间大,两端小)这个特点来实现。
1.3 高斯模糊
有了前面的知识,我们知道如果要想实现高斯模糊的特点,则需要通过构建对应的权重矩阵来进行滤波。
1.3.1 正态分布
正态分布
正态分布中,越接近中心点,取值越大,越远离中心,取值越小。
计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。正态分布显然是一种可取的权重分配模式。
1.3.2 高斯函数
如何反映出正态分布?则需要使用高函数来实现。
上面的正态分布是一维的,而对于图像都是二维的,所以我们需要二维的正态分布。
正态分布的密度函数叫做"高斯函数"(Gaussian function)。它的一维形式是:
其中,μ是x的均值,σ是x的方差。因为计算平均值的时候,中心点就是原点,所以μ等于0。
根据一维高斯函数,可以推导得到二维高斯函数:
有了这个函数 ,就可以计算每个点的权重了。
1.3.3 获取权重矩阵
假定中心点的坐标是(0,0),那么距离它最近的8个点的坐标如下:
x正方向水平向右,y正方向
更远的点以此类推。
为了计算权重矩阵,需要设定σ的值。假定σ=1.5,则模糊半径为1的权重矩阵如下:
这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。
除以总值这个过程也叫做”归一问题“
目的是让滤镜的权重总值等于1。否则的话,使用总值大于1的滤镜会让图像偏亮,小于1的滤镜会让图像偏暗。
1.3.4 计算模糊值
有了权重矩阵,就可以计算高斯模糊的值了。假设现有9个像素点,灰度值(0-255)如下:
每个点乘以自己的权重值:
得到
将这9个值加起来,就是中心点的高斯模糊的值。
对所有点重复这个过程,就得到了高斯模糊后的图像。对于彩色图片来说,则需要对RGB三个通道分别做高斯模糊。
1.3.5 边界值问题
既然是根据权重矩阵来进行处理的,如果一个点处于边界,周边没有足够的点,怎么办?
- ① 对称处理,就是把已有的点拷贝到另一面的对应位置,模拟出完整的矩阵。
- ② 赋0,想象图像是无限长的图像的一部分,除了我们给定值的部分,其他部分的像素值都是0
- ③ 赋边界值,想象图像是无限制长,但是默认赋值的不是0而是对应边界点的值
以上参考https://www.jianshu.com/p/8d2d93c4229b
高斯滤波:其函数声明为:
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT ) ;
功能:对输入的图像src进行高斯滤波后用dst输出。
参数:src和dst当然分别是输入图像和输出图像。Ksize为高斯滤波器模板大小,sigmaX和sigmaY分别为高斯滤波在横线和竖向的滤波系数(有点晦涩,等下解释)。borderType为边缘点插值类型。
理解:数字图像的滤波可以简单的这么理解,就是对原图像的每一个像素滤波,那么对应这个像素滤波后的值是根据其相邻像素(包括自己那个点)与一个滤波模板进行相乘即可。所以具体到高斯滤波,我们只要知道这个高斯滤波的模板即可。
那怎么确定这个模板呢?首先这个模板的大小为ksize,其每个数字的计算是这样的:看成2维的高斯函数
其中 是归一化系数,因为其和要为1. μ是均值,σ是方差
为了简化,一般在二维图像处理中,ui和uj取0,sigma1和sigma2取相等。所以公式就简化为 :
因此很容易就计算出模板每个位置的数字了,简单吧!
但是要注意2点:
1、ksize的宽和高必须是奇数;
2、如果参数sigmaX=sigmaY=0, 则实际用的是公式sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 .
接下来的工作就是进入GaussianBlur函数内部,跟踪其函数代码,经过分析,在该函数内部调用了很多其他的函数,其调用的函数层次结构如下图所示:
这里我们分析源代码不需要深入到最底层,我们只需分析到函数createSeparableLinearFilter和getGaussianKernel这一层。
那就开始我们的源码分析工作吧!
从函数调用层次结构图可以看出,要分析函数GaussianBlur,必须先分析其调用过的内部函数。
因此首先分析函数getGaussianKernel。
功能:返回一个ksize*1的数组,数组元素满足高斯公式:
其中只有系数alpha和参数sigma未知,sigma的求法为:
如果输入sigma为非正,则计算公式为:sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 .
如果输入sigma为正,则就用该输入参数sigma。
最后alpha为归一化系数,即计算出的ksize个数之和必须为1,所以后面只需求ksize个数,计算其和并求倒即可。
cv::Mat cv::getGaussianKernel( int n, double sigma, int ktype )
{
const int SMALL_GAUSSIAN_SIZE = 7;
static const float small_gaussian_tab[][SMALL_GAUSSIAN_SIZE] =
{
{1.f},
{0.25f, 0.5f, 0.25f},
{0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f},
{0.03125f, 0.109375f, 0.21875f, 0.28125f, 0.21875f, 0.109375f, 0.03125f}
};
/*如果sigma小于0,且n为不大于7的奇整数,则核的滤波系数固定了,其固定在数组
small_gaussian_tab中,根据其n的长度来选择具体的值 ,如果不满足上面的,则固定核为0
固定核为0表示自己计算其核*/
const float* fixed_kernel = n % 2 == 1 && n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 ?
small_gaussian_tab[n>>1] : 0;
CV_Assert( ktype == CV_32F || ktype == CV_64F );//确保核元素为32位浮点数或者64位浮点数
Mat kernel(n, 1, ktype);//建立一个n*1的数组kernel,一个Mat矩阵包括一个矩阵头和一个指向矩阵元素的指针
float* cf = (float*)kernel.data;//定义指针cf指向kernel单精度浮点型数据
double* cd = (double*)kernel.data;//定义指针cd指向kernerl双精度浮点型数据
double sigmaX = sigma > 0 ? sigma : ((n-1)*0.5 - 1)*0.3 + 0.8;//当sigma小于0时,采用公式得到sigma(只与n有关)
double scale2X = -0.5/(sigmaX*sigmaX);//高斯表达式后面要用到
double sum = 0;
int i;
for( i = 0; i < n; i++ )
{
double x = i - (n-1)*0.5;
//如果自己算其核的话,就常用公式exp(scale2X*x*x)计算,否则就用固定系数的核
double t = fixed_kernel ? (double)fixed_kernel[i] : std::exp(scale2X*x*x);
if( ktype == CV_32F )
{
cf[i] = (float)t;//单精度要求时存入cf数组中
sum += cf[i];//进行归一化时要用到
}
else
{
cd[i] = t;//双精度时存入cd数组中
sum += cd[i];
}
}
sum = 1./sum;//归一化时核中各元素之和为1
for( i = 0; i < n; i++ )
{
if( ktype == CV_32F )
cf[i] = (float)(cf[i]*sum);//归一化后的单精度核元素
else
cd[i] *= sum;//归一化后的双精度核元素
}
return kernel;//返回n*1的数组,其元素或是单精度或是双精度,且符合高斯分布
}
高斯函数的使用方法:
-参数 InputArray表示输入图像的Mat对象。
-参数OutputArray表示模糊之后输出Mat对象
-参数Size表示卷积和大小,此参数决定模糊程度,Size(x,y)中x,y取值越大表现模糊程度越深,而且x,y必须为奇数。
-参数SigmaX表示高斯方程中x方向的标准方差
-参数SigmaY表示高斯方程中y方向的标准方差
-最后一个参数表示对边缘的处理方法,一般取默认值
使用方法一:Size(x,y)中x,y取正奇数,SigmaX,SigmaY取0,此时SigmaX,SigmaY会自动计算,公式为sigma = ((n-1)*0.5 - 1)*0.3 + 0.8,xy方向上都计算。
使用方法二:Size(x,y) 中的xy取0,SigmaX,SigmaY都不去0,此时xy会自动计算,公式为sigma1*(depth == CV_8U ? 3 : 4)*2 + 1)|1。xy都计算。
使用方法三:Size(x,y)中xy,SigmaX,SigmaY都不为0.
前两中方法总之x是SigmaX,y是SigmaY的6-9倍。