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 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 );
複製代碼

 

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 image
20   src = imread( argv[1] );
21 
22   if( src.empty() )
23     { return -1; }
24 
25   /// Remove noise by blurring with a Gaussian filter
26   GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
27 
28   /// Convert the image to grayscale
29   cvtColor( src, src_gray, COLOR_RGB2GRAY );
30 
31   /// Create window
32   namedWindow( window_name, WINDOW_AUTOSIZE );
33 
34   /// Apply Laplace function
35   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 got
41   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 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;
}
複製代碼

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

發佈了6 篇原創文章 · 獲贊 127 · 訪問量 86萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章