Canny边缘检测算子

时间:2021-11-16 14:36:39

背景简述

Canny提出一种新的边缘检测方法[1][2],它对受白噪声影响的阶跃型边缘是最优的。Canny检测子的最优性与三个标准有关:第一、检测标准:不失去重要的边缘,不应有虚假的边缘;第二、定位标准:实际边缘与检测到的边缘位置之间的偏差最小;第三、单位应标准:将多个响应降低为单个边缘响应。这一点被第一个标准部分地覆盖了。因为当有两个响应对应于单个边缘时,其中之一应该被是虚假的。这第三个标准解决受噪声影响的边缘问题,起亦制非平滑边缘检测算子的作用。

基本理论

首先,Canny算子是针对1D信号和前两个最优标准表达的。用微积分方法可以得到完整的解。如果我们加上第三个标准,需要通达数值优化的办法得到最优解。其最优滤波器可有有效地为标准差的高斯平滑滤波器的一阶微分,其误差小于20%。然后,将边缘检测算子映射到2D情况。阶跃边缘由位置、方向和可能的幅度(强度)来确定。

由于噪声引起的对单个边缘的虚假响应通常造成的所谓“纹状”问题。一般而言,该问题在边缘检测中是非常普遍。边缘检测算子的输出通常要做阈值化处理,以确定哪些边缘是突出的。纹状是指边缘轮廓断开的情形,是由算子输出超出或阈值的波动引起。我可以通过来Thresholding with hysteresis消除。在一般情况下,我选择具有最小尺度的算子,因为它定位最准确。

Canny提出了特征综合方法。首先标记出所有由最小尺度算子得到的突出边缘。而整个Canny边缘检测器算法分成如下四步:

  1. 噪声去除。因为这个检测器用到了微分算子,所以对于局部的不连续是敏感的,某区域的噪声点很容易造成边缘的模糊。在我做的这个应用里,因为要检测的是文本的边缘,而文本的背景是比较规则的变形后的正方形方格,所以如果用wiki里建议的高斯滤波器,会造成整个图像都变成一种颜色,即全黑或者全白。因此如果换一种观点,把这些变形后的方格也看成图的一部分,因为字体的纹理和方格的纹理不同,所以可以看作是两种区域组合成的图像。由此我改用一般的镜子,增强图像边缘。

  2. 计算图像的边缘梯度。这个是常规运算,用了Sobel算子,分别计算图像的x和y方向的梯度值,最后计算出图像各点的梯度值以及梯度角。计算得到梯度角需要进行近似,近似四个值{-45(或135), 0, 45,90}。

  3. 非最大梯度值点抑制第2步计算后得到两组值,第一组是各点的梯度值,第二组是各点梯度角的近似值。这一个非最大梯度值点抑制是比较不好理解的一步。遍历各点,做如下操作。(1)如果该点(x, y)的梯度角是0,如果其梯度值比北(x - 1, y)和南(x + 1, y)的梯度值大,则认为(x,y)点是一个边缘点,否则抑制其值,该其梯度值为设定的背景值(0或255);(2)如果该点(x, y)的梯度角是90,如果其梯度值比西(x, y - 1)和东(x, y +1)的梯度值大,则认为(x, y)点是一个边缘点,否则抑制其值,该其梯度值为设定的背景值(0或255);(3)如果该点(x,y)的梯度角是135(或-45),如果其梯度值比东北(x - 1, y + 1)和西南(x + 1, y -1)的梯度值大,则认为(x, y)点是一个边缘点,否则抑制其值,该其梯度值为设定的背景值(0或255);(4)如果该点(x,y)的梯度角是45,如果其梯度值比西北(x - 1, y - 1)和东南(x + 1, y + 1)的梯度值大,则认为(x,y)点是一个边缘点,否则抑制其值,该其梯度值为设定的背景值(0或255)。

  4. 产生边缘.在第3步里直接用边缘值和背景值对两种图像区域进行了划分。这么做对于图像不同区域像素值区别较大的场合比较方便,计算也快,但是对于图像不同区域像素。

参考代码

Opencv版Canny函数

