第 13 章

影像梯度与边缘侦测

本章共 5 个小节 · 影像梯度 · Sobel · Scharr · Laplacian · Canny
边缘是影像中亮度或颜色变化明显的位置。影像梯度用来描述像素值变化的方向与强度, OpenCV 提供 Sobel()Scharr()Laplacian()Canny() 等函数来检测边缘。
13-1

影像梯度的基础观念

13-1-1 认识影像梯度

如果一排像素值从暗到亮快速变化,变化最明显的位置通常就是边界。水平边界需要观察垂直方向的变化; 垂直边界则需要观察水平方向的变化。梯度越大,表示该位置越可能是边缘。

观念 边缘检测通常不会只看单一像素,而是用卷积核比较邻域中亮度变化。不同算子使用不同核,输出结果也会不同。
梯度观念图
影像梯度与边缘概念示意
机器视觉常以梯度变化寻找轮廓与边界,参考原书第 13-3 页。

13-1-2 影像梯度与机器视觉

一幅影像的梯度可以看成影像函数在 x 轴与 y 轴方向上的变化量。机器视觉常用梯度影像抽取边缘、轮廓和纹理特征,因为梯度对拍摄亮度的整体变化比较不敏感。

13-2

OpenCV 函数 Sobel()

13-2-1 Sobel 算子

Sobel 算子使用一阶导数近似,分别计算 x 方向和 y 方向的梯度。 x 方向梯度强调垂直边缘;y 方向梯度强调水平边缘。由于梯度可能出现负值,若输出深度使用 cv2.CV_64F, 通常会再用 convertScaleAbs() 转成可显示的 8 位影像。

13-2-4 Sobel() 函数

dst = cv2.Sobel(src, ddepth, dx, dy, ksize, scale, delta, borderType)
参数说明
ddepth输出影像深度。若需保留负梯度,常用 cv2.CV_64F
dxx 方向导数阶数。设为 1 时检测垂直方向变化。
dyy 方向导数阶数。设为 1 时检测水平方向变化。
ksizeSobel 核大小,常用 357

13-2-5 考量 ddepth 与取绝对值函数 convertScaleAbs()

程序实例 ch13_1.py:convertScaleAbs() 处理负梯度

# ch13_1.py import cv2 import numpy as np x = np.array([-17, -65, 136, 100], dtype=np.int16) dst = cv2.convertScaleAbs(x) print(dst)

13-2-6 x 轴方向的影像梯度

程序实例 ch13_2.py:Sobel x 方向梯度

# ch13_2.py import cv2 src = cv2.imread("map.jpg") dst = cv2.Sobel(src, cv2.CV_64F, 1, 0) cv2.imshow("src", src) cv2.imshow("dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()
x 方向梯度
ch13_2.py Sobel x 方向梯度结果
x 方向梯度主要显示垂直线条,参考原书第 13-7 页。

程序实例 ch13_3.py:Sobel x 方向梯度加 convertScaleAbs()

# ch13_3.py import cv2 src = cv2.imread("map.jpg") dst = cv2.Sobel(src, cv2.CV_64F, 1, 0) dst = cv2.convertScaleAbs(dst) cv2.imshow("src", src) cv2.imshow("dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()

13-2-7 y 轴方向的影像梯度

程序实例 ch13_4.py:Sobel y 方向梯度

# ch13_4.py import cv2 src = cv2.imread("map.jpg") dst = cv2.Sobel(src, -1, 0, 1) cv2.imshow("src", src) cv2.imshow("dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch13_5.py:Sobel y 方向梯度加 convertScaleAbs()

# ch13_5.py import cv2 src = cv2.imread("map.jpg") dst = cv2.Sobel(src, cv2.CV_64F, 0, 1) dst = cv2.convertScaleAbs(dst) cv2.imshow("src", src) cv2.imshow("dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()
y 方向梯度
ch13_5.py Sobel y 方向梯度结果
y 方向梯度主要显示水平线条,参考原书第 13-8 页。

13-2-8 x 轴和 y 轴影像梯度的融合

程序实例 ch13_6.py:合成 x/y 方向梯度

