当前位置: 首页 > article >正文

Sobel算子原理及OpenCv实现

索贝尔算子Sobeloperator主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测。

在技术上,它是一个离散的一阶差分算子,用来计算图像亮度函数的一阶梯度之近似值,用来运算图像亮度函数的灰度值近似值。

在图像的任何一点使用此算子,将会产生该点对应的梯度矢量或是其法矢量。



一、Sobel边缘检测算子

在讨论边缘算子之前,首先给出一些术语的定义:

(1)边缘:灰度或结构等信息的突变处,边缘是一个区域的结束,也是另一个区域的开始,利用该特征可以分割图像。

(2)边缘点:图像中具有坐标[xy],且处在强度显著变化的位置上的点。

(3)边缘段:对应于边缘点坐标[xy]及其方位,边缘的方位可能是梯度角。

二、Sobel算子的基本原理

Sobel算子是一阶导数的边缘检测算子,在算法实现过程中,通过3×3模板作为核与图像中的每个像素点做卷积和运算,然后选取合适的阈值以提取边缘。

采用3×3邻域可以避免在像素之间内插点上计算梯度。Sobel算子也是一种梯度幅值,即:

其中的偏导数SxSy可用卷积模板来实现。

Sx=(Z3+Z6+Z9)-(Z1+Z4+Z7)

Sy=(Z7+Z8+Z9)-(Z1+Z2+Z3)



Sobel算子算法的优点是计算简单,速度快。但是由于只采用了2个方向的模板,只能检测水平和垂直方向的边缘,因此这种算法对于纹理较为复杂的图像,其边缘检测效果就不是很理想。该算法认为:凡灰度新值大于或等于阈值的像素点时都是边缘点。这种判断欠合理,会造成边缘点的误判,因为许多噪声点的灰度值也很大。



以灰度图像为例,它的理论基础是这样的,如果出现一个边缘,那么图像的灰度就会有一定的变化,为了方便假设由黑渐变为白代表一个边界,那么对其灰度分析,在边缘的灰度函数就是一个一次函数y=kx,对其求一阶导数就是其斜率k,就是说边缘的一阶导数是一个常数,而由于非边缘的一阶导数为零,这样通过求一阶导数就能初步判断图像的边缘了。通常是X方向和Y方向的导数,也就是梯度。理论上计算机就是通过这种方式来获得图像的边缘。

但是,具体应用到图像中你会发现这个导数是求不了的,因为没一个准确的函数去求导,而且计算机在求解析解要比求数值解麻烦得多,所以就想到了一种替代的方式来求导数。就是用一个3×3的窗口来对图像进行近似求导。拿对X方向求导为例,某一点的导数为第三列的元素之和减去第一列元素之和,这样就求得了某一点的近似导数。其实也很好理解为什么它就近似代表导数,导数就代表一个变化率,从第一列变为第三列,灰度值相减,当然就是一个变化率了。这就是所谓的Prewitt算子。这样近似X方向导数就求出来了。Y方向导数与X方向导数求法相似,只不过是用第三行元素之和减去第一行元素之和。X方向和Y方向导数有了,那么梯度也就出来了。这样就可以找出一幅图中的边缘了。

