Canny边缘检测算法的实现

电脑杂谈  发布时间:2020-04-25 16:23:46  来源:网络整理

边缘性行为算睡过吗_canny算子边缘检测的简单理解_canny边缘检测算法

图像边缘信息主要集中在高频段. 一般来说,图像锐化或边缘检测本质上是高频滤波. 我们知道,差分运算是为了找到信号的变化率,并且具有增强高频分量的作用. 在空间域操作中,锐化图像是为了计算微分. 由于数字图像的离散信号,微分运算成为差分或梯度的计算. 在图像处理中有多种边缘检测(梯度)算子. 基于找到梯度强度,常用的包括普通的一阶差分,Robert运算符(交叉差分),Sobel运算符等. 拉普拉斯算子(二阶差)基于零交叉检测. 通过计算梯度并设置阈值,可以获得边缘图像.

Canny边缘检测算子是一种多级检测算法. 它是由John F. Canny在1986年提出的,同时提出了三种边缘检测标准:

具有低错误率的边缘检测: 检测算法应准确地找到图像中尽可能多的边缘,并最大程度地减少漏检和错误检测. 最佳定位: 检测到的边缘点应准确定位在边缘的中心. 图像中的任何边缘都应仅标记一次,并且图像噪声不应产生假边缘.

Canny算法出现后,它已被用作标准的边缘检测算法. 从那时起,出现了各种基于Canny算法的改进算法. 时至今日,Canny算法及其变体仍然是一种出色的边缘检测算法. 而且,除非满足先决条件,否则您很难找到比Canny运算符做得更好的边缘检测运算符.

关于各种差值运算符,以及Canny运算符的简要介绍,在这里不罗word,可以在网上找到. 直接进入Canny算法的实现. Canny算法分为几个步骤.

1. 高斯模糊.

此步骤非常简单,类似于LoG运算符(高斯的拉普拉斯算子)的高斯模糊,主要功能是消除噪声. 由于噪声也集中在高频信号上,因此很容易将其识别为虚假边缘. 高斯模糊用于去除噪声并减少错误边缘识别. 但是,由于图像边缘信息也是高频信号,因此选择高斯模糊的半径非常重要,并且过大的半径很容易使某些弱边缘无法检测到.

Lena原始图像Lena高斯模糊,半径2

2. 计算梯度幅度和方向.

图像的边缘可以指向不同的方向,因此经典的Canny算法使用四个梯度算子分别计算水平,垂直和对角线方向上的梯度. 但是通常不使用四个梯度运算符来分别计算四个方向. 常用的边缘差异算子(例如Rober,Prewitt,Sobel)计算水平和垂直方向的差异Gx和Gy. 这样,梯度模量和方向可以计算如下:

边缘性行为算睡过吗_canny算子边缘检测的简单理解_canny边缘检测算法

倾斜角θ从弧度-π到π,然后近似于四个方向,分别代表水平,垂直和两个对角线方向(0°,45°,90°,135°). 可以将其除以±iπ/ 8(i = 1,3,5,7),并且将每个区域中的倾斜角度指定为特定值canny算子边缘检测的简单理解,代表四个方向之一.

在这里,我选择Sobel运算符来计算梯度. Sobel算法非常简单,随处可见,因此未列出代码. 与其他边缘算子相比,Sobel算子获得的边缘既厚又亮.

下图是Sobel算子在不应用任何阈值的情况下应用于半径2以上的高斯模糊图像L通道(HSL)的梯度模量图.

Sobel运算符,没有阈值

3. 非最大抑制.

非最大抑制是一种边缘稀疏方法. 生成的渐变边缘通常大于一个像素宽,但多个像素宽. 正如我们所说,从上面的Lena图的Sobel结果可以看出,Sobel算符获得的边缘既厚又明亮. 因此,这样的梯度图仍然非常“模糊”. 准则3要求边仅具有精确的点宽. 非最大抑制可以帮助保留局部最大梯度,同时抑制所有其他梯度值. 这意味着仅保留梯度变化中最尖锐的位置. 算法如下:

比较当前点的梯度强度和正负梯度方向点的梯度强度. 如果当前点的梯度强度与相同方向上其他点的梯度强度相比最大,则保留其值. 否则,将其设置为0. 例如,当前点的方向指向正上方90°的方向,则需要将其与垂直方向上,正下方的像素进行比较.

请注意,方向符号无效. 例如,东南方向和西北方向相同,并且被认为是对角线方向. 之前,我们将梯度方向近似为四个方向: 水平,垂直和两个对角线,因此,每个像素都根据其自身的方向在这四个方向之一中进行比较,并决定是否保留它. 下面列出的这部分代码也非常简单. pModule和pDirection分别记录上一步的梯度模量和梯度方向.

