Sobel(索贝尔)边缘检测算子分析及opencv程序测试

[原创]Sobel(索贝尔)边缘检测算子分析及opencv程序测试

Author: chad
Mail: [email protected]

推荐参考文件<经典边缘检测算子比较-张丽>

前言

图像的边缘时图像最基本的特征之一。所谓边缘(或边沿)是指周围像素灰度有阶跃性变化或“屋顶”变化的那些像素的集合。边缘广泛存在于物体与背景之间、物体与物体之间、基元与基元之间,因此它是图像分割依赖的重要特征。图像边缘对图像识别和计算机分析十分有用,边缘能勾划出目标物体,使观察者一目了然;边缘蕴含了丰富的内在信息(如方向、阶跃性质、形状等)。从本质上说,图像边缘是图像局部特性不连续性(灰度突变、颜色突变、纹理结构突变等)的反应,它标志着一个区域的终结和另一个区域的开始。

边缘检测技术是所有基于边界分割的图像分析方法的第一步,首先检测出图像局部特性的不连续性,再将它们连成边界,这些边界把图像分成不同的区域,检测出边缘的图像就可以进行特征提取和形状分析。为了得到较好的边缘效果,现在已经有了很多的边缘检测算法以及一些边缘检测算子的改进算法。但各算子有自己的优缺点和适用领域。本文着重对一些经典边缘检测算子进行理论分析、实际验证并对各自性能特点做出比较和评价,以便实际应用中更好地发挥其长处,为新方法的研究提供衡量尺度和改进依据。

一 各种经典边缘检测算子原理简介

图像的边缘对人的视觉具有重要的意义,一般而言,当人们看一个有边缘的物体时,首先感觉到的便是边缘。灰度或结构等信息的突变处称为边缘。边缘是一个区域的结束,也是另一个区域的开始,利用该特征可以分割图像。需要指出的是,检测出的边缘并不等同于实际目标的真实边缘。由于图像数据时二维的,而实际物体是三维的,从三维到二维的投影必然会造成信息的丢失,再加上成像过程中的光照不均和噪声等因素的影响,使得有边缘的地方不一定能被检测出来,而检测出的边缘也不一定代表实际边缘。图像的边缘有方向和幅度两个属性,沿边缘方向像素变化平缓,垂直于边缘方向像素变化剧烈。边缘上的这种变化可以用微分算子检测出来,通常用一阶或两阶导数来检测边缘,如下图所以。不同的是一阶导数认为最大值对应边缘位置,而二阶导数则以过零点对应边缘位置。

这里写图片描述

基于一阶导数的边缘检测算子包括Roberts算子、Sobel算子、Prewitt算子等,在算法实现过程中,通过(Roberts算子)或者模板作为核与图像中的每个像素点做卷积和运算,然后选取合适的阈值以提取边缘。拉普拉斯边缘检测算子是基于二阶导数的边缘检测算子,该算子对噪声敏感。一种改进方式是先对图像进行平滑处理,然后再应用二阶导数的边缘检测算子,其代表是LOG算子。前边介绍的边缘检测算子法是基于微分方法的,其依据是图像的边缘对应一阶导数的极大值点和二阶导数的过零点。Canny算子是另外一类边缘检测算子,它不是通过微分算子检测边缘,而是在满足一定约束条件下推导出的边缘检测最优化算子。

二 Sobel(索贝尔)边缘检测算子

索贝尔算子(Sobel operator)主要用作边缘检测,在技术上,它是一离散性差分算子,用来运算图像亮度函数的灰度之近似值。在图像的任何一点使用此算子,将会产生对应的灰度矢量或是其法矢量.

这里写图片描述
根据上面的公式,得出Sobel卷积因子模板为:
这里写图片描述

该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:
这里写图片描述

具体计算如下:

Gx = (-1)*f(x-1, y-1) + 0*f(x,y-1) + 1*f(x+1,y-1)

      +(-2)*f(x-1,y) + 0*f(x,y)+2*f(x+1,y)

      +(-1)*f(x-1,y+1) + 0*f(x,y+1) + 1*f(x+1,y+1)

= [f(x+1,y-1)+2*f(x+1,y)+f(x+1,y+1)]-[f(x-1,y-1)+2*f(x-1,y)+f(x-1,y+1)]



Gy =1* f(x-1, y-1) + 2*f(x,y-1)+ 1*f(x+1,y-1)

      +0*f(x-1,y) 0*f(x,y) + 0*f(x+1,y)

      +(-1)*f(x-1,y+1) + (-2)*f(x,y+1) + (-1)*f(x+1, y+1)

= [f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1)]-[f(x-1, y+1) + 2*f(x,y+1)+f(x+1,y+1)]

其中f(a,b), 表示图像(a,b)点的灰度值;
图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小:

这里写图片描述

通常,为了提高效率 使用不开平方的近似值:
这里写图片描述

如果梯度G大于某一阀值 则认为该点(x,y)为边缘点。

然后可用以下公式计算梯度方向:
这里写图片描述

Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
  

三 程序测试

实际测试效果如下:
这里写图片描述
要完成测试首先要保证电脑上正确安装了OPENCV开发环境.测试用opencv程序如下:

