文章目录
图像梯度与边缘提取
Sobel和Scharr算子
Sobel算子是高斯平滑加微分运算的联合运算,因此它更抗噪声。如果ksize = -1,则使用3x3 Scharr滤波器,比3x3 Sobel滤波器具有更好的结果。
Sobel算子:
k
e
r
n
e
l
=
[
−
1
0
1
−
2
0
2
−
1
0
1
]
/
/
水
平
kernel = \left[ {\begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \\ \end{matrix}} \right]//水平
kernel=⎣⎡−1−2−1000121⎦⎤//水平
k
e
r
n
e
l
=
[
−
1
−
2
−
1
0
0
0
1
2
1
]
/
/
垂
直
kernel = \left[ {\begin{matrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \\ \end{matrix}} \right]//垂直
kernel=⎣⎡−101−202−101⎦⎤//垂直
Scharr算子:
k
e
r
n
e
l
=
[
−
3
0
3
−
10
0
10
−
3
0
3
]
/
/
水
平
kernel = \left[ {\begin{matrix} -3 & 0 & 3 \\ -10 & 0 & 10 \\ -3 & 0 & 3 \\ \end{matrix}} \right]//水平
kernel=⎣⎡−3−10−30003103⎦⎤//水平
k
e
r
n
e
l
=
[
−
3
−
10
−
3
0
0
0
3
10
3
]
/
/
垂
直
kernel = \left[ {\begin{matrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ 3 & 10 & 3 \\ \end{matrix}} \right]//垂直
kernel=⎣⎡−303−10010−303⎦⎤//垂直
cv2.Sobel
Sobel滤波器
Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst
- sec:是需要处理的图像;
- ddepth:图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度
- dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
- dst:目标图像
- ksize是Sobel算子的大小,必须为1、3、5、7。
- scale是缩放导数的比例常数,默认情况下没有伸缩系数。
- delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中。
- borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
注意: Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,所以Sobel建立的图像位数不够,会有截断。第二个参数可以传cv.CV_32F。在经过处理后,要用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。
cv2.convertScaleAbs
在输入数组的每个元素上,函数convertScaleAbs依次执行三个操作:缩放,获取绝对值,转换为无符号的8位类型
convertScaleAbs(src[, dst[, alpha[, beta]]]) -> dst
- src: 输入数组。
- dst: 输出数组。
- alpha: 可选比例因子。
- beta: 可选增量添加到缩放值。
cv2.Scharr
Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) -> dst
参数与Sobel()基本一致
Scharr()函数提供了比标准Sobel函数更精确的计算结果。
示例
def sobel_gradient(image):
"""sobel算子梯度滤波(一阶导数)"""
grad_x = cv.Sobel(image, cv.CV_32F, 1, 0) # x方向的
grad_y = cv.Sobel(image, cv.CV_32F, 0, 1) # y方向的
# grad_x = cv.Scharr(image, cv.CV_32F, 1, 0) # 采用Scharr边缘更突出
# grad_y = cv.Scharr(image, cv.CV_32F, 0, 1)
gradx = cv.convertScaleAbs(grad_x) # 由于算完的图像有正有负,所以对其取
绝对值并转换回uint8
grady = cv.convertScaleAbs(grad_y)
# 计算两个图像的权值和,dst = src1*alpha + src2*beta + gamma
gradxy = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
cv.imshow("gradx", gradx)
cv.imshow("grady", grady)
cv.imshow("gradient", gradxy)
结果:
参考链接:
Laplacian算子
计算了由关系
Δ s r c = δ 2 s r c δ x 2 + δ 2 s r c δ y 2 \Delta src = \frac{\delta^2 src}{\delta x^2}+\frac{\delta^2 src}{\delta y^2} Δsrc=δx2δ2src+δy2δ2src
给出的图像的拉普拉斯图,它是每一阶导数通过Sobel算子计算。如果ksize = 1,然后使用以下内核用于过滤:
k
e
r
n
e
l
=
[
0
1
0
1
−
4
1
0
1
0
]
/
/
4
邻
域
式
,
默
认
的
是
这
个
kernel = \left[ {\begin{matrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \\ \end{matrix}} \right]//4邻域式,默认的是这个
kernel=⎣⎡0101−41010⎦⎤//4邻域式,默认的是这个
补充:
k
e
r
n
e
l
=
[
1
1
1
1
−
8
1
1
1
1
]
/
/
8
邻
域
式
kernel = \left[ {\begin{matrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \\ \end{matrix}} \right]//8邻域式
kernel=⎣⎡1111−81111⎦⎤//8邻域式
cv2.Laplacian
Laplacian滤波器
Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst
- src:原图像
- ddepth:图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度
- dst:目标图像
- ksize:算子的大小,必须为1、3、5、7。默认为1
- scale:是缩放导数的比例常数,默认情况下没有伸缩系数
- delta:是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
- borderType:是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT
示例
def laplace_gradient(image):
"""Laplacian算子梯度滤波(二阶导数)"""
dst = cv.Laplacian(image,cv.CV_32F)
lpls = cv.convertScaleAbs(dst)
cv.imshow("laplace_gradient", lpls)
结果:
canny边缘提取
- 高斯模糊——gaussian
- 灰度转换——cvtColor
- 计算梯度——Sobel/Scharr
- 非最大信号抑制
- 高低阈值输出二值图像
-
第一步:使用高斯滤波器进行滤波,去除噪音点
- 使用5x5高斯滤波器消除图像中的噪声
-
第二步:使用sobel算子,计算出每个点的梯度大小和梯度方向
- Sobel核在水平和垂直方向上对平滑的图像进行滤波,以在水平方向(Gx)和垂直方向(Gy)上获得一阶导数
E d g e _ G r a d i e n t ( G ) = G x 2 + G y 2 A n g l e ( θ ) = tan − 1 ( G y G x ) Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2} \\ Angle \; (\theta) = \tan^{-1} \bigg(\frac{G_y}{G_x}\bigg) Edge_Gradient(G)=Gx2+Gy2Angle(θ)=tan−1(GxGy)
- Sobel核在水平和垂直方向上对平滑的图像进行滤波,以在水平方向(Gx)和垂直方向(Gy)上获得一阶导数
-
第三步:使用非极大值抑制(只有最大的保留),消除边缘检测带来的杂散效应
- 在获得梯度大小和方向后,将对图像进行全面扫描,以去除可能不构成边缘的所有不需要的像素。为此,在每个像素处,检查像素是否是其在梯度方向上附近的局部最大值。
点A在边缘(垂直方向)上。渐变方向垂直于边缘。点B和C在梯度方向上。因此,将A点与B点和C点进行检查,看是否形成局部最大值。如果是这样,则考虑将其用于下一阶段,否则将其抑制(置为零)。 简而言之,得到的结果是带有“细边”的二进制图像。
-
第四步:应用双阈值(磁滞阈值),来确定真实和潜在的边缘
- 需要两个阈值minVal和maxVal。强度梯度大于maxVal的任何边缘必定是边缘,而小于minVal的那些边缘必定是非边缘,因此将其丢弃。介于这两个阈值之间的对象根据其连通性被分类为边缘或非边缘。如果将它们连接到“边缘”像素,则将它们视为边缘的一部分。否则,它们也将被丢弃。
边缘A在maxVal之上,因此被视为“确定边缘”。尽管边C低于maxVal,但它连接到边A,因此也被视为有效边,我们得到了完整的曲线。但是边缘B尽管在minVal之上并且与边缘C处于同一区域,但是它没有连接到任何“确保边缘”,因此被丢弃。因此,非常重要的一点是我们必须相应地选择minVal和maxVal以获得正确的结果。
-
第五步:通过抑制弱边缘来完成最终的边缘检测
cv2.canny
canny边缘检测
Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges
- image:要检测的图像
- threshold1:阈值1(最小值)
- threshold2:阈值2(最大值),使用此参数进行明显的边缘检测。
- edges:图像边缘信息
- apertureSize:sobel算子(卷积核)大小,默认情况下为3
- L2gradient :布尔值。
- True: 使用更精确的L2范数进行计算(即两个方向的导数的平方和再开方)
E d g e _ G r a d i e n t ( G ) = G x 2 + G y 2 Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2} \\ Edge_Gradient(G)=Gx2+Gy2 - False:使用L1范数(直接将两个方向导数的绝对值相加)
E d g e _ G r a d i e n t ( G ) = ∣ G x ∣ + ∣ G y ∣ Edge\_Gradient \; (G) = \left| G_x\right| + \left| G_y\right| Edge_Gradient(G)=∣Gx∣+∣Gy∣
- True: 使用更精确的L2范数进行计算(即两个方向的导数的平方和再开方)
注意: 一般来说,threshold1 : threshold2 = 1 : 3 / 1 : 2 (我也不知道为什么)
示例
def canny(image):
"""canny边缘提取"""
blurred = cv.GaussianBlur(image, (3, 3), 0)
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
# image:要检测的图像,threshold1:阈值1(最小值),threshold2:阈值2(最大值),使用此参数进行明显的边缘检测,
# canny_output2 = cv.Canny(grad_x, grad_y, 30, 150)
canny_output1 = cv.Canny(gray, 50, 150) # 也可以直接传入gray
cv.imshow("image", image)
cv.imshow("Canny", canny_output1)
# cv.imshow("Canny2", canny_output2)
结果:
参考链接: