zl程序教程

您现在的位置是:首页 >  后端

当前栏目

(九)OpenCV Canny边缘检测

Opencv边缘 检测 Canny
2023-09-27 14:26:28 时间

1.基础原理

参考自《数字图象处理》第十章

及OpenCV Tutorial Canny Edge Detector

1.1边缘检测概述

边缘检测是根据灰度突变来分割图像的一种常用方法。边缘模型可根据它们的灰度剖面来分类,通常分为台阶模型,斜坡模型和屋顶边缘模型。台阶模型多见于计算机生成的图像中,如实体建模和动画领域。实际图像中多为斜坡边缘模型。在使用二阶梯度来获取图像边缘时,二阶导数会产生一个局部极大正值和一个局部极小负值,在图像上体现为双线性效应。

在这里插入图片描述

在这里插入图片描述

1.2Canny边缘检测

1986年由John F. CannyA computational approach to edge detection.中提出,Canny算法主要分为如下几个步骤:

  • 使用高斯核平滑输入图像
  • 计算梯度幅度图像和角度图像
  • 对梯度幅度图像应用非极大值抑制
  • 使用双阈值处理和连同性分析来检测和连接边缘

分别展开讨论,

  • 高斯核平滑图像去除噪声

  • 计算梯度幅度图和梯度方向图,令f(x,y)表示输入图像,梯度幅度和方向分别为:

    M ( x , y ) = ∣ ∣ ▽ f ( x , y ) ∣ ∣ = g x 2 ( x , y ) + g y 2 ( x , y ) M(x,y)=||\triangledown f(x,y)||=\sqrt{g_x^2(x,y)+g_y^2(x,y)} M(x,y)=f(x,y)=gx2(x,y)+gy2(x,y)
    α ( x , y ) = a r c t a n [ g y ( x , y ) g x ( x , y ) ] \alpha(x,y)=arctan[\frac{g_y(x,y)}{g_x(x,y)}] α(x,y)=arctan[gx(x,y)gy(x,y)],
    其中, g x ( x , y ) = ∂ f ( x , y ) ∂ x g_x(x,y)=\frac{\partial f(x,y)}{\partial x} gx(x,y)=xf(x,y), g y ( x , y ) = ∂ f ( x , y ) ∂ y g_y(x,y)=\frac{\partial f(x,y)}{\partial y} gy(x,y)=yf(x,y),可使用(七)图像处理中常用算子Laplacian\Sobel\Roberts\Prewitt\Kirsch中的算子来求 g x ( x , y ) g_x(x,y) gx(x,y) g y ( x , y ) g_y(x,y) gy(x,y)

  • 对梯度幅度图像应用非极大值抑制,梯度图像 ∣ ∣ ▽ f ( x , y ) ∣ ∣ ||\triangledown f(x,y)|| f(x,y)通常在局部极大值附近包含一些宽脊,可以使用非极大值抑制来细化这些宽脊。在一个3x3区域内,可以定义4个边缘法线(梯度向量)的4个方向,水平,垂直,+45度, -45度,每个方向包含的角度范围见下图,
    在这里插入图片描述