# ch13_6.py import cv2 src = cv2.imread("map.jpg") dstx = cv2.Sobel(src, cv2.CV_64F, 1, 0) dsty = cv2.Sobel(src, cv2.CV_64F, 0, 1) dstx = cv2.convertScaleAbs(dstx) dsty = cv2.convertScaleAbs(dsty) dst = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0) cv2.imshow("src", src) cv2.imshow("dstx", dstx) cv2.imshow("dsty", dsty) cv2.imshow("dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch13_7.py:真实影像 Sobel 比较

# ch13_7.py import cv2 src = cv2.imread("lena.jpg") dstx = cv2.Sobel(src, cv2.CV_64F, 1, 0) dsty = cv2.Sobel(src, cv2.CV_64F, 0, 1) dstx = cv2.convertScaleAbs(dstx) dsty = cv2.convertScaleAbs(dsty) dst = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0) cv2.imshow("src", src) cv2.imshow("x", dstx) cv2.imshow("y", dsty) cv2.imshow("xy", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch13_8.py:灰阶影像比较 Sobel 与 Scharr

# ch13_8.py import cv2 src = cv2.imread("lena.jpg", cv2.IMREAD_GRAYSCALE) dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0) dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1) dst_sobel = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0) dstx = cv2.Scharr(src, cv2.CV_32F, 1, 0) dsty = cv2.Scharr(src, cv2.CV_32F, 0, 1) dst_scharr = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0) cv2.imshow("Sobel", dst_sobel) cv2.imshow("Scharr", dst_scharr) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch13_8_1.py:彩色影像比较 Sobel 与 Scharr

# ch13_8_1.py import cv2 src = cv2.imread("lena.jpg") dstx = cv2.Sobel(src, cv2.CV_32F, 1, 0) dsty = cv2.Sobel(src, cv2.CV_32F, 0, 1) dst_sobel = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0) dstx = cv2.Scharr(src, cv2.CV_32F, 1, 0) dsty = cv2.Scharr(src, cv2.CV_32F, 0, 1) dst_scharr = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0) cv2.imshow("Sobel", dst_sobel) cv2.imshow("Scharr", dst_scharr) cv2.waitKey(0) cv2.destroyAllWindows()
真实影像 Sobel
ch13_7.py lena Sobel 结果
分别显示原图、x 方向、y 方向与合成梯度结果,参考原书第 13-11 页。
13-3

OpenCV 函数 Scharr()

13-3-1 Scharr 算子

Scharr 算子可以看作 Sobel 的强化版本,常用于 3x3 核大小下的边缘检测。 它对梯度变化较敏感,输出的边缘通常更明显。函数参数与 Sobel 接近,但没有 ksize 参数。

13-3-2 Scharr() 函数

dst = cv2.Scharr(src, ddepth, dx, dy, scale, delta, borderType)

程序实例 ch13_9.py:Scharr 边缘侦测

# ch13_9.py import cv2 src = cv2.imread("snow.jpg") dstx = cv2.Scharr(src, cv2.CV_64F, 1, 0) dsty = cv2.Scharr(src, cv2.CV_64F, 0, 1) dstx = cv2.convertScaleAbs(dstx) dsty = cv2.convertScaleAbs(dsty) dst = cv2.addWeighted(dstx, 0.5, dsty, 0.5, 0)
Scharr 结果
ch13_9.py Scharr 执行结果
Scharr 对雪景图边缘的增强效果,参考原书第 13-13 页。
Sobel 与 Scharr 比较
Sobel 与 Scharr 比较结果
同一影像上比较 Sobel 与 Scharr 的边缘强度,参考原书第 13-15 页。
13-4

OpenCV 函数 Laplacian()

13-4-1 二阶微分

Laplacian 使用二阶导数检测边缘,会同时响应多个方向的强度变化。和 Sobel、Scharr 分别计算 x/y 方向不同, Laplacian 更像是一次取得综合边缘信息,但也更容易受到噪声影响。

13-4-2 Laplacian 算子

Sobel 和 Scharr 属于一阶微分;Laplacian 属于二阶微分。二阶微分对像素值变化大的区域会有明显反应,因此可用于取得影像边缘。

13-4-3 Laplacian() 函数

dst = cv2.Laplacian(src, ddepth, ksize, scale, delta, borderType)

程序实例 ch13_10.py:Laplacian 边缘侦测

