分享

OpenCV 图像处理篇之边缘检测算子

 心中无海 2016-12-05


来源:xiahouzuoxin

链接:http://blog.csdn.net/xiahouzuoxin/article/details/41173379


3 种边缘检测算子


灰度或结构等信息的突变位置是图像的边缘,图像的边缘有幅度和方向属性,沿边缘方向像素变化缓慢,垂直边缘方向像素变化剧烈。因此,边缘上的变化能通过梯度计算出来。


一阶导数的梯度算子


对于二维的图像,梯度定义为一个向量,



Gx对于x方向的梯度,Gy对应y方向的梯度,向量的幅值本来是 mag(f)?=?(Gx2?+?Gy2)1/2,为简化计算,一般用mag(f)=|Gx|+|Gy|近似,幅值同时包含了x而后y方向的梯度信息。梯度的方向为 α?=?arctan(Gx/Gy) 。


由于图像的数字离散特性,所以梯度微分运算用差分代替,并且用小的空域模板和图像进行卷积近似计算梯度,由于模板的不同,因此衍生处多种梯度算子:Roberts算子、Sobel算子和Prewitt算子。


Sobel与Prewitt算子模板


平滑模板都有一个特点,即模板内所有平滑值的和为0,因此梯度计算的步骤是:


  • 计算Gx与Gy

  • 用mag(f)=|Gx|+|Gy|近似近似计算(x,y)点处的梯度值G(x,y)

  • 模板中心移动一个像素点,计算下一像素点的梯度值(这一平移求和的过程实质就是卷积运算)

  • 计算完所有的像素点处的梯度值后,选择一个阈值T,如果(x,y)处的G(x,y)>T,则认为该点是边缘点


高斯拉普拉斯算子


上面的一阶导数算子,是各向异性的,因此分x方向和y方向的梯度值,而高斯拉普拉斯算子是对图像求二阶导数,边缘对应二阶导数的过零点。



由上式可知,xy进行互换的结果是一样的,所以拉普拉斯算子没有x方向和y方向的区分,拉普拉斯算子对应图像中的差分运算是:




也可以通过卷积模板实现,



LOG算子


相对于一阶导数,高斯拉普拉斯算子(Laplacian of Gaussian, LOG算子)由于求二阶导数,很容易将点噪声判别为边界,因此常在使用LOG算子前先用高斯平滑滤波器去除正态分布的噪声,二维高斯分布为:



其中 σ 为高斯分布标准差,决定高斯滤波器的宽度,用该函数对图像平滑滤波,可以减少椒盐噪声对LOG算子的影响。


Canny 算子


1983,MIT,Canny提出的边缘检测三个标准:


  • 检测标准:不丢失重要的边缘,不应有虚假的边缘;

  • 定位标准:实际边缘与检测到的边缘位置之间的偏差最小;

  • 单响应标准:将多个响应降低为单个边缘响应。也就是说,图像中本来只有一个边缘点,可是却检测出多个边缘点,这就对应了多个响应。


Canny算子力图在抗噪声干扰与精度之间寻求最佳方案,Canny算子有相关的复杂理论,其基本的步骤是:


  1. 使用高斯滤波器平滑图像,卷积核尺度通过高斯滤波器的标准差确定

  2. 计算滤波后图像的梯度幅值和方向 可以使用Sobel算子计算Gx与Gy方向的梯度,则梯度幅值和梯度的方向依次为 

  3. 使用非最大化抑制方法确定当前像素点是否比邻域像素点更可能属于边缘的像素,以得到细化的边缘 其实现是:将当前像素位置的梯度值与其梯度方向上相邻的的梯度方向的梯度值进行比较,如果周围存在梯度值大于当前像素的梯度值,则不认为查找到的当前像素点为边缘点。举例来说,Gx方向的3个梯度值依次为[2 4 3],则在Gx梯度方向上4所在像素点就是边缘点,如果把改成[2 4 1]就不是边缘点。如果用全向的梯度方向作为非最大抑制的判断依据,则要求G(x,y)>所有4邻域的或8邻域的梯度值才被认为是边缘点。

  4. 使用双阈值[T1,T2]法检测边缘的起点和终点,这样能形成连接的边缘。T2>T1,T2用来找到没条线段,T1用来在这条线段两端延伸寻找边缘的断裂处,并连接这些边缘。


OpenCV 中相关源码


Sobel算子及LOG算子的源码在/modules/imgproc/src/deriv.cpp中,Canny算子实现在/modules/imgproc/src/canny.cpp中。


经过之前的基础准备,感觉只要知道什么时候该用什么OpenCV函数,其它的一切都变得简单起来了。于是感觉学着去探索OpenCV的源码对自己的受益会更大,就从这里开始吧。


deriv.cpp中有Sobel算子的源码:




getSobelKernels是实际创建卷积模板的函数,被上面的getDerivKernels调用,不妨看看OpenCV中Sobel创建的卷积模板是啥样的,下面只是getSobelKernels函数的一部分




ksize表示卷积核的大小,之前理论分析中取的是3×3的模板,对应到if( ksize == 3 ),order变量确定对x梯度方向的卷积模板进行赋值还是y梯度方向的卷积进行赋值,因此,当且仅当Sobel函数的输入实参中dx=1时才计算Gx方向的梯度,dy=1时才计算dy方向的梯度。OpenCV没有给出Prewiit算子的源码,但可以自己通过修改替换getDerivKernels函数实现Prewiit的功能。


LOG算子也可以进行相同的分析,这里就不写下来了。再看Canny算子,




C++版本的Canny算子实际就是调用原来C版本中的函数,只是进行了下封装而已,在cvCanny函数中我看到这么几行代码:




Canny就是调用Sobel算子计算x方向的梯度Gx和y方向的梯度Gy。计算梯度角度和非最大化抑制的代码有些长






最后就是使用双阈值跟踪边界,形成连续边缘




这其中貌似用到了栈对邻域梯度信息进行保存,以上详细的实现没做太多的分析,但流程就摆在那里了。请注意,OpenCV中的Canny实现包含了Canny算子的3个步骤,唯独没有第一步中的高斯平滑滤波,因此调用前得先使用高斯平滑滤波。


试试身手


Sobel算子的源码:





简单的换一下函数,就是LOG算子的源码:




Canny算子的源码也很简单,只不过使用了GaussianBlur进行高斯平滑




请注意,上面的Sobel和LOG算子代码都没有在计算结果后使用阈值判断是否属于边界,而直接显示了边缘信息。




Sobel边缘检测结果:右上为x梯度结果,左下为y梯度结果,右下为G(x,y)梯度结果




Laplace边缘检测结果



Canny边缘检测结果


Canny检测算子的效果还是很不错的



关注「CPP开发者」

看更多精选C/C++技术文章

↓↓↓

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多