还有一个问题,由于求的是3×3中心点的导数,所以给第二列加了一个权重,它的权重为2,第一列和第三列的权重为1,好了,这就是Sobel算子了。相比Prewitt算子,Sobel的抗噪能力更强。如图所示:这样,中心点的Y方向导数就求出来了。

 举个例子吧。X点以Sobel方式求导数ΔX=1×50+2×30+1×50-(1×50+2×30+1×50=0。这样可以看出这个点不是边界。

好了,了解了基本理论之后,我们看看OpenCv下的Sobel函数吧

voidcvSobel( const CvArr* src, CvArr* dst, int xorder, int yorder, intaperture_size=3 );

src:输入图像;dst:输出图像;xorderx方向上的差分阶数;yordery方向上的差分阶数;

aperture_size扩展Sobel核的大小(既窗口阶数),必须是1(注意这是一个3×11×3向量而不是一个方阵),3, 5 7

代码实现:

#include<cv.h>

#include<highgui.h>

voidmain()

{

IplImage*frame,*gray,*sobel;

frame=cvLoadImage("lena.jpg");//加载图像

gray=cvCreateImage(cvGetSize(frame),frame->depth,1);//分配图像空间

sobel=cvCreateImage(cvGetSize(frame),frame->depth,1);

cvNamedWindow("frame");

cvNamedWindow("gray");

cvNamedWindow("sobel");

cvCvtColor(frame,gray,CV_BGR2GRAY);//转为灰度

cvSobel(gray,sobel,1,0,3);

cvShowImage("frame",frame);//显示图像

cvShowImage("gray",gray);

cvShowImage("sobel",sobel);

cvWaitKey(0);//等待

cvReleaseImage(&frame);//释放空间(对视频处理很重要,不释放会造成内存泄露)

cvReleaseImage(&gray);

cvReleaseImage(&sobel);

cvDestroyWindow("frame");

cvDestroyWindow("gray");

cvDestroyWindow("sobel");

}

运行,你会发现出错,仔细看看没有问题啊。其实,这里是问题的,因为以Sobel方式求完导数后会有负值,还有会大于255的值而你建的Sobel的图像是IPL_DEPTH_8U,也就是8位无符号数,所以Sobel建立的图像位数不够,要16位有符号的,也就是IPL_DEPTH_16S。把建立图像这句改为

sobel=cvCreateImage(cvGetSize(frame),IPL_DEPTH_16S,1);

运行,发现不报错了,但是Sobel图像显示不出来,这是什么原因呢?

原来图像显示是以8位无符号显示的,现在是16位有符号,当然显示会出问题了。

所以还要将Sobel转为8位无符号。

OpenCv里提供了一个函数,就是cvConvertScaleAbs(const CvArr* src, CvArr* dst, double scale=1, double shift=0 );

src:源图像;dst:目标图像;scale:转化前乘的系数;shift转化前加的系数。这样新建一个无符号图像再转换就可以实现了。

IplImage*sobel8u=cvCreateImage(cvGetSize(sobel),IPL_DEPTH_8U,1);

再在显示图像前加上cvConvertScaleAbs(sobel,sobel8u,1,0);这样就可以看到cvSobel的效果了。可以看X方向或Y方向求导是什么效果。

为了方便大家,我把改好后的程序也放上来了。

#include<cv.h>

#include<highgui.h>

voidmain()

{

IplImage*frame,*gray,*sobel;

frame=cvLoadImage("e:/p1.jpg");//加载图像

gray=cvCreateImage(cvGetSize(frame),frame->depth,1);//分配图像空间

sobel=cvCreateImage(cvGetSize(frame),IPL_DEPTH_16S,1);

cvNamedWindow("frame");

cvNamedWindow("gray");

cvNamedWindow("sobel");

cvCvtColor(frame,gray,CV_BGR2GRAY);//转为灰度

cvSobel(gray,sobel,1,0,3);

IplImage*sobel8u=cvCreateImage(cvGetSize(sobel),IPL_DEPTH_8U,1);

cvConvertScaleAbs(sobel,sobel8u,1,0);

cvShowImage("frame",frame);//显示图像

cvShowImage("gray",gray);

cvShowImage("sobel",sobel8u);

cvWaitKey(0);//等待

cvReleaseImage(&frame);//释放空间(对视频处理很重要,不释放会造成内存泄露)

cvReleaseImage(&gray);

cvReleaseImage(&sobel);

cvDestroyWindow("frame");

cvDestroyWindow("gray");

cvDestroyWindow("sobel");

}

问题:

cvSobel(gray,sobel,1,0,3);表示对图像x方向进行一阶差分,得到的图像垂直的边缘比较明显,而cvSobel(gray,sobel,0,1,3);表示对图像y方向进行一阶差分,得到的图像水平的边缘比较明显,如果使用cvSobel(gray,sobel,1,1,3);两个方向的边缘反而都不清楚了。

感谢:

http://blog.csdn.net/yanmy2012/article/details/8110316

http://blog.sina.com.cn/s/blog_4bdbec750100mufo.html

http://blog.csdn.net/goodshot/article/details/10170073



http://www.lryc.cn/news/2417030.html

相关文章:

  • C#中progressBar控件详细使用方法
  • epoches,batch_size,batches,steps
  • bochs用户手册-3.4编译
  • MATLAB的MEX文件编写和调试
  • 国外VPS如何进行网络加速?最新VPS加速访问方法
  • Latex制作beamer幻灯片、写论文的基本语句
  • C++中的fstream、ofstream、ifstream详解
  • RYU基础整理
  • knox 配置
  • Linux服务器ping不通域名出现的unknown host 错误解决办法
  • 【Socket网络编程】16.UDP 循环读取recvfrom() 与 循环发送 sendto()
  • onmouseover 、onmouseout 与onmouseenter 、onmouseleave的区别
  • F5 GTM DNS 知识点和实验 4 -智能DNS基础
  • 全面认识“互联网数据中心(IDC)”,读这一篇就够了
  • 深入理解C#中常见的委托【详细】
  • Verilog曼彻斯特编解码器设计
  • SLI、SLO和SLA,一文彻底搞懂!!!
  • win10系统玩生化危机5提示缺少xlive.dll怎么办?快速修复方法介绍
  • python中isalpha的用法_python函数--isalpha()方法
  • 晟盾科技加入龙蜥社区,共建开源新生态
  • 一套详细的综合布线系统设计方案素材
  • 5.CGLIB动态代理源码之Enhancer的创建
  • Cluster概念详解
  • XSD 初学 (三)
  • 电脑技巧:盘点10个非常实用且有趣的网站
  • 盘点世界上著名的游戏公司
  • 重复文件清理绿色工具——DoubleKiller
  • 图的应用【最短路径】 —— Floyd 算法
  • System类中setProperty()和getProperty()方法
  • 在 Ubuntu 20.04 上安装 GCC