OpenCV联合C++/Qt 学习笔记(十三)----边缘检测
一、边缘检测原理
边缘(Edge)是指图像中局部灰度值(强度)发生显著变化的区域,通常存在于:
- 目标与目标之间
- 目标与背景之间
- 区域与区域之间
- 不同颜色或纹理之间
图像强度变化的两种典型形式:
- 阶跃变化(Step Edge):图像灰度在某一点两侧发生突变,即像素值突然从一个灰度跳到另一个灰度。
- 屋顶变化(Roof Edge)/线条变化:图像灰度突然升高,保持一小段距离后又下降回原值。
- 前向差分(Forward Difference)计算梯度:
当前像素与左侧像素的差值 → 近似水平变化率
- 改进后(中心差分):
二、Sobel算子边缘检测
/* 用途:用于计算图像的一阶或高阶梯度,通过Sobel卷积算子检测图像在
x方向或y方向的灰度变化,从而提取边缘信息 */
void cv::Sobel( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, int ksize = 3,
double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
/*
src:输入图像
dst:输出图像,与输入图像具有相同的尺寸(但数据类型由ddepth决定)
ddepth:输出图像的数据类型(深度)
dx:x方向求导阶数
dy:y方向求导阶数
ksize:Sobel算子核大小(必须为1、3、5、7等奇数)
scale:缩放系数,对计算结果进行比例放大或缩小
delta:偏移量,在结果中额外加上的值
borderType:像素外推法选择标志
*/
三、Scharr算子边缘检测
/* 用途:用于计算图像在x方向或y方向的梯度,是Sobel算子的增强版本。
在使用3×3核时,Scharr算子相比Sobel具有更高的精度和更好的旋转对称性,
能更准确地检测图像边缘细节与灰度变化*/
void cv::Scharr( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
/*
src:输入图像
dst:输出图像,与输入图像具有相同的尺寸(数据类型由ddepth决定)
ddepth:输出图像的数据类型(深度)
dx:x方向求导阶数
dy:y方向求导阶数
scale:缩放系数,对计算结果进行比例放大或缩小
delta:偏移量,在结果中额外加上的值
borderType:像素外推法选择标志
*/
四、两种算子的生成
/* 用途:用于生成图像导数运算所需的一维卷积核(x方向与y方向),
这些卷积核通常用于Sobel、Scharr等梯度计算,
或与sepFilter2D配合实现自定义微分滤波。
通过该函数可灵活构造一阶导数、二阶导数等算子,
常用于边缘检测、梯度分析、特征提取以及高性能可分离卷积计算 */
void cv::getDerivKernels( OutputArray kx, OutputArray ky,
int dx, int dy, int ksize,
bool normalize = false, int ktype = CV_32F );
/*
kx:行滤波器系数的输出矩阵,尺寸为ksize * 1
ky:列滤波器系数的输出矩阵,尺寸为ksize * 1
dx:X方向导数的阶次
dy:y方向导数的阶次
ksize:滤波器的大小,可以选择的参数为FILTER_SCHARR,1,3,5或7
normalize:是否对滤波器系数进行归一化的标志,默认值为false,表示不进行系数归一化
ktype:滤波器系数类型,可以选择CV_32F或CV_64F,默认参数为CV_32F
*/
五、示例代码
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/equalLena.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
Mat resultX, resultY, resultXY;
/*X方向一阶边缘*/
Sobel(img, resultX, CV_16S, 1, 0, 3);
convertScaleAbs(resultX, resultX);/*求取绝对值*/
/*Y方向一阶边缘*/
Sobel(img, resultY, CV_16S, 0, 1, 3);
convertScaleAbs(resultY, resultY);
/*整幅图像的一阶边缘*/
resultXY = resultX + resultY;
imshow("resultX", resultX);
imshow("resultY", resultY);
imshow("resultXY", resultXY);
waitKey(0);
/*X方向一阶边缘*/
Scharr(img, resultX, CV_16S, 1, 0);
convertScaleAbs(resultX, resultX);/*求取绝对值*/
/*Y方向一阶边缘*/
Scharr(img, resultY, CV_16S, 0, 1);
convertScaleAbs(resultY, resultY);/*求取绝对值*/
resultXY = resultX + resultY;
imshow("resultX", resultX);
imshow("resultY", resultY);
imshow("resultXY", resultXY);
waitKey(0);
/*生成边缘检测器*/
Mat sobel_x1, sobel_y1;/*存放分离的sobel算子*/
Mat scharr_x, scharr_y;/*存放分离的scharr算子*/
Mat sobelX1, scharrX;/*存放最终算子*/
getDerivKernels(sobel_x1, sobel_y1, 1, 0, 3);
sobel_x1 = sobel_x1.reshape(CV_8U, 1);
sobelX1 = sobel_y1 * sobel_x1;/*计算滤波器*/
getDerivKernels(scharr_x, scharr_y, 1, 0, FILTER_SCHARR);
scharr_x = scharr_x.reshape(CV_8U, 1);
scharrX = scharr_y * scharr_x;/*计算滤波器*/
cout << "X sobel: " << endl << sobelX1 << endl;
cout << "X scharr: " << endl << scharrX << endl;
waitKey(0);
destroyAllWindows();
六、Laplacian算子边缘检测
1、Laplacian算子
Laplacian算子是一种基于二阶导数的边缘检测算法,主要用于识别图像中的快速亮度变化,即边缘。由于它直接与图像的二阶导数相关,因此能够有效地找到边缘位置,但对噪声比较敏感。
Sobel和Scharr边缘检测算法存在的问题:
- 分别计算两个方向边缘
- 边缘与方向相关性较大
- Laplacian算子:
- 方向无关
- 容易受到噪声的影响
- 3 * 3的Laplacian算子:
2、Laplacian边缘检测函数
/* 用途:用于计算图像的二阶导数(Laplacian算子),通过检测灰度变化的“变化率”,
强调图像中灰度突变的位置,从而突出边缘信息。
与Sobel(一阶导数)相比,Laplacian对边缘方向不敏感,
能同时检测各个方向的边缘,但对噪声也更敏感 */
void cv::Laplacian( InputArray src, OutputArray dst, int ddepth,
int ksize = 1, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
/*
src:输入原图像,可以是灰度图像和彩色图像
dst:输出图像,与输入图像src具有相同的尺寸和通道数
ddepth:输出图像的数据类型(深度),根据输入图像的数据类型不同拥有不同的取值范围
ksize:滤波器的大小,必须为正奇数
scale:对导数计算结果进行缩放的缩放因子,默认系数为1,不进行缩放
delta:偏值,在计算结果中加上偏值
borderType:像素外推法选择标志
*/
七、Canny算子边缘检测
1、Canny边缘检测原理介绍
Canny边缘检测的主要步骤:
1> 使用高斯滤波平滑图像
在进行边缘检测之前,首先需要对原始图像进行平滑处理。因为图像在采集、传输过程中不可避免会引入噪声,而边缘检测本质上依赖于灰度变化率(导数),这种运算会对噪声非常敏感,甚至会把噪声误判为边缘。
常用高斯滤波平滑图像滤波器:
![]()
2> 计算图像中每个像素的梯度方向和幅度
在平滑后的图像上,计算每个像素点的灰度变化情况。通常通过Sobel算子分别计算水平方向和垂直方向的变化量,得到两个梯度分量 Ix 和 Iy。
然后将这两个分量合成为:
![]()
- 梯度方向 θ:表示变化发生的方向,用于确定边缘的走向
- 梯度幅值 G:表示灰度变化的强度,用来判断该点是否可能是边缘
3> 应用非极大值抑制算法边缘检测带来的杂散响应
在上一步中得到的边缘通常是比较“粗”的带状结构,而不是理想的单像素边缘。因此需要进一步细化。
非极大值抑制的核心思想是:沿着梯度方向,只保留局部最大的那个像素点,其余的全部抑制为0。也就是说,如果某个像素点不是该方向上的“最高点”,就认为它不是最真实的边缘位置。
经过这一步处理后,边缘会从“宽线条”变成“细线条”,更加精确。
4> 应用双阈值法划分强边缘和弱边缘
经过非极大值抑制后,图像中仍然存在一些不确定的边缘,需要进一步筛选。Canny算法采用双阈值策略,而不是简单的单阈值。
具体做法是设置一个高阈值和一个低阈值:
- 梯度值高于高阈值的像素 → 明确的强边缘
- 梯度值介于两者之间 → 可能的弱边缘
- 梯度值低于低阈值 → 直接认为不是边缘
这种方法的优势在于:既能保留明显边缘,又不会因为阈值过高导致边缘断裂。
5> 消除孤立的弱边缘(边缘连接 / 滞后处理)
在弱边缘中,既包含真实边缘(只是强度较低),也包含噪声。为了区分它们,Canny引入“边缘连接”策略。
具体规则是:
- 如果一个弱边缘像素与强边缘相连(8邻域连接),则认为它属于真实边缘 → 保留
- 如果一个弱边缘像素是孤立的,没有连接到强边缘 → 认为是噪声 → 删除
通过这一步,可以有效去除孤立噪声点,同时保证边缘的连续性。
2、Canny算法函数
/* 用途:用于进行高质量边缘检测,通过一整套优化流程(高斯滤波、梯度计算、
非极大值抑制、双阈值筛选和边缘连接)提取图像中的清晰边缘。
相比Sobel、Laplacian等基础算子,Canny能够有效抑制噪声、
保留真实边缘并去除伪边缘 */
void cv::Canny( InputArray image, OutputArray edges,
double threshold1, double threshold2,
int apertureSize = 3, bool L2gradient = false );
/*
image:输入图像,必须是CV_8U单通道或者三通道图像
edges:输出图像,与输入图像具有相同尺寸的单通道图像,且数据类型为CV_8U
threshold1:第一个滞后阈值(低阈值----弱边缘判定)
threshold2:第二个滞后阈值(高阈值----强边缘判定)
apertureSize:Sobel算子的直径
L2gradient:计算图像梯度幅值的标志(是否使用更精确的梯度计算方式)
*/
八、示例代码
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/equalLena.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
Mat result, result_g, result_G;
/*未滤波提取边缘*/
Laplacian(img, result, CV_16S, 3, 1, 0);
convertScaleAbs(result, result);
/*滤波后提取Laplacian边缘*/
GaussianBlur(img, result_g, Size(3, 3), 5, 0);
Laplacian(result_g, result_G, CV_16S, 3, 1, 0);
convertScaleAbs(result_G, result_G);
imshow("result", result);
imshow("result_G", result_G);
waitKey(0);
Mat resultHigh, resultLow, resultG;
/*大阈值检测图像边缘*/
Canny(img, resultHigh, 100, 200, 3);
/*小阈值检测图像边缘*/
Canny(img, resultLow, 20, 40, 3);
/*高斯模糊后检测图像边缘*/
GaussianBlur(img, resultG, Size(3, 3), 5);
Canny(resultG, resultG, 100, 200, 3);
imshow("resultHigh", resultHigh);
imshow("resultLow", resultLow);
imshow("resultG", resultG);
waitKey(0);
destroyAllWindows();
九、四种边缘检测算子对比表
| 对比维度 | Sobel算子 | Scharr算子 | Laplacian算子 | Canny算子 |
|---|---|---|---|---|
| 算子类型 | 一阶梯度 | 一阶梯度(优化版) | 二阶导数 | 多阶段算法 |
| 数学本质 | 一阶偏导近似 | 改进的一阶偏导 | 二阶偏导(散度) | 梯度 + 优化流程 |
| 是否有方向性 | 有(x/y) | 有(x/y) | 无 | 有(梯度方向) |
| 核心公式 | 梯度 ∇f | 梯度 ∇f(更精确) | ∇²f | 多步骤(非单一公式) |
| 卷积核大小 | 3×3 / 5×5 | 固定3×3 | 3×3(常用) | 不固定(组合) |
| 抗噪声能力 | 中等(带平滑) | 中等(优于Sobel) | 差(极敏感) | 强(高斯滤波) |
| 边缘定位精度 | 中等 | 高 | 高(但不稳定) | 很高 |
| 边缘连续性 | 一般 | 一般 | 较差(易断裂) | 很好 |
| 是否产生双边缘 | 可能 | 可能 | 容易产生 | 基本不会 |
| 对细节敏感度 | 中 | 高 | 很高(含噪声) | 高(受阈值控制) |
| 计算复杂度 | 低 | 低 | 低 | 较高 |
| 实时性 | 强 | 强 | 强 | 一般 |
| 典型问题 | 方向误差 | 基本无明显问题 | 噪声放大 | 参数敏感 |
| 是否需要预处理 | 可选 | 可选 | 必须平滑 | 内置高斯 |
| 是否需要后处理 | 可选 | 可选 | 可选 | 已包含 |
| OpenCV函数 | Sobel() | Scharr() | Laplacian() | Canny() |
精度优先:Canny > Scharr > Sobel > Laplacian
速度优先:Sobel ≈ Scharr ≈ Laplacian > Canny
稳定性:Canny > Scharr > Sobel > Laplacian
更多推荐
所有评论(0)