OpenCV 笔记(12):常用的边缘检测算子—— Canny

这篇具有很好参考价值的文章主要介绍了OpenCV 笔记(12):常用的边缘检测算子—— Canny。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Part11. Canny 算子产生的背景

一阶导数、二阶导数的边缘算子虽然简单易用,但存在一些缺点。例如容易受噪声影响,容易产生虚假边缘。

John F. Canny 在 1986 年提出了 Canny 边缘检测算法。它是结合了梯度计算方法和非极大值抑制技术的一种边缘检测算法。该算法克服了之前的边缘检测算法在抑制噪声和保持边缘信息方面的缺陷,具有较好的性能。

Canny 边缘检测算法的优点:

  • 能够有效地抑制噪声,同时保持边缘信息。

  • 能够检测到细小的边缘。

  • 具有较高的鲁棒性,能够处理各种噪声类型的图像

Canny 边缘检测算法是最经典的边缘检测算法之一,它在图像分割、目标检测、图像识别等领域有着广泛的应用。

Part22.  Canny 算子

12.1 Canny 边缘检测的步骤

2.1.1 使用高斯滤波器平滑图像去除噪声

高斯滤波器是一种常用的平滑滤波器,它可以用来去除图像中的噪声。

使用高斯滤波器与原图进行卷积,该步骤将平滑图像,以便在进行梯度计算时可以更好地抑制噪声。

2.1.2 计算图像的梯度幅度和方向角

在该系列的第八篇文章中,我们曾介绍过图像的梯度,它是图像灰度变化的速率,可以用来表示图像的边缘信息。

梯度的大小表示图像灰度变化的大小,梯度的方向表示图像灰度变化的方向。梯度幅度表示梯度的大小,用来表示图像灰度变化的剧烈程度。

Canny 边缘检测算法通常使用 Sobel 算子来计算图像的梯度幅度和方向角。

其中,用 L1 范数来近似梯度幅度:

梯度的方向角:

// 计算梯度、梯度幅度和方向
Mat gradXY, theta;
theta = Mat::zeros(src.size(), CV_8U);
Mat grad_x, grad_y;
Sobel(gauss, grad_x, CV_32F, 1, 0, 3);
Sobel(gauss, grad_y, CV_32F, 0, 1, 3);

Mat gradX, gradY;
convertScaleAbs(grad_x, gradX);
convertScaleAbs(grad_y, gradY);
gradXY = gradX + gradY;

2.1.3 使用非极大值抑制消除边缘检测的虚假响应

非极大值抑制(Non-Maximum Suppression,NMS) 其思想是搜素局部最大值,抑制非极大值。

在 Canny 边缘检测算法中,非极大值抑制是指在一个邻域内,对于一个像素点如果其梯度幅度小于其邻域内同方向梯度幅度的最大值,则该像素点不是边缘点。在 8 邻域内,非极大值抑制就只是在 0 度、90 度、45 度、135 度四个梯度方向上进行的,每个像素点梯度方向按照相近程度用这四个方向来代替

OpenCV 笔记(12):常用的边缘检测算子—— Canny,opencv,笔记,人工智能,计算机视觉
非极大值抑制.png

这样简化了计算,因为现实中图像中的边缘梯度方向不一定是沿着这四个方向的,就需要进行线性插值,会略显繁琐。

