您的位置:首页 > 运维架构

opencv边缘检测 canny源码剖析

2014-07-15 22:48 519 查看

前言

在做图像处理的过程中,经常需要提取边缘,这里主要针对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);
}
}



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: