1. 程式人生 > >學習OpenCV範例(十四)——sobel,laplace,canny的運用

學習OpenCV範例(十四)——sobel,laplace,canny的運用

本次範例將要學習關於邊緣提取,影象銳化的三個基本函式,風別是Sobel(),Laplacian(),Canny(),會從原理講起,再到程式碼實現,最後會貼出執行結果,進行三種結果的對比。

1、原理及計算

Sobel:

原理:

How intensity changes in an edge

由上圖,你可以看到在 邊緣 ,相素值顯著的 改變 了。表示這一 改變 的一個方法是使用 導數 。 梯度值的大變預示著影象中內容的顯著變化。

用更加形象的影象來解釋,假設我們有一張一維圖形。下圖中灰度值的”躍升”表示邊緣的存在:

Intensity Plot for an edge

使用一階微分求導我們可以更加清晰的看到邊緣”躍升”的存在(這裡顯示為高峰值)

First derivative of Intensity - Plot for an edge

從上例中我們可以推論檢測邊緣可以通過定位梯度值大於鄰域的相素的方法找到(或者推廣到大於一個閥值).

計算:

  1. 在兩個方向求導:

    1. 水平變化: 將 I 與一個奇數大小的核心 G_{x} 進行卷積。比如,當核心大小為3時, G_{x} 的計算結果為:

      G_{x} = \begin{bmatrix}-1 & 0 & +1  \\-2 & 0 & +2  \\-1 & 0 & +1\end{bmatrix} * I

    2. 垂直變化: 將I 與一個奇數大小的核心 G_{y} 進行卷積。比如,當核心大小為3時, G_{y} 的計算結果為:

      G_{y} = \begin{bmatrix}-1 & -2 & -1  \\0 & 0 & 0  \\+1 & +2 & +1\end{bmatrix} * I

  2. 在影象的每一點,結合以上兩個結果求出近似 梯度:

    G = \sqrt{ G_{x}^{2} + G_{y}^{2} }

    有時也用下面更簡單公式代替:

    G = |G_{x}| + |G_{y}|

注:Sobel操作,一般使用大一點的核會比小一點的核好(來自學習OpenCV),當核心大小為 3 時, 以上Sobel核心可能產生比較明顯的誤差(畢竟,Sobel運算元只是求取了導數的近似值)。 為解決這一問題,OpenCV提供了 Scharr 函式,但該函式僅作用於大小為3的核心。該函式的運算與Sobel函式一樣快,但結果卻更加精確,其核心為:

G_{x} = \begin{bmatrix}-3 & 0 & +3  \\-10 & 0 & +10  \\-3 & 0 & +3\end{bmatrix}G_{y} = \begin{bmatrix}-3 & -10 & -3  \\0 & 0 & 0  \\+3 & +10 & +3\end{bmatrix}


Laplace:

原理:

  1. Sobel運算元,其基礎來自於一個事實,即在邊緣部分,畫素值出現”跳躍“或者較大的變化。如果在此邊緣部分求取一階導數,你會看到極值的出現。正如下圖所示:

    Previous theory
  2. 如果在邊緣部分求二階導數會出現什麼情況?

    Second derivative

    你會發現在一階導數的極值位置,二階導數為0。所以我們也可以用這個特點來作為檢測影象邊緣的方法。 但是, 二階導數的0值不僅僅出現在邊緣(它們也可能出現在無意義的位置),但是我們可以過濾掉這些點。

計算

Laplacian 運算元 的定義:

   Laplace(f) = \dfrac{\partial^{2} f}{\partial x^{2}} + \dfrac{\partial^{2} f}{\partial y^{2}}

核心為:

         \vecthreethree {0}{1}{0}{1}{-4}{1}{0}{1}{0}

OpenCV函式 Laplacian 實現了Laplacian運算元。 實際上,由於 Laplacian使用了影象梯度,它內部呼叫了 Sobel 運算元。

Canny:

操作步驟:

  1. 消除噪聲。 使用高斯平滑濾波器卷積降噪。 下面顯示了一個 size = 5 的高斯核心示例:

    K = \dfrac{1}{159}\begin{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. 計算梯度幅值和方向。 此處,按照Sobel濾波器的步驟:

    1. 運用一對卷積陣列 (分別作用於 x 和 y 方向):

      G_{x} = \begin{bmatrix}-1 & 0 & +1  \\-2 & 0 & +2  \\-1 & 0 & +1\end{bmatrix}G_{y} = \begin{bmatrix}-1 & -2 & -1  \\0 & 0 & 0  \\+1 & +2 & +1\end{bmatrix}

    2. 使用下列公式計算梯度幅值和方向:

      \begin{array}{l}G = \sqrt{ G_{x}^{2} + G_{y}^{2} } \\\theta = \arctan(\dfrac{ G_{y} }{ G_{x} })\end{array}

      梯度方向近似到四個可能角度之一(一般 0, 45, 90, 135)

  3. 非極大值 抑制。 這一步排除非邊緣畫素, 僅僅保留了一些細線條(候選邊緣)。

  4. 滯後閾值: 最後一步,Canny 使用了滯後閾值,滯後閾值需要兩個閾值(高閾值和低閾值):

    1. 如果某一畫素位置的幅值超過 高 閾值, 該畫素被保留為邊緣畫素。
    2. 如果某一畫素位置的幅值小於 低 閾值, 該畫素被排除。
    3. 如果某一畫素位置的幅值在兩個閾值之間,該畫素僅僅在連線到一個高於 高 閾值的畫素時被保留。

    Canny 推薦的 高:低 閾值比在 2:1 到3:1之間。

如果想自適應確定閾值,那麼請查閱這篇部落格在OpenCV中自適應確定canny演算法的分割門限

2、程式碼實現


#include "stdafx.h"

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// 全域性變數

Mat src, src_gray,grad,laplacian_dst;
Mat grad_x, grad_y,abs_grad_x, abs_grad_y,laplacian_dst1,dst, detected_edges;

int edgeThresh = 1;
int lowThreshold;
int slobel_lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
char* window_name = "Edge Map";
char* sobel_window_name = "Sobel Demo - Simple Edge Detector";

/**
 * @函式 CannyThreshold
 * @簡介: trackbar 互動回撥 - Canny閾值輸入比例1:3
 */
void CannyThreshold(int, void*)
{
  /// 使用 3x3核心降噪
  blur( src_gray, detected_edges, Size(3,3) );

  /// 執行Canny運算元
  Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );

  /// 使用 Canny運算元輸出邊緣作為掩碼顯示原影象
  dst = Scalar::all(0);

  src.copyTo( dst, detected_edges);
  imshow( window_name, dst );
 }
