最近老师布置了一个边缘检测的作业,我借此机会详细学习了一下canny算子,在此进行总结,并分别给出OpenCV源码和MATLAB源码,自己水平有限,若有错误或者更好的编程方法,请广大网友留言,一定虚心学习。好了废话少说,直接开始吧。
基本原理
- 须满足条件:抑制噪声;精确定位边缘。
- 从数学上表达了三个准则[信噪比准则(低错误率)、定位精度准则、单边缘响应准则],并寻找表达式的最佳解。
- 属于先平滑后求导的方法。
算法步骤
- 高斯平滑滤波(这个比较简单,后续介绍会省略)
- 计算图像梯度的幅值和方向
- 对幅值图像进行非极大值抑制
- 用双阈值算法检测和连接边缘
详细过程
1、高斯平滑滤波(略)
2、计算图像梯度的幅值和方向
可选用的模板:soble算子、Prewitt算子、一阶差分卷积模板等等;
在此选用Prewitt算子为例:
由此可算得x方向梯度幅值:
y
方向梯度幅值:
进一步可以得到图像梯度的幅值和方向:
如下图表示了中心点的梯度向量、方位角以及边缘方向。(任一点的边缘与梯度向量正交)
注:在opencv中由于反三角函数 cv::fastAtan2()得到的角度是0~360°,故划分为:水平(0°)、45°、垂直(90°)、135°,原理同上。
3、对幅值图像进行非极大值抑制
首先将角度划分成四个方向范围 :水平(0°)、−45°、垂直(90°)、+45°。如下图:下面看上述角度划分对应于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
原创申明:转载请注明出处