// 非最大值抑制
void nonMaximumSuppression (Mat srcGx, Mat srcGy, Mat &gradXY, Mat &theta, Mat &dst) {
    dst = gradXY.clone();
    for (int j = 1; j < gradXY.rows-1; j++) {
        for (int i = 1; i < gradXY.cols-1; i++) {
            double gradX = srcGx.ptr<uchar>(j)[i];
            double gradY = srcGy.ptr<uchar>(j)[i];

            theta.ptr<uchar>(j)[i] = atan(gradY/gradX); //计算梯度方向

            double t = double(theta.ptr<uchar>(j)[i]);
            double g = double(dst.ptr<uchar>(j)[i]);
            if (g == 0.0) {
                continue;
            }
            double g0, g1;
            if ((t >= -(3*M_PI/8)) && (t < -(M_PI/8))) {
                g0 = double(dst.ptr<uchar>(j-1)[i-1]);
                g1 = double(dst.ptr<uchar>(j+1)[i+1]);
            }
            else if ((t >= -(M_PI/8)) && (t < M_PI/8)) {
                g0 = double(dst.ptr<uchar>(j)[i-1]);
                g1 = double(dst.ptr<uchar>(j)[i+1]);
            }
            else if ((t >= M_PI/8) && (t < 3*M_PI/8)) {
                g0 = double(dst.ptr<uchar>(j-1)[i+1]);
                g1 = double(dst.ptr<uchar>(j+1)[i-1]);
            }
            else {
                g0 = double(dst.ptr<uchar>(j-1)[i]);
                g1 = double(dst.ptr<uchar>(j+1)[i]);
            }

            if (g <= g0 || g <= g1) {
                dst.ptr<uchar>(j)[i] = 0.0;
            }
        }
    }
}

2.1.4 使用双阈值处理确定潜在边缘

使用两个阈值来确定边缘。

  • 强边缘点是梯度值大于高阈值的像素点,将像素值置为255。

  • 弱边缘点是梯度值大于低阈值但小于高阈值的像素点,保留像素值不变。

  • 如果梯度值小于低阈值,则会被抑制,将像素值置为0。

// 双阈值算法
void doubleThreshold (Mat &src, double low, double high, Mat &dst) {
    dst = src.clone();

    // 区分出强边缘点和弱边缘点
    for (int j = 0; j < src.rows - 1; j++) {
        for (int i = 0; i < src.cols - 1; i++) {
            double x = double(dst.ptr<uchar>(j)[i]);
            // 像素点为强边缘点,置255
            if (x > high) {
                dst.ptr<uchar>(j)[i] = 255;
            }
            // 像素点置0,被抑制掉
            else if (x < low) {
                dst.ptr<uchar>(j)[i] = 0;
            }
        }
    }
}

2.1.5 滞后连接

强边缘点可以认为是真的边缘,弱边缘点则可能是真的边缘也可能是噪声或颜色变化引起的。

通常会认为,真实边缘引起的弱边缘点和强边缘点是连通的,而又噪声引起的弱边缘点则不会。

Canny 算子中的滞后连接,是指将弱边缘点连接到强边缘点,从而减少边缘断裂。也就是在一个弱边缘点的 8 邻域像素内,只要有强边缘点存在,那么这个弱边缘点的像素点置为 255 被保留下来;如果它的 8 邻域内不存在强边缘点,则这是一个孤立的弱边缘点,像素点置为 0。

// 通过滞后连接断开的边缘
void hysteresis (Mat &src) {
    // 循环找到强边缘点,把其领域内的弱边缘点变为强边缘点
    for (int j = 1; j < src.rows - 2; j++) {
        for (int i = 1; i < src.cols - 2; i++) {
            // 如果该点是强边缘点
            if (src.ptr<uchar>(j)[i] == 255) {
                // 遍历该强边缘点领域
                for (int m = -1; m < 1; m++) {
                    for (int n = -1; n < 1; n++) {
                        // 该点为弱边缘点(不是强边缘点,也不是被抑制的0点)
                        if (src.ptr<uchar>(j + m)[i + n] != 0 && src.ptr<uchar>(j + m)[i + n] != 255) {
                            src.ptr<uchar>(j + m)[i + n] = 255; //该弱边缘点补充为强边缘点
                        }
                    }
                }
            }
        }
    }

    for (int j = 0; j < src.rows - 1; j++) {
        for (int i = 0; i < src.cols - 1; i++) {
            // 如果该点依旧是弱边缘点,及此点是孤立边缘点
            if (src.ptr<uchar>(j)[i] != 255 && src.ptr<uchar>(j)[i] != 255) {
                src.ptr<uchar>(j)[i] = 0; //该孤立弱边缘点抑制
            }
        }
    }
}

22.2 Canny 算子的实现

下面,将上述五个步骤结合起来,一步步实现 Canny 边缘检测算法。

#include <opencv2/opencv.hpp>
#include <math.h>

