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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章