目录
1、边缘检测概述
(1)What(相关概念)
- 边缘检测:找到有差异的相邻像素
- 锐度:边缘的对比度
- 图像锐化:增加边缘的对比度
- 边缘点:图像中灰度显著变化的点
- 边缘段:边缘点坐标及方向的总和,边缘的方向可以是梯度角
- 轮廓:边缘列表
- 边缘检测器:抽取边缘的算法
- 边缘连接:从无序边缘形成有序边缘的过程
- 边缘跟踪:确定轮廓图像的搜索过程
(2)How(边缘检测的一般步骤)
- A.图像获取:将获取的图像转化为灰度图像,进而进行边缘检测操作
- B.图像去噪:使用滤波器来改善与噪音有关的边缘检测器的性能
- C.图像增强:突出邻域强度的变化值
- D.图像检测:计算图像的梯度,根据阈值进行调整
- E.图像定位:得到单像素的二值边缘图像
(3)Why(边缘检测的目标)
- 边缘定位精度高,单像素
- 对噪声不敏感
- 检测的灵敏度受方向影响小
2、经典的边缘检测算法
(1)梯度基本介绍
在多维连续函数z=f(x,y)中,函数在任一点P=(x,y)的梯度为函数f(x,y)对每个维度的分量[x,y]分别进行偏微分组成的向量,该向量即为函数z=f(x,y)在P=(x,y)处的梯度。
(2)差分边缘检测(手写算法)
x方向上的一阶差分:f(x+1, y) - f(x, y)
y方向上的一阶差分:f(x, y+1) - f(x, y)
二阶差分:在一阶差分的基础上再进行一次差分操作
以下是本人利用一阶差分编写的提取图像边缘的算法,供大家参考,该函数提供x方向,y方向和对角方向上的一阶差分运行以提取图像边缘
/*@author: @还下着雨ZG
* @brief 一阶差分运算提取图像边缘
* @param[in], imSrc, 待提取边缘的图像
* @param[out], imEdg, 输出边缘图像
* @param[in], diffType, 差分类型:0表示对x方向进行差分(垂直边缘),1表示对y方向进行差分(水平边缘),2表示对角方向进行差分(倾斜边缘)
* @return, 正整数表示提取成功,负数表示提取失败
*/
int DiffEdgDtct(const cv::Mat& imSrc, cv::Mat& imEdg, int diffType)
{
if(imSrc.empty()) return -1;
//转换为对灰度图像的操作以减少计算量
cv::Mat imGray;
if (imSrc.channels() == 1)
imGray = imSrc.clone();
else
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
//图像预处理:去噪
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 0);
//对预处理的灰度图像进行差分运算:0表示x方向上的差分,1表示y方向差分,2表示对角线方向差分
cv::Mat kernel;
if (diffType == 0)
{
kernel = (cv::Mat_<int>(3, 3) << 0, 0, 0,
-1, 1, 0,
0, 0, 0);
}
else if (diffType == 1)
{
kernel = (cv::Mat_<int>(3, 3) << 0, -1, 0,
0, 1, 0,
0, 0, 0);
}
else if (diffType == 2)
{
kernel = (cv::Mat_<int>(3, 3) << -1, 0, 0,
0, 1, 0,
0, 0, 0);
}
else
{
std::cerr<<"diffType you input is error!"<<std::endl;
return -1;
}
cv::filter2D(imGray, imEdg, CV_16S, kernel); //对图像进行卷积操作
cv::convertScaleAbs(imEdg, imEdg, 30); //转为CV_8U
//自适应二值化图像处理
cv::Mat imEdgTmp;
cv::threshold(imEdg, imEdgTmp, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
cv::medianBlur(imEdg, imEdg, 5);
return 1;
}
使用方式:通常调用两次该函数并进行加权处理得到边缘图像,但该函数提取的边缘较粗,需要结合形态学操作以提取更加精确的边缘
(3)Roberts边缘检测(手写算法)
Roberts算子又称“交叉微分算法“,当图像边缘接近正负45度的时候,该算法处理效果很不错。缺点是边缘定位不是很精确,且提取的边缘也较粗。
/*@author: @还下着雨ZG
* @brief Robert算子提取图像边缘
* @param[in], imSrc, 待提取边缘的图像
* @param[out], imEdg, 提取的边缘图像
* @param[in], type, 差分类型:0表示对x方向进行差分,1表示对y方向进行差分
* @return, 正整数表示提取成功,负数表示提取失败
*/
int RobertEdgDtct(const cv::Mat &imSrc, cv::Mat &imEdg, int type=0)
{
if(imSrc.empty()) return -1;
//转换为对灰度图像的操作以减少计算量
cv::Mat imGray;
if (imSrc.channels() == 1)
imGray = imSrc.clone();
else
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
//图像预处理:去噪
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 0);
//对预处理的灰度图像进行差分运算:0表示x方向上的差分,1表示y方向差分
cv::Mat kernel;
if (diffType == 0)
{
kernel = (cv::Mat_<int>(2, 2) << -1, 0,
0, 1);
}
else if (diffType == 1)
{
kernel = (cv::Mat_<int>(2, 2) << 0, -1,
1, 0);
}
else
{
std::cerr<<"The type you input is error!"<<std::endl;
return -2;
}
cv::filter2D(imGray,imEdg,CV_16S, kernel); //对图像进行卷积操作
cv::convertScaleAbs(imEdg,imEdg); //转为CV_8U
//自适应二值化图像处理
cv::Mat imEdgTmp;
cv::threshold(imEdg, imEdgTmp, 0, 255, cv::THRESH_BINARY+cv::THRESH_OTSU);
cv::medianBlur(imEdg, imEdg, 5);
return 1;
}
使用方法:在实际应用时,应该利用该函数分别提取正45度方向上的边缘,再提取负45度方向上的边缘,最后加权,并结合形态学操作和轮廓查找函数findContours得到图像的轮廓或边缘。
(4)Sobel算子检测边缘(手写算法)
Sobel算子利用像素的上下左右邻域进行加权的算法,根据在边缘点处达到极值这一原理进行边缘检测。该方法能很好地检测出边缘,且对噪声具有平滑作用,提取的边缘信息比较精确。缺点是Sobel算子并没有严格地将前景和背景分开。
/*@author, @还下着雨ZG
* @brief Sobel算子提取图像边缘
* @param[in], imSrc, 待提取边缘的图像
* @param[out], imEdg, 提取的边缘图像
* @param[in], type, 差分类型:0表示x方向,1表示y方向差分
* @return, 正整数表示提取成功,负数表示提取失败
*/
int SobelEdgDtct(const cv::Mat &imSrc, cv::Mat &imEdg, int type=0)
{
if(imSrc.empty()) return -1;
//转换为对灰度图像的操作以减少计算量
cv::Mat imGray;
if (imSrc.channels() == 1)
imGray = imSrc.clone();
else
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
//图像预处理:去噪
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 0);
//对预处理的灰度图像进行差分运算:0表示x方向上的差分,1表示y方向差分
cv::Mat kernel;
if (diffType == 0)
{
kernel = (cv::Mat_<int>(3, 3) << -1, 0, 1,
-2, 0, 2,
-1, 0, 1);
}
else if (diffType == 1)
{
kernel = (cv::Mat_<int>(3, 3) << -1, -2, -1,
0, 0, 0,
1, 2, 1);
}
else
{
std::cerr<<"The type you input is error!"<<std::endl;
return -2;
}
cv::filter2D(imGray,imEdg,CV_16S, kernel); //对图像进行卷积操作
cv::convertScaleAbs(imEdg,imEdg); //转为CV_8U,30根据自己需求确定
//自适应二值化图像处理
cv::Mat imEdgTmp;
cv::threshold(imEdg, imEdg, 0, 255, cv::THRESH_BINARY+cv::THRESH_OTSU);
cv::medianBlur(imEdg, imEdg, 5);
return 1;
}
另外,opencv提供了自带的Sobel函数来探测图像的边缘信息:
void cv::Sobel(
cv::Mat &imSrc,
cv::Mat &imDst,
int ddepth, //imDst的图像数据格式,如CV_8U,CV_16S,CV_32S等
int dx, //表示x方向的差分阶数
int dy, //表示y方向上的差分阶数
int ksize = 3,//Sobel边缘算子的尺寸
double scale = 1, //对计算结果的缩放因子
double delta = 0, //对计算结果的偏置,res = scale * x + delta
int borderType = BORDER_DEFAULT //表示不包含边界值的倒序填充
);
一般dx,dy和ksize存在一定关系
- dx和dy一定小于ksize,特殊情况时当ksize=1时,
- dx和dy都应该小于3
- dx或dy最大值为1时,ksize=3
- dx或dy最大值为2时,ksize=5
- dx或dy最大值为3时,ksize=7 dx和dy都应该小于等于
(5)Prewitt算子边缘检测(手写算法)
相比于Robert算子,Prewitt算子对噪声具有抑制作用,抑制原理时进行像素平均,因此对噪声不敏感,但由于像素平均相当于对图像进行低通滤波,所以定位不如Roberts精确。
/*@author, @还下着雨ZG
* @brief Prewitt算子提取图像边缘
* @param[in], imSrc, 待提取边缘的图像
* @param[out], imEdg, 提取的边缘图像
* @param[in], type, 差分类型:0表示对正45度方向进行差分
* @return, 正整数表示提取成功,负数表示提取失败
*/
int PrewittEdgDtct(const cv::Mat &imSrc, cv::Mat &imEdg, int type=0)
{
if(imSrc.empty()) return -1;
//转换为对灰度图像的操作以减少计算量
cv::Mat imGray;
if (imSrc.channels() == 1)
imGray = imSrc.clone();
else
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
//图像预处理:去噪
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 0);
//对预处理的灰度图像进行差分运算:0表示x方向上的差分,1表示y方向差分
cv::Mat kernel;
if (diffType == 0)
{
kernel = (cv::Mat_<int>(3, 3) << -1, 0, 1,
-1, 0, 1,
-1, 0, 1);
}
else if (diffType == 1)
{
kernel = (cv::Mat_<int>(3, 3) << 1, 1, 1,
0, 0, 0,
-1, -1, -1);
}
else
{
std::cerr<<"The type you input is error!"<<std::endl;
return -2;
}
cv::filter2D(imGray,imEdg,CV_16S, kernel); //对图像进行卷积操作
cv::convertScaleAbs(imEdg,imEdg); //转为CV_8U,30根据自己需求确定
//自适应二值化图像处理
cv::Mat imEdgTmp;
cv::threshold(imEdg, imEdg, 0, 255, cv::THRESH_BINARY+cv::THRESH_OTSU);
cv::medianBlur(imEdg, imEdg, 5);
return 1;
}
(6)Laplacian边缘检测
void Laplacian(
InputArray imSrc, //输入图像
outputArray imDst, //输出的边缘图像
int ddepth, //imDst的数据类型CV_16S,CV_32S等
int ksize=1, //拉普拉斯算子的尺寸
double scale=1, //计算结果的缩放尺度
doubel delta=0, //偏置值
int borderType=BORDER_DEFAULT);
(7)LoG边缘检测算子(手写算法)
LoG算子把高斯滤波器和拉普拉斯滤波器结合起来使用,先平滑掉噪声,再进行边缘检测,形成的LoG滤波器核如下面代码kernel所示。LoG是检测边缘比较好的边缘检测器。
/*@author, @还下着雨ZG
* @brief LoG算子提取图像边缘
* @param[in], imSrc, 待提取边缘的图像
* @param[out], imEdg, 提取的边缘图像
* @return, 正整数表示提取成功,负数表示提取失败
*/
int LoGEdgDtct(const cv::Mat &imSrc, cv::Mat &imEdg)
{
if(imSrc.empty()) return -1;
//转换为对灰度图像的操作以减少计算量
cv::Mat imGray;
if (imSrc.channels() == 1)
imGray = imSrc.clone();
else
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
//图像预处理:去噪
cv::GaussianBlur(imGray, imGray, cv::Size(3, 3), 0);
//对预处理的灰度图像进行差分运算:0表示x方向上的差分,1表示y方向差分
cv::Mat kernel;
kernel = (cv::Mat_<int>(5, 5) << -2, -4, -4, -4, -2,
-4, 0, 8, 0, -4,
-4, 8, 24, 8, -4,
-4, 0, 8, 0, -4,
-2, -4, -4, -4, -2);
cv::filter2D(imGray,imEdg,CV_16S, kernel); //对图像进行卷积操作
cv::convertScaleAbs(imEdg,imEdg); //转为CV_8U,30根据自己需求确定
//自适应二值化图像处理
cv::Mat imEdgTmp;
cv::threshold(imEdg, imEdg, 0, 255, cv::THRESH_BINARY+cv::THRESH_OTSU);
cv::medianBlur(imEdg, imEdg, 5);
return 1;
}
3、边缘检测新技术和方法
(1)基于小波与分形理论的边缘检测技术
小波变换在时域和频域中都具有良好的局部特性,可将信号或图像分成交织在一起的多尺度组成成分,并对大小不同的尺度成分使用相应粗细的时域或空域取样步长。其灵活的信号处理能力能够不断地聚焦对象的任意微小细节。
边缘检测就是准确定位出信号突变的部分,在数学中表现为不连续点或尖点。
(2)基于数学形态学的边缘检测技术
用集合论的方法定量描述几何结构的技术。
常见的形态学操作如下:膨胀和腐蚀是最基本的两种操作,其它操作时这两种操作的组合
- 膨胀:增加图像中亮色部分,可以对彩色图像、灰度图像和二值图像进行操作
- 腐蚀:增加图像中暗色部分
- 开操作:先腐蚀后膨胀,效果:去除暗色区域中的亮点
- 闭操作:先膨胀后腐蚀,效果:去除亮色区域中的暗点
- 击中与击不中操作:三种值(-1,0,1),-1表示黑,1表示白,0表示任意(黑或白),通过设计模板核,进行击中与击不中操作实现边缘检测、模式匹配等功能。
- 黑帽操作:先对图像进行闭运算,然后将闭运算的结果与原始图像相减,用于增强小细节
- 厚化操作:先对图像进行开运算,然后将结果和原始图像相减,用于强调大细节
形态学操作进行边缘提取的基本使用如下代码所示:
//形态学操作实现边缘提取,可根据具体场景优化后直接拷贝使用
int MorphEdgDtct(const cv::Mat& imSrc, cv::Mat& imEdg)
{
if (imSrc.empty()) return -1;
cv::Mat imGray;
if (imSrc.channels() == 3)
{
cv::cvtColor(imSrc, imGray, cv::COLOR_RGB2GRAY);
}
else
{
imGray = imSrc.clone();
}
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
int iTms = 1;
cv::Mat imTmp01, imTmp02;
cv::dilate(imGray, imTmp01, kernel, cv::Point(-1, -1), iTms);
cv::erode(imGray, imTmp02, kernel, cv::Point(-1, -1), iTms);
cv::Mat imSobel = imTmp01 - imTmp02; //膨胀-腐蚀=边缘
//自适应二值化图像处理
cv::threshold(imSobel, imSobel, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
cv::medianBlur(imSobel, imEdg, 5);
return 1;
}
(3)基于神经网络的边缘检测技术
利用神经网络的方法得到的边缘图像边界连续性比较好,边界封闭性也比较好,并且对于任何灰度图检测都能得到良好的效果。