Canny算法解析,opencv源码实现及实例[通俗易懂]

Canny算法解析,opencv源码实现及实例[通俗易懂]参考:http://baike.baidu.com/link?url=tkyXCTmiihKboar3IQ7yo-ECZK95tq2Bn02H1aKwGdt00xrbfD6Lezzjk5ArZF0lCnx8rOQiq4d7o24bUDN75_1392ZOUPYgdt3PZsmLrFShttp://blog.csdn.net/xiaowei_cqu/article/details/783

大家好,又见面了,我是你们的朋友全栈君。

—————–Canny算法原理部分—————–

Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。

Canny边缘检测基本原理:

(1)图象边缘检测必须满足两个条件:一能有效地抑制噪声;二必须尽量精确确定边缘的位置。
(2)根据对信噪比与定位乘积进行测度,得到最优化逼近算子。这就是Canny边缘检测算子。
(3)类似与Marr(LoG)边缘检测方法,也属于先平滑后求导数的方法。
Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:
(1)好的检测 – 算法能够尽可能多地标识出图像中的实际边缘。
(2)好的定位 – 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近。
(3)最小响应 – 图像中的边缘只能标识一次,并且可能存在的图像雜訊不应标识为边缘。

Canny算子求边缘点具体算法步骤如下:

1. 用高斯滤波器平滑图像.

图像高斯滤波的实现可以用两个一维高斯核分别两次加权实现,也可以通过一个二维高斯核一次卷积实现。

1)高斯核实现

Canny算法解析,opencv源码实现及实例[通俗易懂]

上式为离散化的二维高斯函数,确定参数就可以得到二维核向量。然后对灰度图像进行卷积计算,在求的高斯核后,要对整个核进行归一化处理,即每一个点的系数除以所有系数之和,这样得到最终的二维高斯模板。

附讲解:要想计算上面的系数,需要知道高斯函数的标准差σ (sigma),还需要知道选3*3还是5*5的模板,也就是模板要多大,实际应用的时候,这两者是有关系的,根据数理统计的知识,高斯分布的特点就是数值分布在(μ—3σ,μ+3σ)中的概率为0.9974,也就是模板的大小其实就是6σ这么大就OK了,但是6σ可能不是奇数,因为我们一定要保证有核心。所以模板窗口的大小一般采用1+2*ceil(3*nSigma) ceil是向上取整函数,例如ceil(0.6)=1。

详细讲解参见:http://www.open-open.com/lib/view/open1453460512558.html

//高斯滤波 
    double nSigma=0.2;
    int nWindowSize=1+2*ceil(3*nSigma);//通过sigma得到窗口大小
    int nCenter=nWindowSize/2;
    int nWidth=OpenCvGrayImage->width;
    int nHeight=OpenCvGrayImage->height;
    IplImage * pCanny;
    pCanny=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);
    //生成二维滤波核
    double *pKernel_2=new double[nWindowSize*nWindowSize];
    double d_sum=0.0;
    for(int i=0;i<nWindowSize;i++)
    {
        for (int j=0;j<nWindowSize;j++)
        {
            double n_Disx=i-nCenter;//水平方向距离中心像素距离
            double n_Disy=j-nCenter;
            pKernel_2[j*nWindowSize+i]=exp(-0.5*(n_Disx*n_Disx+n_Disy*n_Disy)/(nSigma*nSigma))/(2.0*3.1415926)*nSigma*nSigma; 
            d_sum=d_sum+pKernel_2[j*nWindowSize+i];
        }
    }
    for(int i=0;i<nWindowSize;i++)//归一化处理
    {
        for (int j=0;j<nWindowSize;j++)
        {
          pKernel_2[j*nWindowSize+i]=pKernel_2[j*nWindowSize+i]/d_sum;
        }
    }
    //输出模板
    for (int i=0;i<nWindowSize*nWindowSize;i++)
    {
        if (i%(nWindowSize)==0)
        {
          printf("\n");
        }
        printf("%.10f ",pKernel_2[i]);
    }
    //滤波处理
    for (int s=0;s<nWidth;s++)
    {
        for (int t=0;t<nHeight;t++)
        {
            double dFilter=0;
            double dSum=0;
            //当前坐标(s,t),获取8邻域
            for (int x=-nCenter;x<=nCenter;x++)
            {
                for (int y=-nCenter;y<=nCenter;y++)
                {
                    if ((x+s>=0)&&(x+s<nWidth)&&(y+t>=0)&&(y+t<nHeight))//判断是否越界
                    {
                        double currentvalue=(double)OpenCvGrayImage->imageData[(y+t)*OpenCvGrayImage->widthStep+x+s];
                        dFilter+=currentvalue*pKernel_2[x+nCenter+(y+nCenter)*nCenter];
                        dSum+=pKernel_2[x+nCenter+(y+nCenter)*nCenter];
                    }
                }
            }
            pCanny->imageData[t*pCanny->widthStep+s]=(uchar)(dFilter/dSum);
        }
    }
    cvNamedWindow("GaussImage",0);  
    cvShowImage("GaussImage",pCanny);               //显示高斯图
    cvWaitKey(0);  
    cvDestroyWindow("GaussImage"); 

