美文网首页
OpenCV 中的滤波函数

OpenCV 中的滤波函数

作者: OurNote | 来源:发表于2019-12-20 17:51 被阅读0次
blur

也称为 box filter、均值滤波器,就是简单地将每个像素的值替换成邻域平均值。

cv::blur(image, result, cv::Size(3,3));

如果用 kernel (也称为 mask) 表示,就是

boxFilter1.png

如果采用积分图的方法,可以更快的计算这种 box filter 的结果。
在积分图中,只需要三次加法运算,一次乘法运算即可,即通过积分图,算出 kernel 内部区域的像素和,然后取平均。

积分图

积分图中每一点 (x,y)​ 的值是原图中对应位置左上角区域所有值的和:
I(x,y) = \sum_{x'\le x \\ y'\le y}i(x',y')
积分图的计算可以很高效:
I(x,y) = i(x,y) + I(x-1, y) + I(x,y-1) - I(x-1,y-1)

每次计算只需要新增一个像素值,其他值都是之前已经计算出来的。
积分图一旦计算出来,对任意矩形区域内像素和的计算都可以在常数时间(即计算时间固定,与区域的大小无关)内完成,例如:

integral.png

\sum_{A(x)<x'<C(x) \\ ~ A(y)<y'<C(y)} i(x', y') = I(C) -I(B) -I(D) + I(A)

GaussianBlur

在高斯滤波器中,当前像素值取邻域的加权平均,离当前像素越近,权重越大,权重服从高斯分布。
在实际应用中,几乎总是首选高斯滤波器,很少直接用 box filter.

cv::GaussianBlur(image, result, cv::Size(5,5), 1.5); //最后的参数表示高斯分布的方差 sigma

上述命令中,最后两个参数 kernel size 和 ​\sigma 如果只设置一个,则另一个可以通过以下公式推出:
\sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 \\ ksize = round(3 * \sigma * 2 + 1)

第二个式子很好理解,就是借助高斯函数的性质(距离均值 3 个标准差范围内的取值占总数的 99.7%),因此窗口大小就是 3 倍的 ​\sigma *2 (两边)然后再加上 1 (自身)。
第一个式子与第二个非常近似,但是又做了一些微调。

上述高斯滤波器内部实际上是先调用如下函数,产生服从高斯分布的一系列权重:

cv::getGaussianKernel(9, sigma, CV_32F); // 产生 9 个权重,与中心像素的距离依次为 -4,-3, ...3, 4

上述 9 个权重是经过归一化的,即和为 1,其公式为
k_i = \alpha \cdot e^{-\frac{(i-(ksize-1)/2)^2}{2\sigma ^2}}

其中 \alpha​ 满足归一化的要求,ksize 必须是奇数。如果要生成二维的高斯矩阵权重,则是先产生一个权重列向量,然后令该列向量与自身的转置相乘,得到高斯矩阵权重,最后再统一进行归一化,保证矩阵所有元素和为 1。

另外,还可以分别产生两个不同的高斯权重向量,对应行和列方向上的高斯模糊权重,然后把它们相乘得到高斯矩阵。由于满足这种分离的性质,高斯滤波器被称为可分离的滤波器。

非线性滤波器:中值滤波器

前边在进行滤波操作时,都只包含线性操作(算数平均、加权平均)。
另外还可以采用非线性操作,对应非线性滤波器。非线性滤波器不能表示成 kernel 矩阵卷积操作的形式。

中值滤波器是一种非线性滤波器。它把当前像素和邻域像素组成一个集合,排序之后,选择中间值(即排序中间位置的数值)替换当前像素值。

椒盐噪声:像素随机替换成白色或者黑点。在通讯时,如果部分像素值丢失,就会产生这种噪声。

中值滤波器可以有效的消除椒盐噪声,因为这些噪声点在排序时很难成为中间值,因此全都被剔除了。

Sobel 边缘检测滤波器

Sobel 也是线性滤波器,只不过采用了特殊的 kernel 矩阵:

sobel1.png

分别针对水平方向和垂直方向的操作。

用上述 kernel 进行操作,就是计算水平或者垂直方向像素值的差分,近似反映了像素值水平和垂直变化的速度,合在一起就是图像梯度的近似。

// 横向的
cv::Sobel(image,  // 输入
          sobelX, // 输出
          CV_8U,  // 输出数据类型
          1, 0,  // 采用第一个 kernel,即计算 x 轴方向(横向)像素变化率 
          3,  // kernel 的大小
          0.4, 128); // 缩放因子和偏移量

// 纵向的
cv::Sobel(image,  
          sobelY, 
          CV_8U,  
          0, 1,  // 采用第二个 kernel,即计算 y 轴方向(纵向)像素变化率 
          3, 
          0.4, 128); 

在默认情况下,差分运算的结果很可能超过 [0,255] 这个范围,而且有正有负,应该用 CV_16S 数据类型表示。经过上述缩放和偏移之后,才勉强适合用 CV_8U 表示,但还是需要饱和截断操作。

cv::Sobel(image, sobelX, CV_16S, 1, 0);

在分别得到横向、纵向变化率之后,可以整合起来计算梯度的大小

cv::Mat sobel;

sobel = abs(sobelX) + abs(sobelY); // 这里采用了 L1 范数

