边缘检测是图像处理和计算机视觉中的基础任务,其目的是识别图像中亮度变化剧烈的像素点,这些点通常对应物体的边界。通过边缘检测,我们可以提取图像的关键结构信息,大幅减少数据量,同时保留重要的形状属性。OpenCV提供了多种边缘检测算子,本文将详细介绍Sobel、Scharr、Laplacian和Canny四种常用方法。

一、Sobel算子

Sobel算子是一种离散微分算子,结合了高斯平滑和微分求导,用于计算图像灰度函数的近似梯度。它通过计算图像在水平和垂直方向上的梯度来检测边缘。

函数语法

dst = cv2.Sobel(src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]])
  • src:输入图像

  • ddepth:输出图像的深度(数据类型),通常使用cv2.CV_64F来保留负数梯度,避免信息丢失

  • dx, dy:导数的阶数,通常取0或1。dx=1, dy=0计算水平方向梯度;dx=0, dy=1计算垂直方向梯度

  • ksize:Sobel核的大小,可选1、3、5、7,默认3

  • scale:缩放因子,默认1

  • delta:偏移量,默认0

注意事项

Sobel算子计算出的梯度可能为负值,而图像像素通常用0~255表示(uint8类型),负数会被截断为0,导致边缘信息丢失。因此,常用cv2.CV_64F保留负数,再通过cv2.convertScaleAbs()取绝对值,将负数转换为正数显示。

代码示例

import cv2
import numpy as np

# 读取图像(以灰度方式,边缘检测通常在灰度图上进行)
yuan = cv2.imread('yuan.png', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Original', yuan)
cv2.waitKey(0)

# 1. x方向边缘(直接使用-1深度,负数丢失)
yuan_x = cv2.Sobel(yuan, -1, dx=1, dy=0)
cv2.imshow('Sobel X (uint8)', yuan_x)
cv2.waitKey(0)

# 2. x方向边缘(使用CV_64F保留负数,但显示为灰色)
yuan_x_64 = cv2.Sobel(yuan, cv2.CV_64F, dx=1, dy=0)
cv2.imshow('Sobel X (CV_64F)', yuan_x_64)
cv2.waitKey(0)

# 3. x方向边缘(取绝对值,显示完整边缘)
yuan_x_full = cv2.convertScaleAbs(yuan_x_64)
cv2.imshow('Sobel X (Absolute)', yuan_x_full)
cv2.waitKey(0)

# 4. y方向边缘(类似处理)
yuan_y_64 = cv2.Sobel(yuan, cv2.CV_64F, dx=0, dy=1)
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)
cv2.imshow('Sobel Y (Absolute)', yuan_y_full)
cv2.waitKey(0)

# 5. 同时计算x和y(不推荐直接组合)
yuan_xy = cv2.Sobel(yuan, -1, dx=1, dy=1)
cv2.imshow('Sobel XY (direct)', yuan_xy)
cv2.waitKey(0)

# 6. 正确组合:加权合并x和y方向的完整边缘
yuan_xy_sobel = cv2.addWeighted(yuan_x_full, 1, yuan_y_full, 1, 0)
cv2.imshow('Sobel Combined', yuan_xy_sobel)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果说明

  • 直接使用uint8类型的Sobel结果会丢失负梯度信息,边缘不完整。

  • 使用cv2.CV_64F能保留负数,但显示为灰色图像,因为负值在显示时被视为黑色。

  • 取绝对值后,正负梯度都变为正值,边缘显示为白色。

  • 单独计算x方向检测垂直边缘,y方向检测水平边缘,加权组合后得到完整的边缘图像。

实际应用示例

# 读取另一张图像测试Sobel效果
zl = cv2.imread('zl.png', cv2.IMREAD_GRAYSCALE)

zl_x = cv2.Sobel(zl, cv2.CV_64F, 1, 0)
zl_y = cv2.Sobel(zl, cv2.CV_64F, 0, 1)
zl_x_abs = cv2.convertScaleAbs(zl_x)
zl_y_abs = cv2.convertScaleAbs(zl_y)
zl_sobel = cv2.addWeighted(zl_x_abs, 1, zl_y_abs, 1, 0)

cv2.imshow('Sobel Result', zl_sobel)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果展示:

二、Scharr算子

