Canny边缘检测及C++实现(转载)

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. gausArray[ array]=gaus[i][j]; //二维数组到一维 方便计算
  25. array++;
  26. }
  27. }
  28. //滤波
  29. for( int i= 0;i<imageSource.rows;i++)
  30. {
  31. for( int j= 0;j<imageSource.cols;j++)
  32. {
  33. int k= 0;
  34. for( int l=-size/ 2;l<=size/ 2;l++)
  35. {
  36. for( int g=-size/ 2;g<=size/ 2;g++)
  37. {
  38. //以下处理针对滤波后图像边界处理,为超出边界的值赋值为边界值
  39. int row=i+l;
  40. int col=j+g;
  41. row=row< 0? 0:row;
  42. row=row>=imageSource.rows?imageSource.rows -1:row;
  43. col=col< 0? 0:col;
  44. col=col>=imageSource.cols?imageSource.cols -1:col;
  45. //卷积和
  46. imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);
  47. k++;
  48. }
  49. }
  50. }
  51. }
  52. }

高斯滤波后的图像:



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


  • 二、用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. int step=imageSource.step;
  20. int stepXY=imageSobelX.step;
  21. int k= 0;
  22. int m= 0;
  23. int n= 0;
  24. for( int i= 1;i<(imageSource.rows -1);i++)
  25. {
  26. for( int j= 1;j<(imageSource.cols -1);j++)
  27. {
  28. //通过指针遍历图像上每一个像素
  29. 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];
  30. PY[i*stepXY+j*(stepXY/step)]= abs(gradY);
  31. 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];
  32. PX[i*stepXY+j*(stepXY/step)]= abs(gradX);
  33. if(gradX== 0)
  34. {
  35. gradX= 0.00000000000000001; //防止除数为0异常
  36. }
  37. pointDrection[k]= atan(gradY/gradX)* 57.3; //弧度转换为度
  38. pointDrection[k]+= 90;
  39. k++;
  40. }
  41. }
  42. convertScaleAbs(imageSobelX,imageSobelX);
  43. convertScaleAbs(imageSobelY,imageSobelY);
  44. }


数组指针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. if(pointDrection[k]> 0&&pointDrection[k]<= 45)
  25. {
  26. if(value11<=(value12+(value02-value12)* tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)* tan(pointDrection[i*imageOutput.rows+j]))))
  27. {
  28. imageOutput.at<uchar>(i,j)= 0;
  29. }
  30. }
  31. if(pointDrection[k]> 45&&pointDrection[k]<= 90)
  32. {
  33. if(value11<=(value01+(value02-value01)/ tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/ tan(pointDrection[i*imageOutput.cols+j])))
  34. {
  35. imageOutput.at<uchar>(i,j)= 0;
  36. }
  37. }
  38. if(pointDrection[k]> 90&&pointDrection[k]<= 135)
  39. {
  40. if(value11<=(value01+(value00-value01)/ tan( 180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/ tan( 180-pointDrection[i*imageOutput.cols+j])))
  41. {
  42. imageOutput.at<uchar>(i,j)= 0;
  43. }
  44. }
  45. if(pointDrection[k]> 135&&pointDrection[k]<= 180)
  46. {
  47. if(value11<=(value10+(value00-value10)* tan( 180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)* tan( 180-pointDrection[i*imageOutput.cols+j])))
  48. {
  49. imageOutput.at<uchar>(i,j)= 0;
  50. }
  51. }
  52. k++;
  53. }
  54. }
  55. }


进过非极大值抑制后的图像如下:




跟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++工程完整代码,有兴趣可以浏览一下:



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


  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值