实现边缘检测的一般流程:
- 使用高斯滤波器,以平滑图像,滤除噪声
- 计算图像中每个像素点的梯度强度和方向
- 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应
- 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘
- 通过抑制孤立的弱边缘最终完成边缘检测
去噪
由于边缘检测很容易受到噪声影响,所以第一步是使用高斯滤波器去除噪声,
计算图像梯度
对平滑后的图像使用 Sobel 算子计算水平方向和竖直方向的一阶导数(图像梯度)(Gx 和 Gy)。根据得到的这两幅梯度图(Gx 和 Gy)找到边界的梯度和方向,
θ
=
a
r
c
t
a
n
(
G
y
/
G
x
)
θ = arctan(Gy/Gx)
θ=arctan(Gy/Gx) 求梯度的方向
具体的Sobel算子点这里
非极大值抑制
在获得梯度的方向和大小之后,应该对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。
如上图,我们如果想算A是不是边界点,在相同的梯度方向上,离A最近的分别为B、C,三者进行比较,如果A比B、C都大,就把A的边界(梯度的方向一般总是与边界垂直)保留下来。
双阈值检测
在得到的边界里面,想要确定哪些是真正的边界,此时就需要双阈值minVal和maxVal。
详细的来说就是,当图像的灰度梯度高于 maxVal 时被认为是真的边界,那些低于 minVal 的边界会被抛弃。如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃。
我们可以看下上图中的几个点:
A高于maxVal----------->
是真正的边界
C低于maxVal但高于minVal且与A相连---------->
是真正的边界
B低于maxVal但高于minVal,无真正的边界相连----------->
抛弃
在这一步中选择合适的 maxVal和 minVal 对于能否得到好的结果非常重要
高低阈值比在 2 : 1 到 3 : 1 之间
Canny边缘检测
Canny函数
OpenCV中的Canny() 可以完成我们前面所说的几个步骤。
- 第一个参数 :是输入图像。
- 第二个参数:minVal
- 第三个参数:maxVal
- 第四个参数:是设置用来计算图像梯度的 Sobel卷积核的大小,默认值为 3
- 第五个参数:L2gradient,用来设定求梯度大小的方程。如果设为 True,就会使用我们上面提到过的方程,否则使用方程: Edge−Gradient (G) = jG2 xj + jG2 yj 代替,默认值为 False。
示例
import cv2
import numpy as np
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = cv2.imread('car.png', cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img, 100, 150)
v2 = cv2.Canny(img, 50, 150)
res = np.hstack((v1, v2))
cv2.namedWindow('res', cv2.WINDOW_NORMAL)
cv_show('res', res)
可以直观的看出来,第一个没有第二个提取的图像丰富,这也是合适的双阈值的重要性。
换张图看看
也可以创建滑动条来进行调整阈值,更加深刻的理解阈值的重要性。
# 创建滑动条进行调整阈值
minVal, maxVal = 0, 0 # 阈值
def nothing(x): # 更新图像
# 获取滑条位置
minVal = cv2.getTrackbarPos('minVal', 'Canny')
maxVal = cv2.getTrackbarPos('maxVal', 'Canny')
canny = cv2.Canny(img, minVal, maxVal)
cv2.imshow('Canny', canny)
# cv_show('Canny', canny)
img = cv2.imread('lena.jpg', 0)
cv2.namedWindow('Canny')
# 创建两个滑条
cv2.createTrackbar('minVal', 'Canny', 0, 255, nothing)
cv2.createTrackbar('maxVal', 'Canny', 0, 255, nothing)
while(True):
#等待关闭
k=cv2.waitKey(1)&0xFF
if k==27:
break
结果如下:
这个不是出现错误,滑动阈值以后就会显示检测图像。