/*
    2015-07-24 [email protected]
    编译命令如下:
    g++ `pkg-config opencv --cflags` sobel.c -o sobel `pkg-config opencv --libs` 
*/
#include <opencv/highgui.h>
#include <stdio.h>
/* Sobel template
a00 a01 a02
a10 a11 a12
a20 a21 a22
    | -1 0 +1 
Gy= | -2 0 +2
    | -1 0 +1

    | -1 -2 -1 
Gx= |  0  0  0
    | +1 +2 +1 
*/  
char Gx00,Gx01,Gx02,Gx10,Gx11,Gx12,Gx20,Gx21,Gx22;
char Gy00,Gy01,Gy02,Gy10,Gy11,Gy12,Gy20,Gy21,Gy22;
unsigned char a00, a01, a02;
unsigned char a10, a11, a12;
unsigned char a20, a21, a22;
/*
void MySobel(IplImage* gray, IplImage* gradient)
{
    CvScalar color ;
    for (int i=1; i<gray->height-1; ++i)
    {
        for (int j=1; j<gray->width-1; ++j)
        {
            a00 = cvGet2D(gray, i-1, j-1).val[0];
            a01 = cvGet2D(gray, i-1, j).val[0];
            a02 = cvGet2D(gray, i-1, j+1).val[0];
            a10 = cvGet2D(gray, i, j-1).val[0];
            a11 = cvGet2D(gray, i, j).val[0];
            a12 = cvGet2D(gray, i, j+1).val[0];
            a20 = cvGet2D(gray, i+1, j-1).val[0];
            a21 = cvGet2D(gray, i+1, j).val[0];
            a22 = cvGet2D(gray, i+1, j+1).val[0];
            // x方向上的近似导数
            double ux = a20 * (1) + a21 * (2) + a22 * (1)
            + (a00 * (-1) + a01 * (-2) + a02 * (-1));
            // y方向上的近似导数
            double uy = a02 * (1) + a12 * (2) + a22 * (1)
            + a00 * (-1) + a10 * (-2) + a20 * (-1);
            color.val[0] = sqrt(ux*ux + uy*uy);
            cvSet2D(gradient, i, j, color);
        }
    }
}*/
//使用指针方式比使用内敛函数的方式效率提升非常多,经测试cpu占用从60%下降到30%
void MySobel(IplImage* gray, IplImage* gradient)
{
    for (int i=1; i<gray->height-1; ++i)
    {
        unsigned char *in_0 = (unsigned char*)(gray->imageData + (i-1) * gray->widthStep);
        unsigned char *in_1 = (unsigned char*)(gray->imageData + i * gray->widthStep);
        unsigned char *in_2 = (unsigned char*)(gray->imageData + (i+1) * gray->widthStep);
        unsigned char *out = (unsigned char*)(gradient->imageData + i * gradient->widthStep);
        for (int j=1; j<gray->width-1; ++j)
        {
            a00 = in_0[j-1];
            a01 = in_0[j];
            a02 = in_0[j+1];
            a10 = in_1[j-1];
            a11 = in_1[j];
            a12 = in_1[j+1];
            a20 = in_2[j-1];
            a21 = in_2[j];
            a22 = in_2[j+1];
            // x方向上的近似导数
            double ux = a20 * Gy20 + a21 * Gy21 + a22 * Gy22
            + (a00 * Gy00 + a01 * Gy01 + a02 * Gy02);
            // y方向上的近似导数
            double uy = a02 * Gx02 + a12 * Gx12 + a22 * Gx22
            + a00 * Gx00 + a10 * Gx10 + a20 * Gx20;
            out[j] = sqrt(ux*ux + uy*uy);
        }
    }
}
int main(int argc,char *argv[])
{
    char tmpbuf[100];

    CvCapture* capture ;

    cvNamedWindow("test");
    if( argc != 1 )
        capture = cvCreateFileCapture( argv[1] );   //从视频文件获取图像
    else 
        capture = cvCreateCameraCapture( 1 );       //从视频设备获取图像
    IplImage *frame;
    IplImage *out = NULL;
    IplImage *grayimg = NULL;
    int i = 0;

/* Sobel template
    | -1 0 +1 
Gx= | -2 0 +2
    | -1 0 +1

    | -1 -2 -1 
Gy= |  0  0  0
    | +1 +2 +1 
*/  
    Gy00=-1;Gy01=0;Gy02=1;
    Gy10=-2;Gy11=0;Gy12=2;
    Gy20=-1;Gy21=0;Gy22=1;

    Gx00=-1;Gx01=-2;Gx02=-1;
    Gx10=0;Gx11=0;Gx12=0;
    Gx20=1;Gx21=2;Gx22=1;
    while(1)
    {
        frame = cvQueryFrame( capture );
        if( !frame ) break;
        if(!out) {
            out = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,1); //深度8bit 单通道
        }
        if(!grayimg){
            grayimg = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,1);
        }

        cvCvtColor( frame, grayimg, CV_RGB2GRAY ); //rgb转换为灰度图像
        MySobel( grayimg, out );
        cvShowImage("test",out);  //显示图像        
        char c = cvWaitKey(50);   //等待按键输入
        if( c == 27 ) break;
    }       

    cvReleaseCapture(&capture);
    cvReleaseImage( &frame );
    cvReleaseImage( &out );
    cvReleaseImage( &grayimg );
    cvDestroyWindow("test");

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章