圖像邊緣檢測——canny算子原理與代碼

轉自:http://blog.csdn.net/dcrmg/article/details/5234490

另有:http://blog.csdn.net/likezhaobin/article/details/6892629

            http://blog.csdn.net/ftxv95x5/article/details/55657534

            http://blog.csdn.net/tigerda/article/details/61192943        參考

常見邊緣檢測算子:Roberts 、Sobel 、Prewitt、Laplacian、Log/Marr、Canny、Kirsch、Nevitia

       一階微分算子:Roberts 、Sobel 、Prewitt

     二階微分算子:Laplacian、Log/Marr

            非微分邊緣檢測算子:Canny


Canny邊緣檢測算法是澳大利亞科學家John F. Canny在1986年提出來的,不得不提一下的是當年John Canny本人才28歲!到今天已經30年過去了,Canny算法仍然是圖像邊緣檢測算法中最經典、有效的算法之一。

一起睹一下大家Canny的風采:



John Canny研究了最優邊緣檢測方法所需的特性,給出了評價邊緣檢測性能優劣的3個指標:

  • 1  好的信噪比,即將非邊緣點判定爲邊緣點的概率要低,將邊緣點判爲非邊緣點的概率要低;
  • 2 高的定位性能,即檢測出的邊緣點要儘可能在實際邊緣的中心;
  • 3 對單一邊緣僅有唯一響應,即單個邊緣產生多個響應的概率要低,並且虛假響應邊緣應該得到最大抑制;
Canny算法就是基於滿足這3個指標的最優解實現的,在對圖像中物體邊緣敏感性的同時,也可以抑制或消除噪聲的影響。

Canny算子邊緣檢測的具體步驟如下:

  • 一、用高斯濾波器平滑圖像
  • 二、用Sobel等梯度算子計算梯度幅值和方向
  • 三、對梯度幅值進行非極大值抑制
  • 四、用雙閾值算法檢測和連接邊緣


一、用高斯濾波器平滑圖像


高斯濾波是一種線性平滑濾波,適用於消除高斯噪聲,特別是對抑制或消除服從正態分佈的噪聲非常有效。濾波可以消除或降低圖像中噪聲的影響,使用高斯濾波器主要是基於在濾波降噪的同時也可以最大限度保留邊緣信息的考慮。


高斯濾波實現步驟:

1.1  彩色RGB圖像轉換爲灰度圖像

邊緣檢測是基於對圖像灰度差異運算實現的,所以如果輸入的是RGB彩色圖像,需要先進行灰度圖的轉換。
RGB轉換成灰度圖像的一個常用公式是:

Gray = R*0.299 + G*0.587 + B*0.114

C++代碼實現起來也比較簡單,注意一般情況下圖像處理中彩色圖像各分量的排列順序是B、G、R

  1. //******************灰度轉換函數*************************  
  2. //第一個參數image輸入的彩色RGB圖像;  
  3. //第二個參數imageGray是轉換後輸出的灰度圖像;  
  4. //*************************************************************  
  5. void ConvertRGB2GRAY(const Mat &image,Mat &imageGray)  
  6. {  
  7.     if(!image.data||image.channels()!=3)  
  8.     {  
  9.         return ;  
  10.     }  
  11.     imageGray=Mat::zeros(image.size(),CV_8UC1);  
  12.     uchar *pointImage=image.data;  
  13.     uchar *pointImageGray=imageGray.data;  
  14.     int stepImage=image.step;  
  15.     int stepImageGray=imageGray.step;  
  16.     for(int i=0;i<imageGray.rows;i++)  
  17.     {  
  18.         for(int j=0;j<imageGray.cols;j++)  
  19.         {  
  20.             pointImageGray[i*stepImageGray+j]=0.114*pointImage[i*stepImage+3*j]+0.587*pointImage[i*stepImage+3*j+1]+0.299*pointImage[i*stepImage+3*j+2];  
  21.         }  
  22.     }  
  23. }  

RGB原圖像:                                                                 轉換後的灰度圖:

                                             

1.2 生成高斯濾波卷積核


高斯濾波的過程是將灰度圖像跟高斯卷積核卷積,所以第一步是先要求解出給定尺寸和Sigma的高斯卷積核參數,以下代碼實現對卷積核參數求解:

  1. //******************高斯卷積核生成函數*************************  
  2. //第一個參數gaus是一個指向含有N個double類型數組的指針;  
  3. //第二個參數size是高斯卷積核的尺寸大小;  
  4. //第三個參數sigma是卷積核的標準差  
  5. //*************************************************************  
  6. void GetGaussianKernel(double **gaus, const int size,const double sigma)  
  7. {  
  8.     const double PI=4.0*atan(1.0); //圓周率π賦值  
  9.     int center=size/2;  
  10.     double sum=0;  
  11.     for(int i=0;i<size;i++)  
  12.     {  
  13.         for(int j=0;j<size;j++)  
  14.         {  
  15.             gaus[i][j]=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));  
  16.             sum+=gaus[i][j];  
  17.         }  
  18.     }  
  19.     for(int i=0;i<size;i++)  
  20.     {  
  21.         for(int j=0;j<size;j++)  
  22.         {  
  23.             gaus[i][j]/=sum;  
  24.             cout<<gaus[i][j]<<"  ";  
  25.         }  
  26.         cout<<endl<<endl;  
  27.     }  
  28.     return ;  
  29. }  

