5、双边滤波
双边滤波对于图像的边缘信息能够更好的保存,其原理为一个与空间距离相关的高斯函数与一个灰度距离相关的高斯函数相乘。
空间距离:
- 指的是当前点与中心点的欧式距离。空间域高斯函数其数学形式为:
- 其中, (xi,yi)为当前位置, (xc,yc)为中心点的位置,σ 为空间域标准差。
灰度距离:
- 指的是当前点灰度与中心点灰度的差的绝对值。值域高斯函数其数学形式为
- 其中, gray(xi,yi)为当前点灰度值, gray(xc,yc)为中心点灰度值, σ 为值域标准差。
注意:双边滤波本质上是高斯滤波,不同的是:
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算子采用离散差分算子计算图像像素点亮度值的近似梯度。
图像是二维的,即沿着宽度/高度两个方向,我们使用两个卷积核对原图像进行处理。
这样的话,我们就得到了两个新的矩阵,分别反映了每一点像素在水平方向上的亮度变化和在垂直方向上的亮度变化情况。
综合考虑这两个方向的变化,我们使用以下公式反映某个像素的梯度变化情况:
有时候为了简单起见,也直接使用绝对值相加替代:
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值,放大了像素变换的情况:
注意: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算子计算梯度和方向。
- 梯度的方向被归为四类:垂直、水平和两个对角线。
- 计算出来的梯度和方向大概如下图:
- 非极大值抑制(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()