2. 用一阶偏导有限差分计算梯度幅值和方向

Canny算法解析,opencv源码实现及实例[通俗易懂]

Canny算法解析,opencv源码实现及实例[通俗易懂]
其中S为图像灰度值,P代表X方向梯度幅值,Q代表Y方向 梯度幅值

//计算梯度值和方向 

 double *P = new double[nWidth*nHeight];
 double *Q = new double[nWidth*nHeight];
 int *M = new int[nWidth*nHeight];
    //IplImage * M;//梯度结果
    //M=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);
 double *Theta = new double[nWidth*nHeight];
 int nwidthstep = pCanny->widthStep;
	for (int iw = 0; iw<nWidth - 1; iw++)
	{
	    for (int jh = 0; jh<nHeight - 1; jh++)
		    {
		       P[jh*nWidth + iw] = (double)(pCanny->imageData[min(iw + 1, nWidth - 1) + jh*nwidthstep] - pCanny->imageData[iw + jh*nwidthstep] +
			   pCanny->imageData[min(iw + 1, nWidth - 1) + min(jh + 1, nHeight - 1)*nwidthstep] - pCanny->imageData[iw + min(jh + 1, nHeight - 1)*nwidthstep]) / 2;
		       Q[jh*nWidth + iw] = (double)(pCanny->imageData[iw + jh*nwidthstep] - pCanny->imageData[iw + min(jh + 1, nHeight - 1)*nwidthstep] +
			   pCanny->imageData[min(iw + 1, nWidth - 1) + jh*nwidthstep] - pCanny->imageData[min(iw + 1, nWidth - 1) + min(jh + 1, nHeight - 1)*nwidthstep]) / 2;
		    }
	}
 //计算幅值和方向
     for (int iw = 0; iw<nWidth - 1; iw++)
     {
	    for (int jh = 0; jh<nHeight - 1; jh++)
		    {
		        M[jh*nWidth + iw] = (int)sqrt(P[jh*nWidth + iw] * P[jh*nWidth + iw] + Q[jh*nWidth + iw] * Q[jh*nWidth + iw] + 0.5);
		        Theta[jh*nWidth + iw] = atan2(Q[jh*nWidth + iw], P[jh*nWidth + iw]) * 180 / 3.1415;
		        if (Theta[jh*nWidth + iw]<0)
			        {
			          Theta[jh*nWidth + iw] = 360 + Theta[jh*nWidth + iw];
			        }
		    }
	 }

3. 对梯度幅值进行非极大值抑制

图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不能说明该点就是边缘。在Canny算法中,非极大值抑制是边缘检测的重要步骤,即寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除一大部分非边缘点。

Canny算法解析,opencv源码实现及实例[通俗易懂]