Sigma爲1時,求得的3*3大小的高斯卷積核參數爲:



Sigma爲1,5*5大小的高斯卷積核參數爲:



以下運算中Canny算子使用的是尺寸5*5,Sigma爲1的高斯核。
更詳細的高斯濾波及高斯卷積核生成介紹可以移步這裏查看:http://blog.csdn.net/dcrmg/article/details/52304446

1.3  高斯濾波


用在1.2中生成的高斯卷積核跟灰度圖像卷積,得到灰度圖像的高斯濾波後的圖像,抑制噪聲。
代碼實現:

  1. //******************高斯濾波*************************  
  2. //第一個參數imageSource是待濾波原始圖像;  
  3. //第二個參數imageGaussian是濾波後輸出圖像;  
  4. //第三個參數gaus是一個指向含有N個double類型數組的指針;  
  5. //第四個參數size是濾波核的尺寸  
  6. //*************************************************************  
  7. void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size)  
  8. {  
  9.     imageGaussian=Mat::zeros(imageSource.size(),CV_8UC1);  
  10.     if(!imageSource.data||imageSource.channels()!=1)  
  11.     {  
  12.         return ;  
  13.     }  
  14.     double gausArray[100];   
  15.     for(int i=0;i<size*size;i++)  
  16.     {  
  17.         gausArray[i]=0;  //賦初值,空間分配  
  18.     }  
  19.     int array=0;  
  20.     for(int i=0;i<size;i++)  
  21.     {  
  22.         for(int j=0;j<size;j++)  
  23.   
  24.         {  
  25.             gausArray[array]=gaus[i][j];//二維數組到一維 方便計算  
  26.             array++;  
  27.         }  
  28.     }  
  29.     //濾波  
  30.     for(int i=0;i<imageSource.rows;i++)  
  31.     {  
  32.         for(int j=0;j<imageSource.cols;j++)  
  33.         {  
  34.             int k=0;  
  35.             for(int l=-size/2;l<=size/2;l++)  
  36.             {  
  37.                 for(int g=-size/2;g<=size/2;g++)  
  38.                 {  
  39.                     //以下處理針對濾波後圖像邊界處理,爲超出邊界的值賦值爲邊界值  
  40.                     int row=i+l;  
  41.                     int col=j+g;  
  42.                     row=row<0?0:row;  
  43.                     row=row>=imageSource.rows?imageSource.rows-1:row;  
  44.                     col=col<0?0:col;  
  45.                     col=col>=imageSource.cols?imageSource.cols-1:col;  
  46.                     //卷積和  
  47.                     imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);  
  48.                     k++;  
  49.                 }  
  50.             }  
  51.         }  
  52.     }  
  53. }  

高斯濾波後的圖像:



跟原圖相比,圖像有一定程度的模糊。


  • 二、用Sobel等梯度算子計算梯度幅值和方向

圖像灰度值的梯度可以使用最簡單的一階有限差分來進行近似,使用以下圖像在x和y方向上偏導數的兩個矩陣:


計算公式爲:



其中f爲圖像灰度值,P代表X方向梯度幅值,Q代表Y方向 梯度幅值,M是該點幅值,Θ是梯度方向,也就是角度。


2.1  Sobel卷積覈計算X、Y方向梯度和梯度角


這裏使用較爲常用的Sobel算子計算X和Y方向上的梯度以及梯度的方向角,Sobel的X和Y方向的卷積因子爲:




更多關於Sobel算子的介紹可以移步這裏查看:http://blog.csdn.net/dcrmg/article/details/52280768