使用 d 1 , d 2 , d 3 , d 4 d_1,d_2,d_3,d_4 d1,d2,d3,d4表示前述3x3区域的4个基本边缘方向,对以 α \alpha α中的任意点 ( x , y ) (x,y) (x,y)为中心的3x3区域,对应的非极大值抑制方案为:
- 1)寻找 α ( x , y ) \alpha(x,y) α(x,y)的方向 d k d_k dk
- 2)令K表示 ∣ ∣ ▽ f ∣ ∣ ||\triangledown f|| f ( x , y ) (x,y) (x,y)处的值。若K小于 d k d_k dk方向上点(x,y)的一个或两个邻点处的 ∣ ∣ ▽ f ∣ ∣ ||\triangledown f|| f值,则令 g N ( x , y ) = 0 g_N(x,y)=0 gN(x,y)=0;否则,令 g N ( x , y ) = K g_N(x,y)=K gN(x,y)=K
对(x,y)的所有值重复这一过程,得到一幅与 f ( x , y ) f(x,y) f(x,y)大小相同的非极大值抑制图像 g N ( x , y ) g_N(x,y) gN(x,y),图像 g N ( x , y ) g_N(x,y) gN(x,y)中只包含细化后的边缘。

  • 使用双阈值处理和连同性分析来检测和连接边缘,得到 g N ( x , y ) g_N(x,y) gN(x,y)后对其进行阈值处理来求得边缘。Canny算法使用滞后阈值处理,设置两个阈值,低阈值 T L T_L TL和高阈值 T H T_H TH。可以将滞后阈值运算当作为创建两幅额外的图像:
    g N H ( x , y ) = g N ( x , y ) ≥ T H g_{NH}(x,y)=g_N(x,y)\ge T_H gNH(x,y)=gN(x,y)TH

    g N L ( x , y ) = g N ( x , y ) ≥ T L g_{NL}(x,y)=g_N(x,y)\ge T_L gNL(x,y)=gN(x,y)TL
    g N L ( x , y ) g_{NL}(x,y) gNL(x,y)相比 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)非零值更少, g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中的非零像素都在 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中,通过

    g N L ( x , y ) = g N L ( x , y ) − g N H ( x , y ) g_{NL}(x,y) = g_{NL}(x,y) - g_{NH}(x,y) gNL(x,y)=gNL(x,y)gNH(x,y) g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中删除来自 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)的非零像素点。进行上述裁减后 g N L ( x , y ) g_{NL}(x,y) gNL(x,y) g N H ( x , y ) g_{NH}(x,y) gNH(x,y)分别表示“弱”边缘像素和“强”边缘像素。 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中的边缘像素被假设为有效的边缘像素,但其一般存在缝隙,通常需通过如下处理来形成更长的边缘:
    • 1)在 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中定位下一个未被访问的边缘像素p
    • 2)将 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中与p 8 连通域中的所有弱像素点标记为有效边缘像素
    • 3)若 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中所有非零像素都已被访问,则转到4,否则返回1
    • 4)将 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中未标记为有效边缘像素的所有像素设置为0
      经过上述步骤,将 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中的所有非零像素附加到 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)上,形成Canny算子输出的最终图像。

2.OpenCV API

Canny()

void cv::Canny	(	
    InputArray 	image,
    OutputArray 	edges,
    double 	threshold1,
    double 	threshold2,
    int 	apertureSize = 3,
    bool 	L2gradient = false 
)
  • image:单通道灰度图
  • edges:存储边缘像素的图像
  • threshold1: 滞后阈值处理的超参数
  • threshold2: 滞后阈值处理的超参数
  • apertureSize:使用Sobel算子时的卷积核大小
  • L2gradient:计算梯度幅值时是否使用 L 2 L_2 L2范数

3.示例

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <iostream>

using namespace std;
using namespace cv;

Mat src, src_gray;
Mat dst, detected_edges;

int low_threshold = 0;
const int max_low_threshold = 100;
const int ratio = 3;
const int ks = 3;
const char *win_name = "Canny Edge Map";

static void CannyThreshold(int, void*)
{
    blur(src_gray, detected_edges, Size(3,3));
    Canny(detected_edges, detected_edges, low_threshold, low_threshold*ratio, ks);
    dst = Scalar::all(0);
    src.copyTo(dst, detected_edges);
    imshow(win_name, dst);
    imwrite("seg_res.png", dst);
}

int main(int argc, char **argv)
{
    CommandLineParser parser( argc, argv, "{@input | fruits.jpg | input image}" );
    src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); // Load an image
    if( src.empty() )
    {
    std::cout << "Could not open or find the image!\n" << std::endl;
    std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl;
    return -1;
    }
    dst.create( src.size(), src.type() );
    cvtColor( src, src_gray, COLOR_BGR2GRAY );
    namedWindow( win_name, WINDOW_AUTOSIZE);
    createTrackbar( "Min Threshold:", win_name, &low_threshold, max_low_threshold, CannyThreshold );
    CannyThreshold(0, 0);
    waitKey(0);
    return 0;
}

在这里插入图片描述