引言
图像边缘是像素灰度值发生剧烈变化的区域,包含了物体形状的关键信息。边缘检测作为计算机视觉的基础任务,在目标识别、图像分割、场景理解等领域都有着广泛应用。本文将系统讲解边缘检测的基本原理,深入剖析 OpenCV 中常用的边缘检测算子,并通过实战案例展示其应用方法。
一、边缘检测的基本原理
边缘检测的本质是检测图像中灰度变化剧烈的像素点。从数学角度看,图像的边缘对应着灰度函数的突变,可通过计算图像的梯度来定位。
1.1 梯度与边缘的关系
图像的梯度可以用一阶导数表示,梯度的大小反映了灰度变化的剧烈程度,梯度的方向则指向灰度变化最剧烈的方向:
1.梯度幅值越大,边缘越明显
2.梯度方向垂直于边缘方向
在数字图像中,梯度通常通过差分运算近似:
1.水平方向差分:Gx = I (x+1,y) - I (x,y)
2.垂直方向差分:Gy = I (x,y+1) - I (x,y)
3.梯度幅值:G = √(Gx² + Gy²)
4.梯度方向:θ = arctan (Gy/Gx)
1.2 边缘检测的基本流程
一个完整的边缘检测流程通常包含以下步骤:
1.噪声去除:通过高斯模糊等方法平滑图像,减少噪声干扰
2.梯度计算:计算图像在水平和垂直方向的梯度
3.边缘增强:通过非极大值抑制等方法增强边缘信号
4.边缘连接:通过阈值处理等方法确定最终的边缘
二、常用边缘检测算子详解
OpenCV 实现了多种经典的边缘检测算子,每种算子都有其独特的原理和适用场景。
2.1 Sobel 算子
Sobel 算子是最常用的边缘检测算子之一,它通过 3x3 的卷积核计算图像的梯度,对噪声具有一定的鲁棒性。
原理特点:
分别计算水平和垂直方向的梯度
采用加权平均的方式,对邻近像素赋予不同权重
计算效率高,适合实时处理

