Canny算子边缘检测详细原理(OpenCV+MATLAB实现)

  最近老师布置了一个边缘检测的作业,我借此机会详细学习了一下canny算子,在此进行总结,并分别给出OpenCV源码和MATLAB源码,自己水平有限,若有错误或者更好的编程方法,请广大网友留言,一定虚心学习。好了废话少说,直接开始吧。


基本原理

  • 须满足条件:抑制噪声;精确定位边缘。
  • 从数学上表达了三个准则[信噪比准则(低错误率)、定位精度准则、单边缘响应准则],并寻找表达式的最佳解。
  • 属于先平滑后求导的方法。

算法步骤

  1. 高斯平滑滤波(这个比较简单,后续介绍会省略)
  2. 计算图像梯度的幅值和方向
  3. 对幅值图像进行非极大值抑制
  4. 用双阈值算法检测和连接边缘

详细过程

1、高斯平滑滤波(略)

2、计算图像梯度的幅值和方向

可选用的模板:soble算子、Prewitt算子、一阶差分卷积模板等等;
在此选用Prewitt算子为例:
                             
由此可算得x方向梯度幅值:
                                          
                y 方向梯度幅值:
                                         

进一步可以得到图像梯度的幅值和方向:
                                         

如下图表示了中心点的梯度向量、方位角以及边缘方向。(任一点的边缘与梯度向量正交) 


3、对幅值图像进行非极大值抑制

首先将角度划分成四个方向范围 :水平(0°)、−45°、垂直(90°)、+45°。如下图: 

注:在opencv中由于反三角函数 cv::fastAtan2()得到的角度是0~360°,故划分为:水平(0°)、45°、垂直(90°)、135°,原理同上。 

下面看上述角度划分对应于3*3邻域的4种可能组合,如下图:

                                                               

扇形区标号d1~d4,对应3*3领域的4种可能的组合,1-x-5 , 7-x-3 , 2-x-6 , 8-x-4。

在每一点上,领域中心 x 与沿着其对应的梯度方向的两个像素相比,若中心像素为最大值,则保留,否则中心置0,这样可以抑制非极大值,保留局部梯度最大的点,以得到细化的边缘。


4、用双阈值算法检测和连接边缘

  • 选取系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1);
  • 取出非极大值抑制后的图像中的最大梯度幅值,定义高低阈值。即:TH×Max,TL×Max (当然可以自己给定) ;
  • 将小于低阈值的点抛弃,赋0;将大于高阈值的点立即标记(这些点就是边缘点),赋1;
  • 将大于高阈值,小于低阈值的点使用8连通区域确定(即:只有与TH像素连接时才会被接受,成为边缘点,赋  1)。


算法实现

matlab代码

clc
clear all
img_in=imread('lenna.jpg');
img_in=rgb2gray(img_in);
figure,imshow(img_in);
title('原图');

[rows,cols]=size(img_in);
thresh=graythresh(img_in);
img_bw=im2bw(img_in,thresh);%二值图像

%%%%step1:高斯滤波
template=fspecial('gaussian',3,0.8);%生成一个3*3的高斯模板,标准差选0.8
img_filt=imfilter(img_bw,template);

%%%%step2:计算梯度(幅度和方向)
%Prewitt梯度模板
%也可选择一阶差分卷积模板:
%dx=[-1,-1;1,1] dy=[1,-1;1,-1]
%*********************
dx = [-1 -1 -1;0 0 0;1 1 1];%x方向的梯度模板
dy = [-1 0 1; -1 0 1;-1 0 1];%y方向的梯度模板
img_filt=double(img_filt);

grad_x=conv2(img_filt,dx,'same');%获取x方向的梯度图像.使用梯度模板进行二维卷积,结果与原图像大小相同
grad_y=conv2(img_filt,dy,'same');%获取y方向的梯度图像.使用梯度模板进行二维卷积,结果与原图像大小相同
grad=sqrt((grad_x.^2)+(grad_y.^2));%梯度幅值图像

figure,imshow(grad);
title('梯度幅值图');

grad_dir=atan2(grad_y,grad_x);%获取梯度方向弧度
grad_dir=grad_dir*180/pi;
%%%%step3:对梯度幅值进行非极大值抑制
%首先将角度划分成四个方向范围:水平(0°)、-45°、垂直(90°)、+45°
for i = 1:rows
    for j = 1:cols
        if((grad_dir(i,j)>=-22.5 && grad_dir(i,j)<=22.5) || (grad_dir(i,j)>=157.5 && grad_dir(i,j)<=180)...
                                       ||(grad_dir(i,j)<=-157.5 && grad_dir(i,j)>=-180) )
            grad_dir(i,j) = 0;
        elseif((grad_dir(i,j) >= 22.5) && (grad_dir(i,j) < 67.5) || (grad_dir(i,j) <= -112.5) && (grad_dir(i,j) > -157.5))
            grad_dir(i,j) = -45;
        elseif((grad_dir(i,j) >= 67.5) && (grad_dir(i,j) < 112.5) || (grad_dir(i,j) <= -67.5) && (grad_dir(i,j) >- 112.5))
            grad_dir(i,j) = 90;
        elseif((grad_dir(i,j) >= 112.5) && (grad_dir(i,j) < 157.5) || (grad_dir(i,j) <= -22.5) && (grad_dir(i,j) > -67.5))
            grad_dir(i,j) = 45;  
        end
    end
