【OpenCV图像处理6】滤波器

六、滤波器

1、卷积

1.1 什么是图像卷积

图像卷积就是卷积核在图像上按行滑动遍历像素时不断的相乘求和的过程。

1.2 步长

步长就是卷积核在图像上移动的步幅。

上面的例子中卷积核每次移动一个像素步长的结果,如果将这个步长修改为2,结果会如何?

为了充分扫描图像,步长一般设为1。

1.3 padding

从上面例子中我们发现,卷积之后图像的长宽会变小,如果要保持图像大小不变,我们需要在图像周围填充0,padding指的就是填充0的圈数。

我们可以通过公式计算出需要填充0的圈数:

  • 输入体积大小:H1 * W1 * D1
  • 四个超参数:
    • Filter数量K
    • Filter大小F
    • 步长S
    • 零填充大小P
  • 输出体积大小:H2 * W2 * D2
    • H2 = (H1 - F + 2P) / S + 1
    • W2 = (W1 - F + 2P) / S +1
    • D2 = K

如果要保持卷积之后图像大小不变,可以得出等式:(N + 2P - F + 1) = N 从而可以推导出 P = F − 1 2 P = \frac {F-1} {2} P=2F1

1.4 卷积核的大小

图像卷积中,卷积核一般为奇数,比如 3 * 3,5 * 5,7 * 7,为什么一般是奇数呢?出于以下两个方面的考虑:

1、根据上面padding的计算公式,如果要保持图像大小不变,采用偶数卷积核的话,比如 4 * 4,将会出现填充1.5圈零的情况。

2、奇数维度的滤波器有中心,便于指出滤波器的位置,即OpenCV卷积中的锚点。

1.5 卷积案例

filter2D()用法:

cv2.filter2D(src, ddepth, kernel, dst, anchor, delta, borderType)

参数说明:

  • ddepth:卷积之后图像的位深,即卷积之后图像的数据类型,一般设为-1,表示和原图像类型一致。
  • kernel:卷积核大小,用元组或者ndarray表示,要求数据类型必须是float型。
  • anchor:锚点,即卷积核的中心点,是可选参数,默认是(-1, -1)。
  • delta:可选参数,表示卷积之后额外加的一个值,相当于线性方程中的偏差,默认是0。
  • borderType:边界类型,一般不设置。

代码实现(锐化)

import cv2
import numpy as np

img = cv2.imread('../resource/r_cat.jpg')

# 相当于原始图像中的每个点都被平均了一下,所以图像变模糊了
# kernel = np.ones((5, 5), np.float32) / 25

# 突出轮廓
# kernel = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]])

# 浮雕效果
# kernel = np.array([[-2, 1, 0], [-1, 1, 1], [0, 1, 2]])

# 锐化
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])

dst = cv2.filter2D(img, -1, kernel)

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

2、方盒滤波和均值滤波

2.1 方盒滤波

boxFilter()用法:

cv2.boxFilter(src, ddepth, ksize, dst, anchor, normalize, borderType)

参数说明:

  • ddepth:卷积之后图像的位深,即卷积之后图像的数据类型,一般设为-1,表示和原图像类型一致。

  • Ksize:方盒滤波卷积核大小

方盒滤波的卷积核形式如下:

  • 当 normalize = True 时,a = 1 / (W * H) 滤波器的宽高
  • 当 normalize = False 时,a = 1
  • 一般情况下都使用 normalize = True,这是 方盒滤波 等价于 均值滤波

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/r_cat.jpg')

# 不用手动创建卷积核,只需要告诉方盒滤波,卷积核的大小是多少
dst = cv2.boxFilter(img, -1, (5, 5), normalize=True)

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

2.2 均值滤波

blur()用法:

cv2.blur(src, ksize, dst, anchorborderType)

参数说明:

  • anchorborderType:用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/r_cat.jpg')

dst = cv2.blur(img, (5, 5))

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

3、高斯滤波

要理解高斯滤波首先要知道什么是高斯函数:

高斯函数是符合高斯分布(也叫正态分布)数据的概率密度函数。

高斯函数的特点是以x轴某一点(这一点称为均值)为对称轴,越靠近中心数据发生的概率越高,最终形成一个两边平缓,中间陡峭的钟型(也被称为帽子)图形。

高斯函数的一般形式为:

高斯滤波就是使用符合高斯分布的卷积核对图像进行卷积操作,所以高斯滤波的重点是如何计算符合高斯分布的卷积核,即高斯模板

假定中心点的坐标是(0, 0),那么取距离它最近的8个点坐标,为了计算,需要设 定 σ \sigma σ 的值。假定 σ \sigma σ = 1.5,则模糊半径为1的高斯模板如下:

