5. 滤波器

5.1 卷积

5.1.1 什么是图片卷积

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

在这里插入图片描述

5.1.2 步长

**步长就是卷积核在图像上移动的步幅.**上面例子中卷积核每次移动一个像素步长的结果, 如果将这个步长修改为2, 结果会如何? 为了充分扫描图片, 步长一般设为1.

5.1.3 padding

从上面例子中我们发现, 卷积之后图片的长宽会变小. 如果要保持图片大小不变, 我们需要在图片周围填充0. padding指的就是填充的0的圈数.
如果要保持卷积之后图片大小不变, 可以得出等式: ( N + 2 P − F + 1 ) = N (N + 2P - F + 1) = N (N+2PF+1)=N从而可以推导出 P = F − 1 2 P = \frac{F -1}{2} P=2F1

5.1.4 卷积核的大小

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

  1. 根据上面padding的计算公式, 如果要保持图片大小不变, 采用偶数卷积核的话, 比如4 * 4, 将会出现填充1.5圈零的情况.
  2. 奇数维度的过滤器有中心,便于指出过滤器的位置, 即OpenCV卷积中的锚点.

5.1.5 卷积案例

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

#导入图片
img = cv2.imread('./dog.jpeg')

# 相当于原始图片中的每个点都被平均了一下, 所以图像变模糊了.
kernel = np.ones((5, 5), np.float32) / 25
# ddepth = -1 表示图片的数据类型不变
dst = cv2.filter2D(img, -1, kernel)

