【opencv源码剖析】边缘检测 canny

前言

在做图像处理的过程中,经常需要提取边缘,这里主要针对canny源码进行一些简单解释。


相关原理可以去其他地方看:

1)英文wiki

2)中文博客

实际上,canny源码内没有使用高斯滤波。

Canny源码

下面只贴出算法实现的关键代码,其他略掉。

void cv::Canny( InputArray _src, OutputArray _dst,
                double low_thresh, double high_thresh,
                int aperture_size, bool L2gradient )
{
	...

	//一. 计算横向、纵向sobel梯度
    Sobel(src, dx, CV_16S, 1, 0, aperture_size, 1, 0, cv::BORDER_REPLICATE);
    Sobel(src, dy, CV_16S, 0, 1, aperture_size, 1, 0, cv::BORDER_REPLICATE);

    ...
    #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 supression.
    // 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 (int i = 0; i <= src.rows; i++)
    {
        int* _norm = mag_buf[(i > 0) + 1] + 1;
        if (i < src.rows)
        {
            short* _dx = dx.ptr<short>(i);
            short* _dy = dy.ptr<short>(i);

			//计算sobel横、纵梯度的norm. 即边缘响应的强度
            if (!L2gradient)
            {
				//默认采用最简单的方式:norm = sobel横梯度绝对值 + sobel纵梯度绝对值
                for (int j = 0; j < src.cols*cn; j++)
                    _norm[j] = std::abs(int(_dx[j])) + std::abs(int(_dy[j]));
            }
            else
            {
                for (int j = 0; j < src.cols*cn; j++)
                    _norm[j] = int(_dx[j])*_dx[j] + int(_dy[j])*_dy[j];
            }
			
			...
        }
   
		...

        
        int prev_flag = 0;

		//非极大值抑制(这名字起得霸气!其实就是判断该坐标是否为最大值而已,当然要记得考虑方向)
        for (int j = 0; j < src.cols; j++)
        {
            #define CANNY_SHIFT 15
			//tan22.5,在后面的非极大值抑制中判断梯度方向
            const int TG22 = (int)(0.4142135623730950488016887242097*(1<<CANNY_SHIFT) + 0.5);

            int m = _mag[j];

			//如果梯度响应(即上面计算出来的norm)大于低阈值,则判断该坐标位置是否为最大梯度响应
			//否则,直接标记为1,不是边缘. 
            if (m > low)
            {
                int xs = _x[j];
                int ys = _y[j];
                int x = std::abs(xs);
                int y = std::abs(ys) << CANNY_SHIFT;

                int tg22x = x * TG22;
				
				//四个方向极大值判断,如果是极大值则进入__ocv_canny_push,进一步判断该点是边缘(标记为2)或者可能是边缘(标记为0)
				
                if (y < tg22x)
                {
					//左右邻域
                    if (m > _mag[j-1] && m >= _mag[j+1]) goto __ocv_canny_push;
                }
                else
                {
                    int tg67x = tg22x + (x << (CANNY_SHIFT+1));
                    if (y > tg67x)
                    {
						//上下邻域
                        if (m > _mag[j+magstep2] && m >= _mag[j+magstep1]) goto __ocv_canny_push;
                    }
                    else
                    {
						//45° -45°邻域
                        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);
            continue;

			//梯度响应大于大阈值,且左侧和上侧都不是已确定的边缘(标记为2)时,才确定为边缘
			//否则标记为可能边缘-0 
__ocv_canny_push:
            if (!prev_flag && m > high && _map[j-mapstep] != 2)
            {
                CANNY_PUSH(_map + j);
                prev_flag = 1;
            }
            else
                _map[j] = 0;
        }

		...
        
    }

	//三.边缘跟踪
	//可简单理解为:在已确定为边缘点(标记为2)的8个邻域上,将可能是边缘的点(标记为0),判断为边缘(0->2).
    // now track the edges (hysteresis thresholding)
    while (stack_top > stack_bottom)
    {
        
		...

		//取出一个边缘点
        CANNY_POP(m);

		//如果该边缘点8个邻域上的点是可能边缘(标记为0),则判断其为边缘,重新标记为2.
        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);
    }

	//四.根据标记值0,1,2生成边缘。
    // 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)
    {
		//标记0: (uchar)-(0>>1)等于0
		//标记1:(uchar)-(1>>1)也等于0
		//标记2:(uchar)-(2>>1)=uchar(-1)=255
		//边缘像素值全部是255,非边缘全部是0
        for (int j = 0; j < src.cols; j++)
            pdst[j] = (uchar)-(pmap[j] >> 1);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值