[cpp] view plaincopyprint?Canny边缘检测算子Canny边缘检测算子
  1. void canny (float s, IMAGE im, IMAGE mag, IMAGE ori){  
  2.     int width;  
  3.     float **smx,**smy;  
  4.     float **dx,**dy;  
  5.     int i,j,n;  
  6.     float gau[MAX_MASK_SIZE], dgau[MAX_MASK_SIZE], z;  
  7.   
  8.         // Create a Gaussian and a derivative of Gaussian filter mask  
  9.     for(i=0; i<MAX_MASK_SIZE; i++){  
  10.       gau[i] = meanGauss ((float)i, s);  
  11.       if (gau[i] < 0.005){  
  12.         width = i;  
  13.         break;  
  14.       }  
  15.       dgau[i] = dGauss ((float)i, s);  
  16.     }  
  17.   
  18.     n = width+width + 1;  
  19.     WIDTH = width/2;  
  20.     printf ("Smoothing with a Gaussian (width = %d) ...\n", n);  
  21.   
  22.     smx = f2d (im->info->nr, im->info->nc);  
  23.     smy = f2d (im->info->nr, im->info->nc);  
  24.   
  25.         //Convolution of source image with a Gaussian in X and Y directions   
  26.     seperable_convolution (im, gau, width, smx, smy);  
  27.   
  28.         //Now convolve smoothed data with a derivative  
  29.     printf ("Convolution with the derivative of a Gaussian...\n");  
  30.     dx = f2d (im->info->nr, im->info->nc);  
  31.     dxy_seperable_convolution (smx, im->info->nr, im->info->nc,dgau, width, dx, 1);  
  32.     free(smx[0]); free(smx);  
  33.   
  34.     dy = f2d (im->info->nr, im->info->nc);  
  35.     dxy_seperable_convolution (smy, im->info->nr, im->info->nc,  
  36.          dgau, width, dy, 0);  
  37.     free(smy[0]); free(smy);  
  38.   
  39.         // Create an image of the norm of dx,dy  
  40.     for (i=0; i<im->info->nr; i++)  
  41.       for (j=0; j<im->info->nc; j++)  
  42.       {  
  43.           z = norm (dx[i][j], dy[i][j]);  
  44.           mag->data[i][j] = (unsigned char)(z*MAG_SCALE);  
  45.       }  
  46.   
  47.         //Non-maximum suppression - edge pixels should be a local max  
  48.     nonmax_suppress (dx, dy, (int)im->info->nr, (int)im->info->nc, mag, ori);  
  49.   
  50.     free(dx[0]); free(dx);  
  51.     free(dy[0]); free(dy);  
  52. }  

Opencv-Python版Canny

[cpp] view plaincopyprint?Canny边缘检测算子Canny边缘检测算子
  1. #coding=utf-8  
  2. import cv2    
  3. import numpy as np  
  4.   
  5. img = cv2.imread("test.jpg", 0)  
  6. img = cv2.GaussianBlur(img,(3,3),0)    
  7. canny = cv2.Canny(img, 50, 150)    
  8.         
  9. cv2.imshow('Canny', canny)  
  10. cv2.imwrite("Canny.jpg",canny)  
  11. cv2.waitKey(0)    
  12. cv2.destroyAllWindows()    

Opencv-Python版Canny带阈值

[cpp] view plaincopyprint?Canny边缘检测算子Canny边缘检测算子
  1. import cv2    
  2. import numpy as np    
  3.     
  4. def CannyThreshold(lowThreshold):    
  5.     detected_edges = cv2.GaussianBlur(gray,(3,3),0)    
  6.     detected_edges = cv2.Canny(detected_edges,lowThreshold,  
  7.                       lowThreshold*ratio,apertureSize = kernel_size)    
  8.     dst = cv2.bitwise_and(img,img,mask = detected_edges)    
  9.     cv2.imshow('canny demo',dst)  
  10.     cv2.imwrite("CannyThreshold.jpg",dst)  
  11.     
  12. lowThreshold = 0    
  13. max_lowThreshold = 100    
  14. ratio = 3    
  15. kernel_size = 3    
  16.     
  17. img = cv2.imread('test.jpg')    
  18. gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)    
  19.     
  20. cv2.namedWindow('canny demo')    
  21.     
  22. cv2.createTrackbar('Min threshold','Canny demo',  
  23.                    lowThreshold, max_lowThreshold, CannyThreshold)    
  24.     
  25. CannyThreshold(0)  # initialization    
  26. if cv2.waitKey(0) == 27:    
  27.     cv2.destroyAllWindows()   

测试输出结果

经Canny算子处理图像如下图所示:

Canny边缘检测算子

经Canny算子并带阈值为100的处理图像如下图所示:

Canny边缘检测算子

参考文献

[1] Canny J. F. "Finding edges and lines in images" Technical Report AI-TR-720,MIT,Artifical Inteligence Labortay,Cambridg,MA,1983.

[2] Canny J. F. "a Computational Approach to Edge Detection", IEEE Trancsctions on Pattern Analysis and Machine Intelligence, 8(6):679-698,1996.

原文地址:http://blog.csdn.net/songzitea/article/details/8827781