计算机视觉全系列实战教程:(十一)边缘检测(差分、Roberts、Sobel、Prewitt、LoG、基于形态学的边缘检测等)

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方向和对角方向上的一阶差分运行以提取图像边缘

cpp 复制代码
/*@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度的时候,该算法处理效果很不错。缺点是边缘定位不是很精确,且提取的边缘也较粗。

cpp 复制代码
/*@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算子并没有严格地将前景和背景分开。

cpp 复制代码
/*@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函数来探测图像的边缘信息:

cpp 复制代码
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精确。

cpp 复制代码
/*@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边缘检测

cpp 复制代码
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是检测边缘比较好的边缘检测器。

cpp 复制代码
/*@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表示任意(黑或白),通过设计模板核,进行击中与击不中操作实现边缘检测、模式匹配等功能。
  • 黑帽操作:先对图像进行闭运算,然后将闭运算的结果与原始图像相减,用于增强小细节
  • 厚化操作:先对图像进行开运算,然后将结果和原始图像相减,用于强调大细节
    形态学操作进行边缘提取的基本使用如下代码所示:
cpp 复制代码
//形态学操作实现边缘提取,可根据具体场景优化后直接拷贝使用
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)基于神经网络的边缘检测技术

利用神经网络的方法得到的边缘图像边界连续性比较好,边界封闭性也比较好,并且对于任何灰度图检测都能得到良好的效果。

相关推荐
深度学习实战训练营19 分钟前
基于keras的停车场车位识别
人工智能·深度学习·keras
乔代码嘚26 分钟前
AI2.0时代,普通小白如何通过AI月入30万
人工智能·stable diffusion·aigc
墨@#≯27 分钟前
机器学习系列篇章0 --- 人工智能&机器学习相关概念梳理
人工智能·经验分享·机器学习
Elastic 中国社区官方博客34 分钟前
Elasticsearch:使用 LLM 实现传统搜索自动化
大数据·人工智能·elasticsearch·搜索引擎·ai·自动化·全文检索
_.Switch44 分钟前
Python机器学习模型的部署与维护:版本管理、监控与更新策略
开发语言·人工智能·python·算法·机器学习
XiaoLiuLB1 小时前
ChatGPT Canvas:交互式对话编辑器
人工智能·自然语言处理·chatgpt·编辑器·aigc
Hoper.J1 小时前
PyTorch 模型保存与加载的三种常用方式
人工智能·pytorch·python
菜就多练_08281 小时前
《深度学习》OpenCV 摄像头OCR 过程及案例解析
人工智能·深度学习·opencv·ocr
达柳斯·绍达华·宁1 小时前
CNN中的平移不变性和平移等变性
人工智能·神经网络·cnn
没有余地 EliasJie2 小时前
Windows Ubuntu下搭建深度学习Pytorch训练框架与转换环境TensorRT
pytorch·windows·深度学习·ubuntu·pycharm·conda·tensorflow