如上图所示,非极大值抑制首先确定像素点C的灰度值在其8邻域内是否为最大。其中蓝线方向为C点的梯度方向,可以确定局部最大值分布在这条线上,除了C点外,梯度方向的交点dTmp1和dTmp也可能是局部最大值。判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大点。如果C点灰度值小于这两个点中的任何一个,就说明C点不是局部极大值,C点不是边缘。

当然在实际运算中只能得到C点8邻域值,而dTmp1和dTmp2是未知的,所以需要对这两个点两端的灰度进行插值计算,即根据g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值。

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

用白话叙述下Canny算子的原理:边缘是指图象局部区域亮度变化显著的部分,对于灰度图像来说,即是灰度值有一个明显的变化,既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值。怎么表征这种灰度值的变化呢–>导数,但是数字图像都是离散的,导数要用差分来代替,即是具体算法中的步骤2。但是在真实的图像中,一般会有噪声,噪声会影响梯度的计算,所以先要步骤1滤波。从理论上,图像梯度幅值越大,说明该点的梯度值越大,但这不能说明该点就是边缘。在Canny算法中,非极大值抑制(步骤3)是进行边缘检测的重要步骤, 通俗意义上是指寻找像素点局部最大值,沿着梯度方向,比较它前面和后面的梯度值进行了 。步骤4,是一个典型算法,有时候我们并不像一刀切,也就是超过阈值的都是边缘点,而是设为两个阈值,希望在高阈值和低阈值之间的点也可能是边缘点,而且这些点最好在高阈值的附近,也就是说这些中间阈值的点是高阈值边缘点的一种延伸。所以步骤4用了双阈值来检测和连接边缘。

—————–Canny算法opencv源码部分—————–

基本原理简单说完,具体代码按照下面6步骤叙述。http://www.open-open.com/lib/view/open1453460512558.html
第一步:灰度化
第二步:高斯滤波
第三步:计算梯度值和方向
第四步:非极大值抑制
第五步:双阈值的选取
第六步:边缘检测

PO一下opencv中实现canny算法的源码(带注释):

#include "precomp.hpp"  
  
/* 
#if defined (HAVE_IPP) && (IPP_VERSION_MAJOR >= 7) 
#define USE_IPP_CANNY 1 
#else 
#undef USE_IPP_CANNY 
#endif 
*/  
#ifdef USE_IPP_CANNY  
namespace cv  
{  
static bool ippCanny(const Mat& _src, Mat& _dst, float low,  float high)  
{  
    int size = 0, size1 = 0;  
    IppiSize roi = { _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);  
  
    AutoBuffer<uchar> buf(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<short>(), (int)_dx.step, roi,  
                    ippMskSize3x3, ippBorderRepl, 0, buffer) < 0 )  
        return false;  
  
    Mat _dy(_src.rows, _src.cols, CV_16S);  
    if( ippiFilterSobelHorizBorder_8u16s_C1R(_src.data, (int)_src.step,  
                    _dy.ptr<short>(), (int)_dy.step, roi,  
                    ippMskSize3x3, ippBorderRepl, 0, buffer) < 0 )  
        return false;  
  
    if( ippiCanny_16s8u_C1R(_dx.ptr<short>(), (int)_dx.step,  
                            _dy.ptr<short>(), (int)_dy.step,  
                            _dst.data, (int)_dst.step, roi, low, high, buffer) < 0 )  
        return false;  
    return true;  
}  
}  
#endif  
  
void cv::Canny( InputArray _src, OutputArray _dst,  
                double low_thresh, double high_thresh,  
                int aperture_size, bool L2gradient )  
{  
    Mat src = _src.getMat();           //输入图像,必须为单通道灰度图  
    CV_Assert( src.depth() == CV_8U ); // 8位无符号  
  
    _dst.create(src.size(), CV_8U);    //根据src的大小构造目标矩阵dst  
    Mat dst = _dst.getMat();           //输出图像,为单通道黑白图  
      
      
    // low_thresh 表示低阈值, high_thresh表示高阈值  
    // aperture_size 表示算子大小,默认为3  
    // L2gradient计算梯度幅值的标识,默认为false  
  
    // 如果L2gradient为false 并且 apeture_size的值为-1(-1的二进制标识为:1111 1111)  
    // 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  
        //backward compatibility  
          
    // ~标识按位取反  
        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); // 则交换低阈值和高阈值  
  