# 很明显卷积之后的图片模糊了.
cv2.imshow('img', np.hstack((img, dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

5.2 方盒滤波与均值滤波(模糊效果)

boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) 方盒滤波.

  • 方盒滤波的卷积核的形式如下:
    • 全是1的矩阵
    • normalize = True时, a = 1 / (W * H) 滤波器的宽高
    • normalize = False是. a = 1
    • 一般情况我们都使用normalize = True的情况. 这时 方盒滤波 等价于 均值滤波
  • blur(src, ksize[, dst[, anchor[, borderType]]]) 均值滤波.
import cv2
import numpy as np

#导入图片
img = cv2.imread('./dog.jpeg')

# kernel = np.ones((5, 5), np.float32) / 25
# ddepth = -1 表示图片的数据类型不变
dst = cv2.blur(img, (5, 5))

# 很明显卷积之后的图片模糊了.
cv2.imshow('img', img)
cv2.imshow('dst', dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

5.3 高斯滤波(模糊效果)

高斯滤波的核心思想是让临近的像素具有更高的重要度. 对周围像素计算加权平均值, 较近的像素具有较大的权重值.高斯函数在是符合高斯分布(也叫正态分布)的数据的概率密度函数.高斯滤波就是使用符合高斯分布的卷积核对图片进行卷积操作. 所以高斯滤波的重点就是如何计算符合高斯分布的卷积核, 即高斯模板.
在这里插入图片描述

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

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

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

高斯滤波实战

# 高斯滤波
import cv2
import numpy as np

#导入图片
img = cv2.imread('./gaussian.png')
dst = cv2.GaussianBlur(img, (5, 5), sigmaX=1)
# np.hstack 同一窗口显示多张图片
cv2.imshow('img', np.hstack((img, dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

5.4 中值滤波(去噪点效果较好)

中值滤波原理非常简单, 假设有一个数组[1556789], 取其中的中间值(即中位数)作为卷积后的结果值即可.中值滤波对胡椒噪音(也叫椒盐噪音)效果明显.

# 中值滤波
import cv2
import numpy as np

#导入图片
img = cv2.imread('./papper.png')
# 注意这里的ksize就是一个数字
dst = cv2.medianBlur(img, 5)
cv2.imshow('img', np.hstack((img, dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

5.5 双边滤波(模糊效果)

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

双边滤波本质上是高斯滤波, 双边滤波和高斯滤波不同的就是:双边滤波既利用了位置信息又利用了像素信息来定义滤波窗口的权重。而高斯滤波只用了位置信息.

双边滤波中加入了对灰度信息的权重,即在邻域内,灰度值越接近中心点灰度值的点的权重更大,灰度值相差大的点权重越小。此权重大小,则由值域高斯函数确定。两者权重系数相乘,得到最终的卷积模板。

双边滤波可以保留边缘, 同时可以对边缘内的区域进行平滑处理.

双边滤波的作用就相当于做了美颜.

bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])

  • sigmaColor是计算像素信息使用的sigma
  • sigmaSpace是计算空间信息使用的sigma
# 双边滤波
import cv2
import numpy as np

#导入图片
img = cv2.imread('./lena.png')
dst = cv2.bilateralFilter(img, 7, 20, 50)
cv2.imshow('img', np.hstack((img, dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

5.6 索贝尔(sobel)算子(边缘检测)

分别通过卷积计算x轴边缘, y轴边缘, 然后相加得到图像的边缘

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

人眼如何识别图像边缘?

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

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

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

图像是二维的,即沿着宽度/高度两个方向.
我们使用两个卷积核对原图像进行处理:

  • 水平方向
    在这里插入图片描述
  • 垂直方向
    在这里插入图片描述
    这样的话,我们就得到了两个新的矩阵,分别反映了每一点像素在水平方向上的亮度变化情况和在垂直方向上的亮度变换情况.

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

# 索贝尔算子
import cv2
import numpy as np

#导入图片
img = cv2.imread('./chess.png')#
# x轴方向, 获取的是垂直边缘
dx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
dy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)

# 可利用numpy的加法, 直接整合两张图片
# dst = dx + dy
# 也可利用opencv的加法, 直接相加是因为非边缘部分的亮度很低, 基本为接近于0的黑色
dst = cv2.add(dx, dy)
cv2.imshow('dx', np.hstack((dx, dy, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.7 沙尔(Scharr)算子~~(Sobel算子的补充)~~

  • Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])
  • 内核大小为 3 时, 以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。 为解决这一问题,OpenCV提供了 Scharr函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确.
  • Scharr算子和Sobel很类似, 只不过使用不同的kernel值, 放大了像素变换的情况:
  • Scharr算子只支持3 * 3 的kernel所以没有kernel参数了.
  • Scharr算子只能求x方向或y方向的边缘.
  • Sobel算子的ksize设为-1就是Scharr算子.
  • Scharr擅长寻找细小的边缘, 一般用的较少.
    在这里插入图片描述
# 索贝尔算子.
import cv2
import numpy as np
#导入图片
img = cv2.imread('./lena.png')#
# x轴方向, 获取的是垂直边缘
dx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
# y轴方向, 获取的是水平边缘
dy = cv2.Scharr(img, cv2.CV_64F, 0, 1)

# 可利用numpy的加法, 直接整合两张图片
# dst = dx + dy
# 也可利用opencv的加法
dst = cv2.add(dx, dy)
cv2.imshow('dx', np.hstack((dx, dy, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()

5.8 拉普拉斯算子(边缘检测)

索贝尔算子是模拟一阶求导,导数越大的地方说明变换越剧烈,越有可能是边缘.
如果继续对f’(t)求导, 可以发现"边缘处"的二阶导数=0, 我们可以利用这一特性去寻找图像的边缘. 注意有一个问题,二阶求导为0的位置也可能是无意义的位置.

  • Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
  • 可以同时求两个方向的边缘
  • 对噪音敏感, 一般需要先进行去噪再调用拉普拉斯
# 拉普拉斯
import cv2
import numpy as np

#导入图片
img = cv2.imread('./chess.png')#
dst = cv2.Laplacian(img, -1, ksize=3)

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

5.9 Canny边缘检测

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

  • 低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
  • 高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
  • 最小响应: 图像中的边缘只能标识一次。

Canny边缘检测的一般步骤:

  • 去噪. 边缘检测容易受到噪声影响, 在进行边缘检测前通常需要先进行去噪, 一般用高斯滤波去除噪声.
  • 计算梯度: 对平滑后的图像采用sobel算子计算梯度和方向.
  • 非极大值抑制
    • 在获取了梯度和方向后, 遍历图像, 去除所有不是边界的点.
    • 实现方法: 逐个遍历像素点, 判断当前像素点是否是周围像素点中具有相同方向梯度的最大值.
    • 下图中, 点A,B,C具有相同的方向, 梯度方向垂直于边缘.
    • 判断点A是否为A,B,C中的局部最大值, 如果是, 保留该点;否则,它被抑制(归零)
  • 滞后阈值
  • Canny(img, minVal, maxVal, …)
# Canny
import cv2
import numpy as np

#导入图片
img = cv2.imread('./lena.png')#
# 阈值越小, 细节越丰富
lena1 = cv2.Canny(img, 100, 200)
lena2 = cv2.Canny(img, 64, 128)

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

更多推荐