【一文讀懂】Contours Hierarchy ——opencv邊界的繼承結構,表格的提取,表格孔洞處理,空心形狀結構的提取

【參考】opencv文檔
Contours Hierarchy

關於邊界的繼承結構,例如邊界的父子結構

使用 cv2.findContours(),我們需要選擇邊界回溯模式,例如:cv2.RETR_LIST 或者 cv2.RETR_TREE
函數得到的三個數組,第一個是圖片,第二個是邊界,第三個是繼承結構 hierarchy,一般我們都只用後面兩個。
形式如:
python:

contours, hierarchy = cv2.findContours(img, 
             cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

有時一些圖片對象在不同的位置上,但有時候,一些圖形會在另一些圖形之中,被包含在內部。
例如:黑色區域就被包含在白色區域中,而白色區域外面又有黑色區域,這樣就把外層稱爲是 父層(紅色),內層稱爲是孩子層(綠色)。
在這裏插入圖片描述
這樣一來,我們就可以找出不同的形狀間的關係了,例如我們可以分辨出一個邊界是如何和其他邊界聯繫的了,例如它是否是一些邊界的父邊界,或者是另一些邊界的孩子邊界,這就叫做hierarchy

例如:
在這裏插入圖片描述

說明:

在這張圖片裏,0,1,2邊界是最外層的邊界,稱爲0層(綠色),或者簡單地說,他們在同一層次上(沒有互相之間的包含關係)
下一層是2a,它是層2的子層,注意是白框內部的部分!我們這裏說的一直是邊界!!所以它是層1(粉色)。
類似的,邊界3是2a的子層,是層2(藍色),
3a是3的子層,是層3(紅色)
最後是4,5層,他們是3a層的子層,是最後一層,是層4(黃色)
在這裏插入圖片描述所以每個邊界都有關於它是哪一個層次的信息,記錄結構爲:[Next, Previous, First_Child, Parent]
[下一個,上一個,第一個孩子層,父層(上一層)]
參考java版本Java OpenCV findContours函數RETR_CCOMP輪廓順序

ds[0]->同層級的下一個輪廓(不存在爲-1)
ds[1]->同層級的上一個輪廓(不存在爲-1)
ds[2]->第一個子輪廓(不存在爲-1)
ds[3]->父輪廓(不存在爲-1)
例如:

對於邊界0,它的下一個同級邊界是邊界1,上一個同級輪廓沒有,第一個子輪廓沒有,沒有父輪廓,所以是
[1,-1,-1,-1]

邊界2的上一個同級邊界是邊界1,下一個同級邊界沒有,第一個子輪廓是2a,沒有父輪廓,所以是
[-1,1,2a,-1]

不同的邊界flag表示不一樣的意思:

cv2.RETR_LIST, cv2.RETR_TREE, cv2.RETR_CCOMP, cv2.RETR_EXTERNAL

1. RETR_LIST

父子結構都不管了,他們只是單純的邊界結構,他們都屬於同一層。所以得到的結果是:

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

2. RETR_EXTERNAL

這個模式只返回外層邊界,所有的子層都不要了
在這個規則下,只考慮“最老的人”,其他人全都不考慮。

    1 >>> hierarchy
    2 array([[[ 1, -1, -1, -1],
    3         [ 2,  0, -1, -1],
    4         [-1,  1, -1, -1]]])

當你只要外層邊界的時候,這個標誌位很有用。

3. RETR_CCOMP

這個標誌會返回全部的邊界,但是會把它們分爲兩層,可以算是一種簡化吧,例如:
在這裏插入圖片描述
用兩種顏色標誌就是下面這樣的:一層綠色一層粉色

在這裏插入圖片描述
所以可以看到只有兩層結構,要麼是外層要麼是裏層,

    1 >>> hierarchy
    2 array([[[ 3, -1,  1, -1],
    3                [ 2, -1, -1,  0],
    4                [-1,  1, -1,  0],
    5                [ 5,  0,  4, -1],
    6                [-1, -1, -1,  3],
    7                [ 7,  3,  6, -1],
    8                [-1, -1, -1,  5],
    9                [ 8,  5, -1, -1],
   10               [-1,  7, -1, -1]]])
注意:
ds[0]->同層級的下一個輪廓(不存在爲-1)
ds[1]->同層級的上一個輪廓(不存在爲-1)
ds[2]->第一個子輪廓(不存在爲-1)
ds[3]->父輪廓(不存在爲-1)

這裏爲什麼父層不是爲0或者-1呢?
因爲每層標誌都必須用真實標誌,所以層也要按照父子關係來,需要用每一層的真實標號。

如果只想顯示父層或者子層,可以通過層與層記錄之間的關係來達到,隔一層記錄一次,或者僅僅遍歷某層之間的上一個和下一個。

例如,

hierachy[0][0][0]開始,有上一個輪廓不存在,下一個輪廓是3,
所以查詢輪廓hierachy[0][0][3],可見[ 5, 0, 4, -1],同級別的下一個輪廓是5,查詢hierachy[0][0][5],下一個輪廓是hierachy[0][0][7] [ 8, 5, -1, -1],,下一個輪廓是hierachy[0][0][8], [-1, 7, -1, -1],下一個輪廓是-1不存在,得到了0,3,5,7,8,都是一層,和圖片相符,而且有子輪廓,是外層(父層)
同理,1,2,4,6是一層,都是子層(內層)

代碼實現:

OpenCV中findContours輪廓提取一個邊緣只對應的一個輪廓
opencv輪廓提取的時候,圖像中一條邊緣有查找到兩個輪廓。當然只提取最外輪廓是不會出現重複情況,但設置提取所有輪廓會出現兩個輪廓,對於利用得到的輪廓進一步處理帶來不必要的麻煩,所以需要從一條輪廓開始,然後找他的同級鏈,直到找到結尾。
這樣的處理方式就不會有雙層輪廓的問題了。

由於截圖有些噪點,邊框白色部分又比較小,顯示出來的效果一般。
在這裏插入圖片描述
當我把圖片反轉爲~img時,用於檢測表格:
在這裏插入圖片描述
表格檢測代碼:

import cv2


if __name__ =='__main__':
    image = cv2.imread('mask1.jpg')
    img = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    img = cv2.adaptiveThreshold(~img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -2) 
    contours,hierachy = cv2.findContours(img, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
    # 查找內層的例子:
    i=0
    hierach_id = 0
    inner_class =[]
    # 一直搜索到找到沒有子輪廓的結構,且注意搜索長度不能超過邊界數目
    while(hierachy[0][i][2]!=-1 and hierachy[0][i][1]==-1 and i<len(contours) and hierachy[0][i][3]!=-1):
        hierach_id = i
        i+=1
    print(hierach_id,hierachy[0][hierach_id])
    while(hierachy[0][hierach_id][0]!=-1):
        inner_class.append(contours[hierach_id])
        hierach_id=hierachy[0][hierach_id][0]
        print(hierach_id,hierachy[0][hierach_id])
    for cnt in inner_class: # 畫圖
        cv2.drawContours(image,[cnt],0,(122,122,0),3)
    cv2.imshow('img',image)
    cv2.waitKey(10000)

img = cv2.adaptiveThreshold(~img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -2)

改爲:

img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -2) 

也就是圖片黑白反轉回來就是檢測外側邊框了:
在這裏插入圖片描述
在這裏插入圖片描述

最後一個4. RETR_TREE

It even tells, who is the grandpa, father, son, grandson and even beyond… 😃.
也就說,保存了全部的層次結構,

例如:

在這裏插入圖片描述
得到的結構:

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

用途:

ds[0]->同層級的下一個輪廓(不存在爲-1)
ds[1]->同層級的上一個輪廓(不存在爲-1)
ds[2]->第一個子輪廓(不存在爲-1)
ds[3]->父輪廓(不存在爲-1)

這個其實就可以隨意diy使用了,想要哪層要哪層,
保存了全部的信息,
如果需要查找最裏面的孔洞,直接找

hierachy[0][i][2]==-1 and hierachy[0][i][1]==-1 

的層就行。

例如下面的圖片:
在這裏插入圖片描述這是用 RETR_CCMP標誌位得到的。但是,現在我不需要紅色標註的邊界,我需要綠色標註的邊界,也就是最小的方格子裏面的內容。
在這裏插入圖片描述如何獲取這些內容呢?
首先我們觀察到,他們都是最裏層,所以有:

# 無子輪廓                  無上一個輪廓,找到同輩的起點
hierachy[0][i][2]==-1 and hierachy[0][i][1]==-1 

找到第一個最內層,然後找尋它的同層就好了。

——理論上是這樣,然而經過反覆實驗:

在這裏插入圖片描述很明顯的其中有一些邊框是存在子結構的,
但是hierachy[0][i][2]也是-1,
可能這個標誌位的註釋和實現其實也是存在問題的吧。。

在這裏插入圖片描述即使用自己生成的基本無噪點的圖片也不行:
在這裏插入圖片描述
而且仔細看,還有兩條重複的邊緣,不知道是什麼意思。。。
可能還是得看opencv的源碼才能揭開這個未解之謎了。
不過好事情是,我們還有其他方法來提取這些小格子,例如,先近似爲矩形,利用cv2.approxPolyDP(contour, 3, True) # 趨近矩形 然後再計算是否有重疊,有重疊的就不是小格子,沒有重疊的就是最小的格子。
圖上我們就可以看到,格子之間不是重疊的,某些邊緣可能包含了這些格子,但恰巧這些邊緣都是我們不要的。
用這樣的辦法去除非小格子的部分。
還有許多其他辦法,我就不一一贅述了。
具體實現我做好了,由於項目相關,這個又比較簡單,我就不單獨寫了。

在這裏插入圖片描述
最內層搜索代碼:

import cv2


if __name__ =='__main__':
    image = cv2.imread('test2.jpg')
    img = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -2) 
    contours,hierachy = cv2.findContours(img, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
    # 查找最內層的例子:
    hierach_ids = []
    inner_class =[]
    # 一直搜索到找到沒有子輪廓的結構,且注意搜索長度不能超過邊界數目,且需要找到第一個,
    #    這樣的邊界可能有多個,因爲不存在包含的關係,需要完整的搜索。
    for i in range(len(contours)):
        if(hierachy[0][i][2]==-1 and hierachy[0][i][1]==-1):
            hierach_ids.append(i)
    
   
    for hierach_id in hierach_ids:
        print(hierachy[0][hierach_id])
        inner_class.append(contours[hierach_id])
        idx = hierachy[0][hierach_id][0]
        while(hierachy[0][idx][0]!=-1):
            inner_class.append(contours[idx])
            idx=hierachy[0][idx][0]
            print(hierachy[0][idx])
    for cnt in inner_class:
        cv2.drawContours(image,[cnt],0,(122,122,0),2)
    cv2.imshow('img',image)
    cv2.waitKey(10000)
    cv2.imwrite('save.png',image)

用contours做連通域掩膜

拓展連接:用contours做連通域掩膜
2020-05-22 02-32-37屏幕截圖
在這裏插入圖片描述

findContour的規律:

規律

只有將內部輪廓全部找出才繼續進行同級輪廓查找

結論
1. 不同層級的輪廓順序從裏到外
2. 同級輪廓根據輪廓最上像素點Y座標從大到小排序(圖片的從下到上),若存在Y座標相同情況下根據X座標從大到小排序(圖片的從右到左)
3. 若一個輪廓內有子輪廓,回先查找該輪廓的所有子輪廓後纔會繼續同級輪廓的查找。(有點像深搜)
4. RETR_TREE是分層的所以同層級的下一個輪廓(ds[0]),同層級的上一個輪廓(ds[1])需要注意是否爲同級輪廓
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章