轉自: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算子邊緣檢測的具體步驟如下:
- 一、用高斯濾波器平滑圖像
- 二、用Sobel等梯度算子計算梯度幅值和方向
- 三、對梯度幅值進行非極大值抑制
- 四、用雙閾值算法檢測和連接邊緣
一、用高斯濾波器平滑圖像
高斯濾波是一種線性平滑濾波,適用於消除高斯噪聲,特別是對抑制或消除服從正態分佈的噪聲非常有效。濾波可以消除或降低圖像中噪聲的影響,使用高斯濾波器主要是基於在濾波降噪的同時也可以最大限度保留邊緣信息的考慮。
1.1 彩色RGB圖像轉換爲灰度圖像
- //******************灰度轉換函數*************************
- //第一個參數image輸入的彩色RGB圖像;
- //第二個參數imageGray是轉換後輸出的灰度圖像;
- //*************************************************************
- void ConvertRGB2GRAY(const Mat &image,Mat &imageGray)
- {
- if(!image.data||image.channels()!=3)
- {
- return ;
- }
- imageGray=Mat::zeros(image.size(),CV_8UC1);
- uchar *pointImage=image.data;
- uchar *pointImageGray=imageGray.data;
- int stepImage=image.step;
- int stepImageGray=imageGray.step;
- for(int i=0;i<imageGray.rows;i++)
- {
- for(int j=0;j<imageGray.cols;j++)
- {
- 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];
- }
- }
- }
1.2 生成高斯濾波卷積核
- //******************高斯卷積核生成函數*************************
- //第一個參數gaus是一個指向含有N個double類型數組的指針;
- //第二個參數size是高斯卷積核的尺寸大小;
- //第三個參數sigma是卷積核的標準差
- //*************************************************************
- void GetGaussianKernel(double **gaus, const int size,const double sigma)
- {
- const double PI=4.0*atan(1.0); //圓周率π賦值
- int center=size/2;
- double sum=0;
- for(int i=0;i<size;i++)
- {
- for(int j=0;j<size;j++)
- {
- gaus[i][j]=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));
- sum+=gaus[i][j];
- }
- }
- for(int i=0;i<size;i++)
- {
- for(int j=0;j<size;j++)
- {
- gaus[i][j]/=sum;
- cout<<gaus[i][j]<<" ";
- }
- cout<<endl<<endl;
- }
- return ;
- }
1.3 高斯濾波
- //******************高斯濾波*************************
- //第一個參數imageSource是待濾波原始圖像;
- //第二個參數imageGaussian是濾波後輸出圖像;
- //第三個參數gaus是一個指向含有N個double類型數組的指針;
- //第四個參數size是濾波核的尺寸
- //*************************************************************
- void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size)
- {
- imageGaussian=Mat::zeros(imageSource.size(),CV_8UC1);
- if(!imageSource.data||imageSource.channels()!=1)
- {
- return ;
- }
- double gausArray[100];
- for(int i=0;i<size*size;i++)
- {
- gausArray[i]=0; //賦初值,空間分配
- }
- int array=0;
- for(int i=0;i<size;i++)
- {
- for(int j=0;j<size;j++)
- {
- gausArray[array]=gaus[i][j];//二維數組到一維 方便計算
- array++;
- }
- }
- //濾波
- for(int i=0;i<imageSource.rows;i++)
- {
- for(int j=0;j<imageSource.cols;j++)
- {
- int k=0;
- for(int l=-size/2;l<=size/2;l++)
- {
- for(int g=-size/2;g<=size/2;g++)
- {
- //以下處理針對濾波後圖像邊界處理,爲超出邊界的值賦值爲邊界值
- int row=i+l;
- int col=j+g;
- row=row<0?0:row;
- row=row>=imageSource.rows?imageSource.rows-1:row;
- col=col<0?0:col;
- col=col>=imageSource.cols?imageSource.cols-1:col;
- //卷積和
- imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);
- k++;
- }
- }
- }
- }
- }
跟原圖相比,圖像有一定程度的模糊。
- 二、用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方向梯度和梯度方向角代碼實現:
- //******************Sobel卷積因子計算X、Y方向梯度和梯度方向角********************
- //第一個參數imageSourc原始灰度圖像;
- //第二個參數imageSobelX是X方向梯度圖像;
- //第三個參數imageSobelY是Y方向梯度圖像;
- //第四個參數pointDrection是梯度方向角數組指針
- //*************************************************************
- void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection)
- {
- pointDrection=new double[(imageSource.rows-1)*(imageSource.cols-1)];
- for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++)
- {
- pointDrection[i]=0;
- }
- imageSobelX=Mat::zeros(imageSource.size(),CV_32SC1);
- imageSobelY=Mat::zeros(imageSource.size(),CV_32SC1);
- uchar *P=imageSource.data;
- uchar *PX=imageSobelX.data;
- uchar *PY=imageSobelY.data;
- int step=imageSource.step;
- int stepXY=imageSobelX.step;
- int k=0;
- int m=0;
- int n=0;
- for(int i=1;i<(imageSource.rows-1);i++)
- {
- for(int j=1;j<(imageSource.cols-1);j++)
- {
- //通過指針遍歷圖像上每一個像素
- 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];
- PY[i*stepXY+j*(stepXY/step)]=abs(gradY);
- 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];
- PX[i*stepXY+j*(stepXY/step)]=abs(gradX);
- if(gradX==0)
- {
- gradX=0.00000000000000001; //防止除數爲0異常
- }
- pointDrection[k]=atan(gradY/gradX)*57.3;//弧度轉換爲度
- pointDrection[k]+=90;
- k++;
- }
- }
- convertScaleAbs(imageSobelX,imageSobelX);
- convertScaleAbs(imageSobelY,imageSobelY);
- }
數組指針pointDirection裏存放了每個點上的梯度方向角,以度爲單位。由於atan求得的角度範圍是-π/2~π/2,爲了便於計算,這裏對每個梯度角加了一個π/2,使範圍變成0~π,便於計算。
X方向梯度圖: Y方向梯度圖:
2.2 求梯度圖的幅值
求得X、Y方向的梯度和梯度角之後再來計算X和Y方向融合的梯度幅值,計算公式爲:
代碼實現,較爲簡單:
- //******************計算Sobel的X和Y方向梯度幅值*************************
- //第一個參數imageGradX是X方向梯度圖像;
- //第二個參數imageGradY是Y方向梯度圖像;
- //第三個參數SobelAmpXY是輸出的X、Y方向梯度圖像幅值
- //*************************************************************
- void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY)
- {
- SobelAmpXY=Mat::zeros(imageGradX.size(),CV_32FC1);
- for(int i=0;i<SobelAmpXY.rows;i++)
- {
- for(int j=0;j<SobelAmpXY.cols;j++)
- {
- 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));
- }
- }
- convertScaleAbs(SobelAmpXY,SobelAmpXY);
- }
求得的X和Y方向幅度和疊加了兩個方向上的幅值:
- 三、對梯度幅值進行非極大值抑制
四種情況下需要分別計算,代碼實現如下:
- //******************局部極大值抑制*************************
- //第一個參數imageInput輸入的Sobel梯度圖像;
- //第二個參數imageOutPut是輸出的局部極大值抑制圖像;
- //第三個參數pointDrection是圖像上每個點的梯度方向數組指針
- //*************************************************************
- void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection)
- {
- //imageInput.copyTo(imageOutput);
- imageOutput=imageInput.clone();
- int k=0;
- for(int i=1;i<imageInput.rows-1;i++)
- {
- for(int j=1;j<imageInput.cols-1;j++)
- {
- int value00=imageInput.at<uchar>((i-1),j-1);
- int value01=imageInput.at<uchar>((i-1),j);
- int value02=imageInput.at<uchar>((i-1),j+1);
- int value10=imageInput.at<uchar>((i),j-1);
- int value11=imageInput.at<uchar>((i),j);
- int value12=imageInput.at<uchar>((i),j+1);
- int value20=imageInput.at<uchar>((i+1),j-1);
- int value21=imageInput.at<uchar>((i+1),j);
- int value22=imageInput.at<uchar>((i+1),j+1);
- if(pointDrection[k]>0&&pointDrection[k]<=45)
- {
- if(value11<=(value12+(value02-value12)*tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)*tan(pointDrection[i*imageOutput.rows+j]))))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- if(pointDrection[k]>45&&pointDrection[k]<=90)
- {
- if(value11<=(value01+(value02-value01)/tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/tan(pointDrection[i*imageOutput.cols+j])))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- if(pointDrection[k]>90&&pointDrection[k]<=135)
- {
- if(value11<=(value01+(value00-value01)/tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrection[i*imageOutput.cols+j])))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- if(pointDrection[k]>135&&pointDrection[k]<=180)
- {
- if(value11<=(value10+(value00-value10)*tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrection[i*imageOutput.cols+j])))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- k++;
- }
- }
- }
進過非極大值抑制後的圖像如下:
跟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;
實現如下:
- //******************雙閾值處理*************************
- //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;
- //第二個參數lowThreshold是低閾值
- //第三個參數highThreshold是高閾值
- //******************************************************
- void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold)
- {
- for(int i=0;i<imageIput.rows;i++)
- {
- for(int j=0;j<imageIput.cols;j++)
- {
- if(imageIput.at<uchar>(i,j)>highThreshold)
- {
- imageIput.at<uchar>(i,j)=255;
- }
- if(imageIput.at<uchar>(i,j)<lowThreshold)
- {
- imageIput.at<uchar>(i,j)=0;
- }
- }
- }
- }
這裏取低閾值爲60,高閾值100,處理效果:
經過雙閾值處理後,灰度值較低的點被消除掉,較高的點置爲了255。上圖中仍有灰度值小於255的點,它們是介於高、低閾值間的像素點。
4.2 雙閾值中間像素濾除或連接處理
以下是C++編碼實現,其中在連接的操作上涉及到一個遞歸調用:
- //******************雙閾值中間像素連接處理*********************
- //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;
- //第二個參數lowThreshold是低閾值
- //第三個參數highThreshold是高閾值
- //*************************************************************
- void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold)
- {
- for(int i=1;i<imageInput.rows-1;i++)
- {
- for(int j=1;j<imageInput.cols-1;j++)
- {
- if(imageInput.at<uchar>(i,j)>lowThreshold&&imageInput.at<uchar>(i,j)<255)
- {
- if(imageInput.at<uchar>(i-1,j-1)==255||imageInput.at<uchar>(i-1,j)==255||imageInput.at<uchar>(i-1,j+1)==255||
- imageInput.at<uchar>(i,j-1)==255||imageInput.at<uchar>(i,j)==255||imageInput.at<uchar>(i,j+1)==255||
- imageInput.at<uchar>(i+1,j-1)==255||imageInput.at<uchar>(i+1,j)==255||imageInput.at<uchar>(i+1,j+1)==255)
- {
- imageInput.at<uchar>(i,j)=255;
- DoubleThresholdLink(imageInput,lowThreshold,highThreshold); //遞歸調用
- }
- else
- {
- imageInput.at<uchar>(i,j)=0;
- }
- }
- }
- }
- }
濾除或連接後的最終效果:
完整工程代碼
到這裏,Canny算子檢測邊緣的四個步驟就全部完成了,以下是整個C++工程完整代碼,有興趣可以瀏覽一下:
ps:我沒運行出來,改天再看看
- #include "core/core.hpp"
- #include "highgui/highgui.hpp"
- #include "imgproc/imgproc.hpp"
- #include "iostream"
- #include "math.h"
- using namespace std;
- using namespace cv;
- //******************灰度轉換函數*************************
- //第一個參數image輸入的彩色RGB圖像;
- //第二個參數imageGray是轉換後輸出的灰度圖像;
- //*************************************************************
- void ConvertRGB2GRAY(const Mat &image,Mat &imageGray);
- //******************高斯卷積核生成函數*************************
- //第一個參數gaus是一個指向含有N個double類型數組的指針;
- //第二個參數size是高斯卷積核的尺寸大小;
- //第三個參數sigma是卷積核的標準差
- //*************************************************************
- void GetGaussianKernel(double **gaus, const int size,const double sigma);
- //******************高斯濾波*************************
- //第一個參數imageSource是待濾波原始圖像;
- //第二個參數imageGaussian是濾波後輸出圖像;
- //第三個參數gaus是一個指向含有N個double類型數組的指針;
- //第四個參數size是濾波核的尺寸
- //*************************************************************
- void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size);
- //******************Sobel算子計算梯度和方向********************
- //第一個參數imageSourc原始灰度圖像;
- //第二個參數imageSobelX是X方向梯度圖像;
- //第三個參數imageSobelY是Y方向梯度圖像;
- //第四個參數pointDrection是梯度方向數組指針
- //*************************************************************
- void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection);
- //******************計算Sobel的X和Y方向梯度幅值*************************
- //第一個參數imageGradX是X方向梯度圖像;
- //第二個參數imageGradY是Y方向梯度圖像;
- //第三個參數SobelAmpXY是輸出的X、Y方向梯度圖像幅值
- //*************************************************************
- void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY);
- //******************局部極大值抑制*************************
- //第一個參數imageInput輸入的Sobel梯度圖像;
- //第二個參數imageOutPut是輸出的局部極大值抑制圖像;
- //第三個參數pointDrection是圖像上每個點的梯度方向數組指針
- //*************************************************************
- void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection);
- //******************雙閾值處理*************************
- //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;
- //第二個參數lowThreshold是低閾值
- //第三個參數highThreshold是高閾值
- //******************************************************
- void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold);
- //******************雙閾值中間像素連接處理*********************
- //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;
- //第二個參數lowThreshold是低閾值
- //第三個參數highThreshold是高閾值
- //*************************************************************
- void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold);
- Mat imageSource;
- Mat imageGray;
- Mat imageGaussian;
- int main(int argc,char *argv[])
- {
- imageSource=imread(argv[1]); //讀入RGB圖像
- imshow("RGB Image",imageSource);
- ConvertRGB2GRAY(imageSource,imageGray); //RGB轉換爲灰度圖
- imshow("Gray Image",imageGray);
- int size=5; //定義卷積核大小
- double **gaus=new double *[size]; //卷積核數組
- for(int i=0;i<size;i++)
- {
- gaus[i]=new double[size]; //動態生成矩陣
- }
- GetGaussianKernel(gaus,5,1); //生成5*5 大小高斯卷積核,Sigma=1;
- imageGaussian=Mat::zeros(imageGray.size(),CV_8UC1);
- GaussianFilter(imageGray,imageGaussian,gaus,5); //高斯濾波
- imshow("Gaussian Image",imageGaussian);
- Mat imageSobelY;
- Mat imageSobelX;
- double *pointDirection=new double[(imageSobelX.cols-1)*(imageSobelX.rows-1)]; //定義梯度方向角數組
- SobelGradDirction(imageGaussian,imageSobelX,imageSobelY,pointDirection); //計算X、Y方向梯度和方向角
- imshow("Sobel Y",imageSobelY);
- imshow("Sobel X",imageSobelX);
- Mat SobelGradAmpl;
- SobelAmplitude(imageSobelX,imageSobelY,SobelGradAmpl); //計算X、Y方向梯度融合幅值
- imshow("Soble XYRange",SobelGradAmpl);
- Mat imageLocalMax;
- LocalMaxValue(SobelGradAmpl,imageLocalMax,pointDirection); //局部非極大值抑制
- imshow("Non-Maximum Image",imageLocalMax);
- Mat cannyImage;
- cannyImage=Mat::zeros(imageLocalMax.size(),CV_8UC1);
- DoubleThreshold(imageLocalMax,90,160); //雙閾值處理
- imshow("Double Threshold Image",imageLocalMax);
- DoubleThresholdLink(imageLocalMax,90,160); //雙閾值中間閾值濾除及連接
- imshow("Canny Image",imageLocalMax);
- waitKey();
- system("pause");
- return 0;
- }
- //******************高斯卷積核生成函數*************************
- //第一個參數gaus是一個指向含有N個double類型數組的指針;
- //第二個參數size是高斯卷積核的尺寸大小;
- //第三個參數sigma是卷積核的標準差
- //*************************************************************
- void GetGaussianKernel(double **gaus, const int size,const double sigma)
- {
- const double PI=4.0*atan(1.0); //圓周率π賦值
- int center=size/2;
- double sum=0;
- for(int i=0;i<size;i++)
- {
- for(int j=0;j<size;j++)
- {
- gaus[i][j]=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));
- sum+=gaus[i][j];
- }
- }
- for(int i=0;i<size;i++)
- {
- for(int j=0;j<size;j++)
- {
- gaus[i][j]/=sum;
- cout<<gaus[i][j]<<" ";
- }
- cout<<endl<<endl;
- }
- return ;
- }
- //******************灰度轉換函數*************************
- //第一個參數image輸入的彩色RGB圖像;
- //第二個參數imageGray是轉換後輸出的灰度圖像;
- //*************************************************************
- void ConvertRGB2GRAY(const Mat &image,Mat &imageGray)
- {
- if(!image.data||image.channels()!=3)
- {
- return ;
- }
- imageGray=Mat::zeros(image.size(),CV_8UC1);
- uchar *pointImage=image.data;
- uchar *pointImageGray=imageGray.data;
- int stepImage=image.step;
- int stepImageGray=imageGray.step;
- for(int i=0;i<imageGray.rows;i++)
- {
- for(int j=0;j<imageGray.cols;j++)
- {
- 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];
- }
- }
- }
- //******************高斯濾波*************************
- //第一個參數imageSource是待濾波原始圖像;
- //第二個參數imageGaussian是濾波後輸出圖像;
- //第三個參數gaus是一個指向含有N個double類型數組的指針;
- //第四個參數size是濾波核的尺寸
- //*************************************************************
- void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size)
- {
- imageGaussian=Mat::zeros(imageSource.size(),CV_8UC1);
- if(!imageSource.data||imageSource.channels()!=1)
- {
- return ;
- }
- double gausArray[100];
- for(int i=0;i<size*size;i++)
- {
- gausArray[i]=0; //賦初值,空間分配
- }
- int array=0;
- for(int i=0;i<size;i++)
- {
- for(int j=0;j<size;j++)
- {
- gausArray[array]=gaus[i][j];//二維數組到一維 方便計算
- array++;
- }
- }
- //濾波
- for(int i=0;i<imageSource.rows;i++)
- {
- for(int j=0;j<imageSource.cols;j++)
- {
- int k=0;
- for(int l=-size/2;l<=size/2;l++)
- {
- for(int g=-size/2;g<=size/2;g++)
- {
- //以下處理針對濾波後圖像邊界處理,爲超出邊界的值賦值爲邊界值
- int row=i+l;
- int col=j+g;
- row=row<0?0:row;
- row=row>=imageSource.rows?imageSource.rows-1:row;
- col=col<0?0:col;
- col=col>=imageSource.cols?imageSource.cols-1:col;
- //卷積和
- imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);
- k++;
- }
- }
- }
- }
- }
- //******************Sobel算子計算X、Y方向梯度和梯度方向角********************
- //第一個參數imageSourc原始灰度圖像;
- //第二個參數imageSobelX是X方向梯度圖像;
- //第三個參數imageSobelY是Y方向梯度圖像;
- //第四個參數pointDrection是梯度方向角數組指針
- //*************************************************************
- void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection)
- {
- pointDrection=new double[(imageSource.rows-1)*(imageSource.cols-1)];
- for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++)
- {
- pointDrection[i]=0;
- }
- imageSobelX=Mat::zeros(imageSource.size(),CV_32SC1);
- imageSobelY=Mat::zeros(imageSource.size(),CV_32SC1);
- uchar *P=imageSource.data;
- uchar *PX=imageSobelX.data;
- uchar *PY=imageSobelY.data;
- int step=imageSource.step;
- int stepXY=imageSobelX.step;
- int k=0;
- int m=0;
- int n=0;
- for(int i=1;i<(imageSource.rows-1);i++)
- {
- for(int j=1;j<(imageSource.cols-1);j++)
- {
- //通過指針遍歷圖像上每一個像素
- 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];
- PY[i*stepXY+j*(stepXY/step)]=abs(gradY);
- 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];
- PX[i*stepXY+j*(stepXY/step)]=abs(gradX);
- if(gradX==0)
- {
- gradX=0.00000000000000001; //防止除數爲0異常
- }
- pointDrection[k]=atan(gradY/gradX)*57.3;//弧度轉換爲度
- pointDrection[k]+=90;
- k++;
- }
- }
- convertScaleAbs(imageSobelX,imageSobelX);
- convertScaleAbs(imageSobelY,imageSobelY);
- }
- //******************計算Sobel的X和Y方向梯度幅值*************************
- //第一個參數imageGradX是X方向梯度圖像;
- //第二個參數imageGradY是Y方向梯度圖像;
- //第三個參數SobelAmpXY是輸出的X、Y方向梯度圖像幅值
- //*************************************************************
- void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY)
- {
- SobelAmpXY=Mat::zeros(imageGradX.size(),CV_32FC1);
- for(int i=0;i<SobelAmpXY.rows;i++)
- {
- for(int j=0;j<SobelAmpXY.cols;j++)
- {
- 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));
- }
- }
- convertScaleAbs(SobelAmpXY,SobelAmpXY);
- }
- //******************局部極大值抑制*************************
- //第一個參數imageInput輸入的Sobel梯度圖像;
- //第二個參數imageOutPut是輸出的局部極大值抑制圖像;
- //第三個參數pointDrection是圖像上每個點的梯度方向數組指針
- //*************************************************************
- void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection)
- {
- //imageInput.copyTo(imageOutput);
- imageOutput=imageInput.clone();
- int k=0;
- for(int i=1;i<imageInput.rows-1;i++)
- {
- for(int j=1;j<imageInput.cols-1;j++)
- {
- int value00=imageInput.at<uchar>((i-1),j-1);
- int value01=imageInput.at<uchar>((i-1),j);
- int value02=imageInput.at<uchar>((i-1),j+1);
- int value10=imageInput.at<uchar>((i),j-1);
- int value11=imageInput.at<uchar>((i),j);
- int value12=imageInput.at<uchar>((i),j+1);
- int value20=imageInput.at<uchar>((i+1),j-1);
- int value21=imageInput.at<uchar>((i+1),j);
- int value22=imageInput.at<uchar>((i+1),j+1);
- if(pointDrection[k]>0&&pointDrection[k]<=45)
- {
- if(value11<=(value12+(value02-value12)*tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)*tan(pointDrection[i*imageOutput.rows+j]))))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- if(pointDrection[k]>45&&pointDrection[k]<=90)
- {
- if(value11<=(value01+(value02-value01)/tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/tan(pointDrection[i*imageOutput.cols+j])))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- if(pointDrection[k]>90&&pointDrection[k]<=135)
- {
- if(value11<=(value01+(value00-value01)/tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrection[i*imageOutput.cols+j])))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- if(pointDrection[k]>135&&pointDrection[k]<=180)
- {
- if(value11<=(value10+(value00-value10)*tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrection[i*imageOutput.cols+j])))
- {
- imageOutput.at<uchar>(i,j)=0;
- }
- }
- k++;
- }
- }
- }
- //******************雙閾值處理*************************
- //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;
- //第二個參數lowThreshold是低閾值
- //第三個參數highThreshold是高閾值
- //******************************************************
- void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold)
- {
- for(int i=0;i<imageIput.rows;i++)
- {
- for(int j=0;j<imageIput.cols;j++)
- {
- if(imageIput.at<uchar>(i,j)>highThreshold)
- {
- imageIput.at<uchar>(i,j)=255;
- }
- if(imageIput.at<uchar>(i,j)<lowThreshold)
- {
- imageIput.at<uchar>(i,j)=0;
- }
- }
- }
- }
- //******************雙閾值中間像素連接處理*********************
- //第一個參數imageInput輸入和輸出的的Sobel梯度幅值圖像;
- //第二個參數lowThreshold是低閾值
- //第三個參數highThreshold是高閾值
- //*************************************************************
- void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold)
- {
- for(int i=1;i<imageInput.rows-1;i++)
- {
- for(int j=1;j<imageInput.cols-1;j++)
- {
- if(imageInput.at<uchar>(i,j)>lowThreshold&&imageInput.at<uchar>(i,j)<255)
- {
- if(imageInput.at<uchar>(i-1,j-1)==255||imageInput.at<uchar>(i-1,j)==255||imageInput.at<uchar>(i-1,j+1)==255||
- imageInput.at<uchar>(i,j-1)==255||imageInput.at<uchar>(i,j)==255||imageInput.at<uchar>(i,j+1)==255||
- imageInput.at<uchar>(i+1,j-1)==255||imageInput.at<uchar>(i+1,j)==255||imageInput.at<uchar>(i+1,j+1)==255)
- {
- imageInput.at<uchar>(i,j)=255;
- DoubleThresholdLink(imageInput,lowThreshold,highThreshold); //遞歸調用
- }
- else
- {
- imageInput.at<uchar>(i,j)=0;
- }
- }
- }
- }
- }