计算机视觉全系列实战教程:(十一)边缘检测(差分、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)基于神经网络的边缘检测技术

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

相关推荐
TO ENFJ22 分钟前
day 10 机器学习建模与评估
人工智能·机器学习
高效匠人26 分钟前
文章五《卷积神经网络(CNN)与图像处理》
图像处理·人工智能·python·cnn
卧式纯绿36 分钟前
卷积神经网络基础(五)
人工智能·深度学习·神经网络·目标检测·机器学习·计算机视觉·cnn
乌恩大侠37 分钟前
【东枫科技】代理销售 NVIDIA DGX Spark 您的桌上有一台 Grace Blackwell AI 超级计算机。
大数据·人工智能·科技·spark·nvidia
zhanzhan01091 小时前
ubantu安装CUDA
人工智能·python·深度学习
IT古董1 小时前
【漫话机器学习系列】243.数值下溢(Underflow)
人工智能·机器学习
奋斗者1号1 小时前
《机器学习中的过拟合与模型复杂性:理解与应对策略》
人工智能·机器学习
Blossom.1181 小时前
机器学习在网络安全中的应用:守护数字世界的防线
人工智能·深度学习·神经网络·安全·web安全·机器学习·计算机视觉
Ven%1 小时前
LangChain:大语言模型应用的“瑞士军刀”入门指南
人工智能·语言模型·langchain
Echo``1 小时前
4:机器人目标识别无序抓取程序二次开发
开发语言·图像处理·人工智能·qt·计算机视觉·机器人·视觉检测