using namespace std;
using namespace cv;

// 非最大值抑制
void nonMaximumSuppression (Mat srcGx, Mat srcGy, Mat &gradXY, Mat &theta, Mat &dst) {
    dst = gradXY.clone();
    for (int j = 1; j < gradXY.rows-1; j++) {
        for (int i = 1; i < gradXY.cols-1; i++) {
            double gradX = srcGx.ptr<uchar>(j)[i];
            double gradY = srcGy.ptr<uchar>(j)[i];

            theta.ptr<uchar>(j)[i] = atan(gradY/gradX); //计算梯度方向

            double t = double(theta.ptr<uchar>(j)[i]);
            double g = double(dst.ptr<uchar>(j)[i]);
            if (g == 0.0) {
                continue;
            }
            double g0, g1;
            if ((t >= -(3*M_PI/8)) && (t < -(M_PI/8))) {
                g0 = double(dst.ptr<uchar>(j-1)[i-1]);
                g1 = double(dst.ptr<uchar>(j+1)[i+1]);
            }
            else if ((t >= -(M_PI/8)) && (t < M_PI/8)) {
                g0 = double(dst.ptr<uchar>(j)[i-1]);
                g1 = double(dst.ptr<uchar>(j)[i+1]);
            }
            else if ((t >= M_PI/8) && (t < 3*M_PI/8)) {
                g0 = double(dst.ptr<uchar>(j-1)[i+1]);
                g1 = double(dst.ptr<uchar>(j+1)[i-1]);
            }
            else {
                g0 = double(dst.ptr<uchar>(j-1)[i]);
                g1 = double(dst.ptr<uchar>(j+1)[i]);
            }

            if (g <= g0 || g <= g1) {
                dst.ptr<uchar>(j)[i] = 0.0;
            }
        }
    }
}

// 用双阈值算法检测
void doubleThreshold (Mat &src, double low, double high, Mat &dst) {
    dst = src.clone();

    // 区分出强边缘点和弱边缘点
    for (int j = 0; j < src.rows - 1; j++) {
        for (int i = 0; i < src.cols - 1; i++) {
            double x = double(dst.ptr<uchar>(j)[i]);
            // 像素点为强边缘点,置255
            if (x > high) {
                dst.ptr<uchar>(j)[i] = 255;
            }
            // 像素点置0,被抑制掉
            else if (x < low) {
                dst.ptr<uchar>(j)[i] = 0;
            }
        }
    }
}

// 通过滞后连接断开的边缘
void hysteresis (Mat &src) {
    // 循环找到强边缘点,把其领域内的弱边缘点变为强边缘点
    for (int j = 1; j < src.rows - 2; j++) {
        for (int i = 1; i < src.cols - 2; i++) {
            // 如果该点是强边缘点
            if (src.ptr<uchar>(j)[i] == 255) {
                // 遍历该强边缘点领域
                for (int m = -1; m < 1; m++) {
                    for (int n = -1; n < 1; n++) {
                        // 该点为弱边缘点(不是强边缘点,也不是被抑制的0点)
                        if (src.ptr<uchar>(j + m)[i + n] != 0 && src.ptr<uchar>(j + m)[i + n] != 255) {
                            src.ptr<uchar>(j + m)[i + n] = 255; //该弱边缘点补充为强边缘点
                        }
                    }
                }
            }
        }
    }

    for (int j = 0; j < src.rows - 1; j++) {
        for (int i = 0; i < src.cols - 1; i++) {
            // 如果该点依旧是弱边缘点,及此点是孤立边缘点
            if (src.ptr<uchar>(j)[i] != 255 && src.ptr<uchar>(j)[i] != 255) {
                src.ptr<uchar>(j)[i] = 0; //该孤立弱边缘点抑制
            }
        }
    }
}

