OpenCV Canny 源码解析.docx
《OpenCV Canny 源码解析.docx》由会员分享,可在线阅读,更多相关《OpenCV Canny 源码解析.docx(19页珍藏版)》请在冰豆网上搜索。
OpenCVCanny源码解析
OpenCVCanny源码解析
OpenCVCanny源码注释与分析
1986年,JohnF.Canny完善了边缘检测理论,Canny算法以此命名。
Canny算法的步骤:
1.使用滤波器卷积降噪
2.使用Sobel导数计算梯度幅值和方向
3.非极大值抑制+滞后阈值
在正式处理前,用高斯滤平滑波器对图像做滤波降噪的操作,避免噪声点的干扰,但在OpenCV的canny源码中,没有进行高斯滤波,需要使用者自行滤波;有些资料将非极大值抑制和滞后阈值视为两个步骤也是可行的,但是在源码中非极大值抑制和滞后阈值是同时进行的。
canny源码的位置:
\opencv\sources\modules\imgproc\src\canny.cpp
参考了网上许多资料,有不足之处请指正,谢谢。
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
/*M///////////////////////////////////////////////////////////////////////////////////////
//
//IMPORTANT:
READBEFOREDOWNLOADING,COPYING,INSTALLINGORUSING.
//
//Bydownloading,copying,installingorusingthesoftwareyouagreetothislicense.
//Ifyoudonotagreetothislicense,donotdownload,install,
//copyorusethesoftware.
//
//
//IntelLicenseAgreement
//ForOpenSourceComputerVisionLibrary
//
//Copyright(C)2000,IntelCorporation,allrightsreserved.
//Thirdpartycopyrightsarepropertyoftheirrespectiveowners.
//
//Redistributionanduseinsourceandbinaryforms,withorwithoutmodification,
//arepermittedprovidedthatthefollowingconditionsaremet:
//
//*Redistribution'sofsourcecodemustretaintheabovecopyrightnotice,
//thislistofconditionsandthefollowingdisclaimer.
//
//*Redistribution'sinbinaryformmustreproducetheabovecopyrightnotice,
//thislistofconditionsandthefollowingdisclaimerinthedocumentation
//and/orothermaterialsprovidedwiththedistribution.
//
//*ThenameofIntelCorporationmaynotbeusedtoendorseorpromoteproducts
//derivedfromthissoftwarewithoutspecificpriorwrittenpermission.
//
//Thissoftwareisprovidedbythecopyrightholdersandcontributors"asis"and
//anyexpressorimpliedwarranties,including,butnotlimitedto,theimplied
//warrantiesofmerchantabilityandfitnessforaparticularpurposearedisclaimed.
//InnoeventshalltheIntelCorporationorcontributorsbeliableforanydirect,
//indirect,incidental,special,emplary,orconsequentialdamages
//(including,butnotlimitedto,procurementofsubstitutegoodsorservices;
//lossofuse,data,orprofits;orbusinessinterruption)howevercaused
//andonanytheoryofliability,whetherincontract,strictliability,
//ortort(includingnegligenceorotherwise)arisinginanywayoutof
//theuseofthissoftware,evenifadvisedofthepossibilityofsuchdamage.
//
//M*/
#include"precomp.hpp"
/*
#ifdefined(HAVE_IPP)&&(IPP_VERSION_MAJOR>=7)
#defineUSE_IPP_CANNY1
#else
#undefUSE_IPP_CANNY
#endif
*/
#ifdefUSE_IPP_CANNY
namespacecv
{
staticboolippCanny(constMat&_src,Mat&_dst,floatlow,floathigh)
{
intsize=0,size1=0;
IppiSizeroi={_src.cols,_src.rows};
ippiFilterSobelNegVertGetBufferSize_8u16s_C1R(roi,ippMskSize3x3,&size);
ippiFilterSobelHorizGetBufferSize_8u16s_C1R(roi,ippMskSize3x3,&size1);
size=std:
:
max(size,size1);
ippiCannyGetSize(roi,&size1);
size=std:
:
max(size,size1);
AutoBufferbuf(size+64);
uchar*buffer=alignPtr((uchar*)buf,32);
Mat_dx(_src.rows,_src.cols,CV_16S);
if(ippiFilterSobelNegVertBorder_8u16s_C1R(_src.data,(int)_src.step,
_dx.ptr(),(int)_dx.step,roi,
ippMskSize3x3,ippBorderRepl,0,buffer)<0)
returnfalse;
Mat_dy(_src.rows,_src.cols,CV_16S);
if(ippiFilterSobelHorizBorder_8u16s_C1R(_src.data,(int)_src.step,
_dy.ptr(),(int)_dy.step,roi,
ippMskSize3x3,ippBorderRepl,0,buffer)<0)
returnfalse;
if(ippiCanny_16s8u_C1R(_dx.ptr(),(int)_dx.step,
_dy.ptr(),(int)_dy.step,
_dst.data,(int)_dst.step,roi,low,high,buffer)<0)
returnfalse;
returntrue;
}
}
#endif
voidcv:
:
Canny(InputArray_src,OutputArray_dst,
doublelow_thresh,doublehigh_thresh,
intaperture_size,boolL2gradient)
{
Matsrc=_src.getMat();//输入图像,必须为单通道灰度图
CV_Assert(src.depth()==CV_8U);//8位无符号
_dst.create(src.size(),CV_8U);//根据src的大小构造目标矩阵dst
Matdst=_dst.getMat();//输出图像,为单通道黑白图
//low_thresh表示低阈值,high_thresh表示高阈值
//aperture_size表示算子大小,默认为3
//L2gradient计算梯度幅值的标识,默认为false
//如果L2gradient为false并且apeture_size的值为-1(-1的二进制标识为:
11111111)
//L2gradient为false则计算sobel导数时,用G=|Gx|+|Gy|
//L2gradient为true则计算sobel导数时,用G=Math.sqrt((Gx)^2+(Gy)^2)根号下开平方
if(!
L2gradient&&(aperture_size&CV_CANNY_L2_GRADIENT)==CV_CANNY_L2_GRADIENT)
{
//CV_CANNY_L2_GRADIENT宏定义其值为:
Value=(1<<31)1左移31位即2147483648
//backwardcompatibility
//~标识按位取反
aperture_size&=~CV_CANNY_L2_GRADIENT;//相当于取绝对值
L2gradient=true;
}
//判别条件1:
aperture_size是奇数
//判别条件2:
aperture_size的范围应当是[3,7],默认值3
if((aperture_size&1)==0||(aperture_size!
=-1&&(aperture_size<3||aperture_size>7)))
CV_Error(CV_StsBadFlag,"");//报错
if(low_thresh>high_thresh)//如果低阈值>高阈值
std:
:
swap(low_thresh,high_thresh);//则交换低阈值和高阈值
#ifdefHAVE_TEGRA_OPTIMIZATION
if(tegra:
:
canny(src,dst,low_thresh,high_thresh,aperture_size,L2gradient))
return;
#endif
#ifdefUSE_IPP_CANNY
if(aperture_size==3&&!
L2gradient&&
ippCanny(src,dst,(float)low_thresh,(float)high_thresh))
return;
#endif
constintcn=src.channels();//cn为输入图像的通道数
Matdx(src.rows,src.cols,CV_16SC(cn));//存储x方向方向导数的矩阵,CV_16SC(cn):
16位有符号cn通道
Matdy(src.rows,src.cols,CV_16SC(cn));//存储y方向方向导数的矩阵......
/*Sobel参数说明:
(参考cvSobel)
cvSobel(
constCvArr*src,//输入图像
CvArr*dst,//输入图像
intxorder,//x方向求导的阶数
intyorder,//y方向求导的阶数
intaperture_size=3//滤波器的宽和高必须是奇数
);
*/
//BORDER_REPLICATE表示当卷积点在图像的边界时,原始图像边缘的像素会被复制,并用复制的像素扩展原始图的尺寸
//计算x方向的sobel方向导数,计算结果存在dx中
Sobel(src,dx,CV_16S,1,0,aperture_size,1,0,cv:
:
BORDER_REPLICATE);
//计算y方向的sobel方向导数,计算结果存在dy中
Sobel(src,dy,CV_16S,0,1,aperture_size,1,0,cv:
:
BORDER_REPLICATE);
//L2gradient为true时,表示需要根号下开平方运算,阈值也需要平方
if(L2gradient)
{
low_thresh=std:
:
min(32767.0,low_thresh);
high_thresh=std:
:
min(32767.0,high_thresh);
if(low_thresh>0)low_thresh*=low_thresh;//低阈值平方运算
if(high_thresh>0)high_thresh*=high_thresh;//高阈值平方运算
}
intlow=cvFloor(low_thresh);//cvFloor返回不大于参数的最大整数值,相当于取整
inthigh=cvFloor(high_thresh);
//ptrdiff_t是C/C++标准库中定义的一个数据类型,signed类型,通常用于存储两个指针的差(距离),可以是负数
//mapstep用于存放
ptrdiff_tmapstep=src.cols+2;//+2表示左右各扩展一条边
//AutoBuffer会自动分配一定大小的内存,并且指定内存中的数据类型是uchar
//列数+2表示图像左右各自扩展一条边(用于复制边缘像素,扩大原始图像)
//行数+2表示图像上下各自扩展一条边
AutoBufferbuffer((src.cols+2)*(src.rows+2)+cn*mapstep*3*sizeof(int));
int*mag_buf[3];//定义一个大小为3的int型指针数组,
mag_buf[0]=(int*)(uchar*)buffer;
mag_buf[1]=mag_buf[0]+mapstep*cn;
mag_buf[2]=mag_buf[1]+mapstep*cn;
memset(mag_buf[0],0,/*cn**/mapstep*sizeof(int));
uchar*map=(uchar*)(mag_buf[2]+mapstep*cn);
memset(map,1,mapstep);
memset(map+mapstep*(src.rows+1),1,mapstep);
intmaxsize=std:
:
max(1<<10,src.cols*src.rows/10);//2的10次幂1024
std:
:
vectorstack(maxsize);//定义指针类型向量,用于存地址
uchar**stack_top=&stack[0];//栈顶指针(指向指针的指针),指向stack[0],stack[0]也是一个指针
uchar**stack_bottom=&stack[0];//栈底指针,初始时栈底指针==栈顶指针
//梯度的方向被近似到四个角度之一(0,45,90,135四选一)
/*sectornumbers
(Top-LeftOrigin)
123
***
***
0*******0
***
***
321
*/
//define定义函数块
//CANNY_PUSH(d)是入栈函数,参数d表示地址指针,让该指针指向的内容为2(int型强制转换成uchar型),并入栈,栈顶指针+1
//2表示像素属于某条边缘可以看下方的注释
//CANNY_POP(d)是出栈函数,栈顶指针-1,然后将-1后的栈顶指针指向的值,赋给d
#defineCANNY_PUSH(d)*(d)=uchar
(2),*stack_top++=(d)
#defineCANNY_POP(d)(d)=*--stack_top
//calculatemagnitudeandangleofgradient,performnon-maximasuppression.
//fillthemapwithoneofthellowingvalues:
//0-thepixelmightbelongtoanedge可能属于边缘
//1-thepixelcannotbelongtoanedge不属于边缘
//2-thepixeldoesbelongtoanedge一定属于边缘
//for内进行非极大值抑制+滞后阈值处理
for(inti=0;i<=src.rows;i++)//i表示第i行
{
//i==0时,_norm指向mag_buf[1]
//i>0时,_norm指向mag_buf[2]
//+1表示跳过每行的第一个元素,因为是后扩展的边,不可能是边缘
int*_norm=mag_buf[(i>0)+1]+1;
if(i{
short*_dx=dx.ptr(i);//_dx指向dx矩阵的第i行
short*_dy=dy.ptr(i);//_dy指向dy矩阵的第i行
if(!
L2gradient)//如果L2gradient为false
{
for(intj=0;j_norm[j]=std:
:
abs(int(_dx[j]))+std:
:
abs(int(_dy[j]));//用||+||计算
}
else
{
for(intj=0;j//用平方计算,当L2gradient为true时,高低阈值都被平方了,所以此处_norm[j]无需开平方
_norm[j]=int(_dx[j])*_dx[j]+int(_dy[j])*_dy[j];//
}
if(cn>1)//如果不是单通道
{
for(intj=0,jn=0;j{
intmaxIdx=jn;
for(intk=1;kif(_norm[jn+k]>_norm[maxIdx])maxIdx=jn+k;
_norm[j]=_norm[maxIdx];
_dx[j]=_dx[maxIdx];
_dy[j]=_dy[maxIdx];
}
}
_norm[-1]=_norm[src.cols]=0;//最后一列和第一列的梯度幅值设置为0
}
//当i==src.rows(最后一行)时,申请空间并且每个空间的值初始化为0,存储在mag_buf[2]中
else
memset(_norm-1,0,/*cn**/mapstep*sizeof(int));
//attheverybeginningwedonothaveacompletering
//bufferof3magnituderowsfornon-maximasuppression
if(i==0)
continue;
uchar*_map=map+mapstep*i+1;//_map指向第i+1行,+1表示跳过该行第一个元素
_map[-1]=_map[src.cols]=1;//第一列和最后一列不是边缘,所以设置为1
int*_mag=mag_buf[1]+1;//takethecentralrow中间那一行
ptrdiff_tmagstep1=mag_buf[2]-mag_buf[1];
ptrdiff_tmagstep2=mag_buf[0]-mag_buf[1];
constshort*_x=dx.ptr(i-1);
constshort*