使用Sobel卷積因子計算X、Y方向梯度和梯度方向角代碼實現:

  1. //******************Sobel卷積因子計算X、Y方向梯度和梯度方向角********************  
  2. //第一個參數imageSourc原始灰度圖像;  
  3. //第二個參數imageSobelX是X方向梯度圖像;  
  4. //第三個參數imageSobelY是Y方向梯度圖像;  
  5. //第四個參數pointDrection是梯度方向角數組指針  
  6. //*************************************************************  
  7. void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection)  
  8. {  
  9.     pointDrection=new double[(imageSource.rows-1)*(imageSource.cols-1)];  
  10.     for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++)  
  11.     {  
  12.         pointDrection[i]=0;  
  13.     }  
  14.     imageSobelX=Mat::zeros(imageSource.size(),CV_32SC1);  
  15.     imageSobelY=Mat::zeros(imageSource.size(),CV_32SC1);  
  16.     uchar *P=imageSource.data;    
  17.     uchar *PX=imageSobelX.data;    
  18.     uchar *PY=imageSobelY.data;    
  19.   
  20.     int step=imageSource.step;    
  21.     int stepXY=imageSobelX.step;   
  22.     int k=0;  
  23.     int m=0;  
  24.     int n=0;  
  25.     for(int i=1;i<(imageSource.rows-1);i++)    
  26.     {    
  27.         for(int j=1;j<(imageSource.cols-1);j++)    
  28.         {    
  29.             //通過指針遍歷圖像上每一個像素   
  30.             double gradY=P[(i-1)*step+j+1]+P[i*step+j+1]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[i*step+j-1]*2-P[(i+1)*step+j-1];  
  31.             PY[i*stepXY+j*(stepXY/step)]=abs(gradY);  
  32.             double gradX=P[(i+1)*step+j-1]+P[(i+1)*step+j]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[(i-1)*step+j]*2-P[(i-1)*step+j+1];  
  33.             PX[i*stepXY+j*(stepXY/step)]=abs(gradX);  
  34.             if(gradX==0)  
  35.             {  
  36.                 gradX=0.00000000000000001;  //防止除數爲0異常  
  37.             }  
  38.             pointDrection[k]=atan(gradY/gradX)*57.3;//弧度轉換爲度  
  39.             pointDrection[k]+=90;  
  40.             k++;  
  41.         }    
  42.     }   
  43.     convertScaleAbs(imageSobelX,imageSobelX);  
  44.     convertScaleAbs(imageSobelY,imageSobelY);  
  45. }  


數組指針pointDirection裏存放了每個點上的梯度方向角,以度爲單位。由於atan求得的角度範圍是-π/2~π/2,爲了便於計算,這裏對每個梯度角加了一個π/2,使範圍變成0~π,便於計算。


X方向梯度圖:                                                 Y方向梯度圖:

                              


2.2  求梯度圖的幅值

求得X、Y方向的梯度和梯度角之後再來計算X和Y方向融合的梯度幅值,計算公式爲:




代碼實現,較爲簡單:

  1. //******************計算Sobel的X和Y方向梯度幅值*************************  
  2. //第一個參數imageGradX是X方向梯度圖像;  
  3. //第二個參數imageGradY是Y方向梯度圖像;  
  4. //第三個參數SobelAmpXY是輸出的X、Y方向梯度圖像幅值  
  5. //*************************************************************  
  6. void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY)  
  7. {  
  8.     SobelAmpXY=Mat::zeros(imageGradX.size(),CV_32FC1);  
  9.     for(int i=0;i<SobelAmpXY.rows;i++)  
  10.     {  
  11.         for(int j=0;j<SobelAmpXY.cols;j++)  
  12.         {  
  13.             SobelAmpXY.at<float>(i,j)=sqrt(imageGradX.at<uchar>(i,j)*imageGradX.at<uchar>(i,j)+imageGradY.at<uchar>(i,j)*imageGradY.at<uchar>(i,j));  
  14.         }  
  15.     }  
  16.     convertScaleAbs(SobelAmpXY,SobelAmpXY);  
  17. }  

求得的X和Y方向幅度和疊加了兩個方向上的幅值:



  • 三、對梯度幅值進行非極大值抑制

求幅值圖像進行非極大值抑制,可以進一步消除非邊緣的噪點,更重要的是,可以細化邊緣。
抑制邏輯是:沿着該點梯度方向,比較前後兩個點的幅值大小,若該點大於前後兩點,則保留,若該點小於前後兩點,則置爲0
示意圖如下:



圖中四條虛線代表圖像中每一點可能的梯度方向,沿着梯度方向與邊界的上下兩個交點,就是需要拿來與中心點點(X0,Y0)做比較的點。交點值的計算採用插值法計算,以黃色的虛線所代表的梯度角Θ爲例,交點處幅值爲:
P(X0,Y0)+(P(X0-1,Y0+1)-P(X0,Y0))*tan(Θ)


