图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
一. Sobel
1. 简介
Sobel算子利用一个横轴方向的算子和纵轴方向的算子,分别求得图像的亮度差分,再计算平方和或者绝对值之和,得到近似梯度(即最后的结果)。
横轴方向的算子为:
纵轴方向的算子为:
(注意:这里正负号在左边还是右边并无影响,因为最后计算的时候会取左右或者上下之差的绝对值)
2. 举例说明
假设现在有输入,如下所示:
以a13点为例,
- 横轴方向计算:
可得:Gx = (a9 - a7) + 2 * (a14 - a12) +(a19 - a17)
-
纵轴方向计算:
可得:Gy = (a19 - a9) +2 * (a18 - a8) +(a17 - a7) -
计算平方和,求得a13点的值:
一般会将其简化为:
所以:G = |Gx| + |Gy| = |(a9 - a7) + 2 * (a14 - a12) +(a19 - a17) | + (a19 - a9) +2 * (a18 - a8) +(a17 - a7)
3. 代码
现在有图片ten.jpg,如下图所示:
代码如下:
import cv2
img = cv2.imread('ten.jpg', 0)
img = cv2.resize(img, (300, 500))
_, img = cv2.threshold(img, 180, 250, 0)
# 只计算纵轴方向结果
sobel_y = cv2.Sobel(img, -1, 0, 1)
cv2.imshow('sobel_y', sobel_y)
# 只计算横轴方向结果
sobel_x = cv2.Sobel(img, -1, 1, 0)
cv2.imshow('sobel_x', sobel_x)
# 同时计算x,y轴方向结果
sobel_xy = cv2.Sobel(img, -1, 1, 1)
cv2.imshow('sobel_xy', sobel_xy)
# 清除窗口
cv2.waitKey()
cv2.destroyAllWindows()
输出结果如下图所示:
从上述结果中发现,直接在x,y轴上调用cv2.Sobel并不能得到满意的结果。
改进方法:分别计算x轴,y轴的近似梯度,在加权求和。
改进代码如下:
import cv2
img = cv2.imread('ten.jpg', 0)
img = cv2.resize(img, (300, 500))
_, img = cv2.threshold(img, 180, 250, 0)
x = cv2.Sobel(img, cv2.CV_64F, 1, 0)
x = cv2.convertScaleAbs(x)
y = cv2.Sobel(img, cv2.CV_64F, 0, 1)
y = cv2.convertScaleAbs(y)
z = cv2.addWeighted(x, 0.5, y, 0.5, 0)
cv2.imshow('z', z)
cv2.waitKey()
cv2.destroyAllWindows()
输出结果如下所示:
二. Scharr
1. 简介
Scharr算子跟Sobel类似,只不过它的算子系数如下所示:
- 横轴方向:
- 纵轴方向:
2. 代码
Scharr的使用跟Sobel一样,代码如下:
import cv2
img = cv2.imread('ten.jpg', 0)
img = cv2.resize(img, (300, 500))
_, img = cv2.threshold(img, 180, 250, 0)
x = cv2.Scharr(img, cv2.CV_64F, 1, 0)
x = cv2.convertScaleAbs(x)
y = cv2.Scharr(img, cv2.CV_64F, 0, 1)
y = cv2.convertScaleAbs(y)
z = cv2.addWeighted(x, 0.5, y, 0.5, 0)
cv2.imshow('Scharr', z)
cv2.waitKey()
cv2.destroyAllWindows()
输出结果如下所示:
注意:调用Scharr算子不能跟Sobel算子一样,同时在横轴和纵轴方向算出结果,必须满足条件 dx >= 0 && dy >= 0 && dx+dy == 1,例如:x = cv2.Scharr(img, cv2.CV_64F, 1, 1) 这样调用就会报错。
三. Laplacian
1. 简介
拉普拉斯算子,如下所示:
可以看出,拉普拉斯的计算其实就是取中心点跟它相邻的四个点作差,包括横轴和纵轴方向,它在左右方向上算了两次,在上下方向上也算了两次,因此它是二阶的。
2. 代码
import cv2
img = cv2.imread('ten.jpg', 0)
img = cv2.resize(img, (300, 500))
_, img = cv2.threshold(img, 180, 250, 0)
z = cv2.Laplacian(img, cv2.CV_64F)
cv2.imshow('Laplacian', z)
cv2.waitKey()
cv2.destroyAllWindows()
输出结果如下所示:
可以看到,拉普拉斯算子的调用也是比较简单的,只要一句话就能运行出结果。
四. Canny
1. 简介
Canny算子相比Sobel而言,要稍微复杂一点,它的计算步骤可以总结为以下4点:
1.1 去噪
一般用高斯滤波。高斯滤波的介绍可以我前面写的博文:https://blog.csdn.net/weixin_43508499/article/details/107904998
1.2 计算梯度和方向
- 梯度大小计算:利用第一节介绍的Sobel算子计算
- 梯度方向,包括四个方向:水平,垂直,45°,135°,计算方法,利用Sobel算出Gx和Gy之后,角度 θ = argtan-1(Gy/Gx),这个角度用于下一步的非极大值抑制判断
1.3 非极大值抑制
- 一般通过Sobel计算出来的边缘不止一个像素,边缘可能粗大又明亮。
- 利用非极大值抑制,可以保留局部最大的梯度点,起到细化边缘的作用。
- 举例说明,如果计算出来某一点的梯度方向是45°,则它就跟它的右上角和左下角的点进行梯度比较,如果它的梯度最大,就认为它是边缘,保留它。
1.4 滞后阈值
二次判断,设置最小阈值minV和最大阈值maxV:
-
如果计算出来的梯度小于minV,则将其抛弃,
-
如果计算出来的梯度大于maxV,则将其保留
-
如果计算出来的梯度在 minV~maxV之间,就分两种情况考虑:
———如果该点与边缘相连,则保留;
———如果该点与边缘不相连,则抛弃。 -
对于阈值,如果设置的越小,保留的细节更丰富。
2. 代码
import cv2
img = cv2.imread('ten.jpg', 0)
img = cv2.resize(img, (300, 500))
_, img = cv2.threshold(img, 180, 250, 0)
canny = cv2.Canny(img, 100, 200)
cv2.imshow('canny', canny)
cv2.waitKey()
cv2.destroyAllWindows()
输出结果如下图所示:
可以看到,相比于Sobel计算的边缘,用Canny计算出来的结果边缘更细,没有那么粗大明亮。
总结
- 调用cv2.Sobel的时候,一般不直接计算x, y轴直接结算出结果,而是分别计算出x轴和y轴的结果,再分配权重相加,权重一般取0.5。原因是,如果直接调用Sobel同时计算x,y的结果,一般达不到预期结果。
- Canny的原理相对复杂一点,但是调用的时候比较简单,直接一句话就能使用了。
推荐阅读
-
「图像处理」Canny边缘检测原理解释
-
图像处理之图像的边缘、轮廓检测
-
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
-
opencv图像处理之Canny边缘检测
-
【图像处理】Sobel算子实现水平边缘检测、垂直边缘检测;45度、135度角边缘检测
-
C#图像处理之边缘检测(Smoothed)的方法
-
C#图像处理之边缘检测(Sobel)的方法
-
图像处理之应用卷积– 轧花与边缘检测
-
OpenCV图像处理教程C++(十五)边缘检测算法--sobel算子、拉普拉斯算子、Canny算子
-
python—opencv图像膨胀|图像腐蚀|图像边缘检测sobel算子/拉普拉斯算子/canny算子