我们可以观察到越靠近中心,数值越大,越边缘的数值越小,符合高斯分布的特点。

通过高斯函数计算出来的是概率分布函数,所以我们还要确保这九个点加起来为1,这九个点的权重总和等于0.4787147,因此,上面九个值还要分别除以0.4787147,得到最终的高斯模板。

注:有些整数高斯模板是在归一化后的高斯模板的基础上每个数除以左上角的值,然后取整。

有了卷积核,计算高斯滤波就简单了。假设现在有9个像素点,灰度值(0 ~ 255)的高斯滤波计算如下:

将这9个值加起来,就是中心点的高斯滤波的值。对所有点重复这个过程,就得到了高斯模糊后的图像。

GaussianBlur()用法:

cv2.GaussianBlur(src, ksize, sigmaX, dst, sigmaY, borderType)

参数说明:

  • ksize:高斯核的大小
  • sigmaX:X轴的标准差
  • sigmaY:Y轴的标准差,默认为0,这时 sigmaY = sigmaX
  • 如果没有指定sigma值,则会分别从ksize的宽度和高度中计算sigma
  • 选择不同的sigma值会得到不同的平滑效果,sigma越大,平滑效果越明显。

代码实现一: 模糊

import cv2
import numpy as np

img = cv2.imread('../resource/r_cat.jpg')

# dst = cv2.GaussianBlur(img, (9, 9), sigmaX=100)

# 如果不指定sigmaX,会使用ksize计算sigma
dst = cv2.GaussianBlur(img, (9, 9), sigmaX=0)

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

代码实现二: 去噪

import cv2
import numpy as np

img = cv2.imread('../resource/cv.webp')

dst = cv2.GaussianBlur(img, (5, 5), sigmaX=1)

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

4、中值滤波

中值滤波原理非常简单,假设有一个数组[1 5 5 6 7 8 9],取1其中的中间值(即中位数)作为卷积后的结果值即可。

中值滤波对胡椒噪音(也叫椒盐噪音)效果明显。

medianBlur()用法:

cv2.medianBlur(src, ksize, dst)

参数说明:

  • ksize:就是一个数字

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/cv_noise.webp')

dst = cv2.medianBlur(img, 5)

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5、双边滤波

双边滤波对于图像的边缘信息能够更好的保存,其原理为一个与空间距离相关的高斯函数与一个灰度距离相关的高斯函数相乘。

空间距离:

  • 指的是当前点与中心点的欧式距离。空间域高斯函数其数学形式为: e − ( x i − x c ) 2 + ( y i − y c ) 2 2 σ 2 e^{-\frac {(x_i-x_c)^2 + (y_i-y_c)^2} {2\sigma^2}} e2σ2(xixc)2+(yiyc)2

  • 其中, ( x i , y i ) (x_i, y_i) (xi,yi)为当前位置, ( x c , y c ) (x_c, y_c) (xc,yc)为中心点的位置, σ \sigma σ 为空间域标准差。

灰度距离:

  • 指的是当前点灰度与中心点灰度的差的绝对值。值域高斯函数其数学形式为: e − ( g r a y ( x i , y i ) − g r a y ( x c , y c ) ) 2 2 σ 2 e^{-\frac {(gray(x_i, y_i)-gray(x_c, y_c))^2}{2\sigma^2}} e2σ2(gray(xi,yi)gray(xc,yc))2

  • 其中, g r a y ( x i , y i ) gray(x_i, y_i) gray(xi,yi)为当前点灰度值, g r a y ( x c , y c ) gray(x_c, y_c) gray(xc,yc)为中心点灰度值, σ \sigma σ 为值域标准差。

注意:双边滤波本质上是高斯滤波,不同的是:

1、双边滤波既利用了位置信息又利用了像素信息来定义滤波窗口的权重。

2、高斯滤波只利用了位置信息。

对于高斯滤波,仅用空间距离的权值系数核与图像卷积后,确定中心点的灰度值。即认为离中心点越近的点,其权重系数越大。

双边滤波中加入了对灰度信息的权重,即在邻域内,灰度值越接近中心点灰度值点的权重更大,灰度值相差大的点的权重越小。此权重大小,则由值域高斯函数确定。

两者权重系数相乘,得到最终的卷积模板。由于双边滤波需要每个中心点邻域的灰度信息来确定其系数,所以其速度比一般的滤波慢很多,而且计算量增长速度为核大小的平方。

双边滤波可以保留边缘,同时可以对边缘内的区域进行平滑处理。双边滤波的作用就相当于做了美颜。

