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等梯度算子计算梯度幅值和方向
计算公式为:
其中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++工程完整代码,有兴趣可以浏览一下:
#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;
}
}
}
}
}