欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

程序员文章站 2024-01-28 09:03:16
...

一. Sobel

1. 简介
Sobel算子利用一个横轴方向的算子和纵轴方向的算子,分别求得图像的亮度差分,再计算平方和或者绝对值之和,得到近似梯度(即最后的结果)。
横轴方向的算子为:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

纵轴方向的算子为:图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
(注意:这里正负号在左边还是右边并无影响,因为最后计算的时候会取左右或者上下之差的绝对值)

2. 举例说明
假设现在有输入,如下所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

以a13点为例,

  • 横轴方向计算:
    图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

可得:Gx = (a9 - a7) + 2 * (a14 - a12) +(a19 - a17)

  • 纵轴方向计算:图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
    可得:Gy = (a19 - a9) +2 * (a18 - a8) +(a17 - a7)

  • 计算平方和,求得a13点的值:
    图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

一般会将其简化为:图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
所以:G = |Gx| + |Gy| = |(a9 - a7) + 2 * (a14 - a12) +(a19 - a17) | + (a19 - a9) +2 * (a18 - a8) +(a17 - a7)

3. 代码
现在有图片ten.jpg,如下图所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
代码如下:

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()

输出结果如下图所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
从上述结果中发现,直接在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()

输出结果如下所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

二. Scharr

1. 简介
Scharr算子跟Sobel类似,只不过它的算子系数如下所示:

  • 横轴方向:
    图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
  • 纵轴方向:
    图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
    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()

输出结果如下所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
注意:调用Scharr算子不能跟Sobel算子一样,同时在横轴和纵轴方向算出结果,必须满足条件 dx >= 0 && dy >= 0 && dx+dy == 1,例如:x = cv2.Scharr(img, cv2.CV_64F, 1, 1) 这样调用就会报错。
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny

三. Laplacian

1. 简介
拉普拉斯算子,如下所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny可以看出,拉普拉斯的计算其实就是取中心点跟它相邻的四个点作差,包括横轴和纵轴方向,它在左右方向上算了两次,在上下方向上也算了两次,因此它是二阶的。

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()

输出结果如下所示:
图像处理之边缘检测:Sobel、Scharr、Laplacian、Canny
可以看到,拉普拉斯算子的调用也是比较简单的,只要一句话就能运行出结果。

四. 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、Scharr、Laplacian、Canny

可以看到,相比于Sobel计算的边缘,用Canny计算出来的结果边缘更细,没有那么粗大明亮。

总结

  • 调用cv2.Sobel的时候,一般不直接计算x, y轴直接结算出结果,而是分别计算出x轴和y轴的结果,再分配权重相加,权重一般取0.5。原因是,如果直接调用Sobel同时计算x,y的结果,一般达不到预期结果。
  • Canny的原理相对复杂一点,但是调用的时候比较简单,直接一句话就能使用了。