canny算子边缘检测的简单理解_边缘性行为算睡过吗_canny边缘检测算法

pmoddrow = pModule + Width + 1; 
pdirdrow = pDirection + Width + 1;
pstrongdrow = pStrong + Width + 1;
for (i = 1; i < Hend - 1; i++)
{
  pstrongd = pstrongdrow;
  pmodd = pmoddrow;
  pdird = pdirdrow;
  for (j = 1; j < Wend - 1; j++)
    {
            switch (*pdird)
            {
            case 0:        // x direction
            case 4:
                if (*pmodd > *(pmodd - 1) && *pmodd > *(pmodd + 1))
                    *pstrongd = 255;
                break;
            case 1:        // northeast-southwest direction. Notice the data order on y direction of bmp data
            case 5:
                if (*pmodd > *(pmodd + Width + 1) && *pmodd > *(pmodd - Width - 1))
                    *pstrongd = 255;
                break;
            case 2:        // y direction
            case 6:
                if (*pmodd > *(pmodd - Width) && *pmodd > *(pmodd + Width))
                    *pstrongd = 255;
                break;
            case 3:        // northwest-southeast direction. Notice the data order on y direction of bmp data
            case 7:
                if (*pmodd > *(pmodd + Width - 1) && *pmodd > *(pmodd - Width + 1))
                    *pstrongd = 255;
                break;
            default:
                ASSERT(0);
                break;
            }
            pstrongd++;
            pmodd++;
            pdird++;
    }
    pstrongdrow += Width;
    pmoddrow += Width;
    pdirdrow += Width;
}

查看代码

下图是非最大抑制的结果. 可以看出,边缘宽度已大大减小. 但是,由于未在此图像中应用阈值canny算子边缘检测的简单理解,因此它还包含大量具有较小梯度模数的点,该点在图中非常暗. 在下面,阈值即将到来.

非最大抑制结果

4. 双阈值.

一般的边缘检测算法使用阈值过滤掉由噪声或颜色变化引起的较小的梯度值,同时保留较大的梯度值. Canny算法使用双阈值,即高阈值和低阈值来区分边缘像素. 如果边缘像素的梯度值大于高阈值,则将其视为强边缘点. 如果边缘梯度值小于高阈值且大于低阈值,则将其标记为弱边缘点. 低于低阈值的点被抑制. 这一步很简单.

5. 滞后边界跟踪.

这时,强边缘点可以视为真实边缘. 弱边缘点可能是真实边缘,或者可能是由于噪点或颜色变化引起的. 为了获得准确的结果,应消除由后者引起的弱边缘点. 通常认为,由真实边缘引起的弱边缘点与强边缘点相连,而由噪声引起的弱边缘点则没有. 所谓的滞后边界跟踪算法将检查8个相连的弱边缘点域像素. 只要有一个强边缘点,那么弱边缘点就被认为是真正的边缘保留.

此算法搜索所有连接的弱边. 如果已连接的弱边缘的任何点连接到强边缘点,则将保留弱边缘,否则将抑制弱边缘. 搜索时,可以使用广度优先或深度优先的算法. 我在这里实现了最简单的深度优先算法. 一次连接一个边缘的深度优先算法如下:

准备堆栈s和队列q,并将连接的指示符变量连接设置为false. 从图像的第一点转到2. 如果此点是弱边界点且未标记,请对其进行标记并将其作为堆栈s上的第一个元素,然后将其放入记录连接的队列q中曲线,转到3. 如果此点不是弱边界或已被标记,请转到图像中的下一个点并重复2. 从堆栈s中获取一个元素,然后找到其8像素字段. 如果场像素是弱边界并且尚未标记,则将该场的像素标记并添加到堆栈s中,还添加到队列q中. 同时,找到与该域相对应的强边界图. 如果像素是强边界,则意味着弱边界曲线与强边界相连,并设置为true. 重复3,直到堆栈中没有更多元素. 如果connected为false,则依次从队列q中取出每个元素,清除标签. 如果连接正确,则保留标记. 清除队列q,将其设置为false,移至图像的下一个点,然后转到2.

边缘性行为算睡过吗_canny边缘检测算法_canny算子边缘检测的简单理解

