【opencv學習筆記】020之邊緣檢測與Canny算子

目錄

一、前言

二、邊緣檢測

1、啥是邊緣檢測

2、邊緣檢測算子

三、Canny算子

1、講解

1.圖像平滑

2.尋找圖像強度梯度

3.消除邊誤檢

4.雙閾值求可能邊

5.邊界跟蹤

2、API

3、代碼展示

4、執行結果


一、前言

繼續填坑。

如果想看其他有關於OpenCV學習方法介紹、學習教程、代碼實戰、常見報錯及解決方案等相關內容,可以直接看我的OpenCV分類:

【OpenCV系列】:https://blog.csdn.net/shuiyixin/article/category/7581855

如果你想了解更多有關於計算機視覺、OpenCV、機器學習、深度學習等相關技術的內容,想與更多大佬一起溝通,那就掃描下方二維碼加入我們吧!

 

二、邊緣檢測

1、啥是邊緣檢測

我們先來簡單理解一下:

原圖

對於這張圖,如果我們想把人物摳出來,應該怎麼辦?我們用摳圖軟件弄一下,就會得到下面的圖像:

摳圖

我們摳圖的過程其實就是找圖像中某個實例(例如人)的邊緣的過程,所以,我們的邊緣檢測,其實就是檢測圖像中的實例的邊緣。

那現在就有一個問題了,我們人是怎麼區分邊緣的呢?

我們發現,每一個實例,它的邊緣跟其周圍的像素差距一般是比較大的。我們的摳圖,就是根據明顯的像素差距,來區分實例的邊緣。

所以摳圖的根據,就是圖像像素的明顯變化。圖像的邊緣,一般都是圖像上像素有明顯變化的位置。

那我們用一個圖來表示圖像的像素:

圖像像素

我們發現,圖像像素明顯變化的位置,圖像的傾斜程度都很大,也就是說斜率的絕對值很大。這和我們之前講的幾個算子的原理是一致的,Sobel算子,就是計算的這個位置:

有沒有覺得這個故事很感人呢???

瞭解了這些之後,現在我們來說一下邊緣檢測比較正規的定義吧!

邊緣檢測是圖像處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字圖像中亮度變化明顯的點。圖像屬性中的顯著變化通常反映了屬性的重要事件和變化。

 

2、邊緣檢測算子

上面講到,Sobel 算子可以用於邊緣檢測,那除了Sobel算子,還有那些算子能用於邊緣檢測呢?

這個問題我想大家應該比較容易回答了,我們之前學的三個算子都能用於邊緣檢測:

Sobel算子、Scharr算子、Laplance算子

除了這三個,我們今天再給大家講一個算子,Canny算子。

 

三、Canny算子

1、講解

接下來,我們來說一個新的算子Canny算子

Canny邊緣檢測算子是John F. Canny於 1986 年開發出來的一個多級邊緣檢測算法。它包括如下五個步驟:

1.應用高斯濾波來平滑圖像,目的是去除噪聲

2.找尋圖像的強度梯度(intensity gradients)

3.應用非最大抑制(non-maximum suppression)技術來消除邊誤檢(本來不是但檢測出來是)

4.應用雙閾值的方法來決定可能的(潛在的)邊界

5.利用滯後技術來跟蹤邊界

接下來,我們來詳細講解一下每一個步驟:

1.圖像平滑

圖像平滑就是讓圖像之間的像素差距更小一些,能夠更好地提取特徵更加明顯的邊緣,所以圖像平滑的目的就是去除部分噪聲。

而圖像平滑操作,我們思考一下,不就是讓挨着的像素之間差距變小,圖像模糊的作用不就是這樣嗎?所以圖像平滑操作就是進行圖像模糊操作,而我們最常用的模糊操作,是高斯模糊。

對沒錯就是我!
世上最帥的男人,沒有之一!

 

科普一下下,高斯模糊不是高斯提出來的!

高斯模糊用的kernel是正態分佈,正態分佈也叫高斯分佈。這是高斯模糊這個名字的由來。不是因爲高斯提出的。甚至,高斯分佈也不是高斯提出的,只是用他命名。高斯分佈的提出者是棣莫佛和拉普拉斯,那個定理叫做棣莫佛-拉普拉斯定理。

但是這絲毫不會影響我們數學人對高斯的推崇。也不會影響我們數學人對任何一個在數學史上貢獻自己一生的人致以最崇高的敬意。

言歸正傳,下面這個示例是一個5×5的kernel。