bilateralFilter()用法:

cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace, dst, borderType)

参数说明:

  • sigmaColor:计算像素信息使用的sigma
  • sigmaSpace:计算空间信息使用的sigma

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/lena.bmp')

dst = cv2.bilateralFilter(img, 10, sigmaColor=20, sigmaSpace=50)

cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

6、索贝尔(Sobel)算子

边缘是像素发生跃迁的位置,是图像的显著特征之一,在图像特征提取,对象检测,模式识别等方面都有重要的作用。

人眼如何识别图像边缘?

比如有一幅图像,里面有一条线,左边很亮,右边很暗,那人眼就很容易识别这条线作为边缘,也就是像素的灰度值快速变化的地方

sobel算子对图像求一阶导数,一阶导数越大,说明像素在该方向的变化越大,边缘信号越强。

因为图像的灰度值都是离散的数字,sobel算子采用离散差分算子计算图像像素点亮度值的近似梯度。

图像是二维的,即沿着宽度/高度两个方向,我们使用两个卷积核对原图像进行处理。
G x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] ∗   I (水平方向) G_x= \begin{bmatrix} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \\ \end{bmatrix} *\ I \tag{水平方向} Gx= 121000+1+2+1  I(水平方向)

G y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] ∗   I (垂直方向) G_y= \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \\ \end{bmatrix} *\ I \tag{垂直方向} Gy= 10+120+210+1  I(垂直方向)

这样的话,我们就得到了两个新的矩阵,分别反映了每一点像素在水平方向上的亮度变化和在垂直方向上的亮度变化情况。

综合考虑这两个方向的变化,我们使用以下公式反映某个像素的梯度变化情况: G = G x 2 + G y 2 G = \sqrt {G_x^2 + G_y^2} G=Gx2+Gy2

有时候为了简单起见,也直接使用绝对值相加替代: G = ∣ G x ∣ + ∣ G y ∣ G = \vert G_x \vert + \vert G_y \vert G=Gx+Gy

Sobel()用法:

cv2.Sobel(src, ddepth, dx, dy, dst, ksize, scale, delta, borderType)

参数说明:

  • x, dy:x,y方向上的求导阶数

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/chess.webp')

# x轴方向,获取的是垂直边缘
dx = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=0, ksize=1)
# y轴方向,获取的是水平边缘
dy = cv2.Sobel(img, cv2.CV_64F, dx=0, dy=1, ksize=1)

# x y 方向合并
dst = cv2.add(dx, dy)

cv2.imshow('img', img)
cv2.imshow('dx dy', np.hstack((dx, dy)))
cv2.imshow('dst', dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

原图:

x,y方向上的图像:

x,y方向上的图像合并之后:

7、沙尔(Scharr)算子

Scharr()用法:

cv2.Scharr(src, ddepth, dx, dy, dst, scale, delta, borderType)

说明:

  • 当内核大小为3时,以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取导数的近似值)。
  • 为解决这一问题,OpenCV提供了Scharr函数,但该函数仅作用于大小为3的内核
  • 该函数的运算与Sobel函数一样快,但结果却更加精确。
  • Scharr算子和Sobel很类似,只不过使用不同的kernel值,放大了像素变换的情况:

G x = [ − 3 0 + 3 − 10 0 + 10 − 3 0 + 3 ] ∗   I (水平方向) G_x= \begin{bmatrix} -3 & 0 & +3 \\ -10 & 0 & +10 \\ -3 & 0 & +3 \\ \end{bmatrix} *\ I \tag{水平方向} Gx= 3103000+3+10+3  I(水平方向)

