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