void SobelThreshold(int,void*)
{
	/// 求 X方向梯度
	Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
//	Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
	convertScaleAbs( grad_x, abs_grad_x );
	/// 求Y方向梯度
	Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
//	Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
	convertScaleAbs( grad_y, abs_grad_y );
	/// 合併梯度(近似)
	addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
	threshold(grad,grad,slobel_lowThreshold,100,THRESH_TOZERO);
	imshow(sobel_window_name,grad);

}


/** @函式 main */
int main( int argc, char** argv )
{

  /// 裝載影象
  src = imread( "lena.png");

  if( !src.data )
  { return -1; }

  /// 使用高斯濾波消除噪聲
  GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

  /// 建立與src同類型和大小的矩陣(dst)
  dst.create( src.size(), src.type() );

  /// 原影象轉換為灰度影象
  cvtColor( src, src_gray, CV_BGR2GRAY );

  /// 建立顯示視窗
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );
  namedWindow(sobel_window_name,CV_WINDOW_AUTOSIZE);

  /// 建立trackbar
  createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
  createTrackbar("Sobel Threshold:",sobel_window_name,&slobel_lowThreshold,max_lowThreshold, SobelThreshold);

  /// 使用Laplace函式
  Laplacian( src_gray, laplacian_dst1, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( laplacian_dst1, laplacian_dst );
  imshow("laplacian",laplacian_dst);

  /// 顯示影象
  CannyThreshold(0, 0);
   SobelThreshold(0,0);

  /// 等待使用者反應
  waitKey(0);

  return 0;
  }

3、執行結果


                                                                         圖1、 原圖

                                                            圖2、Sobel

                                                                圖3、scharr

                                                            圖4、laplace

                                                                   圖5、canny

4、使用的類和函式

Sobel:

功能:使用擴充套件 Sobel 運算元計算一階、二階、三階或混合影象差分

結構:

void Sobel(InputArray src, OutputArray dst, int ddepth, int xorder, int yorder, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
src :源影象
dst :目標影象,和源影象有同樣的size和通道數
ddepth :目標影象的深度
xorder :x 方向上的差分階數
yorder :y 方向上的差分階數
ksize :擴充套件 Sobel 核的大小,必須是 1, 3, 5 或 7
scale :縮放因子
delta :最後加到影象中的數值
borderType :邊界插值型別

通過適當的核對影象進行卷積:

\texttt{dst} =  \frac{\partial^{xorder+yorder} \texttt{src}}{\partial x^{xorder} \partial y^{yorder}}

如果ksize=3,x方向的核為:

\vecthreethree{-1}{0}{1}{-2}{0}{2}{-1}{0}{1}

y方向的核為:

\vecthreethree{-1}{-2}{-1}{0}{0}{0}{1}{2}{1}

Laplacian:

功能:計算影象的 Laplacian 變換

結構:

void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
src :源影象
dst :目標影象,和源影象有同樣的size和通道數
ddepth :目標影象的深度
ksize :核的大小,必須為奇數
scale :縮放因子
delta :最後加到影象中的數值
borderType :邊界插值型別
函式 Laplacian 計算輸入影象的 Laplacian變換,方法是先用 sobel 運算元計算二階 x- 和 y- 差分,再求和:

\texttt{dst} =  \Delta \texttt{src} =  \frac{\partial^2 \texttt{src}}{\partial x^2} +  \frac{\partial^2 \texttt{src}}{\partial y^2}

對 ksize=1 則給出最快計算結果,相當於對影象採用如下核心做卷積:

\begin{bmatrix}     0 & 1 & 0 \\     1 & -4 & 1 \\     0 & 1 & 0  \end{bmatrix}

Canny:

功能:採用 Canny 演算法做邊緣檢測

結構:

void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false )
image :單通道8點陣圖像
edges:目標影象,和源影象有同樣的size和type
threshold1 :第一個閾值
threshold2 :第二個閾值
apertureSize :sobel核心大小
L2gradient :當L2gradient=true,則梯度幅度採用L_2正規化計算=\sqrt{(dI/dx)^2 + (dI/dy)^2},當為false時,使用L_1正規化=|dI/dx|+|dI/dy|

函式 Canny 採用 CANNY 演算法發現輸入影象的邊緣而且在輸出影象中標識這些邊緣。threshold1和threshold2 當中的小閾值用來控制邊緣連線,大的閾值用來控制強邊緣的初始分割。

注意:Canny只接受單通道影象作為輸入。