python
import cv2
"""Sobel算子"""
yuan = cv2.imread('Dog.png')
yuan=cv2.resize(yuan,(300,300))
yuan=cv2.cvtColor(yuan,cv2.COLOR_BGR2GRAY)
cv2.imshow('yuan', yuan)
cv2.waitKey(0)
'''x方向上的边缘'''
# 右端为负值 显示不出来
yuan_x_64 = cv2.Sobel(yuan, cv2.CV_64F, dx=1, dy=0)
cv2.imshow('yuan_x_64', yuan_x_64)
cv2.waitKey(0)
# 进行取绝对值操作即可
yuan_x_full = cv2.convertScaleAbs(yuan_x_64)
cv2.imshow('yuan_x_full', yuan_x_full)
cv2.waitKey(0)
'''y方向上的边缘'''
# y的下端为负值 显示不出来
# 进行取绝对值操作即可
yuan_y_64 = cv2.Sobel(yuan, cv2.CV_64F, dx=0, dy=1)
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)
cv2.imshow('yuan_y_full', yuan_y_full)
cv2.waitKey(0)
'''使用加权运算组合图像得到完整边缘'''
yuan_xy_full = cv2.addWeighted(yuan_x_full, 1, yuan_y_full, 1, 10)
cv2.imshow('yuan_xy_full', yuan_xy_full)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""使用彩色图获取边缘"""
zrn = cv2.imread('../zrn.jpg', cv2.IMREAD_GRAYSCALE)
zrn = cv2.resize(zrn, (400, 400))
x = cv2.Sobel(zrn, cv2.CV_64F, dx=1, dy=0)
y = cv2.Sobel(zrn, cv2.CV_64F, dx=0, dy=1)
x_full = cv2.convertScaleAbs(x)
y_full = cv2.convertScaleAbs(y)
xy = cv2.addWeighted(x_full, 1, y_full, 1, 0)
cv2.imshow('zrn_Sobel', xy)
cv2.waitKey(0)

代码步骤解析:
1.导入图片,并将彩色图转换为灰度图
python
import cv2
"""Sobel算子"""
yuan = cv2.imread('Dog.png')
cv2.imshow('yuan', yuan)
cv2.waitKey(0)
2.处理x轴和y轴的边缘并相加
- x轴:
python
'''x方向上的边缘'''
# 右端为负值 显示不出来
yuan_x_64 = cv2.Sobel(yuan, cv2.CV_64F, dx=1, dy=0)
cv2.imshow('yuan_x_64', yuan_x_64)
cv2.waitKey(0)
# 进行取绝对值操作即可
yuan_x_full = cv2.convertScaleAbs(yuan_x_64)
cv2.imshow('yuan_x_full', yuan_x_full)
cv2.waitKey(0)
输出:
左边的右边边缘没显示出来是因为进行计算之后这些位置的像素值为负值,显示不出来。
经过取绝对值操作之后即可完整显示出来

- 完整边缘:
y轴的处理与x轴一致
完整的边缘只需将两个轴上的数据进行加权相加即可
python
'''y方向上的边缘'''
# y的下端为负值 显示不出来
# 进行取绝对值操作即可
yuan_y_64 = cv2.Sobel(yuan, cv2.CV_64F, dx=0, dy=1)
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)
cv2.imshow('yuan_y_full', yuan_y_full)
cv2.waitKey(0)
'''使用加权运算组合图像得到完整边缘'''
yuan_xy_full = cv2.addWeighted(yuan_x_full, 1, yuan_y_full, 1, 10)
cv2.imshow('yuan_xy_full', yuan_xy_full)
cv2.waitKey(0)
cv2.destroyAllWindows()
最终结果:

2.2Scharr算子
1.Scharr算子
Scharr 算子是 Soble 算子在 ksize=3 时的优化,与 Soble 的速度相同,且精度更高。Scharr 算子与 Sobel 算子的不同点是在平滑部分,其中心元素占的权重更重,相当于使用较小标准差的高斯函数,也就是更瘦高的模板。
2.计算
计算过程与sobel算子相同,只是所用矩阵有差别

3.代码实现
完整代码:
• 实现步骤与Sobel的步骤一模一样,在此就不过多赘述了。
python
import cv2
"""Scharr(xia)算子"""
zrn1 = cv2.imread('Dog.png', cv2.IMREAD_GRAYSCALE)
zrn1 = cv2.resize(zrn1, (400, 400))
x = cv2.Scharr(zrn1, cv2.CV_64F, dx=1, dy=0)
y = cv2.Scharr(zrn1, cv2.CV_64F, dx=0, dy=1)
x_full = cv2.convertScaleAbs(x)
y_full = cv2.convertScaleAbs(y)
xy = cv2.addWeighted(x_full, 1, y_full, 1, 0)
cv2.imshow('zrn_Scharr', xy)
cv2.waitKey(0)
运行结果如下:

2.3 Laplacian算子
aplacian 算子基于二阶导数,通过计算图像的二阶差分来检测边缘,对细节边缘更敏感,但对噪声也更敏感。
原理特点:
对孤立点和线端的响应较强
能检测出所有方向的边缘
对噪声敏感,通常需要先进行高斯模糊
完整代码:
步骤就是将Sobelx轴和y轴的步骤合二为一,并将取绝对值和加权相加的步骤去除了
python
import cv2
"""Laplacian算子"""
zrn1 = cv2.imread('Dog.png', cv2.IMREAD_GRAYSCALE)
zrn1 = cv2.resize(zrn1, (400, 400))
lap = cv2.Laplacian(zrn1, cv2.CV_64F, delta=10)
lap_full = cv2.convertScaleAbs(lap)
cv2.imshow('zrn_Lap', lap_full)
cv2.waitKey(0)
运行结果如下:

2.4 Canny边缘检测算法
Canny 边缘检测是一种多阶段的边缘检测算法,被誉为 "最优边缘检测器",具有边缘定位准确、抗噪声能力强等优点。
算法流程:
1.高斯模糊:使用高斯滤波器平滑图像,去除噪声
2.梯度计算:计算图像的梯度幅值和方向
3.非极大值抑制:保留梯度方向上的局部最大值,细化边缘
4.双阈值处理:使用高、低两个阈值确定潜在边缘和确定边缘
5.边缘连接:通过滞后阈值处理,将潜在边缘与确定边缘连接起来
完整代码:
使用算法的代码就cv2.Canny()那一行,数字参数代表高低阈值,可以自己调试看看效果有何不同
python
import cv2
"""Canny算子"""
suda = cv2.imread('suda.jpg', cv2.IMREAD_GRAYSCALE)
suda = cv2.resize(suda, (400, 400))
cv2.imshow('suda', suda)
cv2.waitKey(0)
can = cv2.Canny(suda, 100, 200) # 低阈值 高阈值
cv2.imshow('suda_canny', can)
cv2.waitKey(0)
运行结果如下:

Canny 算子的优势:
能够检测到真正的弱边缘
边缘定位精度高
输出的边缘是单像素宽度
对噪声具有较强的抑制能力
三、边缘检测算子的对比与选择
不同边缘检测算子各有优缺点,在实际应用中需要根据具体场景选择:

选择建议:
优先考虑 Canny 算子,它在大多数情况下表现最佳
对实时性要求高时,选择 Sobel 算子
检测细微边缘时,考虑 Laplacian 算子(需配合高斯模糊)
噪声较多的图像,避免使用 Laplacian 和 Roberts 算子
边缘检测作为计算机视觉的基础技术,其核心是准确捕捉图像中灰度变化的区域。OpenCV 提供的多种边缘检测算子各有特点,在实际应用中需要根据具体场景选择合适的算子,并结合预处理和后处理技术优化边缘检测结果。