一般如果要显示最后的 sobel 边缘检测的结果,还需要把上述模值转化到 [0,255] 范围内。

高斯导数

sobel 实际上包含了平滑和求导两个操作,其中邻域像素累加相当于高斯平滑,距离越近的像素权重越高。
sobel 的 kernel size 可以选择 1, 3, 5 和 7,其中 1 代表 1×3 或者 3×1,此时是没有高斯平滑的。
对于大的 size,这种平滑更明显。此时,sobel 不是高通滤波器,而是带通滤波器,既消除了部分高频,又消除了部分低频。

其他梯度算子

与 Sobel 算子类似的还有其他几个计算梯度的算子,只是采用不同的 kernel.

  • Prewitt
    cv::Prewitt(image, prewittX, CV_16S, 1, 0);
    
prewitt1.png
  • Roberts
    cv::Roberts(image, robertsX, CV_16S, 1, 0);
    
roberts1.png
  • Scharr
    cv::Scharr(image, robertsX, CV_16S, 1, 0);
    
scharr1.png

上述所有的滤波器都是近似计算图像函数的一阶导数,像素变化大的区域计算得到的值较大,平坦的区域计算值较小。

Laplacian 算子

sobel 算子通过对图片函数求导,那些数值绝对值较高的点对应了边界区域:

laplacian1.jpg

如果继续求二阶导,则导数较大的点对应了过零点:

laplacian2.jpg

因此,也可以通过搜索二阶导的过零点来检测边界点。

Laplacian 算子的定义
L[I(x,y)] = \frac{\partial ^2 I}{\partial x^2} + \frac{\partial ^2 I}{\partial y^2}

对照 Hessian 矩阵:
H[I(x,y)] = \begin{bmatrix} \frac{\partial^2 I} {\partial x^2} & \frac{\partial^2 I}{\partial x \partial y} \\ \frac{\partial^2 I}{\partial x \partial y} & \frac{\partial^2 I}{\partial y^2} \end{bmatrix}

Laplacian 算子实际上就是 Hessian 矩阵的 Trace。
具体到图像操作中,二阶导有如下表达式:
\frac{\partial ^2 I} {\partial x^2} = [f(x+1,y)-f(x,y)]-[f(x,y)-f(x-1,y)] = f(x+1,y)+f(x-1,y)-2f(x,y) \\ \frac{\partial ^2 I}{\partial y^2} = [f(x,y+1)-f(x,y)]-[f(x,y)-f(x,y-1)] = f(x,y+1)+f(x,y-1)-2f(x,y)

所以最终 Laplacian 算子表达式为:
L[I(x,y)] = f(x+1,y)+f(x,y+1)+f(x-1,y)+f(x,y-1)-4f(x,y)

在具体实现中,可以用以下 kernel 进行卷积操作:

laplacian1.png

Laplacian 算子具有旋转 90 度不变性,即一幅图旋转 90 度及其倍数,对应的 Laplacian 算子操作结果相同。
为了得到更好的旋转不变性,可以将 Laplacian 算子 kernel 扩展为如下形式:

laplacian2.png

这样就具有了旋转 45 度及其倍数的不变性。

cv::Laplacian(input, 
              output, 
              int ddepth,  // 元素类型,由于输入的是CV_8U,为了避免数据溢出,一般设定为 CV_16S
              int ksize=1, // 求导数的 Sobel 算子的窗口大小,一般 ksize >1 奇数,如果取 ksize=1,则直接用上述 [1, -4, ...] kernel.
              double scale=1, // 是否对计算得到的值进行放缩
              double delta, // 在存储到 output 之前,是否添加 bias
              int borderType); // 边界像素插值方式,具体选项可以官网查看 https://docs.opencv.org/3.4/d2/de8/group__core__array.html#ga209f2f4869e304c82d07739337eae7c5
LoG (Laplacian of Gaussian)

Laplacian 算子对噪声比较敏感,因此一般在进行 Laplacian 之前先进行高斯平滑滤波。
两个步骤合并称为 LoG (Laplacian of Gaussian)。
在具体实现中,我们并不需要先高斯再拉普拉斯,而是两步并作一步:将拉普拉斯算子作用在高斯 kernel 上,得到新的 kernel,再与 image 做卷积:
\triangledown ^2(f*g) = f*\triangledown^2g

最后作用在 (x,y) ​ 位置上的卷积权重为
LoG(x,y) = -\frac{1}{\pi \sigma^4}\left[ 1-\frac{x^2+y^2}{2\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2} } \right]

同样也是通过 \sigma​ 设定滤波范围。

对高斯函数取拉普拉斯算子操作是什么样子的?

  • 一维形式
log1.png
  • 二维形式
log2.png

二维情况下得到的曲面很像“墨西哥草帽”。
\sigma​ 的大小决定了检测的粗粒度:

log3.png
DoG ( LoG 的近似)

Difference of Gaussians
为了减少 LoG 计算量,用两个不同 ​\sigma 的高斯做差,来近似 LoG

log4.png

上图中两个 \sigma​ 的取值好像反了。。。

相关文章

网友评论

      本文标题:OpenCV 中的滤波函数

      本文链接:https://www.haomeiwen.com/subject/pouinctx.html