# ch13_10.py import cv2 src = cv2.imread("map.jpg") dst = cv2.Laplacian(src, cv2.CV_64F) dst = cv2.convertScaleAbs(dst) cv2.imshow("src", src) cv2.imshow("Laplacian", dst) cv2.waitKey(0) cv2.destroyAllWindows()
Laplacian 结果
ch13_10.py Laplacian 执行结果
Laplacian 在网格图上能同时显示水平与垂直边缘,参考原书第 13-18 页。

程序实例 ch13_11.py:Sobel、Scharr、Laplacian 综合比较

# ch13_11.py import cv2 src = cv2.imread("geneva.jpg") sobel = cv2.Sobel(src, cv2.CV_64F, 1, 0) scharr = cv2.Scharr(src, cv2.CV_64F, 1, 0) laplacian = cv2.Laplacian(src, cv2.CV_64F) sobel = cv2.convertScaleAbs(sobel) scharr = cv2.convertScaleAbs(scharr) laplacian = cv2.convertScaleAbs(laplacian)
三种算子比较
Sobel Scharr Laplacian 比较结果
建筑图像上比较 Sobel、Scharr 与 Laplacian 的边缘表现,参考原书第 13-19 页。
13-5

Canny 边缘侦测

13-5-1 认识 Canny 边缘检测

Canny 是多阶段边缘检测方法,通常包含去噪、计算梯度、非极大值抑制和双阈值连接等步骤。 它比单纯梯度算子更完整,输出常是较细且连续的二值边缘图。

13-5-2 Canny 算法的步骤

  • Noise Reduction:先降低噪声,避免杂讯被误判为边缘。
  • Finding Intensity Gradient:计算梯度强度和方向。
  • Non-maximum Suppression:只保留局部最大梯度,压细边缘。
  • Hysteresis Thresholding:用高低阈值连接强边缘与弱边缘。
  • 13-5-3 Canny() 函数

    edges = cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
    参数说明
    threshold1低阈值。
    threshold2高阈值。
    apertureSizeSobel 内核大小,默认 3。
    L2gradient是否使用更精确的 L2 范数计算梯度。

    程序实例 ch13_12.py:不同阈值的 Canny 结果

    # ch13_12.py import cv2 src = cv2.imread("lena.jpg", cv2.IMREAD_GRAYSCALE) dst1 = cv2.Canny(src, 50, 100) dst2 = cv2.Canny(src, 50, 200) cv2.imshow("src", src) cv2.imshow("minVal=50, maxVal=100", dst1) cv2.imshow("minVal=50, maxVal=200", dst2) cv2.waitKey(0) cv2.destroyAllWindows()
    Canny 阈值比较
    ch13_12.py Canny 阈值比较
    阈值不同会影响保留的边缘数量,参考原书第 13-23 页。

    程序实例 ch13_13.py:加入 Canny 的算子比较

    # ch13_13.py import cv2 src = cv2.imread("geneva.jpg", cv2.IMREAD_GRAYSCALE) sobel = cv2.convertScaleAbs(cv2.Sobel(src, cv2.CV_64F, 1, 0)) scharr = cv2.convertScaleAbs(cv2.Scharr(src, cv2.CV_64F, 1, 0)) laplacian = cv2.convertScaleAbs(cv2.Laplacian(src, cv2.CV_64F)) canny = cv2.Canny(src, 50, 100)
    习题

    1. 建立 300x300 黑底白圆影像,观察没有做绝对值处理的 x 轴影像梯度。

    2. 将前一题改为建立 y 轴影像梯度。

    3. 扩充前 2 个实例,建立完整影像梯度,相当于列出此影像的边缘。

    4. 使用 eagle.jpg,比较 Sobel 和 Scharr 建立的边缘影像。

    5. 修改 ch13_11.pyLaplacian() 参数,比较不同 ksize 的结果。

    6. 使用澳门酒店影像,使用 Canny 边缘检测,比较 L2gradient=FalseL2gradient=True

    第 13 章白圆边缘侦测习题参考图
    白圆边缘侦测参考图,参考原书第 13-24 页。
    第 13 章习题 3 至 5 参考图
    习题 3 至 5 参考图,参考原书第 13-25 页。
    第 13 章习题综合参考图
    鹰与澳门影像的边缘侦测参考图,参考原书第 13-26 页。