#ifdef HAVE_TEGRA_OPTIMIZATION  
    if (tegra::canny(src, dst, low_thresh, high_thresh, aperture_size, L2gradient))  
        return;  
#endif  
  
#ifdef USE_IPP_CANNY  
    if( aperture_size == 3 && !L2gradient &&  
        ippCanny(src, dst, (float)low_thresh, (float)high_thresh) )  
        return;  
#endif  
  
    const int cn = src.channels();           // cn为输入图像的通道数  
    Mat dx(src.rows, src.cols, CV_16SC(cn)); // 存储 x方向 方向导数的矩阵,CV_16SC(cn):16位有符号cn通道  
    Mat dy(src.rows, src.cols, CV_16SC(cn)); // 存储 y方向 方向导数的矩阵 ......  
  
    /*Sobel参数说明:(参考cvSobel) 
      cvSobel( 
            const  CvArr* src,                // 输入图像 
            CvArr*        dst,                // 输入图像 
            int           xorder,            // x方向求导的阶数 
            int           yorder,         // y方向求导的阶数 
            int           aperture_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; //高阈值平方运算  
    }  
  
    int low = cvFloor(low_thresh);   // cvFloor返回不大于参数的最大整数值, 相当于取整  
    int high = cvFloor(high_thresh);  
  
    // ptrdiff_t 是C/C++标准库中定义的一个数据类型,signed类型,通常用于存储两个指针的差(距离),可以是负数  
    // mapstep 用于存放  
    ptrdiff_t mapstep = src.cols + 2; // +2 表示左右各扩展一条边  
      
    // AutoBuffer<uchar> 会自动分配一定大小的内存,并且指定内存中的数据类型是uchar  
    // 列数 +2 表示图像左右各自扩展一条边 (用于复制边缘像素,扩大原始图像)  
    // 行数 +2 表示图像上下各自扩展一条边  
    AutoBuffer<uchar> buffer((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);  
  
    int maxsize = std::max(1 << 10, src.cols * src.rows / 10); // 2的10次幂 1024  
    std::vector<uchar*> stack(maxsize); // 定义指针类型向量,用于存地址  
    uchar **stack_top = &stack[0];      // 栈顶指针(指向指针的指针),指向stack[0], stack[0]也是一个指针  
    uchar **stack_bottom = &stack[0];   // 栈底指针 ,初始时 栈底指针 == 栈顶指针  
  
  
    // 梯度的方向被近似到四个角度之一 (0, 45, 90, 135 四选一)  
    /* sector numbers 
       (Top-Left Origin) 
 
        1   2   3 
         *  *  * 
          * * * 
        0*******0 
          * * * 
         *  *  * 
        3   2   1 
    */  
      
  
    // define 定义函数块  
    // CANNY_PUSH(d) 是入栈函数, 参数d表示地址指针,让该指针指向的内容为2(int型强制转换成uchar型),并入栈,栈顶指针+1  
    // 2表示 像素属于某条边缘 可以看下方的注释  
    // CANNY_POP(d) 是出栈函数, 栈顶指针-1,然后将-1后的栈顶指针指向的值,赋给d  
    #define CANNY_PUSH(d)    *(d) = uchar(2), *stack_top++ = (d)  
    #define CANNY_POP(d)     (d) = *--stack_top  
  
    // calculate magnitude and angle of gradient, perform non-maxima suppression.  
    // fill the map with one of the following values:  
    // 0 - the pixel might belong to an edge 可能属于边缘  
    // 1 - the pixel can not belong to an edge 不属于边缘  
    // 2 - the pixel does belong to an edge 一定属于边缘  
      
    // for内进行非极大值抑制 + 滞后阈值处理  
    for (int i = 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 < src.rows)  
        {  
            short* _dx = dx.ptr<short>(i); // _dx指向dx矩阵的第i行  
            short* _dy = dy.ptr<short>(i); // _dy指向dy矩阵的第i行  
  
            if (!L2gradient) // 如果 L2gradient为false  
            {  
                for (int j = 0; j < src.cols*cn; j++) // 对第i行里的每一个值都进行计算  
                    _norm[j] = std::abs(int(_dx[j])) + std::abs(int(_dy[j])); // 用||+||计算  
            }  
            else  
            {  
                for (int j = 0; j < src.cols*cn; j++)  
            //用平方计算,当 L2gradient为 true时,高低阈值都被平方了,所以此处_norm[j]无需开平方  
                    _norm[j] = int(_dx[j])*_dx[j] + int(_dy[j])*_dy[j]; //  
            }  
  
            if (cn > 1) // 如果不是单通道  
            {  
                for(int j = 0, jn = 0; j < src.cols; ++j, jn += cn)  
                {  
                    int maxIdx = jn;  
                    for(int k = 1; k < cn; ++k)  
                        if(_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));   
  
        // at the very beginning we do not have a complete ring  
        // buffer of 3 magnitude rows for non-maxima suppression  
        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; // take the central row 中间那一行  
        ptrdiff_t magstep1 = mag_buf[2] - mag_buf[1];  
        ptrdiff_t magstep2 = mag_buf[0] - mag_buf[1];  
  
        const short* _x = dx.ptr<short>(i-1);  
        const short* _y = dy.ptr<short>(i-1);  
  
      // 如果栈的大小不够,则重新为栈分配内存(相当于扩大容量)  
        if ((stack_top - stack_bottom) + src.cols > maxsize)  
        {  
            int sz = (int)(stack_top - stack_bottom);  
            maxsize = maxsize * 3/2;  
            stack.resize(maxsize);  
            stack_bottom = &stack[0];  
            stack_top = stack_bottom + sz;  
        }  
  
        int prev_flag = 0; //前一个像素点 0:非边缘点 ;1:边缘点  
        for (int j = 0; j < src.cols; j++) // 第 j 列  
        {  
            #define CANNY_SHIFT 15  
            // tan22.5  
            const int TG22 = (int)(0.4142135623730950488016887242097*(1<<CANNY_SHIFT) + 0.5);  
  
            int m = _mag[j];  
  
            if (m > low) // 如果大于低阈值  
            {  
                int xs = _x[j];    // dx中 第i-1行 第j列  
                int ys = _y[j];    // dy中 第i-1行 第j列  
                int x = std::abs(xs);  
                int y = std::abs(ys) << CANNY_SHIFT;  
  
                int tg22x = x * TG22;  
  
                if (y < tg22x) //角度小于22.5 用区间表示:[0, 22.5)  
                {  
              // 与左右两点的梯度幅值比较,如果比左右都大  
              //(此时当前点是左右邻域内的极大值),则 goto __ocv_canny_push 执行入栈操作  
                    if (m > _mag[j-1] && m >= _mag[j+1]) goto __ocv_canny_push;  
                }  
                else //角度大于22.5  
                {  
                    int tg67x = tg22x + (x << (CANNY_SHIFT+1));  
                    if (y > tg67x) //(67.5, 90)  
                    {  
                        //与上下两点的梯度幅值比较,如果比上下都大  
                        //(此时当前点是左右邻域内的极大值),则 goto __ocv_canny_push 执行入栈操作  
                        if (m > _mag[j+magstep2] && m >= _mag[j+magstep1]) goto __ocv_canny_push;  
                    }  
                    else //[22.5, 67.5]  
                    {  
                        // ^ 按位异或 如果xs与ys异号 则取-1 否则取1  
                        int s = (xs ^ ys) < 0 ? -1 : 1;  
                        //比较对角线邻域  
                        if (m > _mag[j+magstep2-s] && m > _mag[j+magstep1+s]) goto __ocv_canny_push;  
                    }  
                }  
            }  
              
          //比当前的梯度幅值低阈值还低,直接被确定为非边缘  
            prev_flag = 0;  
            _map[j] = uchar(1); // 1 表示不属于边缘  
              
            continue;  
__ocv_canny_push:  
        // 前一个点不是边缘点 并且 当前点的幅值大于高阈值(大于高阈值被视为边缘像素) 并且 正上方的点不是边缘点  
            if (!prev_flag && m > high && _map[j-mapstep] != 2)  
            {  
                //将当前点的地址入栈,入栈前,会将该点地址指向的值设置为2(查看上面的宏定义函数块里)  
                CANNY_PUSH(_map + j);   
                prev_flag = 1;  
            }  
            else  
                _map[j] = 0;  
        }  
  
        // scroll the ring buffer  
        // 交换指针指向的位置,向上覆盖,把mag_[1]的内容覆盖到mag_buf[0]上  
        // 把mag_[2]的内容覆盖到mag_buf[1]上  
    // 最后 让mag_buf[2]指向_mag指向的那一行  
        _mag = mag_buf[0];  
        mag_buf[0] = mag_buf[1];  
        mag_buf[1] = mag_buf[2];  
        mag_buf[2] = _mag;  
    }  
  
      
    // now track the edges (hysteresis thresholding)  
    // 通过上面的for循环,确定了各个邻域内的极大值点为边缘点(标记为2)  
    // 现在,在这些边缘点的8邻域内(上下左右+4个对角),将可能的边缘点(标记为0)确定为边缘  
    while (stack_top > stack_bottom)  
    {  
        uchar* m;  
        if ((stack_top - stack_bottom) + 8 > maxsize)  
        {  
            int sz = (int)(stack_top - stack_bottom);  
            maxsize = maxsize * 3/2;  
            stack.resize(maxsize);  
            stack_bottom = &stack[0];  
            stack_top = stack_bottom + sz;  
        }  
  
        CANNY_POP(m); // 出栈  
  
        if (!m[-1])         CANNY_PUSH(m - 1);  
        if (!m[1])          CANNY_PUSH(m + 1);  
        if (!m[-mapstep-1]) CANNY_PUSH(m - mapstep - 1);  
        if (!m[-mapstep])   CANNY_PUSH(m - mapstep);  
        if (!m[-mapstep+1]) CANNY_PUSH(m - mapstep + 1);  
        if (!m[mapstep-1])  CANNY_PUSH(m + mapstep - 1);  
        if (!m[mapstep])    CANNY_PUSH(m + mapstep);  
        if (!m[mapstep+1])  CANNY_PUSH(m + mapstep + 1);  
    }  
  
    // the final pass, form the final image  
    // 生成边缘图  
    const uchar* pmap = map + mapstep + 1;  
    uchar* pdst = dst.ptr();  
    for (int i = 0; i < src.rows; i++, pmap += mapstep, pdst += dst.step)  
    {  
        for (int j = 0; j < src.cols; j++)  
            pdst[j] = (uchar)-(pmap[j] >> 1);  
    }  
}  
  
