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。
//******************灰度转换函数*************************
//第一个参数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];}}
}

RGB原图像:                                                                 转换后的灰度图:

                               

1.2 生成高斯滤波卷积核

高斯滤波的过程是将灰度图像跟高斯卷积核卷积,所以第一步是先要求解出给定尺寸和Sigma的高斯卷积核参数,以下代码实现对卷积核参数求解:
//******************高斯卷积核生成函数*************************
//第一个参数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 ;
}

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中生成的高斯卷积核跟灰度图像卷积,得到灰度图像的高斯滤波后的图像,抑制噪声。
代码实现:
//******************高斯滤波*************************
//第一个参数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方向幅度和叠加了两个方向上的幅值:

  • 三、对梯度幅值进行非极大值抑制
求幅值图像进行非极大值抑制,可以进一步消除非边缘的噪点,更重要的是,可以细化边缘。
抑制逻辑是:沿着该点梯度方向,比较前后两个点的幅值大小,若该点大于前后两点,则保留,若该点小于前后两点,则置为0;
示意图如下:
图中四条虚线代表图像中每一点可能的梯度方向,沿着梯度方向与边界的上下两个交点,就是需要拿来与中心点点(X0,Y0)做比较的点。交点值的计算采用插值法计算,以黄色的虚线所代表的梯度角Θ为例,交点处幅值为:
P(X0,Y0)+(P(X0-1,Y0+1)-P(X0,Y0))*tan(Θ)

四种情况下需要分别计算,代码实现如下:

//******************局部极大值抑制*************************
//第一个参数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++工程完整代码,有兴趣可以浏览一下:

#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;}               }               }}
}

转载于:https://www.cnblogs.com/mtcnn/p/9411977.html

Canny边缘检测及C++实现相关推荐

  1. OpenCV 笔记(03)— 读取视频、通过摄像头采集视频、采集视频 canny 边缘检测

    我们本节学习如何利用 OpenCV 中的 VideoCapture 类,来对视频进行读取显示,以及调用摄像头. VideoCapture 它提供了从摄像机或视频文件捕获视频的 C++ 接口, 作用是从 ...

  2. OpenCV 笔记(02)— 图像显示、保存、腐蚀、模糊、canny 边缘检测(imread、imshow、namedWindow、imwrite)

    OpenCV 提供两种用户界面选项: 基于原生用户界面的基本界面,适用于 Mac OS X 的 cocoa 或 carbon,以及适用于 Linux 或 Windows 用户界面的 GTK ,这些界面 ...

  3. OpenCV+python:Canny边缘检测算法

    1,边缘处理 图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波.我们知道微分运算是求信号的变化率,具有加强高频分量的作用. 在空域运算中来说,对图像的锐化就是计算微分.由于数字 ...

  4. Python,Opencv cv2.Canny()边缘检测

    Python,Opencv的Canny边缘检测 1. 效果图 2. 源码 参考 这篇博客将介绍Canny边缘检测的概念,并利用cv2.Canny()实现边缘检测: Canny边缘检测是一种流行的边缘检 ...

  5. 【OpenCV 】Sobel 导数/Laplace 算子/Canny 边缘检测

    canny边缘检测见OpenCV [七]----边缘提取算子(图像边缘提取)--canny算法的原理及实现 1 Sobel 导数 1.1.1 原因 上面两节我们已经学习了卷积操作.一个最重要的卷积运算 ...

  6. python边缘检测代码_python Canny边缘检测算法的实现

    图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波.我们知道微分运算是求信号的变化率,具有加强高频分量的作用.在空域运算中来说,对图像的锐化就是计算微分.对于数字图像的离散信号, ...

  7. opencv Canny边缘检测用法

    <span style="color:#000080">1. cv2.Canny(image, threshold1, threshold2[, edges[, ape ...

  8. python opencv 边缘检测_opencv-python-学习笔记十四(Canny边缘检测)

    原理 Canny边缘检测是一种常用的边缘检测算法.由 John F. Canny提出 这是一个多阶段的算法,我们将经历每个阶段. 1.降低噪音 由于边缘检测容易受到图像中噪声的影响,第一步是用5x5高 ...

  9. 利用Canny边缘检测算子进行边缘检测的原理及OpenCV代码实现

    图像处理开发需求.图像处理接私活挣零花钱,请加微信/QQ 2487872782 图像处理开发资料.图像处理技术交流请加QQ群,群号 271891601 Canny算子是John Canny在1986年 ...

  10. Canny边缘检测原理及C#程序实现

    原文:Canny边缘检测原理及C#程序实现 Canny边缘检测是被公认的检测效果最好的边缘检测方法,是由John F. Canny于1986年提出,算法目标是找出一个最优的边缘检测的方法,所谓最优即: ...

最新文章

  1. Spring-boot-文件上传大小限制
  2. 让AI计算无处不在,华为干了一件大事
  3. Several ports (8005, 80, 8009) required by Tomcat v6.0 Server at localhost are already in use
  4. python列表常用方法_第24p,必须掌握,列表的常用方法
  5. 利用金山快盘云服务搭建自己的SVN服务器
  6. java中equals函数所在的类,重写Java中的equals方法介绍
  7. HDU 1028 Ignatius and the Princess III
  8. 世界上迄今为止最安全的加密算法
  9. php.ini 安全配置
  10. ROW_NUMBER() OVER() 函数用法详解 (分组排序,多例子)
  11. 架构整洁之道 pdf_代码有整洁之道,而架构同样有整洁之道
  12. SQL Server监控全解析
  13. 再见Activity!SpringBoot+flowable完美结合,快速实现工作流,so easy!
  14. kali系统下libtorrent的安装编译
  15. 《童趣》——《所见》《小儿垂钓》《村居》《浮生六记·童趣》 ——诗文诵读教学设计
  16. 百度竞价软件测试面试,【百度SEM基础试题】百度推广竞价专员基础知识测试
  17. 初秋进补 粥汤大对决
  18. 单位半夜12点打电话给你,让你通知领导明天早上8点参会,而且要准备汇报,你会怎么做?
  19. 网站seo优化3-7天快速上百度首页靠谱吗?怎么做
  20. 惠普电脑打开BIOS的方法

热门文章

  1. 利用Tomcat的用户名和密码构建“永久”后门
  2. android 升级数据库 修改表结构
  3. .net安全编程 阅读笔记(二)
  4. 模型优化秘诀:从数据的角度分析,零代码也可以提升精度
  5. 北大阿里中科院提出细粒度人体姿态迁移方法,提升外观细节逼真度
  6. 免费中文深度学习课程来了!北大本科生同款,孙剑领衔旷视研究团队开讲
  7. SiamFC++,90 fps的单目标跟踪SOTA
  8. ThunderNet:国防科大、旷视提出首个在ARM上实时运行的通用目标检测算法
  9. 新开源!实时语义分割算法Light-Weight RefineNet
  10. 人工智能入门,怎么选择脚踏实地的工作岗位?