zoukankan      html  css  js  c++  java
  • OpenCV 之 边缘检测

    1  图像边缘   

      上一篇  OpenCV 之 图像平滑 中,提到的图像平滑,从信号处理的角度来看,实际上是一种“低通滤波器”。

      本篇中,图像的边缘,通常都是素值变化剧烈的区域 (“高频”),故可将边缘检测视为一种 “高通滤波器”。

      实际中,对应像素值变化剧烈的情况如下:

        1) 深度的不连续 (物体处在不同的物平面上)

        2) 表面方向的不连续 (例如,正方体的不同的两个面)

        3) 物体材料不同 (光的反射系数也不同)

        4) 场景中光照不同 (例如,有树荫的路面)

      OpenCV 中,边缘检测常用的是索贝尔算子 (Sobel) 和拉普拉斯算子 (Laplace),分别对图像求一阶导和二阶导

           

    2  索贝尔算子 (Sobel)  

    2.1  计算过程

      假定输入图像矩阵为 I,卷积核大小为 3x3,则水平一阶导数 Gx 和垂直一阶导数 Gy 分别为:

    $quad G_x = egin{bmatrix} -1 & 0 & 1 \ -2 & 0 & 2 \ -1 & 0 & 1 \ end{bmatrix} * I qquad G_y = egin{bmatrix} -1 & -2 & -1 \ 0 & 0 & 0 \ 1 & 2 & 1 \ end{bmatrix} * I $

      则输出图像矩阵 G 为:

    $quad G = sqrt{G_{x}^2 + G_{y}^2 } qquad ext{或简化为}  qquad G = |G_x| + |G_y| $

      分别对无特征的图像 (flat)、带边缘的图像 (edge)、带角点的图像 (corner)求一阶导,dx 和 dy 如下所示:

          

       

       

    2.2  Sobel 函数 

     OpenCV 中,Sobel 函数:

    void cv::Sobel   (     
        InputArray   src,   // 输入图像
        OutputArray  dst,   // 输出图像
        int      ddepth,    // 输出图像深度,-1 表示等于 src.depth()
        int      dx,        // 水平方向的阶数
        int      dy,        // 垂直方向的阶数
        int     ksize = 3,    // 卷积核的大小,常取 1, 3, 5, 7 等奇数
        double  scale = 1,    // 缩放因子,应用于计算结果
        double  delta = 0,    // 增量数值,应用于计算结果
    int borderType = BORDER_DEFAULT // 边界处理模式
    )

      dx 和 dy 表示阶数,一般取 0 或 1,但不超过 2;scale = 1,表示计算结果不缩放;delat = 0,表示计算结果无增量。

    2.2  Scharr 卷积核

      当卷积核大小为 3x3 时,使用 sobel 卷积核来计算并不是很精确,此时常用 Scharr 卷积核来代替,如下:

    $quad K_x = egin{bmatrix} -3 & 0 & 3 \ -10 & 0 & 10 \ -3 & 0 & 3 \ end{bmatrix}qquad K_y = egin{bmatrix} -3 & -10 & -3 \ 0 & 0 & 0 \ 3 & 10 & 3 \ end{bmatrix} $

      而 Sharr 函数,本质上就是令 ksize = 3 且使用 Scharr 卷积核的 Sobel 函数。

    void cv::Scharr (     
        InputArray  src,    
        OutputArray  dst,    
        int      ddepth,    
        int      dx,        
        int      dy,        
        double  scale = 1,
        double  delta = 0,
        int     borderType = BORDER_DEFAULT        
    )     

      对于 Scharr 函数,要求 dx 和 dy 都 >= 0 且 dx + dy == 1,假如 dx 和 dy 都设为 1,则会抛出异常。

      因此,对于 Sobel 和 Scharr 函数,通常各自求其 x 和 y 方向的导数,然后通过加权来进行边缘检测。

    // Gradient X
    Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_x, abs_grad_x );
    
    // Gradient Y
    Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );  
    convertScaleAbs( grad_y, abs_grad_y );
    // Total Gradient (approximate) addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

    3  拉普拉斯算子 (Laplace)

      索贝尔算子 (Sobel) 和拉普拉斯算子 (Laplace) 都是用来对图像进行边缘检测的,不同之处在于,前者是求一阶导,后者是求二阶导。

    $quad Laplace(f) = frac{partial^2f}{partial x^2} + frac{partial^2f}{partial y^2} = f(x+1, y) + f(x-1, y) + f(x, y+1) + f(x, y-1) - 4f(x, y)$

      OpenCV 中对应的函数为 Laplacian

    void cv::Laplacian (     
        InputArray     src,
        OutputArray    dst,
        int       ddepth,
        int       ksize = 1,
        double    scale = 1,
        double    delta = 0,
        int       borderType = BORDER_DEFAULT
    ) 

    4  Canny 算子

    4.1  算法步骤

       Canny 边缘检测算子,其算法步骤大体如下:

    1) 用高斯滤波器对输入图像做平滑处理 (大小为 5x5 的高斯核)

    $quad K = frac{1}{159} egin{bmatrix}  2 & 4 & 5 & 4 & 2 \ 4 & 9 & 12 & 9 & 4 \ 5 & 12 & 15 & 12 & 5 \ 4 & 9 & 12 & 9 & 4 \ 2 & 4 & 5 & 4 & 2 end{bmatrix}$

    2) 计算图像的梯度强度和角度方向 ( x 和 y 方向上的卷积核)

    $quad K_x = egin{bmatrix} -1 & 0 & 1 \ -2 & 0 & 2 \ -1 & 0 & 1 \ end{bmatrix}  qquad K_y = egin{bmatrix} -1 & -2 & -1 \ 0 & 0 & 0 \ 1 & 2 & 1 \ end{bmatrix} $

    $quad G = sqrt{G_{x}^2 + G_{y}^2 } qquad heta = arctan(dfrac{ G_y }{ G_x }) $

      角度方向近似为四个可能值,即 0, 45, 90, 135

    3) 对图像的梯度强度进行非极大抑制

       可看做边缘细化:只有候选边缘点被保留,其余的点被移除

    4) 利用双阈值检测和连接边缘

        若候选边缘点大于上阈值,则被保留;小于下阈值,则被舍弃;处于二者之间,须视其所连接的像素点,大于上阈值则被保留,反之舍弃

    4.2  Canny 函数

      OpenCV 中的 Canny 函数如下所示:

    void cv::Canny (     
        InputArray    image,    // 输入图像 (8位)
        OutputArray   edges,    // 输出图像 (单通道,8位)
        double      threshold1,  // 下阈值
        double      threshold2,  // 上阈值
        int         apertureSize = 3,
        bool        L2gradient = false
    ) 

      一般 上阈值 / 下阈值 = 2 ~ 3

      L2gradient 默认 flase,表示图像梯度强度的计算采用近似形式;若为 true,则表示采用更精确的形式。

    5  代码示例

    5.1  OpenCV 示例

      Sobel 或 Scharr 示例中,使用 addWeighted 函数,来加权合成 x 和 y 方向上各自的一阶导数

    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/imgcodecs.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <stdlib.h>
    #include <stdio.h>
    
    using namespace cv;
    
    int main( int, char** argv )
    {
    
      Mat src, src_gray;
      Mat grad;
      const char* window_name = "Sobel Demo - Simple Edge Detector";
      int scale = 1;
      int delta = 0;
      int ddepth = CV_16S;
    
      /// Load an image
      src = imread( argv[1] );
    
      if( src.empty() )
        { return -1; }
    
      GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
    
      /// Convert it to gray
      cvtColor( src, src_gray, COLOR_RGB2GRAY );
    
      /// Create window
      namedWindow( window_name, WINDOW_AUTOSIZE );
    
      /// Generate grad_x and grad_y
      Mat grad_x, grad_y;
      Mat abs_grad_x, abs_grad_y;
    
      /// Gradient X
      //Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
      Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
      convertScaleAbs( grad_x, abs_grad_x );
    
      /// Gradient Y
      //Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
      Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
      convertScaleAbs( grad_y, abs_grad_y );
    
      /// Total Gradient (approximate)
      addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
    
      imshow( window_name, grad );
    
      waitKey(0);
    
      return 0;
    }
    View Code

      Laplacian 示例中,利用了高斯滤波函数来降低噪声

    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/imgcodecs.hpp"
    #include "opencv2/highgui/highgui.hpp"
    
    using namespace cv;
    
    int main( int, char** argv )
    {
      Mat src, src_gray, dst;
      int kernel_size = 3;
      int scale = 1;
      int delta = 0;
      int ddepth = CV_16S;
      const char* window_name = "Laplace Demo";
    
      // 读图
      src = imread("camera1.bmp");
      if( src.empty())
          return -1;
    
      // 高斯滤波
      GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
    
      // 灰度图
      cvtColor( src, src_gray, COLOR_RGB2GRAY );
    
      // 窗体
      namedWindow( window_name, WINDOW_AUTOSIZE );
    
      // Laplace 函数
      Mat abs_dst;
      Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
      convertScaleAbs( dst, abs_dst );
    
      // 显示
      imshow( window_name, abs_dst );
    
      waitKey(0);
    }
    View Code

      在 Canny 函数之前,也需要 blur 函数,来进行降噪处理

    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/imgcodecs.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <stdlib.h>
    #include <stdio.h>
    
    using namespace cv;
    
    /// Global variables
    
    Mat src, src_gray;
    Mat dst, detected_edges;
    
    int edgeThresh = 1;
    int lowThreshold;
    int const max_lowThreshold = 100;
    int ratio = 3;
    int kernel_size = 3;
    const char* window_name = "Edge Map";
    
    /**
     * @function CannyThreshold
     * @brief Trackbar callback - Canny thresholds input with a ratio 1:3
     */
    static void CannyThreshold(int, void*)
    {
        /// Reduce noise with a kernel 3x3
        blur( src_gray, detected_edges, Size(3,3) );
    
        /// Canny detector
        Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
    
        /// Using Canny's output as a mask, we display our result
        dst = Scalar::all(0);
    
        src.copyTo( dst, detected_edges);
        imshow( window_name, dst );
    }
    
    
    int main( int, char** argv )
    {
      /// Load an image
      src = imread( argv[1] );
    
      if( src.empty() )
        { return -1; }
    
      /// Create a matrix of the same type and size as src (for dst)
      dst.create( src.size(), src.type() );
    
      /// Convert the image to grayscale
      cvtColor( src, src_gray, COLOR_BGR2GRAY );
    
      /// Create a window
      namedWindow( window_name, WINDOW_AUTOSIZE );
    
      /// Create a Trackbar for user to enter threshold
      createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
    
      /// Show the image
      CannyThreshold(0, 0);
    
      /// Wait until user exit program by pressing a key
      waitKey(0);
    
      return 0;
    }
    View Code

    5.2  简单对比

      在进行 Sobel,Laplacian 和 Canny 边缘检测之前,统一调用 GaussianBlur 来降低图像噪声

    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/highgui/highgui.hpp"
    
    using namespace std;
    using namespace cv;
    
    int main()
    {
        Mat src, src_gray, dst;
    
        src = imread("E:/Edge/bird.jpg");
        if(src.empty()) 
            return -1;

    imshow("Original", src); Mat grad_x, grad_y, abs_grad_x, abs_grad_y; GaussianBlur(src, src, Size(3,3),0); cvtColor(src,src_gray,COLOR_BGR2GRAY); Sobel(src_gray, grad_x,CV_16S,0,1); // use CV_16S to avoid overflow convertScaleAbs( grad_x, abs_grad_x ); Sobel(src_gray, grad_y,CV_16S,1,0); // use CV_16S to avoid overflow convertScaleAbs( grad_y, abs_grad_y ); addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst ); imshow("Sobel", dst); Laplacian(src_gray,dst,-1,3); imshow("Laplace", dst); Canny(src_gray,dst,100,300); imshow("Canny",dst); waitKey(0);
    }

      三种边缘检测的效果图如下:

    参考资料

     <Learning OpenCV_2nd>

     <OpenCV Tutorials> imgproc module

  • 相关阅读:
    简单背包问题
    拓扑排序
    SPFA--P3905 道路重建
    Floyd--P1119 灾后重建
    Kmp--P3375 【模板】KMP字符串匹配
    练习 后缀数组
    BZOJ1036: [ZJOI2008]树的统计Count(树链剖分)
    BZOJ1503: [NOI2004]郁闷的出纳员(Splay)
    BZOJ2733: [HNOI2012]永无乡(线段树合并)
    BZOJ4196: [Noi2015]软件包管理器(树链剖分)
  • 原文地址:https://www.cnblogs.com/xinxue/p/5348743.html
Copyright © 2011-2022 走看看