版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/dcrmg/article/details/52344902

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.0atan(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))/(2sigma*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. }

转载自:https://blog.csdn.net/dcrmg/article/details/52344902#commentBox

Canny边缘检测及C++实现(转载)相关推荐

  1. OpenCV-Python教程(6)(7)(8): Sobel算子 Laplacian算子 Canny边缘检测

    OpenCV-Python教程(6.Sobel算子) 本篇文章介绍如何用OpenCV-Python来使用Sobel算子. 提示: 转载请详细注明原作者及出处,谢谢! 本文介绍使用OpenCV-Pyth ...

  2. 【算法随记一】Canny边缘检测算法实现和优化分析。

    以前的博文大部分都写的非常详细,有很多分析过程,不过写起来确实很累人,一般一篇好的文章要整理个三四天,但是,时间越来越紧张,后续的一些算法可能就以随记的方式,把实现过程的一些比较容易出错和有价值的细节 ...

  3. 学习笔记-canny边缘检测

    Canny边缘检测 声明:阅读本文需要了解线性代数里面的点乘(图像卷积的原理),高等数学里的二元函数的梯度,极大值定义,了解概率论里的二维高斯分布 1.canny边缘检测原理和简介 2.实现步骤 3. ...

  4. Canny边缘检测及C++实现

    Canny边缘检测算法是澳大利亚科学家John F. Canny在1986年提出来的,不得不提一下的是当年John Canny本人才28岁!到今天已经30年过去了,Canny算法仍然是图像边缘检测算法 ...

  5. 图像处理---《Canny 边缘检测》

    图像处理---<Canny 边缘检测> 很想系统的把图像处理之边缘检测部分详细的过一遍,对比一个各个算子的优良性能.时间紧,精力有限,现在只能走哪补哪,随手记. 有几个简单的场景,有需要, ...

  6. canny检测matlab,matlab houghlines_matlab canny边缘检测_canny边缘检测simulink

    Log和Canny边缘检测(附Matlab程序) 一. 实验目的 (1) 通过实验分析不同尺度下LOG和Canny边缘提取算子的性能. (2) 研究这两种边缘提取方法在不同参数下的边缘提取能力. (3 ...

  7. 图像处理之Canny边缘检测

    图像处理之Canny 边缘检测 一:历史 Canny边缘检测算法是1986年有John F. Canny开发出来一种基于图像梯度计算的边缘 检测算法,同时Canny本人对计算图像边缘提取学科的发展也是 ...

  8. Python OpenCV -- Canny 边缘检测 (十一)

    Canny 边缘检测 原理 Canny 边缘检测算法 是 John F. Canny 于 1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的 最优算法, 最优边缘检测的三个主要评价标 ...

  9. matlab之Canny边缘检测

    转载自:https://blog.csdn.net/humanking7/article/details/46606791 文章目录         Canny边缘检测基本特征如下:         ...

最新文章

  1. poj1330Nearest Common Ancestors 1470 Closest Common Ancestors(LCA算法)
  2. ModelCheckpoint 讲解【TensorFlow2入门手册】
  3. 微信小程序- 初试小程序之tabbar(选项卡,底部导航)的使用
  4. php yii框架源码,yii 源码解读
  5. ios html5 文件上传,【Web前端问题】上传文件使用axios发送FormData数据,参数为空...
  6. php充值奖励部分,BLUE引擎充值角本多充多送脚本累计充值奖励教程
  7. Java实现动态sin和cos函数图像
  8. Java -- 定时任务实现方式
  9. LaTeX常用的希腊字符、数学符号、矩阵、公式、排版、中括号、大括号以及插入图片等操作手册
  10. 普通电机、步进电机、伺服电机、舵机区别?
  11. 【NoteBook】莫瑞亚(Ash Maurya):精益创业实战(一、二部分)
  12. 如何在官网验证cka证书
  13. Centerface + Facenet实现视频人脸识别(附代码)
  14. html+css+javascript满屏雪花爱心520表白网站 (含音乐)520告白/七夕情人节/生日礼物/程序员表白必备
  15. 【Camera】Camera基础概念
  16. 数据挖掘技术的应用领域
  17. 国内唯一!腾讯iOA被权威机构报告列入竞争者能力象限
  18. 新买的iphone如何保证安全
  19. pro e打开服务器文件,EPRO 文件扩展名: 它是什么以及如何打开它?
  20. 单元测试时期望值是抛出异常的情况

热门文章

  1. 行为设计模式 - 解释器设计模式
  2. CCNA初认识——ACL命令
  3. windows git密码 删除
  4. mongoDB mac 安装 小白必备
  5. flask处理http request的时候是多线程还是多进程?
  6. svg矩形参数_SVG矩形和其他SVG形状
  7. 如何在Postgresql中使用模糊字符串匹配
  8. 响应式编程 函数式编程_函数式编程的基本原理简介
  9. 内部服务并行调用_25行以下代码中的并行SOAP调用的Express服务
  10. 小米mysql安装教程_小米 SOAR 开源SQL优化工具安装