void cvCanny( const CvArr* image, CvArr* edges, double threshold1,  
              double threshold2, int aperture_size )  
{  
    cv::Mat src = cv::cvarrToMat(image), dst = cv::cvarrToMat(edges);  
    CV_Assert( src.size == dst.size && src.depth() == CV_8U && dst.type() == CV_8U );  
  
    cv::Canny(src, dst, threshold1, threshold2, aperture_size & 255,  
              (aperture_size & CV_CANNY_L2_GRADIENT) != 0);  
}  
  

—————–Canny算法opencv实例—————–

#include <iostream>
#include <opencv2/opencv.hpp>  
#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

Mat srcImage, grayImage, dstImage;
int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;          //阈值倍数
int kernel_size = 3;    //Sobel算子孔径尺寸
const String windowName = "Canny算子边缘检测";

//声明回调函数
void CannyThreshold(int, void*);

int main()
{
	srcImage = imread("zjl.jpg");
	if (srcImage.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl;

	//高斯滤波,转灰度图
	GaussianBlur(srcImage, srcImage, Size(3, 3), 0, 0, BORDER_DEFAULT);
	cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);

	namedWindow("原始图像", WINDOW_AUTOSIZE);
	imshow("原始图像", grayImage);

	//定义轨迹条属性
	namedWindow(windowName, WINDOW_AUTOSIZE);
	char trackbarName[20];
	sprintf(trackbarName, "阈值", max_lowThreshold);
	lowThreshold = 20;

	//创建轨迹条
	createTrackbar(trackbarName, windowName, &lowThreshold, max_lowThreshold, CannyThreshold);
	CannyThreshold(lowThreshold, 0);

	waitKey(0);
	return 0;
}