G y = [ − 3 − 10 − 3 0 0 0 + 3 + 10 + 3 ] ∗   I (垂直方向) G_y= \begin{bmatrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ +3 & +10 & +3 \\ \end{bmatrix} *\ I \tag{垂直方向} Gy= 30+3100+1030+3  I(垂直方向)

注意:Scharr算子

(1) 只支持3 * 3 的kernel,所以没有kernel参数。

(2) 只能求 x 方向或 y 方向的边缘。

(3) ksize 设为 -1 就是 Scharr 算子。

(4) 擅长寻找细小的边缘,一般用的比较少。

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/lena.bmp')

# x轴方向,获取的是垂直边缘
dx = cv2.Scharr(img, cv2.CV_64F, dx=1, dy=0)
# y轴方向,获取的是水平边缘
dy = cv2.Scharr(img, cv2.CV_64F, dx=0, dy=1)

# x y 方向合并
dst = cv2.add(dx, dy)

cv2.imshow('lena', np.hstack((dx, dy, dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

8、拉普拉斯算子

索贝尔算子是模拟一阶求导,导数越大的地方说明变换越剧烈,越有可能是边缘。

那如果继续对 f’(t) 求导呢?

可以发现边缘处的二阶导数为0,我们可以利用这一特性去寻找图像的边缘。

注意有一个问题:二阶导数为0的位置也可能是无意义的位置

拉普拉斯算子推导过程:(以 x 方向求解为例)

这样就得到了拉普拉斯算子的卷积核即卷积模板。

Laplacian()用法:

cv2.Laplacian(src, ddepth, dst, ksize, scale, delta, borderType)

说明:

  • 可以同时求两个方向的边缘。
  • 对噪音敏感,一般需要先进行去噪再调用拉普拉斯。

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/chess.bmp')

dst = cv2.Laplacian(img, -1, ksize=3)

cv2.imshow('chess', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

9、Canny边缘检测

Canny边缘检测算法 是 John F. Canny 于1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的最优算法,最优边缘检测的三个主要评价标准是:

1、低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。

2、高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。

3、最小响应: 图像中的边缘只能标识一次。

Canny边缘检测的一般步骤:

  • 去噪:边缘检测容易受到噪声影响,在进行边缘检测前通常需要先进行去噪,一般用高斯滤波去除噪声。
  • 计算梯度:对平滑后的图像采用Sobel算子计算梯度和方向。
    • G = G x 2 + G y 2 G = \sqrt {G_x^2 + G_y^2} G=Gx2+Gy2 为了方便一般可以改用绝对值 G = ∣ G x ∣ + ∣ G y ∣ G = \vert G_x \vert + \vert G_y \vert G=Gx+Gy
    • θ = a r c t a n ( G y G x ) \theta = arctan(\frac {G_y} {G_x}) θ=arctan(GxGy)
    • 梯度的方向被归为四类:垂直、水平和两个对角线。
    • 计算出来的梯度和方向大概如下图:

  • 非极大值抑制(NMS)
    • 在获取了梯度和方向之后,遍历图像,去除所有不是边界的点。
    • 实现方法:逐个遍历像素点,判断当前像素点是否是周围像素点中具有相同方向梯度的最大值。
    • 下图中,点A,B,C具有相同的方向,梯度方向垂直于边缘。
    • 判断点A是否为A,B,C中的局部最大值,如果是,保留该点;否则,它被抑制(归零)。

  • 更形象的例子:

  • 滞后阈值

Canny()用法:

cv2.Canny(image, threshold1, threshold2, edges, apertureSize, L2gradient)

代码实现:

import cv2
import numpy as np

img = cv2.imread('../resource/lena.bmp')

lena1 = cv2.Canny(img, 100, 200)
lena2 = cv2.Canny(img, 64, 128)
lena3 = cv2.Canny(img, 80, 150)

cv2.imshow('lena', np.hstack((lena1, lena2, lena3)))
cv2.waitKey(0)
cv2.destroyAllWindows()

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,它提供了丰富的图像处理计算机视觉算法。下面是一些常见的OpenCV图像处理功能: 1. 读取和显示图像:OpenCV可以读取各种图像格式(如JPEG、PNG、BMP等),并提供了显示图像的函数。 2. 图像的基本操作:OpenCV提供了一系列函数来处理图像,包括调整大小、裁剪、旋转、翻转等。 3. 图像滤波:OpenCV支持各种滤波器,如均值滤波、高斯滤波、中值滤波等,用于平滑图像或去除噪声。 4. 边缘检测:OpenCV提供了多种边缘检测算法,如Sobel算子、Canny边缘检测等,用于检测图像中的边缘。 5. 图像分割:OpenCV提供了多种图像分割算法,如基于阈值的分割、基于区域的分割等,用于将图像分成不同的区域或对象。 6. 特征提取和描述:OpenCV支持各种特征提取和描述算法,如SIFT、SURF、ORB等,用于在图像中检测和描述关键点。 7. 目标检测和跟踪:OpenCV提供了多种目标检测和跟踪算法,如Haar级联检测器、HOG+SVM、深度学习等,用于在图像或视频中检测和跟踪目标。 8. 图像配准:OpenCV提供了图像配准算法,用于将多幅图像对齐,如基于特征的配准、基于相位相关的配准等。 9. 图像变换:OpenCV支持各种图像变换,如仿射变换、透视变换等,用于将图像进行形状变换或投影变换。 10. 图像分析和测量:OpenCV提供了多种图像分析和测量函数,如轮廓检测、形状匹配、图像标定等,用于分析和测量图像中的对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_leoatliang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值