end

%讨论对3x3区域的四个基本边缘方向进行非极大值抑制.获取非极大值抑制图像
Nms = zeros(rows,cols);%定义一个非极大值抑制图像
for i = 2:rows-1
    for j= 2:cols-1
        if (grad_dir(i,j) == 90 && grad(i,j) == max([grad(i,j), grad(i,j+1), grad(i,j-1)]))
            Nms(i,j) = grad(i,j);
        elseif (grad_dir(i,j) == -45 && grad(i,j) == max([grad(i,j), grad(i+1,j-1), grad(i-1,j+1)]))
            Nms(i,j) = grad(i,j);
        elseif (grad_dir(i,j) == 0 && grad(i,j) == max([grad(i,j), grad(i+1,j), grad(i-1,j)]))
            Nms(i,j) = grad(i,j);
        elseif (grad_dir(i,j) == 45 && grad(i,j) == max([grad(i,j), grad(i+1,j+1), grad(i-1,j-1)]))
            Nms(i,j) = grad(i,j);
        end;
    end;
end;

figure,imshow(Nms);
title('非极大值抑制图');

%%%%step4:双阈值检测和连接边缘
img_out=zeros(rows,cols);%定义一个双阈值图像
YH_L=0.1*max(max(Nms));%低阈值
YH_H=0.3*max(max(Nms));%高阈值
for i = 1:rows
    for j = 1:cols
        if(Nms(i,j)<YH_L)
           img_out(i,j)=0;
        elseif(Nms(i,j)>YH_H)
                img_out(i,j)=1;
        %对TL < Nms(i, j) < TH 使用8连通区域确定
        
        elseif ( Nms(i+1,j) < YH_H || Nms(i-1,j) < YH_H || Nms(i,j+1) < YH_H || Nms(i,j-1) < YH_H ||...
                Nms(i-1,j-1) < YH_H || Nms(i-1, j+1) < YH_H || Nms(i+1, j+1) < YH_H || Nms(i+1, j-1) < YH_H)
                   img_out(i,j) = 1;   
        end  
    end
end
bw=edge(img_bw,'canny');

figure,imshow(img_out);
title('本实验结果图');

figure,imshow(bw);
title('工具箱Canny算子效果图');

效果图



OpenCV代码(步骤和MATLAB步骤对应,略有改动)

改动点:在上述matlab是先将图像二值化后进行检测,选择prewitt,而在下面opencv是用灰度图直接检测,选用一阶差分卷积模板,可以看看效果的异同;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include"math.h"
using namespace std;
using namespace cv;

