边缘检测
边缘检测
简介
-
边缘检测是一种图像处理技术,用于识别对象的边界(边缘)或图像中的区域
-
图像的边缘指的是图像中像素灰度值突然发生变化的区域
-
图像的边缘有可能是由高像素值变为低像素值,也有可能是由低像素值变成高像素值,因此通过计算得到的边缘信息会有正数值和负数值。为了在图像中同时表示出这两种边缘信息,需要将计算的结果求取绝对值。OpenCV提供了
converScaleAbs()
函数用于计算矩阵中所有数据的绝对值
converScaleAbs(src, dst)
- 另外,由于求取边缘的结果可能会有负数,不在原始图像的CV_8U的数据类型内,因此滤波后的图像数据类型不要用 “-1” ,而应该改为CV_16S
以下所有代码均用此图作为示例
Sobel算子
简介
- Sobel算子通过寻找图像一阶导数中的最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值
- Sobel算子在实际应用中效率比Canny算法要高,但是边缘不如Canny检测的准确,但是很多实际应用的场合,Sobel算子却是首选
- Sobel算子是高斯平滑与微分操作的结合体,所以抗噪声能力很强,用途较多,尤其是效率要求较高,而对细纹理不太关心的时候
实现
以下是 OpenCV 4 应用Sobel边缘检测的语法
Sobel(src, dst, ddepth, dx, dy, ksize)
-
src:待提取边缘的图像
-
dst:输出图像,与输入图像具有相同的尺寸和通道数,数据类型由第三个参数 ddepth 控制
-
ddepth:输出图像的数据类型(深度),根据输入图像数据类型的不同拥有不同的取值范围,具体如下表
输入图像数据类型 输出图像可选数据类型 CV_8U -1 / CV_16S / CV_32F / CV_64F CV_16U / CV_16S -1 / CV_32F / CV_64F CV_12F -1 / CV_32F / CV_64F CV_64F -1 / CV_64F - 当赋值为-1时,输出图像的数据类型自动选择
-
dx:X方向的差分阶数。如果dx = 1和dy = 0,我们提取X方向的边缘信息
-
dy:Y方向的差分阶数。如果两者dx = 1和dy = 1,我们提取两个方向也就是整幅图像的边缘信息
-
ksize:Sobel算子的尺寸,必须是1、3、5或7,默认为3
补:任意一个方向的差分阶数都需要小于算子的尺寸。特殊情况是,当ksize = 1时,任意一个方向的阶数都需要小于3。在一般情况下,当差分阶数的最大值为1时,算子尺寸选3;当差分阶数的最大值为2时,算子尺寸选5;当差分阶数最大值为3时,算子尺寸选7
注,由于提取边缘信息时有可能出现负数,因此不要使用CV_8U类型的输出图像
示例代码如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("/home/kslas/OpenCV/edge.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "Could not read the image" << endl;
return -1;
}
// X方向一阶边缘
Mat resultX;
Sobel(img, resultX, CV_16S, 2, 0, 1);
convertScaleAbs(resultX, resultX);
// Y方向一阶边缘
Mat resultY;
Sobel(img, resultY, CV_16S, 0, 1, 3);
convertScaleAbs(resultY, resultY);
// 整幅图像的一阶边缘
Mat resultXY = resultX + resultY;
// 显示图像
imshow("resultX", resultX);
imshow("resultY", resultY);
imshow("resultXY", resultXY);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
Laplacian算子
简介
- Laplacian算子通过寻找图像二阶导数零穿越来寻找边界
- Laplacian算子具有各方向同性的特点,能够对任意方向的边缘进行提取,具有无方向性的优点
- Laplacian算子是一种二阶导数算子,对噪声比较敏感,因此常需要配合高斯滤波一起使用
实现
以下是 OpenCV 4 应用Laplacian边缘检测的语法
Laplacian(src, dst, ddepth, ksize)
- scr:输入原始图像,可以是灰度图像或彩色图像
- dst:输出图像,与输入图像具有相同的尺寸和通道数
- ddepth:同上
- ksize:滤波器的大小,必须为正奇数
注,由于提取边缘信息时有可能出现负数,因此不要使用CV_8U类型的输出图像,否则会使得图像边缘提取不准确
示例代码如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("/home/kslas/OpenCV/edge.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "Could not read the image" << endl;
return -1;
}
// 未进行滤波提取Laplacian边缘
Mat result;
Laplacian(img, result, CV_16S, 3);
convertScaleAbs(result, result);
// 滤波后提取Laplacian边缘
Mat result_g, result_G;
GaussianBlur(img ,result_g, Size(3, 3), 5, 0); // 高斯滤波
Laplacian(result_g, result_G, CV_16S, 3);
convertScaleAbs(result_G, result_G);
// 显示图像
imshow("result", result);
imshow("result_G", result_G);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
Canny算法
简介
-
Canny算法是目前最优越的边缘检测算法之一
-
Canny算法不容易受到噪声的影响,能够识别图像中的弱边缘和强边缘,并结合强弱边缘的位置关系,综合给出图像整体的边缘信息
-
该方法的检测过程可分为以下5个步骤:
- 使用高斯滤波平滑图像
- 计算图像中每个像素的梯度方向和幅值
- 应用非极大值抑制算法消除边缘检测带来的杂散相应
- 应用双阈值法划分强边缘和弱边缘
- 消除孤立的弱边缘
实现
OpenCV 4 提供了Canny()
函数用于实现Canny算法检测图像中的边缘
Canny(image, edges, threshold1, threshold2, apertureSize, L2gradient)
- image:输入图像,必须是CV_8U的单通道或者三通道图像
- edges:输出图像,与输入图像具有相同尺寸的单通道图像,且数据类型为CV_8U
- threshold1:第一个滞后阈值
- threshold2:第二个滞后阈值
- apertureSize:Sobel算子的直径,默认为3
- L2gradient:计算图像梯度幅度方法的标志,默认为false
补1:在一般情况下,较大阈值与较小阈值的比值在 2 : 1 到 3 : 1 之间
补2:较高的阈值会降低噪声信息对图像提取边缘结果的影响,但同时也会减少结果中的边缘信息
补3:高斯模糊在边缘纹理较多的区域能减少边缘检测的结果,但对纹理较少的区域影响较小
示例代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 读取图像,黑白图像边缘检测结果较为明显
Mat img = imread("/home/kslas/OpenCV/edge.jpg", IMREAD_ANYCOLOR);
if (img.empty())
{
cout << "Could not read the image" << endl;
return -1;
}
// 高阈值检测图像边缘
Mat resultHigh;
Canny(img, resultHigh, 100, 200, 3);
// 低阈值检测图像边缘
Mat resultLow;
Canny(img, resultLow, 20, 40, 3);
// 高斯模糊后检测图像边缘
Mat resultG, resultGC;
GaussianBlur(img, resultG, Size(3, 3), 5);
Canny(resultG, resultGC, 100, 200, 3);
// 显示图像
imshow("resultHigh", resultHigh);
imshow("resultLow", resultLow);
imshow("resultG", resultGC);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果: