新視界-OpenCV教程系列文章

新視界-OpenCV教程(1)-入門介紹

OpenCV 中的圖形用戶界面特徵系列

新視界-OpenCV教程(2)-圖片入門

新視界-OpenCV教程(3)-視頻入門

新視界-OpenCV教程(4)- 繪圖功能

新視界-OpenCV教程(5)- 滑鼠的畫筆功能

新視界-OpenCV教程(6)- 作為調色板的軌跡欄

核心操作系列

新視界-OpenCV教程(7)- 對圖片的基本操作

新視界-OpenCV教程(8)- 圖像運算

新視界-OpenCV教程(9)- 性能測量和改進技術

OpenCV 中的圖像處理系列

新視界-OpenCV教程(10)- 改變顏色空間

新視界-OpenCV教程(11)- 圖像閾值

新視界-OpenCV教程(12)- 圖像的幾何變換

新視界-OpenCV教程(13)- 平滑圖像

新視界-OpenCV教程(14)- 形態變換

新視界-OpenCV教程(15)- 圖像梯度

新視界-OpenCV教程(16)- Canny 邊緣檢測

新視界-OpenCV教程(17)- 圖像金字塔

新視界-OpenCV教程(18)- 輪廓:開端

新視界-OpenCV教程(19)- 輪廓:特徵

新視界-OpenCV教程(20)- 輪廓:性質

新視界-OpenCV教程(21)- 輪廓:更多功能

本文目標

輪廓系列會分為5篇文章一一詳細講解。這是第五篇也是最後一篇啦。

在本篇文章中,我們將學習輪廓的層次結構,即輪廓中的父子關係。

結果圖

理論

在關於輪廓的後面幾篇文章中,我們使用了OpenCV 提供的幾個與輪廓相關的函數。但是當我們使用cv2.findContours() 函數在圖像中找輪廓時,我們傳遞了一個參數,輪廓檢索模式 Contour Retrieval Mode。我們通常會傳遞cv2.RETR_LIST 或 cv2.RETR_TREE 而且其運行得很好。但是否有人和我一樣疑惑這些到底意味著什麼呢?

此外,在輸出中,我們也得到了三個數組,第一個是圖像,第二個是輪廓,最後一個是輸出,我們將其命名為hierarchy(本系列前面幾篇文中的代碼經常用到)。但我們從未在任何地方使用過這種層次結構。那麼這個層次結構是什麼,它的作用又是什麼呢?它和前面提到的函數參數又有什麼關係?

這就是我們將在本文中討論的內容。

什麼是層級?

通常情況下,我們會使用cv2.findContours() 函數來檢測圖像中的對象。但是有時物體會在不同的位置。但在某些情況下,有一些形狀會存在於其他形狀裏,就像是嵌套的圖形。在這種情況下,我們稱外部形狀為父類 parent,稱內部的為子類 child;這樣,圖像中的輪廓就有了某種聯繫。我們可以指定輪廓是如何相互連接的,比如,這個輪廓是另一個輪廓的子類,或則是它的父類等等。這種關係的表示形式稱為層級/層次結構。

讓我們來考慮下面這張圖為例:

在這張圖中,我按照正序用數字0-5給輪廓編號。2和2a 分別表示最外層矩形盒子的外部和內部輪廓。

在這裡,0,1,2 表示向外的(external/outermost)輪廓。我們可以說,它們在0層(hierarchy-0)或者簡單地說它們在相同層的水平(same hierarchy level)。

接著是輪廓-2a,我們可以認為它是輪廓-2的子元素(或者相反,也可以認為輪廓-2是輪廓-2a的父元素)。所以我們就假設讓它在第1層。類似地,輪廓-3是輪廓-2的子元素,因此它在下一個層次結構中。最後,輪廓4,5是輪廓-3a 的子結點,它們位於最後一個層次結構級。根據我們給盒子編號的方式,我們可以說輪廓-4是輪廓-3a 的第一個子元素(當然也可以是輪廓-5)。

在這我們提到這些是為了理解同級別的術語,外部輪廓,子輪廓,父輪廓,第一個子輪廓等等。現在讓我們進入OpenCV。

OpenCV 中的層級表示

所以每個輪廓都有它自己的信息,比如關於它是什麼層次結構,誰是它的子結點,誰是它的父結點等等。OpenCV 將這些信息表示為一個包含四個值的數組:[Next, Previous, First_Child, Parent]。

Next 表示同一層次上的下一個輪廓。「Next denotes next contour at the same hierarchical level.」

舉個例子,比如我們照片中的contour-0,誰是同level 中的下一個輪廓?很明顯,是contour-1。所以我們可以直接代入 Next = 1。對於Contour-1也差不多,下一個是contour-2 ,所以Next = 2。

那麼contour-2 呢?從圖中我們可以看出同一水平中沒有下一個輪廓存在了。所以也很簡單,我們直接說 Next = -1。那麼contour-4 呢?圖中它與contour-5處於同一水平,所以它的下個輪廓是contour-5,也就是說 Next = 5。

Previous 表示同一層次上的前一個輪廓。「Previous denotes previous contour at the same hierarchical level.」

和上面講的一樣,輪廓contour-1 在同一水平上的前一個輪廓就是contour-0。類似地,contour-2 同一層次上的前一個輪廓是 contour-1。contour-0 則沒有,所以我們說是-1。

First_Child表示它的第一個子輪廓。"First_Child denotes its first child contour.」

這裡我認為不需要額外的解釋。contour-2的「孩子」- 子輪廓,從圖中可以看出是contour-2a,所以它被稱作contour-2a,2a 即對應的索引值。那麼contour-3a呢?它有兩個孩子,但是我們第一個孩子只有一個,我們取其中一個位contour-4,所以contour-3a 的第一個孩子表達式為 First_Child = 4。

Parent表示該輪廓的父母輪廓的指數。 「Parent denotes index of its parent contour.」

這就是First_Child 的相反。無論是contour-4 還是 contour-5,它們的父母輪廓是 contour-3a 輪廓。對於contour-3a,它的父母輪廓就是contour-3。

注意:如果某輪廓沒有子輪廓或父母輪廓,那麼該欄位(field)標記為-1。

現在我們知道了OpenCV 使用的層次結構樣式,我們可以學習下一部分OpenCV 的輪廓檢索模式可以在一幅圖像上給我們什麼樣的幫助。舉個例子,下面將要講到的cv2.RETR_LIST, cv2.RETR_TREE, cv2.RETR_CCOMP, cv2.RETR_EXTERNAL 等flags 都代表什麼意思呢?

輪廓檢索模式

  1. RETR_LIST

從解釋的角度來看,這是我們將要介紹的四個標誌(flags)中最簡單的一個。它能幫助我們檢索所有的輪廓,但不產生任何父母或子關係。在這個模式下,父母輪廓和子輪廓是平等的,即他們只是輪廓,在這它們都處於相同層次結構的級別。

所以在這裡,第3個和第4個術語的層次結構數組總是-1。但顯然,下一個Next 和前一個Previous 術語則有相應不同的值。我們可以自行檢查和驗證。

下面是我得到的結果,每一行是對應輪廓的層次細節。例如,第一行對應輪廓0,它的下一個輪廓對應輪廓1,所以Next = 1,而其沒有前一個輪廓,所以Previous = -1。至於剩下的兩個父母輪廓和子輪廓,正如之前說的,因為「在這個模式下,父母輪廓和子輪廓是平等的」都沒有,所以都是-1。

>>> hierarchy
# 得到結果如下,每一行代表[Next, Previous, First_Child, Parent]
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[ 3, 1, -1, -1],
[ 4, 2, -1, -1],
[ 5, 3, -1, -1],
[ 6, 4, -1, -1],
[ 7, 5, -1, -1],
[-1, 6, -1, -1]]])

如果我們不需要使用層次的任何特性,這模式是一個不錯的選擇。

2. RETR_EXTERNAL

如果我們使用這個標誌,它只返回最外部標誌,其餘所有子輪廓都被遺棄。我們可以說,依照該模式的規定,在每個家庭中只有老大是被關心的,而其他家庭成員則被無視了 :(

所以在我們的圖像中,有多少最外部輪廓呢?比如在hierarchy-0 水平的?只有3個,即輪廓0,1,2,對把?現在讓我們試試找到帶著最外部標誌的輪廓。這裡給出的每個元素的值也與上面得到的一樣。我們可以和上面得到的結果進行比較,下面是我得到的答案:

>>> hierarchy
# 就是前面答案的前三行
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])

如果我們只想提取最外部輪廓,我們就可以使用這個標誌。在某些情況下它也能幫上大忙。

3. RETR_CCOMP

