天天看點

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

繼續閱讀