四種情況下需要分別計算,代碼實現如下:


  1. //******************局部極大值抑制*************************  
  2. //第一個參數imageInput輸入的Sobel梯度圖像;  
  3. //第二個參數imageOutPut是輸出的局部極大值抑制圖像;  
  4. //第三個參數pointDrection是圖像上每個點的梯度方向數組指針  
  5. //*************************************************************  
  6. void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection)  
  7. {  
  8.     //imageInput.copyTo(imageOutput);  
  9.     imageOutput=imageInput.clone();  
  10.     int k=0;  
  11.     for(int i=1;i<imageInput.rows-1;i++)  
  12.     {  
  13.         for(int j=1;j<imageInput.cols-1;j++)  
  14.         {  
  15.             int value00=imageInput.at<uchar>((i-1),j-1);  
  16.             int value01=imageInput.at<uchar>((i-1),j);  
  17.             int value02=imageInput.at<uchar>((i-1),j+1);  
  18.             int value10=imageInput.at<uchar>((i),j-1);  
  19.             int value11=imageInput.at<uchar>((i),j);  
  20.             int value12=imageInput.at<uchar>((i),j+1);  
  21.             int value20=imageInput.at<uchar>((i+1),j-1);  
  22.             int value21=imageInput.at<uchar>((i+1),j);  
  23.             int value22=imageInput.at<uchar>((i+1),j+1);  
  24.   
  25.             if(pointDrection[k]>0&&pointDrection[k]<=45)  
  26.             {  
  27.                 if(value11<=(value12+(value02-value12)*tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)*tan(pointDrection[i*imageOutput.rows+j]))))  
  28.                 {  
  29.                     imageOutput.at<uchar>(i,j)=0;  
  30.                 }  
  31.             }     
  32.             if(pointDrection[k]>45&&pointDrection[k]<=90)  
  33.   
  34.             {  
  35.                 if(value11<=(value01+(value02-value01)/tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/tan(pointDrection[i*imageOutput.cols+j])))  
  36.                 {  
  37.                     imageOutput.at<uchar>(i,j)=0;  
  38.   
  39.                 }  
  40.             }  
  41.             if(pointDrection[k]>90&&pointDrection[k]<=135)  
  42.             {  
  43.                 if(value11<=(value01+(value00-value01)/tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrection[i*imageOutput.cols+j])))  
  44.                 {  
  45.                     imageOutput.at<uchar>(i,j)=0;  
  46.                 }  
  47.             }  
  48.             if(pointDrection[k]>135&&pointDrection[k]<=180)  
  49.             {  
  50.                 if(value11<=(value10+(value00-value10)*tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrection[i*imageOutput.cols+j])))  
  51.                 {  
  52.                     imageOutput.at<uchar>(i,j)=0;  
  53.                 }  
  54.             }  
  55.             k++;  
  56.         }  
  57.     }  
  58. }  


進過非極大值抑制後的圖像如下:




跟Sobel梯度幅值圖像相比,去除了一些非局部極大值點,輪廓也進一步細化。


  • 四、用雙閾值算法檢測和連接邊緣

雙閾值的機理是:

 指定一個低閾值A,一個高閾值B,一般取B爲圖像整體灰度級分佈的70%,且B爲1.5到2倍大小的A;

灰度值大於B的,置爲255,灰度值小於A的,置爲0;

灰度值介於A和B之間的,考察該像素點臨近的8像素是否有灰度值爲255的,若沒有255的,表示這是一個孤立的局部極大值點,予以排除,置爲0;若有255的,表示這是一個跟其他邊緣有“接壤”的可造之材,置爲255,之後重複執行該步驟,直到考察完之後一個像素點。


4.1  雙閾值處理


這個步驟裏處理大於高閾值和小於低閾值的像素點,分別置爲255和0;

實現如下:

  1. //******************雙閾值處理*************************  
  2. //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;  
  3. //第二個參數lowThreshold是低閾值  
  4. //第三個參數highThreshold是高閾值  
  5. //******************************************************  
  6. void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold)  
  7. {  
  8.     for(int i=0;i<imageIput.rows;i++)  
  9.     {  
  10.         for(int j=0;j<imageIput.cols;j++)  
  11.         {  
  12.             if(imageIput.at<uchar>(i,j)>highThreshold)  
  13.             {  
  14.                 imageIput.at<uchar>(i,j)=255;  
  15.             }     
  16.             if(imageIput.at<uchar>(i,j)<lowThreshold)  
  17.             {  
  18.                 imageIput.at<uchar>(i,j)=0;  
  19.             }     
  20.         }  
  21.     }  
  22. }  


這裏取低閾值爲60,高閾值100,處理效果:




經過雙閾值處理後,灰度值較低的點被消除掉,較高的點置爲了255。上圖中仍有灰度值小於255的點,它們是介於高、低閾值間的像素點。


4.2  雙閾值中間像素濾除或連接處理


以下是C++編碼實現,其中在連接的操作上涉及到一個遞歸調用

  1. //******************雙閾值中間像素連接處理*********************  
  2. //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;  
  3. //第二個參數lowThreshold是低閾值  
  4. //第三個參數highThreshold是高閾值  
  5. //*************************************************************  
  6. void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold)  
  7. {  
  8.     for(int i=1;i<imageInput.rows-1;i++)  
  9.     {  
  10.         for(int j=1;j<imageInput.cols-1;j++)  
  11.         {  
  12.             if(imageInput.at<uchar>(i,j)>lowThreshold&&imageInput.at<uchar>(i,j)<255)  
  13.             {  
  14.                 if(imageInput.at<uchar>(i-1,j-1)==255||imageInput.at<uchar>(i-1,j)==255||imageInput.at<uchar>(i-1,j+1)==255||  
  15.                     imageInput.at<uchar>(i,j-1)==255||imageInput.at<uchar>(i,j)==255||imageInput.at<uchar>(i,j+1)==255||  
  16.                     imageInput.at<uchar>(i+1,j-1)==255||imageInput.at<uchar>(i+1,j)==255||imageInput.at<uchar>(i+1,j+1)==255)  
  17.                 {  
  18.                     imageInput.at<uchar>(i,j)=255;  
  19.                     DoubleThresholdLink(imageInput,lowThreshold,highThreshold); //遞歸調用  
  20.                 }     
  21.                 else  
  22.             {  
  23.                     imageInput.at<uchar>(i,j)=0;  
  24.             }                 
  25.             }                 
  26.         }  
  27.     }  
  28. }  


