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 + deltaint 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)基于神经网络的边缘检测技术
利用神经网络的方法得到的边缘图像边界连续性比较好,边界封闭性也比较好,并且对于任何灰度图检测都能得到良好的效果。