這個標誌可以檢索所有的輪廓並把它們安排到2種層次結構。舉個例子,我們整個物體的外部輪廓(即它的邊界)可以放置在hierarchy-1 層次,而物體內部的輪廓對象(如果有的話,我們的圖像中有)放在hierarchy-2 層次。如果在這個物體裏還存在任何對象,一樣的道理,該對象的輪廓被放置在hierarchy-1層次,內部的輪廓對象放置在hierarchy-2層次;直至裡面沒有對象為止。

我們可以想像一個「白色的大大的零」處於一個黑色背景裏。零的外圓屬於第一層次結構,它的內圈則屬於第二層次。

我們可以用一個簡單圖像來解釋。我們在圖中,用紅色輪廓標記了它們的原有層次結構順序,而它們的RETR_CCOMP標記我們則用綠色輪廓(1或2)來標記。

這裡讓我們先考慮第一個輪廓,即輪廓-0,是在hierarchy-1層次結構中。它有兩個內孔,輪廓1和2,屬於層次2。所以對於輪廓-0,同一層次結構中的下一個輪廓是輪廓-3,且不存在之前一個輪廓;它的第一個子輪廓是層次2中的輪廓-1,沒有父結點因為它在第1層的關係。因此,在這裡它的層次數組是[3,-1,1,-1]。

接著我們來看輪廓-1,它在層次2中。同一層次結構中的下一個輪廓(在contour-1的父級之下)是contour-2,沒有前一個輪廓,沒有子結點,父結點是-0,數組是[2,-1,-1,0]。

類似的,我們來看輪廓-2,它在層次2中。在輪廓-0下,同一層次中沒有下一個輪廓,所以沒有Next,前一個輪廓是contour-1,沒有子結點,父結點是輪廓-0,所以數組是[- 1,1,1,0]。

然後是輪廓- 3:層次1中的Next是輪廓-5,前一個是contour-0,子輪廓是contour-4,沒有父母輪廓為空,數組是[5,0,4,-1]。

對於輪廓- 4:它在輪廓-3下的層次結構2中,沒有兄弟,沒有下一個,沒有上一個,沒有子結點,父結點是contour-3。數組是[-1,-1,-1,3]。

剩下的輪廓同理加滿即可,這是我最後得到的答案:

>>> hierarchy
# 檢索所有的輪廓並把它們安排到2種層次結構中
array([[[ 3, -1, 1, -1],
[ 2, -1, -1, 0],
[-1, 1, -1, 0],
[ 5, 0, 4, -1],
[-1, -1, -1, 3],
[ 7, 3, 6, -1],
[-1, -1, -1, 5],
[ 8, 5, -1, -1],
[-1, 7, -1, -1]]])

4. RETR_TREE

這是四種檢索模式中的最後一個了,也稱其為完美先生。它檢索所有輪廓並創建完整的家族層次結構列表。它能告訴我們,誰是爺爺、爸爸、兒子、孫子甚至更遠的關係…… :)

舉個例子,我還是用上面的圖片,重寫一次代碼,這次我們使用cv2.RETR_TREE,別的不變;結果就是這次代碼幫助我們按OpenCV 的結果和分析重新排序了輪廓。同樣的,紅色字母代表輪廓序列,綠色字母代表等級序列。

我們以contour-0為例:它處於hierarchy-0 層次。下一個相同層次結構的輪廓是contour-7,沒有前一個輪廓,子輪廓是contour-1,沒有父母輪廓。所以我們得到數組[7,-1,1,-1]。

我們再舉contour-2為例:它處於hierarchy-1 層次。在相同級別中沒有輪廓,沒有前一個輪廓,子輪廓是contour-2,父母輪廓是contour-0。所以數組為[-1,-1,2, 0]。

至於剩餘的,大家可以自己試試。下面是完整的答案:

>>> hierarchy
array([[[ 7, -1, 1, -1],
[-1, -1, 2, 0],
[-1, -1, 3, 1],
[-1, -1, 4, 2],
[-1, -1, 5, 3],
[ 6, -1, -1, 4],
[-1, 5, -1, 4],
[ 8, 0, -1, -1],
[-1, 7, -1, -1]]])


在下一篇文章中,將著重講的是新的一個系列 - 新視界-OpenCV教程(23)- 直方圖:查找,繪製,分析。

如果你覺得我的文章有用,順手點個贊,關注下我的專欄或則留下你的評論吧!


推薦閱讀:
相關文章