濾除或連接後的最終效果:




完整工程代碼

到這裏,Canny算子檢測邊緣的四個步驟就全部完成了,以下是整個C++工程完整代碼,有興趣可以瀏覽一下:

ps:我沒運行出來,改天再看看

  1. #include "core/core.hpp"    
  2. #include "highgui/highgui.hpp"    
  3. #include "imgproc/imgproc.hpp"    
  4. #include "iostream"  
  5. #include "math.h"  
  6.   
  7. using namespace std;   
  8. using namespace cv;    
  9.   
  10. //******************灰度轉換函數*************************  
  11. //第一個參數image輸入的彩色RGB圖像;  
  12. //第二個參數imageGray是轉換後輸出的灰度圖像;  
  13. //*************************************************************  
  14. void ConvertRGB2GRAY(const Mat &image,Mat &imageGray);  
  15.   
  16.   
  17. //******************高斯卷積核生成函數*************************  
  18. //第一個參數gaus是一個指向含有N個double類型數組的指針;  
  19. //第二個參數size是高斯卷積核的尺寸大小;  
  20. //第三個參數sigma是卷積核的標準差  
  21. //*************************************************************  
  22. void GetGaussianKernel(double **gaus, const int size,const double sigma);  
  23.   
  24. //******************高斯濾波*************************  
  25. //第一個參數imageSource是待濾波原始圖像;  
  26. //第二個參數imageGaussian是濾波後輸出圖像;  
  27. //第三個參數gaus是一個指向含有N個double類型數組的指針;  
  28. //第四個參數size是濾波核的尺寸  
  29. //*************************************************************  
  30. void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size);  
  31.   
  32. //******************Sobel算子計算梯度和方向********************  
  33. //第一個參數imageSourc原始灰度圖像;  
  34. //第二個參數imageSobelX是X方向梯度圖像;  
  35. //第三個參數imageSobelY是Y方向梯度圖像;  
  36. //第四個參數pointDrection是梯度方向數組指針  
  37. //*************************************************************  
  38. void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection);  
  39.   
  40. //******************計算Sobel的X和Y方向梯度幅值*************************  
  41. //第一個參數imageGradX是X方向梯度圖像;  
  42. //第二個參數imageGradY是Y方向梯度圖像;  
  43. //第三個參數SobelAmpXY是輸出的X、Y方向梯度圖像幅值  
  44. //*************************************************************  
  45. void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY);  
  46.   
  47. //******************局部極大值抑制*************************  
  48. //第一個參數imageInput輸入的Sobel梯度圖像;  
  49. //第二個參數imageOutPut是輸出的局部極大值抑制圖像;  
  50. //第三個參數pointDrection是圖像上每個點的梯度方向數組指針  
  51. //*************************************************************  
  52. void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection);  
  53.   
  54. //******************雙閾值處理*************************  
  55. //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;  
  56. //第二個參數lowThreshold是低閾值  
  57. //第三個參數highThreshold是高閾值  
  58. //******************************************************  
  59. void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold);  
  60.   
  61. //******************雙閾值中間像素連接處理*********************  
  62. //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;  
  63. //第二個參數lowThreshold是低閾值  
  64. //第三個參數highThreshold是高閾值  
  65. //*************************************************************  
  66. void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold);  
  67.   
  68. Mat imageSource;  
  69. Mat imageGray;  
  70. Mat imageGaussian;  
  71.   
  72. int main(int argc,char *argv[])    
  73. {  
  74.     imageSource=imread(argv[1]);  //讀入RGB圖像  
  75.     imshow("RGB Image",imageSource);  
  76.     ConvertRGB2GRAY(imageSource,imageGray); //RGB轉換爲灰度圖  
  77.     imshow("Gray Image",imageGray);  
  78.     int size=5; //定義卷積核大小  
  79.     double **gaus=new double *[size];  //卷積核數組  
  80.     for(int i=0;i<size;i++)  
  81.     {  
  82.         gaus[i]=new double[size];  //動態生成矩陣  
  83.     }     
  84.     GetGaussianKernel(gaus,5,1); //生成5*5 大小高斯卷積核,Sigma=1;  
  85.     imageGaussian=Mat::zeros(imageGray.size(),CV_8UC1);  
  86.     GaussianFilter(imageGray,imageGaussian,gaus,5);  //高斯濾波  
  87.     imshow("Gaussian Image",imageGaussian);  
  88.     Mat imageSobelY;  
  89.     Mat imageSobelX;  
  90.     double *pointDirection=new double[(imageSobelX.cols-1)*(imageSobelX.rows-1)];  //定義梯度方向角數組  
  91.     SobelGradDirction(imageGaussian,imageSobelX,imageSobelY,pointDirection);  //計算X、Y方向梯度和方向角  
  92.     imshow("Sobel Y",imageSobelY);  
  93.     imshow("Sobel X",imageSobelX);  
  94.     Mat SobelGradAmpl;  
  95.     SobelAmplitude(imageSobelX,imageSobelY,SobelGradAmpl);   //計算X、Y方向梯度融合幅值  
  96.     imshow("Soble XYRange",SobelGradAmpl);  
  97.     Mat imageLocalMax;  
  98.     LocalMaxValue(SobelGradAmpl,imageLocalMax,pointDirection);  //局部非極大值抑制  
  99.     imshow("Non-Maximum Image",imageLocalMax);  
  100.     Mat cannyImage;  
  101.     cannyImage=Mat::zeros(imageLocalMax.size(),CV_8UC1);  
  102.     DoubleThreshold(imageLocalMax,90,160);        //雙閾值處理  
  103.     imshow("Double Threshold Image",imageLocalMax);  
  104.     DoubleThresholdLink(imageLocalMax,90,160);   //雙閾值中間閾值濾除及連接  
  105.     imshow("Canny Image",imageLocalMax);  
  106.     waitKey();  
  107.     system("pause");  
  108.     return 0;  
  109. }  
  110.   
  111. //******************高斯卷積核生成函數*************************  
  112. //第一個參數gaus是一個指向含有N個double類型數組的指針;  
  113. //第二個參數size是高斯卷積核的尺寸大小;  
  114. //第三個參數sigma是卷積核的標準差  
  115. //*************************************************************  
  116. void GetGaussianKernel(double **gaus, const int size,const double sigma)  
  117. {  
  118.     const double PI=4.0*atan(1.0); //圓周率π賦值  
  119.     int center=size/2;  
  120.     double sum=0;  
  121.     for(int i=0;i<size;i++)  
  122.     {  
  123.         for(int j=0;j<size;j++)  
  124.         {  
  125.             gaus[i][j]=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));  
  126.             sum+=gaus[i][j];  
  127.         }  
  128.     }  
  129.     for(int i=0;i<size;i++)  
  130.     {  
  131.         for(int j=0;j<size;j++)  
  132.         {  
  133.             gaus[i][j]/=sum;  
  134.             cout<<gaus[i][j]<<"  ";  
  135.         }  
  136.         cout<<endl<<endl;  
  137.     }  
  138.     return ;  
  139. }  
  140.   
  141. //******************灰度轉換函數*************************  
  142. //第一個參數image輸入的彩色RGB圖像;  
  143. //第二個參數imageGray是轉換後輸出的灰度圖像;  
  144. //*************************************************************  
  145. void ConvertRGB2GRAY(const Mat &image,Mat &imageGray)  
  146. {  
  147.     if(!image.data||image.channels()!=3)  
  148.     {  
  149.         return ;  
  150.     }  
  151.     imageGray=Mat::zeros(image.size(),CV_8UC1);  
  152.     uchar *pointImage=image.data;  
  153.     uchar *pointImageGray=imageGray.data;  
  154.     int stepImage=image.step;  
  155.     int stepImageGray=imageGray.step;  
  156.     for(int i=0;i<imageGray.rows;i++)  
  157.     {  
  158.         for(int j=0;j<imageGray.cols;j++)  
  159.         {  
  160.             pointImageGray[i*stepImageGray+j]=0.114*pointImage[i*stepImage+3*j]+0.587*pointImage[i*stepImage+3*j+1]+0.299*pointImage[i*stepImage+3*j+2];  
  161.         }  
  162.     }  
  163. }  
  164.   
  165. //******************高斯濾波*************************  
  166. //第一個參數imageSource是待濾波原始圖像;  
  167. //第二個參數imageGaussian是濾波後輸出圖像;  
  168. //第三個參數gaus是一個指向含有N個double類型數組的指針;  
  169. //第四個參數size是濾波核的尺寸  
  170. //*************************************************************  
  171. void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size)  
  172. {  
  173.     imageGaussian=Mat::zeros(imageSource.size(),CV_8UC1);  
  174.     if(!imageSource.data||imageSource.channels()!=1)  
  175.     {  
  176.         return ;  
  177.     }  
  178.     double gausArray[100];   
  179.     for(int i=0;i<size*size;i++)  
  180.     {  
  181.         gausArray[i]=0;  //賦初值,空間分配  
  182.     }  
  183.     int array=0;  
  184.     for(int i=0;i<size;i++)  
  185.     {  
  186.         for(int j=0;j<size;j++)  
  187.   
  188.         {  
  189.             gausArray[array]=gaus[i][j];//二維數組到一維 方便計算  
  190.             array++;  
  191.         }  
  192.     }  
  193.     //濾波  
  194.     for(int i=0;i<imageSource.rows;i++)  
  195.     {  
  196.         for(int j=0;j<imageSource.cols;j++)  
  197.         {  
  198.             int k=0;  
  199.             for(int l=-size/2;l<=size/2;l++)  
  200.             {  
  201.                 for(int g=-size/2;g<=size/2;g++)  
  202.                 {  
  203.                     //以下處理針對濾波後圖像邊界處理,爲超出邊界的值賦值爲邊界值  
  204.                     int row=i+l;  
  205.                     int col=j+g;  
  206.                     row=row<0?0:row;  
  207.                     row=row>=imageSource.rows?imageSource.rows-1:row;  
  208.                     col=col<0?0:col;  
  209.                     col=col>=imageSource.cols?imageSource.cols-1:col;  
  210.                     //卷積和  
  211.                     imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);  
  212.                     k++;  
  213.                 }  
  214.             }  
  215.         }  
  216.     }  
  217. }  
  218. //******************Sobel算子計算X、Y方向梯度和梯度方向角********************  
  219. //第一個參數imageSourc原始灰度圖像;  
  220. //第二個參數imageSobelX是X方向梯度圖像;  
  221. //第三個參數imageSobelY是Y方向梯度圖像;  
  222. //第四個參數pointDrection是梯度方向角數組指針  
  223. //*************************************************************  
  224. void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection)  
  225. {  
  226.     pointDrection=new double[(imageSource.rows-1)*(imageSource.cols-1)];  
  227.     for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++)  
  228.     {  
  229.         pointDrection[i]=0;  
  230.     }  
  231.     imageSobelX=Mat::zeros(imageSource.size(),CV_32SC1);  
  232.     imageSobelY=Mat::zeros(imageSource.size(),CV_32SC1);  
  233.     uchar *P=imageSource.data;    
  234.     uchar *PX=imageSobelX.data;    
  235.     uchar *PY=imageSobelY.data;    
  236.   
  237.     int step=imageSource.step;    
  238.     int stepXY=imageSobelX.step;   
  239.     int k=0;  
  240.     int m=0;  
  241.     int n=0;  
  242.     for(int i=1;i<(imageSource.rows-1);i++)    
  243.     {    
  244.         for(int j=1;j<(imageSource.cols-1);j++)    
  245.         {    
  246.             //通過指針遍歷圖像上每一個像素   
  247.             double gradY=P[(i-1)*step+j+1]+P[i*step+j+1]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[i*step+j-1]*2-P[(i+1)*step+j-1];  
  248.             PY[i*stepXY+j*(stepXY/step)]=abs(gradY);  
  249.             double gradX=P[(i+1)*step+j-1]+P[(i+1)*step+j]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[(i-1)*step+j]*2-P[(i-1)*step+j+1];  
  250.             PX[i*stepXY+j*(stepXY/step)]=abs(gradX);  
  251.             if(gradX==0)  
  252.             {  
  253.                 gradX=0.00000000000000001;  //防止除數爲0異常  
  254.             }  
  255.             pointDrection[k]=atan(gradY/gradX)*57.3;//弧度轉換爲度  
  256.             pointDrection[k]+=90;  
  257.             k++;  
  258.         }    
  259.     }   
  260.     convertScaleAbs(imageSobelX,imageSobelX);  
  261.     convertScaleAbs(imageSobelY,imageSobelY);  
  262. }  
  263. //******************計算Sobel的X和Y方向梯度幅值*************************  
  264. //第一個參數imageGradX是X方向梯度圖像;  
  265. //第二個參數imageGradY是Y方向梯度圖像;  
  266. //第三個參數SobelAmpXY是輸出的X、Y方向梯度圖像幅值  
  267. //*************************************************************  
  268. void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY)  
  269. {  
  270.     SobelAmpXY=Mat::zeros(imageGradX.size(),CV_32FC1);  
  271.     for(int i=0;i<SobelAmpXY.rows;i++)  
  272.     {  
  273.         for(int j=0;j<SobelAmpXY.cols;j++)  
  274.         {  
  275.             SobelAmpXY.at<float>(i,j)=sqrt(imageGradX.at<uchar>(i,j)*imageGradX.at<uchar>(i,j)+imageGradY.at<uchar>(i,j)*imageGradY.at<uchar>(i,j));  
  276.         }  
  277.     }  
  278.     convertScaleAbs(SobelAmpXY,SobelAmpXY);  
  279. }  
  280. //******************局部極大值抑制*************************  
  281. //第一個參數imageInput輸入的Sobel梯度圖像;  
  282. //第二個參數imageOutPut是輸出的局部極大值抑制圖像;  
  283. //第三個參數pointDrection是圖像上每個點的梯度方向數組指針  
  284. //*************************************************************  
  285. void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection)  
  286. {  
  287.     //imageInput.copyTo(imageOutput);  
  288.     imageOutput=imageInput.clone();  
  289.     int k=0;  
  290.     for(int i=1;i<imageInput.rows-1;i++)  
  291.     {  
  292.         for(int j=1;j<imageInput.cols-1;j++)  
  293.         {  
  294.             int value00=imageInput.at<uchar>((i-1),j-1);  
  295.             int value01=imageInput.at<uchar>((i-1),j);  
  296.             int value02=imageInput.at<uchar>((i-1),j+1);  
  297.             int value10=imageInput.at<uchar>((i),j-1);  
  298.             int value11=imageInput.at<uchar>((i),j);  
  299.             int value12=imageInput.at<uchar>((i),j+1);  
  300.             int value20=imageInput.at<uchar>((i+1),j-1);  
  301.             int value21=imageInput.at<uchar>((i+1),j);  
  302.             int value22=imageInput.at<uchar>((i+1),j+1);  
  303.   
  304.             if(pointDrection[k]>0&&pointDrection[k]<=45)  
  305.             {  
  306.                 if(value11<=(value12+(value02-value12)*tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)*tan(pointDrection[i*imageOutput.rows+j]))))  
  307.                 {  
  308.                     imageOutput.at<uchar>(i,j)=0;  
  309.                 }  
  310.             }     
  311.             if(pointDrection[k]>45&&pointDrection[k]<=90)  
  312.   
  313.             {  
  314.                 if(value11<=(value01+(value02-value01)/tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/tan(pointDrection[i*imageOutput.cols+j])))  
  315.                 {  
  316.                     imageOutput.at<uchar>(i,j)=0;  
  317.   
  318.                 }  
  319.             }  
  320.             if(pointDrection[k]>90&&pointDrection[k]<=135)  
  321.             {  
  322.                 if(value11<=(value01+(value00-value01)/tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrection[i*imageOutput.cols+j])))  
  323.                 {  
  324.                     imageOutput.at<uchar>(i,j)=0;  
  325.                 }  
  326.             }  
  327.             if(pointDrection[k]>135&&pointDrection[k]<=180)  
  328.             {  
  329.                 if(value11<=(value10+(value00-value10)*tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrection[i*imageOutput.cols+j])))  
  330.                 {  
  331.                     imageOutput.at<uchar>(i,j)=0;  
  332.                 }  
  333.             }  
  334.             k++;  
  335.         }  
  336.     }  
  337. }  
  338.   
  339. //******************雙閾值處理*************************  
  340. //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;  
  341. //第二個參數lowThreshold是低閾值  
  342. //第三個參數highThreshold是高閾值  
  343. //******************************************************  
  344. void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold)  
  345. {  
  346.     for(int i=0;i<imageIput.rows;i++)  
  347.     {  
  348.         for(int j=0;j<imageIput.cols;j++)  
  349.         {  
  350.             if(imageIput.at<uchar>(i,j)>highThreshold)  
  351.             {  
  352.                 imageIput.at<uchar>(i,j)=255;  
  353.             }     
  354.             if(imageIput.at<uchar>(i,j)<lowThreshold)  
  355.             {  
  356.                 imageIput.at<uchar>(i,j)=0;  
  357.             }     
  358.         }  
  359.     }  
  360. }  
  361. //******************雙閾值中間像素連接處理*********************  
  362. //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;  
  363. //第二個參數lowThreshold是低閾值  
  364. //第三個參數highThreshold是高閾值  
  365. //*************************************************************  
  366. void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold)  
  367. {  
  368.     for(int i=1;i<imageInput.rows-1;i++)  
  369.     {  
  370.         for(int j=1;j<imageInput.cols-1;j++)  
  371.         {  
  372.             if(imageInput.at<uchar>(i,j)>lowThreshold&&imageInput.at<uchar>(i,j)<255)  
  373.             {  
  374.                 if(imageInput.at<uchar>(i-1,j-1)==255||imageInput.at<uchar>(i-1,j)==255||imageInput.at<uchar>(i-1,j+1)==255||  
  375.                     imageInput.at<uchar>(i,j-1)==255||imageInput.at<uchar>(i,j)==255||imageInput.at<uchar>(i,j+1)==255||  
  376.                     imageInput.at<uchar>(i+1,j-1)==255||imageInput.at<uchar>(i+1,j)==255||imageInput.at<uchar>(i+1,j+1)==255)  
  377.                 {  
  378.                     imageInput.at<uchar>(i,j)=255;  
  379.                     DoubleThresholdLink(imageInput,lowThreshold,highThreshold); //遞歸調用  
  380.                 }     
  381.                 else  
  382.             {  
  383.                     imageInput.at<uchar>(i,j)=0;  
  384.             }                 
  385.             }                 
  386.         }  
  387.     }  
  388. }  

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