// 5. Edge tracking by hysteresis
    stack<CPoint> s;
    queue<CPoint> q;
    BOOL connected = FALSE;
    long row_idx = Width;
    for (i = 1; i < Height - 1; i++, row_idx += Width)
    {
        for (j = 1; j < Width - 1; j++)
        {
            pweakd = pWeak + row_idx + j;
            if (*pweakd == 255)
            {
                s.push(CPoint(j, i));
                q.push(CPoint(j, i));
                *pweakd = 1;        // Label it
                while (!s.empty())
                {
                    CPoint p = s.top();
                    s.pop();
                    // Search weak edge 8-point neiorhood
                    pweakd = pWeak + p.y*Width + p.x;
                    if (*(pweakd - Width - 1) == 255)
                    {
                        CPoint np = CPoint(p.x - 1, p.y - 1);
                        s.push(np);
                        q.push(np);
                        *(pweakd - Width - 1) = 1;        // Label it
                    }
                    if (*(pweakd - Width) == 255)
                    {
                        CPoint np = CPoint(p.x, p.y - 1);
                        s.push(np);
                        q.push(np);
                        *(pweakd - Width) = 1;        // Label it
                    }
                    if (*(pweakd - Width + 1) == 255)
                    {
                        CPoint np = CPoint(p.x + 1, p.y - 1);
                        s.push(np);
                        q.push(np);
                        *(pweakd - Width + 1) = 1;        // Label it
                    }
                    if (*(pweakd - 1) == 255)
                    {
                        CPoint np = CPoint(p.x - 1, p.y);
                        s.push(np);
                        q.push(np);
                        *(pweakd - 1) = 1;        // Label it
                    }
                    if (*(pweakd + 1) == 255)
                    {
                        CPoint np = CPoint(p.x + 1, p.y);
                        s.push(np);
                        q.push(np);
                        *(pweakd + 1) = 1;        // Label it
                    }
                    if (*(pweakd + Width - 1) == 255)
                    {
                        CPoint np = CPoint(p.x - 1, p.y + 1);
                        s.push(np);
                        q.push(np);
                        *(pweakd + Width - 1) = 1;        // Label it
                    }
                    if (*(pweakd + Width) == 255)
                    {
                        CPoint np = CPoint(p.x, p.y + 1);
                        s.push(np);
                        q.push(np);
                        *(pweakd + Width) = 1;        // Label it
                    }
                    if (*(pweakd + Width + 1) == 255)
                    {
                        CPoint np = CPoint(p.x + 1, p.y + 1);
                        s.push(np);
                        q.push(np);
                        *(pweakd + Width + 1) = 1;        // Label it
                    }
                    // Search strong edge 8-point neiorhood
                    if (connected == FALSE)
                    {
                        pstrongd = pStrong + p.y*Width + p.x;
                        for (int m = -1; m <= 1; m++)
                        {
                            for (int n = -1; n <= 1; n++)
                            {
                                if (*(pstrongd + m*Width + n) == 255)
                                {
                                    connected = TRUE;
                                    goto next;
                                }
                            }
                        }
                    }
                next:
                    continue;
                }
                // No more element in the stack
                if (connected == FALSE)
                {
                    // The weak edge is not connected to any strong edge. Suppress it.
                    while (!q.empty())
                    {
                        CPoint p = q.front();
                        q.pop();
                        pWeak[p.y*Width + p.x] = 0;
                    }
                }
                else
                {
                    // Clean the queue
                    while (!q.empty()) q.pop();
                    connected = FALSE;
                }
            }
        }
    }
    // Add the connected weak edges (labeled) into strong edge image.
    // All strong edge pixels are labeled 255, otherwise 0.
    for (i = 0; i < len; i++)
    {
        if (pWeak[i] == 1) pStrong[i] = 255;
    }

查看代码

以下是用于Lena图的Canny边缘检测的梯度模图和二值化图. 高斯半径为2,高阈值为100,低阈值为50.

Canny检测梯度模量图Canny检测梯度二元图

作为比较,以下是使用一阶差分和Sobel运算符(阈值为100)计算原始图像的结果. 由于一阶差分的梯度值相对较小,因此我放大了一阶差分的梯度值具有一定的系数,以便保持与Sobel的梯度值相同的水平.

一阶微分梯度模量图一阶微分梯度二元图

canny边缘检测算法_边缘性行为算睡过吗_canny算子边缘检测的简单理解

Sobel梯度模量图Sobel梯度二元图

显然,Canny边缘检测的效果非常显着. 与普通的梯度算法相比,极大地抑制了噪声引起的虚假边缘,并使边缘变薄,易于跟踪. 对于对比度较低的图像,通过调整参数,Canny算法也可以达到很好的效果.

原图

Canny边缘梯度模,高斯半径2,低阀值30,高阀值100

Canny边缘梯度二值化图,高斯半径2,低阀值30,高阀值100

原始图像Canny梯度模块,高斯半径2,低阈值30,高阈值100 Canny梯度二进制图,高斯半径2,低阈值30,高阈值100

原图

Canny边缘梯度模,高斯半径1,低阀值40,高阀值80

Canny边缘梯度二值化图,高斯半径1,低阀值40,高阀值80

原始图像Canny梯度模块,高斯半径1,低阈值40,高阈值80 Canny梯度二值化图,高斯半径1,低阈值40,高阈值80

参考

Canny边缘检测器

canny算法


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-188535-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...