int main () {
    Mat src = imread(".../street.jpg");
    imshow("src",src);

    Mat gray;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 灰度化
    Mat gauss;

    GaussianBlur(gray, gauss, Size(5, 5),0);

    // 计算梯度、梯度幅度和方向
    Mat gradXY, theta;
    theta = Mat::zeros(src.size(), CV_8U);
    Mat grad_x, grad_y;
    Sobel(gauss, grad_x, CV_32F, 1, 0, 3);
    Sobel(gauss, grad_y, CV_32F, 0, 1, 3);

    Mat gradX, gradY;
    convertScaleAbs(grad_x, gradX);
    convertScaleAbs(grad_y, gradY);
    gradXY = gradX + gradY;

    // 局部非极大值抑制
    Mat nms;
    nonMaximumSuppression(grad_x,grad_y,gradXY, theta, nms);
    imshow("nms",nms);

    // 用双阈值算法检测
    Mat dst;
    doubleThreshold(nms, 50, 100,  dst);
    imshow("thresh",dst);
    // 滞后连接
    hysteresis(dst);
    imshow("edge",dst);

    // OpenCV 的 Canny 函数
    Mat result;
    Canny(gauss, result, 50, 100);
    imshow("canny", result);

    waitKey(0);

    return 0;
}
OpenCV 笔记(12):常用的边缘检测算子—— Canny,opencv,笔记,人工智能,计算机视觉
一步步实现canny.png
OpenCV 笔记(12):常用的边缘检测算子—— Canny,opencv,笔记,人工智能,计算机视觉
自定义的canny和opencv的canny进行对比.png

在上述代码中 Canny() 函数是 OpenCV 自带的函数,放在这里主要是做一个对比。

Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false);

其各个参数的含义:

第一个参数 image:输入的源图像。

第二个参数 edges:输出的边缘图像。

第三个参数 threshold1:低阈值。

第四个参数 threshold2:高阈值。

第五个参数 apertureSize:Sobel 算子的核大小。

第六个参数 L2gradient:是否使用 L2 范数来计算梯度。默认值为 false,使用 L1 范数来计算梯度。

Part33.  边缘检测的三大准则

John F. 在提出 Canny 算子的同时,提出了边缘检测的三大准则:

  • 低错误率的边缘检测:检测算法应该精确地找到图像中的尽可能多的边缘,尽可能的减少漏检和误检。

  • 最优定位:检测的边缘点应该精确地定位于边缘的中心。

  • 图像中的任意边缘应该只被标记一次,同时图像噪声不应产生伪边缘。

Canny 边缘检测算法正是遵循了这些准则。

Part44. 各个算子的关系

将之前介绍的几种算子,整理一下他们的关系:

  • Roberts 算子和 Prewitt 算子是基础算子,Sobel 算子是它们的扩展。

  • Laplace 算子是二阶导数算子,可以计算图像的梯度二阶导数。

  • LoG 算子和 DoG 算子是带高斯核的微分算子,可以抑制噪声。

  • Canny 边缘检测算法是综合使用了高斯滤波、 Sobel 算子、非极大值抑制和双阈值检测的边缘检测算法。

Part55.  总结

本文介绍了 Canny 算子的特点以及如何一步步实现一个 Canny 的函数。并且,在最后总结了之前介绍的几种算子的关系。

Java与Android技术栈】公众号

关注 Java/Kotlin 服务端、桌面端 、Android 、机器学习、端侧智能

更多精彩内容请关注:文章来源地址https://www.toymoban.com/news/detail-770245.html

