1. 程式人生 > 其它 >影象邊緣檢測的方法與實現

影象邊緣檢測的方法與實現

1、影象邊緣

  OpenCV影象平滑中的“平滑”,從訊號處理的角度看,是一種"低通濾波",影象邊緣是 畫素值變化劇烈 的區域 (“高頻”),可視為一種 "高通濾波",對應的場景如下:

    1)  深度的不連續 (物體處在不同的物平面上)

    2)  表面方向的不連續 (如,正方體不同的兩個面)

    3)  物體材料不同 (光的反射係數也不同)

    4)  場景中光照不同 (如,有樹蔭的路面)

    目前邊緣檢測的演算法,多是求影象的微分,例如:Sobel 影象一階導, Laplace 影象二階導

2、索貝爾運算元

2.1計算過程

假定輸入影象矩陣為 I,卷積核大小為 3x3,則水平一階導 

GxGx 和垂直一階導 GyGy 分別為:

    Gx=⎡⎣⎢121000121⎤⎦⎥IGy=⎡⎣⎢101202101⎤⎦⎥IGx=[−101−202−101]∗IGy=[−1−2−1000121]∗I

    輸出影象矩陣 GG 為:

    G=G2x+G2y−−−−−−−√或簡化為G=|Gx|+|Gy|G=Gx2+Gy2或簡化為G=|Gx|+|Gy|

    對無特徵的影象 (flat)、帶邊緣的影象 (edge)、帶角點的影象 (corner),分別求一階導 dx 和 dy 如下:

    

    

     

2.2  Sobel 卷積核

2.2.1 核大小

 分析 Sobel 核的特點,可以看出,Sobel 運算元結合了高斯平滑和微分運算,對噪聲具有一定的抑制作用

    1) 當核大小為 1×11×1 時,取 xorder=1, yorder=0,此時,Sobel 核只有微分運算,不再有高斯平滑:

    Kx=[101]Kx=[−101]

    2) 當核大小為 3×33×3 時,取 xorder=1, yorder=0,則 Sobel 核為:

    Kx=⎡⎣⎢121000121⎤⎦⎥Kx=[−101−202−101]

        此時,Sobel 核作為微分運算的近似,不夠精確,通常的用 Scharr 核來代替:

    Kx=⎡⎣⎢31030003103⎤⎦⎥Kx=[−303−10010−303]

    3) 當核大小為 5×55×5 時,取 xorder=1, yorder=0,按照 Intel IPP 庫中的定義,Sobel 核為:

    Kx=⎡⎣⎢⎢⎢⎢⎢⎢146412812820000028682141241⎤⎦⎥⎥⎥⎥⎥⎥Kx=[−1−2021−4−8084−6−120612−4−8084−1−2021] 

2.2.2 可分離性

    Sobel 卷積核具有可分離性,能被分解為兩個一維的卷積核,如下:

       

    根據這個性質,再結合 OpenCV 中的 getDerivKernels() 函式,可求出各種大小的 Sobel 核        

// kernel size, when ksie ≤ 0, get Scharr kernel
int ksize = 5;
Mat kx, ky;
getDerivKernels(kx, ky, 1, 0, ksize, false, CV_32F);
Mat kernel = ky * kx.t();  

2.3  OpenCV 函式 

2.3.1  Sobel() 函式

OpenCV 中的 Sobel() 函式如下:dx 和 dy 表示階數,一般取 0 或 1,不超過 2

void  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 // 邊界處理模式 )  

2.3.2  Scharr() 函式

Scharr() 函式,本質上就是令 ksize = 3 且使用 Scharr 卷積核的 Sobel() 函式  

void 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) 都是對影象進行邊緣檢測,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    Laplacian (     
    InputArray     src,
    OutputArray    dst,
    int       ddepth,
    int       ksize = 1,
    double    scale = 1,
    double    delta = 0,
    int       borderType = BORDER_DEFAULT
) 

4  Canny 運算元

Canny 運算元,在一階微分的基礎上,增加了非最大值抑制和雙閾值檢測,是邊緣檢測運算元中最常用的一種,常被其它運算元作為標準運算元來進行優劣比較。

4.1  演算法步驟

1) 用高斯濾波器對輸入影象做平滑處理 (大小為 5x5 的高斯核)

   K=1159⎡⎣⎢⎢⎢⎢⎢⎢245424912945121512549129424542⎤⎦⎥⎥⎥⎥⎥⎥K=1159[245424912945121512549129424542]

2) 計算影象的梯度強度和角度方向 ( x 和 y 方向上的卷積核)

   Kx=⎡⎣⎢121000121⎤⎦⎥Ky=⎡⎣⎢101202101⎤⎦⎥Kx=[−101−202−101]Ky=[−1−2−1000121]

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

  角度方向近似為四個可能值,即 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;
}

 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);
}

  在 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;
}

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("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);}

 三種邊緣檢測的效果圖如下: