OpenCV 之 边缘检测

来源:互联网 发布:iphone网络不可用 编辑:程序博客网 时间:2024/06/03 01:51

OpenCV 之 边缘检测

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

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

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

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

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

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

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

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

        

1  索贝尔算子 (Sobel)  

1.1  计算过程

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

Gx=121000121IGy=101202101IGx=[−101−202−101]∗IGy=[−1−2−1000121]∗I

  输出的图像矩阵 G 为:

G=G2x+G2yG=|Gx|+|Gy|G=Gx2+Gy2或简化为G=|Gx|+|Gy|

  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,表示计算结果无增量。

1.2  Scharr 卷积核

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

Kx=31030003103Ky=30310010303Kx=[−303−10010−303]Ky=[−3−10−30003103]

  而 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 XScharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );convertScaleAbs( grad_x, abs_grad_x );// Gradient YScharr( 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 );
复制代码

 

2  拉普拉斯算子 (Laplace)

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

Laplace(f)=2fx2+2fy2=f(x+1,y)+f(x1,y)+f(x,y+1)+f(x,y1)4f(x,y)Laplace(f)=∂2f∂x2+∂2f∂y2=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) 
复制代码

 

3  Canny 算子

3.1  算法步骤

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

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

K=1159245424912945121512549129424542K=1159[245424912945121512549129424542]

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

Kx=121000121Ky=101202101Kx=[−101−202−101]Ky=[−1−2−1000121]

G=G2x+G2yθ=arctan(GyGx)G=Gx2+Gy2θ=arctan⁡(GyGx)

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

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

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

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

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

3.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,则表示采用更精确的形式。

 

4  代码示例

4.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;}
复制代码

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

复制代码
 1 #include "opencv2/imgproc/imgproc.hpp" 2 #include "opencv2/imgcodecs.hpp" 3 #include "opencv2/highgui/highgui.hpp" 4 #include <stdlib.h> 5 #include <stdio.h> 6  7 using namespace cv; 8  9 int main( int, char** argv )10 {11 12   Mat src, src_gray, dst;13   int kernel_size = 3;14   int scale = 1;15   int delta = 0;16   int ddepth = CV_16S;17   const char* window_name = "Laplace Demo";18 19   /// Load an image20   src = imread( argv[1] );21 22   if( src.empty() )23     { return -1; }24 25   /// Remove noise by blurring with a Gaussian filter26   GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );27 28   /// Convert the image to grayscale29   cvtColor( src, src_gray, COLOR_RGB2GRAY );30 31   /// Create window32   namedWindow( window_name, WINDOW_AUTOSIZE );33 34   /// Apply Laplace function35   Mat abs_dst;36 37   Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );38   convertScaleAbs( dst, abs_dst );39 40   /// Show what you got41   imshow( window_name, abs_dst );42 43   waitKey(0);44 45   return 0;46 }
复制代码

  在 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 variablesMat 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;}
复制代码

4.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;    namedWindow("Original", CV_WINDOW_AUTOSIZE);    namedWindow("Sobel", CV_WINDOW_AUTOSIZE);    namedWindow("Laplace", CV_WINDOW_AUTOSIZE);    namedWindow("Canny", CV_WINDOW_AUTOSIZE);    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);    imwrite("Sobel.jpg",dst);    Laplacian(src_gray,dst,-1,3);    imshow("Laplace", dst);    imwrite("Laplace.jpg",dst);    Canny(src_gray,dst,100,300);    imshow("Canny",dst);    imwrite("Canny.jpg",dst);    waitKey(0);    return 0;}
复制代码

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

 

参考资料

 <Learning OpenCV_2nd>

 <OpenCV Tutorials> imgproc module


转载:http://www.cnblogs.com/xinxue/p/5348743.html

0 0
原创粉丝点击