void CannyThreshold(int, void*)
{
	Canny(grayImage, dstImage, lowThreshold, lowThreshold*ratio, kernel_size);
	imshow(windowName, dstImage);
	waitKey(0);
}

Canny算法解析,opencv源码实现及实例[通俗易懂]

参考:

http://blog.csdn.net/xiaowei_cqu/article/details/7839140
http://blog.csdn.net/xiaowei_cqu/article/details/7839140
http://www.open-open.com/lib/view/open1453460512558.html
http://blog.csdn.net/xiajun07061225/article/details/6926108
http://blog.csdn.net/u010429424/article/details/51866361
http://blog.csdn.NET/keith_bb/article/details/56008306?utm_source=tuicool&utm_medium=referral

https://www.cnblogs.com/mightycode/p/6394810.html

http://blog.csdn.net/dcrmg/article/details/52344902

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/139309.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Linux下Opengrok安装部署与使用

    Linux下Opengrok安装部署与使用OpenGrok简单安装配置说明

  • HttpClient与CloseableHttpClient

    前言起因是最近做的一个历史遗留项目,需要加些新需求,在本机进行压测时,发现在并发600的状态下跑一段时间后,就会开始偶现500的错误。可能是老项目用的人少(B2B的项目),实际部署后以前也没有人反馈过这个问题,大致跟踪了下日志,发现是系统在调用第三方服务出现异常,这种情况原因很多,需要仔细看异常堆栈打出来的Exception信息,将问题范围缩小并求证,这次抛出的是java.net.Socket…

  • [New Portal]Windows Azure Web Site (3) 创建Web Site和云端数据库[通俗易懂]

    [New Portal]Windows Azure Web Site (3) 创建Web Site和云端数据库[通俗易懂][New Portal]Windows Azure Web Site (3) 创建Web Site和云端数据库

  • 反掩码的作用是什么?通配符掩码的作用是什么?—Vecloud[通俗易懂]

    反掩码的作用是什么?通配符掩码的作用是什么?—Vecloud[通俗易懂]反掩码即路由器使用的通配符掩码与源或目标地址一起来分辨匹配的地址范围,跟子网掩码刚好相反。它像子网掩码告诉路由器IP地址的哪一位属于网络号一样,通配符掩码告诉路由器为了判断出匹配,它需要检查IP地址中的多少位。这个地址掩码对使我们可以只使用两个32位的号码来确定IP地址的范围。这是十分方便的,因为如果没有掩码的话,你不得不对每个匹配的IP客户地址加入一个单独的访问列表语句。这将造成很多额外的输入和路由器大量额外的处理过程。所以地址掩码对相当有用。在子网掩码中,将掩码的一位设成1表示IP地址对应的位.

  • docker dockerfile详解_docker exec 进入容器

    docker dockerfile详解_docker exec 进入容器前言Dockerfile是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。Dockerfile简介Dockerfile是用来构建Docker镜像的构建文件,是由一系列

  • dsp调音软件通用吗_交叉火力dsp调音数据下载

    dsp调音软件通用吗_交叉火力dsp调音数据下载18。为什么在CCS下编译工具工作不正常?在CCS下有部分客户会碰到编译工具工作不正常,常见错误为:1)autoexec.bat的路径“outofmemory”。修改autoexec.bat,清除无用的PATH路径。2)编译的输出文件(OUT文件)写保护,无法覆盖。删除或修改输出文件的属性。3)Windows有问题。重新安装windows。4)Windows下有程序对CCS有影响。建议用一…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号