到了这里,关于OpenCV 笔记(12):常用的边缘检测算子—— Canny的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Canny算子边缘检测原理讲解及其完整C语言实现(不使用opencv)

    作者:队友调车我吹空调 日期:2023/05/17 版权:遵循CC 4.0 BY-SA版权协议 这里是后期的笔者,本文算是笔者的学习笔记,主要是在单片机中使用的,再者就是由于某些原因,笔者不想使用opencv,因此尝试跟着原理手搓了这份代码,笔者也尽力将代码写到最简和效率最优了。然而

    2024年04月27日
    浏览(31)
  • python --opencv图像处理Canny算子边缘检测(Roberts算子、Prewitt算子、Sobel算子、Laplacian算子、Scharr 算子、 LOG 算子)

    边缘检测是基于灰度突变来分割图像的常用方法,其实质是提取图像中不连续部分的特征。目前常见边缘检测算子有差分算子、 Roberts 算子、 Sobel 算子、 Prewitt 算子、 Log 算子以及 Canny 算子等。 其中, Canny 算子是由计算机科学家 John F. Canny 于 1986 年提出的一种边缘检测算子

    2024年04月12日
    浏览(36)
  • openCV实战-系列教程5:边缘检测(Canny边缘检测/高斯滤波器/Sobel算子/非极大值抑制/线性插值法/梯度方向/双阈值检测 )、原理解析、源码解读 ?????OpenCV实战系列总目录

    打印一个图片可以做出一个函数: Canny是一个科学家在1986年写了一篇论文,所以用自己的名字来命名这个检测算法,Canny边缘检测算法这里写了5步流程,会用到之前《openCV实战-系列教程》的内容。  使用高斯滤波器,以平滑图像,滤除噪声。 计算图像中每个像素点的梯度强

    2024年02月11日
    浏览(40)
  • OpenCV自学笔记十四:Canny边缘检测

    Canny边缘检测是一种经典的图像边缘检测算法,具有以下几个步骤: 1. 噪声抑制:首先对图像进行平滑处理,以去除图像中的噪声。常用的方法是应用高斯滤波器。 2. 计算梯度:通过对平滑后的图像应用Sobel算子(或其他梯度算子),计算图像的梯度幅值和梯度方向。梯度表

    2024年02月08日
    浏览(32)
  • python实现Canny算子边缘检测算法

        边缘检测是一种将图片中关键信息表现出来的一种图片技术,它的结果并不是字面意思上的获取图片边缘,而是将图片有用的信息勾勒出来,类似素描的结果,但是已经去掉了很多信息。如下所示,一张原始的图片是这样的:          通过边缘检测算法,我们最终得到

    2024年02月08日
    浏览(29)
  • Canny边缘检测算子原理和matlab实现代码

      图像边缘是图像的重要信息,而Canny算子则是用于边缘检测的经典算法。在用Canny算子进行边缘检测之前必须有效地抑制噪声,该算法使用的是高斯平滑滤波。接下来计算图像中每一点的梯度向量,根据梯度向量可以得到梯度方向和梯度幅值。梯度方向在(-π,π]范围内,

    2023年04月09日
    浏览(27)
  • Halcon边缘检测Sobel、Laplace和Canny算子

    提示:文章参考了网络上其他作者的文章,以及相关书籍,如有侵权,请联系作者。        除了阈值分割外,也可以通过检测区域的边缘得到目标区域。区域的边缘像素的灰度值往往会发生灰度上的突变,针对这些跳跃性的突变进行检测和计算,可以得到区域的边缘轮廓

    2023年04月08日
    浏览(30)
  • Halcon经典的边缘检测算子Sobel/Laplace/Canny

    关于边缘检测,有许多经典的算子,各大图形处理库都有各自的边缘检测算子,这里简要介绍几种。 Sobel算子结合了高斯平滑和微分求导。它是一阶导数的边缘检测算子,使用卷积核对图像中的每个像素点做卷积和运算,然后采用合适的阈值提取边缘。Soble算子有两个卷积核

    2024年01月22日
    浏览(34)
  • OpenCV——Canny边缘检测算法

    图像分割是将数字图像细分为多个子区域的过程,在计算机视觉/机器视觉领域被广泛应用。它的目的是简化或改变图像的表示形式,以便更容易理解和分析。常见的图像分割方法包括阈值处理、聚类法、边缘检测和区域生长等。解决图像分割问题通常需要结合领域知识,以提

    2024年04月17日
    浏览(27)
  • 《opencv实用探索·十一》opencv之Prewitt算子边缘检测,Roberts算子边缘检测和Sobel算子边缘检测

    1、前言 边缘检测: 图像边缘检测是指在图像中寻找灰度、颜色、纹理等变化比较剧烈的区域,它们可能代表着物体之间的边界或物体内部的特征。边缘检测是图像处理中的一项基本操作,可以用于人脸识别、物体识别、图像分割等多个领域。 边缘检测实质上是计算当前点和

    2024年02月22日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包