2.尋找圖像強度梯度

Canny算法的基本思想是尋找一幅圖像中灰度強度變化最強的位置所謂變化最強,即指梯度方向。平滑後的圖像中每個像素點的梯度可以由Sobel算子(一種卷積運算)來獲得(opencv中有封裝好的函數,可以求圖像中每個像素點的n階導數)。首先,利用如下的核來分別求得沿水平(x)和垂直(y)方向的梯度G_X和G_Y。

Sobel算子,我們很熟悉了,如果大家還有疑問,可以看這篇博客:

【opencv學習筆記】018之Sobel算子與Scharr算子https://blog.csdn.net/shuiyixin/article/details/104484635

3.消除邊誤檢

這一步的目的是將模糊的邊界變得清晰(sharp)。通俗的講,就是保留了每個像素點上梯度強度的極大值,而刪掉其他的值。對於每個像素點,進行如下操作:

a) 將其梯度方向近似爲以下值中的一個:

                               (0,45,90,135,180,225,270,315)(即上下左右和45度方向)

b) 比較該像素點,和其梯度方向正負方向的像素點的梯度強度

c) 如果該像素點梯度強度最大則保留,否則抑制(刪除,即置爲0)

爲了更好的解釋這個概念,看下圖:

圖中的數字代表了像素點的梯度強度,箭頭方向代表了梯度方向。以第二排第三個像素點爲例,由於梯度方向向上,則將這一點的強度(7)與其上下兩個像素點的強度(5和4)比較,由於這一點強度最大,則保留。

4.雙閾值求可能邊

經過非極大抑制後圖像中仍然有很多噪聲點。Canny算法中應用了一種叫雙閾值的技術。即設定一個閾值上界和閾值下界(opencv中通常由人爲指定的),圖像中的像素點如果大於閾值上界則認爲必然是邊界(稱爲強邊界,strong edge),小於閾值下界則認爲必然不是邊界,兩者之間的則認爲是候選項(稱爲弱邊界,weak edge),需進行進一步處理。

5.邊界跟蹤

較高的亮度梯度比較有可能是邊緣,但是沒有一個確切的值來限定多大的亮度梯度是邊緣多大又不是,所以 Canny 使用了滯後閾值。

滯後閾值(Hysteresis thresholding)需要兩個閾值,即高閾值與低閾值。假設圖像中的重要邊緣都是連續的曲線,這樣我們就可以跟蹤給定曲線中模糊的部分,並且避免將沒有組成曲線 的噪聲像素當成邊緣。所以我們從一個較大的閾值開始,這將標識出我們比較確信的真實邊緣,使用前面導出的方向信息,我們從這些真正的邊緣開始在圖像中跟蹤 整個的邊緣。在跟蹤的時候,我們使用一個較小的閾值,這樣就可以跟蹤曲線的模糊部分直到我們回到起點。

 

2、API

接下來我們講一下API

void Canny( 
    InputArray src, 
    OutputArray edges, 
    double threshold1, 
    double threshold2,
    int apertureSize = 3, 
    bool L2gradient = false
);

函數參數含義如下:

 

(1)InputArray類型的src ,輸入圖像。

(2)OutputArray類型的edges,輸出邊緣圖;單通道8位圖像,其大小與圖像相同。

(3)double類型的threshold1,滯後過程的第一個閾值。

(4)double類型的threshold2,滯後過程的第二個閾值。

(5)int類型的apertureSize,Sobel運算符的孔徑大小。

(6)bool類型的L2gradient,一個標誌,指示是否應使用更精確的L2來計算圖像漸變幅度(L2gradient=true),或者是否應使用默認的L1(L2gradient=false)。(注:這裏的L2是平方和開根號,L1是絕對值求和,如下圖

3、代碼展示

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat img, src;
	img = imread("E:/image/girl2.png");
	if (!img.data)
	{
		cout << "could not load image !";
		return -1;
	}
	imshow("【輸入圖像】", img);

	GaussianBlur(img, img, Size(5, 5), 0, 0);
	Canny(img, src, 115, 255);

	imshow("【輸出圖像】", src);

	waitKey(0);
	return 0;
}

4、執行結果

原圖
Canny算子

如果我們不使用模糊操作,得到的結果如下:

不使用模糊操作

我們能夠發現,如果我們模糊了之後,能夠更好的提取我們需要的邊緣,去掉一些不太關注的邊緣,提取到的邊緣更好。

大家也可以自己嘗試一下呀,一定要多做練習!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章