Scharr算子是Sobel算子的改进版本,对核的系数进行了优化,使其对旋转有更好的对称性,且梯度计算更精确,尤其适用于边缘方向复杂的情况。它的函数参数与Sobel几乎相同,但核大小固定为3×3,且不能指定ksize

函数语法

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

参数含义与Sobel一致,但dxdy只能取(1,0)或(0,1),不能同时为1。

代码示例

zl = cv2.imread('zl.png', cv2.IMREAD_GRAYSCALE)

# x方向Scharr
zl_x_64 = cv2.Scharr(zl, cv2.CV_64F, dx=1, dy=0)
zl_x_full = cv2.convertScaleAbs(zl_x_64)

# y方向Scharr
zl_y_64 = cv2.Scharr(zl, cv2.CV_64F, dx=0, dy=1)
zl_y_full = cv2.convertScaleAbs(zl_y_64)

# 组合
zl_scharr = cv2.addWeighted(zl_x_full, 1, zl_y_full, 1, 0)

cv2.imshow('Scharr Result', zl_scharr)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果说明

Scharr算子提取的边缘通常比Sobel更细腻,对细节的响应更强。

三、Laplacian算子

Laplacian算子是一种二阶微分算子,它通过计算图像的二阶导数来检测边缘,对噪声敏感,因此常先对图像进行平滑处理。它的特点是各向同性,即对各个方向的边缘都有响应。

函数语法

dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
  • src:输入图像

  • ddepth:输出深度,推荐cv2.CV_64F

  • ksize:用于计算二阶导数的核大小,必须为正奇数,默认1

  • scale:缩放因子,默认1

  • delta:偏移量,默认0

代码示例

zl = cv2.imread('zl.png', cv2.IMREAD_GRAYSCALE)

zl_lap = cv2.Laplacian(zl, cv2.CV_64F)
zl_lap_full = cv2.convertScaleAbs(zl_lap)

cv2.imshow('Laplacian Result', zl_lap_full)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果说明

Laplacian边缘检测能同时提取各个方向的边缘,但由于对噪声敏感,结果中可能包含较多噪点。

四、Canny边缘检测

Canny边缘检测是一种多阶段算法,包括高斯滤波去噪、计算梯度幅值和方向、非极大值抑制、双阈值检测和边缘连接。它被广泛认为是性能最优的边缘检测方法之一,能提取清晰、连续的边缘。

函数语法

edges = cv2.Canny(image, threshold1, threshold2[, apertureSize[, L2gradient]])
  • image:输入图像(灰度图)

  • threshold1:低阈值

  • threshold2:高阈值

  • apertureSize:Sobel核大小,默认3

  • L2gradient:是否使用更精确的L2范数计算梯度,默认False

算法原理:大于高阈值的像素被确定为强边缘;介于低阈值和高阈值之间的像素,若与强边缘相连则被保留,否则丢弃;小于低阈值的像素被抑制。

代码示例

zl = cv2.imread('zl.png', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Original', zl)
cv2.waitKey(0)

# 调整阈值观察效果
zl_canny = cv2.Canny(zl, 100, 150)  # 低阈值100,高阈值150
cv2.imshow('Canny (100,150)', zl_canny)
cv2.waitKey(0)

# 尝试不同阈值
zl_canny2 = cv2.Canny(zl, 50, 100)
cv2.imshow('Canny (50,100)', zl_canny2)
cv2.waitKey(0)

cv2.destroyAllWindows()

运行结果说明

  • Canny检测的边缘清晰、连续,且噪声较少。

  • 低阈值控制边缘的敏感性,低阈值越低,保留的边缘越多;高阈值控制边缘的连续性,高阈值越高,边缘可能断裂。需根据具体图像调整阈值以获得最佳效果。

五、总结

本文介绍了OpenCV中四种常用的边缘检测算子:

算子 特点 适用场景
Sobel 一阶导数,方向性明显,计算简单 快速边缘检测,特定方向边缘提取
Scharr 改进的Sobel,精度更高 需要更精确梯度的场合
Laplacian 二阶导数,各向同性,对噪声敏感 配合平滑使用,检测所有方向边缘
Canny 多阶段优化,抗噪能力强,边缘连续 通用边缘检测,效果最优

在实际应用中,通常先对图像进行平滑(如高斯滤波)以减少噪声影响,再根据需求选择合适的边缘检测算子。Canny由于其稳定性和效果,成为最常用的选择。掌握这些算子,可以为后续的图像分割、目标识别等任务打下基础。

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