Canny边缘检测原理与C++实现(2)实现部分
转载请说明出处:
http://blog.csdn.net/zhubaohua_bupt/article/details/73844187
本代码实现完全脱离opencv,如果需要显示,可以调用,以便观察检测效果。
首先,由于多次用到图像,所以定义图像数据结构,
DATA.h
#ifndef DATA_ #define DATA_ #include <vector> #include<deque> #include"memory.h" using namespace std; typedef unsigned char PIXUC1; typedef float PIXFC1; //单通道 类型图像 template<class PIXVALUETYPE> class IMGCH1{ public: IMGCH1(unsigned int HEIGHT_,unsigned int WIDTH_,unsigned char INITVALUE); IMGCH1(unsigned int HEIGHT_,unsigned int WIDTH_,PIXVALUETYPE* dataPtr_); ~IMGCH1(); PIXVALUETYPE* dataPtr; unsigned int rows; unsigned int cols; }; //////////////////////////////////////////////////////////////////////////////////////////////////// template<class PIXVALUETYPE> IMGCH1<PIXVALUETYPE>::IMGCH1(unsigned int HEIGHT_,unsigned int WIDTH_,unsigned char INITVALUE):rows(HEIGHT_),cols(WIDTH_) { dataPtr=new PIXVALUETYPE[rows*cols]; memset(dataPtr,INITVALUE,rows*cols); } template<class PIXVALUETYPE> IMGCH1<PIXVALUETYPE>::IMGCH1(unsigned int HEIGHT_,unsigned int WIDTH_,PIXVALUETYPE* dataPtr_):rows(HEIGHT_),cols(WIDTH_) { dataPtr=new PIXVALUETYPE[rows*cols]; long int datalength =rows*cols; //拷贝数据 PIXVALUETYPE*pt=dataPtr; PIXVALUETYPE*pt_=dataPtr_; for(int i=0;i<datalength;i++,pt++,pt_++) *pt=*pt_; } template<class PIXVALUETYPE> IMGCH1<PIXVALUETYPE>:: ~IMGCH1() { delete [] dataPtr; } #endif
MyCanny.h
#include "iostream" #include "math.h" #include"DATA.h" using namespace std; class MyCanny { public: void operator()(const IMGCH1<PIXUC1>& srcimg,IMGCH1<PIXUC1>& CannyImg,int lowthread,int highthread,int gaussSize); //******************灰度转换函数************************* //第一个参数image输入的彩色RGB图像; //第二个参数imageGray是转换后输出的灰度图像; //************************************************************* void ToUchar(const IMGCH1<PIXFC1> &floatImage,IMGCH1<PIXUC1> &imageUchar); //******************高斯卷积核生成函数************************* // gaus是一个指向含有N个double类型数组的二维指针; // size是高斯卷积核的尺寸大小; // gausArray 是一个指向含有N个double类型数组的一维指针; // sigma是卷积核的标准差 //************************************************************* void GetGaussianKernel(float **gaus, float* gausArray,const int size,const float sigma); //******************高斯滤波************************* //imageSource是待滤波原始图像; //imageGaussian是滤波后输出图像; //gausArray是一个指向含有N个double类型数组的指针; //size是滤波核的尺寸 //************************************************************* void GaussianFilter(const IMGCH1<PIXUC1>& srcimg,IMGCH1<PIXUC1>&imageGaussian,float gausArray[],int size); //******************Sobel算子计算梯度和方向******************** //imageSourc原始灰度图像; //imageSobelXY是梯度图像; //pointDrection是梯度方向数组指针 //************************************************************* void SobelGradDirection(const IMGCH1<PIXUC1> &imageSource,IMGCH1<PIXUC1> &imageSobelXY,char *pointDrection); //******************局部极大值抑制************************* //imageInput输入的Sobel梯度图像; //imageOutPut是输出的局部极大值抑制图像; //pointDrection是图像上每个点的梯度方向数组指针 梯度方向角,简化为 0(水平) 45,-45,90(垂直) //************************************************************* void LocalMaxValue(const IMGCH1<PIXUC1> &imageInput, IMGCH1<PIXUC1> &imageOutput, char *pointDrection); //******************双阈值处理************************* //imageInput经过局部极大值抑制的梯度图 //lowThreshold是低阈值 //highThreshold是高阈值 //****************************************************** void DoubleThreshold( IMGCH1<PIXUC1> &imageIput,int lowThreshold,int highThreshold); //******************双阈值中间像素连接处理********************* //imageInput 经过处理的局部极大值抑制的梯度图 <lowThreshold ->0 >highThreshold ->255 //函数执行完后是Canny边缘检测图, 边缘(255)其他(0) //lowThreshold是低阈值 //highThreshold是高阈值 //************************************************************* void DoubleThresholdLink(IMGCH1<PIXUC1> &imageInput,IMGCH1<PIXUC1> &CannyImg,int lowThreshold) ; //******************递归连接边缘********************* //imageInput 梯度幅值图像; // x,y 为要检测点的坐标 //lowThreshold是低阈值 //************************************************************* void LinkEdge(IMGCH1<PIXUC1> &imageInput,int x,int y,int lowThreshold ); void SHOW(const IMGCH1<PIXUC1> &imageInput); int width; int height; };
MyCanny.cpp
#include"MyCanny.h" //////为了显示,可以把类里的show函数注释掉,就可以在实现部分不依赖于opencv #include"opencv.hpp" using namespace cv; void MyCanny::operator()(const IMGCH1<PIXUC1>& srcimg_,IMGCH1<PIXUC1>& CannyImg_ ,int lowthread,int highthread,int size) { if(srcimg_.cols==0&&srcimg_.rows==0) { cerr<<" imageSource is empty"<<endl; return ; } if(lowthread>highthread) { cerr<<"Fourth parameter must large than third parameter. "<<endl; return; } if(size<1) { cerr<<"size must be a Positive. "<<endl; return; } if(size%2!=1) { cout<<" size is expected a Odd. "<<endl; size+=1; } if(lowthread<1) lowthread=1; width=srcimg_.cols; height=srcimg_.rows; //高斯核 float **gaus=new float *[size]; //卷积核数组 for(int i=0;i<size;i++) { gaus[i]=new float[size]; //二维矩阵 } float *gausArray=new float[size*size]; GetGaussianKernel(gaus,gausArray,size,1); //生成size*size 大小高斯卷积核,Sigma=1; //滤波 IMGCH1<PIXUC1> imageGaussian_(height,width,PIXUC1(0)); GaussianFilter(srcimg_,imageGaussian_,gausArray,size); //梯度 IMGCH1<PIXUC1> SobelGradAmpl_(height,width,PIXUC1(0)); char *pointDirection=new char[srcimg_.cols*srcimg_.rows]; //定义梯度方向角数组 SobelGradDirection(imageGaussian_,SobelGradAmpl_,pointDirection); //计算X、Y方向梯度和方向角 //局部非极大值抑制 IMGCH1<PIXUC1> imageLocalMax_(height,width,PIXUC1(0)); LocalMaxValue(SobelGradAmpl_,imageLocalMax_,pointDirection); //双阈值处理 DoubleThreshold(imageLocalMax_,lowthread,highthread); //双阈值中间阈值滤除及连接 DoubleThresholdLink(imageLocalMax_,CannyImg_,lowthread); delete []pointDirection; for(int i=0;i<size;i++) //删除高斯核数组 delete [] gaus[i]; delete []gausArray; } void MyCanny::GetGaussianKernel(float **gaus, float *gausArray, const int size,const float 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; 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++; } return ; } //******************高斯滤波************************* void MyCanny::GaussianFilter(const IMGCH1<PIXUC1>& imageSource, IMGCH1<PIXUC1> &imageGaussian,float *gausArray,int size) { //滤波 for(int _row=0;_row<imageSource.rows;_row++) { for(int _col=0;_col<imageSource.cols;_col++) { int k=0; for(int l=-size/2;l<=size/2;l++) { for(int g=-size/2;g<=size/2;g++) { //以下处理针对滤波后图像边界处理,为超出边界的值赋值为边界值 int row=_row+l; int col=_col+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.dataPtr[_row*width+_col]+=gausArray[k]*imageSource.dataPtr[row*width+col]; k++; } } } } } //******************Sobel算子计算X、Y方向梯度和梯度方向角******************** void MyCanny::SobelGradDirection(const IMGCH1<PIXUC1>& imageSource, IMGCH1<PIXUC1>&SobelAmpXY, char *pointDrection) { for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++) { pointDrection[i]=0; } IMGCH1<PIXFC1> imageSobelX(height,width,PIXFC1(0)); IMGCH1<PIXFC1> imageSobelY(height,width,PIXFC1(0)); PIXUC1 *P=imageSource.dataPtr; PIXFC1 *PX=imageSobelX.dataPtr; PIXFC1 *PY=imageSobelY.dataPtr; int k=0; for(int row=1;row<(imageSource.rows-1);row++) { for(int col=1;col<(imageSource.cols-1);col++) { pointDrection[k]=-1; //通过指针遍历图像上每一个像素 int gradY=P[(row-1)*width+col+1]+P[row*width+col+1]*2+P[(row+1)*width+col+1]-P[(row-1)*width+col-1]-P[row*width+col-1]*2-P[(row+1)*width+col-1]; PY[row*width+col]=abs(gradY); int gradX=P[(row+1)*width+col-1]+P[(row+1)*width+col]*2+P[(row+1)*width+col+1]-P[(row-1)*width+col-1]-P[(row-1)*width+col]*2-P[(row-1)*width+col+1]; PX[row*width+col]=abs(gradX); if(gradX==0) { gradX=0.01; //防止除数为0异常 } float gradDrection=atan2(gradY,gradX)*57.3;//弧度转换为度 if(gradDrection<=-67.5&&gradDrection<=-112.5||gradDrection>=67.5&&gradDrection<=-112.5) pointDrection[k]=90; else if(gradDrection>=22.5&&gradDrection<67.5||gradDrection>=-157.5&&gradDrection<-112.5) pointDrection[k]=45; else if(gradDrection>=-67.5&&gradDrection<22.5||gradDrection>=112.5&&gradDrection<157.5) pointDrection[k]=-45; else pointDrection[k]=0; k++; } } IMGCH1<PIXFC1> imageSobelXY(height,width,PIXFC1(0)); for(int row=0;row<imageSobelXY.rows;row++) for(int col=0;col<imageSobelXY.cols;col++) imageSobelXY.dataPtr[row*width+col]=sqrt(imageSobelX.dataPtr[row*width+col]*imageSobelX.dataPtr[row*width+col]+imageSobelY.dataPtr[row*width+col]*imageSobelY.dataPtr[row*width+col]); ToUchar(imageSobelXY,SobelAmpXY); } //******************局部极大值抑制************************* void MyCanny::LocalMaxValue(const IMGCH1<PIXUC1> &imageInput, IMGCH1<PIXUC1> &imageOutput, char *pointDrection) { for(int row=0;row<height;row++) for(int col=0;col<width;col++) imageOutput.dataPtr[row*width+col]= imageInput.dataPtr[row*width+col]; int k=0; for(int row=1;row<imageInput.rows-1;row++) { for(int col=1;col<imageInput.cols-1;col++) { int U=row-1,D=row+1,L=col-1,R=col+1; int value00=imageInput.dataPtr[U*width+L]; int value01=imageInput.dataPtr[U*width+col]; int value02=imageInput.dataPtr[U*width+R]; int value10=imageInput.dataPtr[row*width+L]; int value11=imageInput.dataPtr[row*width+col]; int value12=imageInput.dataPtr[row*width+R]; int value20=imageInput.dataPtr[D*width+L]; int value21=imageInput.dataPtr[D*width+col]; int value22=imageInput.dataPtr[D*width+R]; if(pointDrection[k]==90) { if(value11<=value01||value11<=value21) imageOutput.dataPtr[row*width+col]=0; } else if(pointDrection[k]=45) { if(value11<=value20||value11<value02) imageOutput.dataPtr[row*width+col]=0; } else if(pointDrection[k]=-45) { if(value11<=value00||value11<=value22) imageOutput.dataPtr[row*width+col]=0; } else { if(value11<=value10||value11<=value12) imageOutput.dataPtr[row*width+col]=0; } k++; } } } //******************双阈值处理************************* void MyCanny::DoubleThreshold( IMGCH1<PIXUC1> &imageIput,int lowThreshold,int highThreshold) { for(int row=0;row<imageIput.rows;row++) for(int col=0;col<imageIput.cols;col++) { if(imageIput.dataPtr[row*width+col]>highThreshold) imageIput.dataPtr[row*width+col]=255; if(imageIput.dataPtr[row*width+col]<lowThreshold) imageIput.dataPtr[row*width+col]=0; } } //******************双阈值中间像素连接处理********************* void MyCanny::DoubleThresholdLink(IMGCH1<PIXUC1> &imageInput,IMGCH1<PIXUC1> &CannyImg,int lowThreshold) { for(int row=1;row<imageInput.rows-1;row++) for(int col=1;col<imageInput.cols-1;col++) { if(imageInput.dataPtr[row*width+col]==255) LinkEdge(imageInput,col, row, lowThreshold ); } for(int row=1;row<imageInput.rows;row++) for(int col=1;col<imageInput.cols;col++) { if(imageInput.dataPtr[row*width+col]==255) CannyImg.dataPtr[row*width+col]=255; } } //******************递归连接边缘********************* void MyCanny::LinkEdge(IMGCH1<PIXUC1> &imageInput,int x,int y,int lowThreshold ) { int nextpoint_x=-1; int nextpoint_y=-1; if(x<1||x>width-1||y<1||y>height-1) return; if( imageInput.dataPtr[(y-1)*width+x-1]>=lowThreshold&& imageInput.dataPtr[(y-1)*width+x-1]!=255) { nextpoint_x=x-1; nextpoint_y=y-1; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if( imageInput.dataPtr[(y-1)*width+x]>=lowThreshold&&imageInput.dataPtr[(y-1)*width+x]!=255) { nextpoint_x=x; nextpoint_y=y-1; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if(imageInput.dataPtr[(y-1)*width+x+1]>=lowThreshold&&imageInput.dataPtr[(y-1)*width+x+1]!=255) { nextpoint_x=x+1; nextpoint_y=y-1; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if( imageInput.dataPtr[y*width+x-1]>=lowThreshold&&imageInput.dataPtr[y*width+x-1]!=255 ) { nextpoint_x=x-1; nextpoint_y=y; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if( imageInput.dataPtr[y*width+x+1]>=lowThreshold&&imageInput.dataPtr[y*width+x+1]!=255 ) { nextpoint_x=x+1; nextpoint_y=y; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if( imageInput.dataPtr[(y+1)*width+x-1]>=lowThreshold&&imageInput.dataPtr[(y+1)*width+x-1]!=255 ) { nextpoint_x=x-1; nextpoint_y=y+1; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if( imageInput.dataPtr[(y+1)*width+x]>=lowThreshold&& imageInput.dataPtr[(y+1)*width+x]!=255 ) { nextpoint_x=x; nextpoint_y=y+1; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } else if(imageInput.dataPtr[(y+1)*width+x+1]>=lowThreshold&&imageInput.dataPtr[(y+1)*width+x+1]!=255) { nextpoint_x=x+1; nextpoint_y=y+1; imageInput.dataPtr[nextpoint_y*width+nextpoint_x]=255; LinkEdge(imageInput,nextpoint_x,nextpoint_y,lowThreshold); } } void MyCanny::ToUchar(const IMGCH1<float> &floatImage,IMGCH1<PIXUC1> &imageUchar) { for(int row=0;row<floatImage.rows;row++) for(int col=0;col<floatImage.cols;col++) imageUchar.dataPtr[row*width+col]= floatImage.dataPtr[row*width+col]>255?255: floatImage.dataPtr[row*width+col]; } void MyCanny::SHOW(const IMGCH1<PIXUC1> &imageInput) { Mat show=Mat::zeros(imageInput.rows,imageInput.cols,CV_8UC1); for(int row=0;row<imageInput.rows;row++) for(int col=0;col<imageInput.cols;col++) { show.at<uchar>(row,col)=imageInput.dataPtr[row*imageInput.cols+col]; } imshow(" ",show); waitKey(0); }
测试
#include"opencv.hpp" using namespace cv; #include"MyCanny.h" using namespace std; int main() { Mat src=imread("E:\\matchpic\\right_16488.jpg",0); //转换数据结构 IMGCH1<PIXUC1> src_(src.rows,src.cols,PIXUC1(0)); for(int row=0;row<src.rows;row++) for(int col=0;col<src.cols;col++) src_.dataPtr[row*src.cols+col]=src.at<uchar>(row,col); Mat Cannyedge=Mat::zeros(src.rows,src.cols,CV_8UC1); IMGCH1<PIXUC1> Cannyedge_(src.rows,src.cols,PIXUC1(0)); MyCanny MyCanny_; MyCanny_(src_,Cannyedge_,10,100,3);//检测 //转换回Mat显示 for(int row=0;row<src_.rows;row++) for(int col=0;col<src_.cols;col++) Cannyedge.at<uchar>(row,col)=Cannyedge_.dataPtr[row*src.cols+col]; imshow("Cannyedge ",Cannyedge); waitKey(0); }