Mat Img_in , Img_gray , Img_out;
Mat scr;
int main()
{
  Img_in=imread("lenna.jpg");
  int rows=Img_in.rows;
  int cols=Img_in.cols;//获取图像尺寸

  cvtColor(Img_in,Img_gray,CV_BGR2GRAY); 
  imshow("【灰度图】",Img_gray);//转化为灰度图

  //step1:高斯平滑
  Mat img_filt;
  GaussianBlur(Img_gray,Img_out,Size(3,3),0,0);
  //adaptiveThreshold(img_filt , Img_out , 255 ,ADAPTIVE_THRESH_MEAN_C , THRESH_BINARY,min(rows,cols), 0);
  //imshow("【二值图】",Img_out );
  Img_out.convertTo(Img_out,CV_32FC1); //将图像转换为float或double型,否则算梯度会报错

 /*step2:计算梯度(幅度和方向)
  选择一阶差分卷积模板:
  dx=[-1,-1;1,1] dy=[1,-1;1,-1]
 */
 Mat gy=(Mat_<char>(2,2)<<1,-1, 
	                  1,-1);
	                         //定义一阶差分卷积梯度模板
 Mat gx=(Mat_<char>(2,2)<<-1,-1,
	                   1,1); //定义一阶差分卷积梯度模板
 Mat img_gx , img_gy , img_g ;//定义矩阵 
 Mat img_dir=Mat::zeros(rows , cols , CV_32FC1);//定义梯度方向矩阵,计算角度为float型
 
 filter2D(Img_out, img_gx , Img_out.depth(), gx);  //获取x方向的梯度图像.使用梯度模板进行二维卷积,结果与原图像大小相同
 filter2D(Img_out, img_gy , Img_out.depth(), gy);  //获取x方向的梯度图像.使用梯度模板进行二维卷积,结果与原图像大小相同
 img_gx=img_gx.mul(img_gx);//点乘(每个像素值平方)
 img_gy=img_gy.mul(img_gy);//点乘(每个像素值平方)
 img_g=img_gx+img_gy;
 sqrt(img_g, img_g); //梯度幅值图像
 imshow("梯度图",img_g);

 //求梯度方向图像
 for(int i=0;i<rows; i++)
 {
   for(int j=0 ; j<cols; j++)
   {
      img_dir.at<float>(i,j)=fastAtan2(img_gy.at<float>(i,j) , img_gx.at<float>(i,j));//求角度
   }
 }


/* step3:对梯度幅值进行非极大值抑制
首先将角度划分成四个方向范围:水平(0°)、45°、垂直(90°)、135°
*/
 Mat Nms=Mat::zeros(rows , cols , CV_32FC1);//定义一个非极大值抑制图像,float型
 for(int i=0;i<rows; i++)
 {
   for(int j=0 ; j<cols; j++)
   {
      if(img_dir.at<float>(i,j) <=22.5 && img_dir.at<float>(i,j)>=0 || img_dir.at<float>(i,j) >=157.5 && img_dir.at<float>(i,j)<=202.5
		  || img_dir.at<float>(i,j)>=337.5 && img_dir.at<float>(i,j)<=360)
        img_dir.at<float>(i,j)=0;

	  else if(img_dir.at<float>(i,j)>22.5 && img_dir.at<float>(i,j)<=67.5 || img_dir.at<float>(i,j)>202.5 && img_dir.at<float>(i,j)<=247.5)
		    img_dir.at<float>(i,j)=45;
      else if(img_dir.at<float>(i,j)>67.5 && img_dir.at<float>(i,j)<=112.5 || img_dir.at<float>(i,j)>247.5 && img_dir.at<float>(i,j)<=292.5)
		    img_dir.at<float>(i,j)=90;
	  else if(img_dir.at<float>(i,j)>112.5 && img_dir.at<float>(i,j)<157.5 || img_dir.at<float>(i,j)>292.5 && img_dir.at<float>(i,j)<337.5)
		    img_dir.at<float>(i,j)=135;
   }
 }
  
  for(int i=1;i<rows-1; i++)
 {
   for(int j=1; j<cols-1; j++)
   {
    if ( img_dir.at<float>(i,j) == 90 && img_g.at<float>(i,j) == max(img_g.at<float>(i,j), max(img_g.at<float>(i,j+1), img_g.at<float>(i,j-1))))
           Nms.at<float>(i,j) = img_g.at<float>(i,j);
     else if ( img_dir.at<float>(i,j) == 45 && img_g.at<float>(i,j) == max(img_g.at<float>(i,j),max(img_g.at<float>(i-1,j+1), img_g.at<float>(i+1,j-1))))
           Nms.at<float>(i,j) = img_g.at<float>(i,j);
        else if( img_dir.at<float>(i,j) == 0 && img_g.at<float>(i,j) == max(img_g.at<float>(i,j),max( img_g.at<float>(i-1,j), img_g.at<float>(i+1,j))))
           Nms.at<float>(i,j) = img_g.at<float>(i,j);
        else if ( img_dir.at<float>(i,j) == 135 && img_g.at<float>(i,j) == max(img_g.at<float>(i,j),max(img_g.at<float>(i-1,j-1), img_g.at<float>(i+1,j+1))))
           Nms.at<float>(i,j) = img_g.at<float>(i,j);
   }  
  }

/*step4:双阈值检测和连接边缘
*/
Mat img_dst=Mat::zeros(rows , cols , CV_32FC1);//定义一个双阈值图像,float型
double TH,TL;
double maxVal=0;//必须为double类型,且必须赋初值,否则报错
Nms.convertTo(Nms,CV_64FC1); //为了计算,将非极大值抑制图像转为double型
minMaxLoc( Nms ,NULL, &maxVal,NULL,NULL); //求矩阵 Nms最大值
TH=0.3*maxVal ;//高阈值
TL=0.1*maxVal;//低阈值

 for(int i=0;i<rows; i++)
 {
   for(int j=0 ; j<cols; j++)
   {
     if( Nms.at<double>(i,j)<TL)
		  img_dst.at<float>(i,j)=0;
	 else if( Nms.at<double>(i,j)>TH)
		  img_dst.at<float>(i,j)=1;
	 else if(Nms.at<double>(i-1,j-1)<TL || Nms.at<double>(i-1,j)<TL || Nms.at<double>(i-1,j+1)<TL ||
		        Nms.at<double>(i,j-1)<TL|| Nms.at<double>(i,j+1)<TL || Nms.at<double>(i+1,j-1)<TL || 
				Nms.at<double>(i+1,j)<TL || Nms.at<double>(i+1,j+1)<TL)
          img_dst.at<float>(i,j)=1;
   }
 }
imshow("非极大值抑制图",Nms);
imshow(" 边缘检测图", img_dst);
imwrite(" 边缘检测效果图.jpg",img_dst);//保存图像
waitKey(0); 
 return 0;
}

效果图

                                 

参考:https://blog.csdn.net/liuzhuomei0911/article/details/51345591

原创申明:转载请注明出处

猜你喜欢

转载自blog.csdn.net/weixin_40647819/article/details/80377343