OpenCV-Python 圖像分割與Watershed算法

目標

在本章中,

  • 我們將學習使用分水嶺算法實現基於標記的圖像分割
  • 我們將看到:cv.watershed()

理論

任何灰度圖像都可以看作是一個地形表面,其中高強度表示山峰,低強度表示山谷。你開始用不同顏色的水(標籤)填充每個孤立的山谷(局部最小值)。隨著水位的上升,根據附近的山峰(坡度),來自不同山谷的水明顯會開始合併,顏色也不同。為了避免這種情況,你要在水融合的地方建造屏障。你繼續填滿水,建造障礙,直到所有的山峰都在水下。然後你創建的屏障將返回你的分割結果。這就是Watershed背後的“思想”。你可以訪問Watershed的CMM網頁,瞭解它與一些動畫的幫助。

但是這種方法會由於圖像中的噪聲或其他不規則性而產生過度分割的結果。因此OpenCV實現了一個基於標記的分水嶺算法,你可以指定哪些是要合併的山谷點,哪些不是。這是一個交互式的圖像分割。我們所做的是給我們知道的對象賦予不同的標籤。用一種顏色(或強度)標記我們確定為前景或對象的區域,用另一種顏色標記我們確定為背景或非對象的區域,最後用0標記我們不確定的區域。這是我們的標記。然後應用分水嶺算法。然後我們的標記將使用我們給出的標籤進行更新,對象的邊界值將為-1。

代碼

下面我們將看到一個有關如何使用距離變換和分水嶺來分割相互接觸的對象的示例。

考慮下面的硬幣圖像,硬幣彼此接觸。即使你設置閾值,它也會彼此接觸。

OpenCV-Python 圖像分割與Watershed算法 | 三十四

我們先從尋找硬幣的近似估計開始。因此,我們可以使用Otsu的二值化。

<code>import numpy as npimport cv2 as cvfrom matplotlib import pyplot as pltimg = cv.imread('coins.png')gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)/<code>
OpenCV-Python 圖像分割與Watershed算法 | 三十四

現在我們需要去除圖像中的任何白點噪聲。為此,我們可以使用形態學擴張。要去除對象中的任何小孔,我們可以使用形態學侵蝕。因此,現在我們可以確定,靠近對象中心的區域是前景,而離對象中心很遠的區域是背景。我們不確定的唯一區域是硬幣的邊界區域。

因此,我們需要提取我們可確定為硬幣的區域。侵蝕會去除邊界像素。因此,無論剩餘多少,我們都可以肯定它是硬幣。如果物體彼此不接觸,那將起作用。但是,由於它們彼此接觸,因此另一個好選擇是找到距離變換並應用適當的閾值。接下來,我們需要找到我們確定它們不是硬幣的區域。為此,我們擴張了結果。膨脹將對象邊界增加到背景。這樣,由於邊界區域已刪除,因此我們可以確保結果中背景中的任何區域實際上都是背景。參見下圖。

OpenCV-Python 圖像分割與Watershed算法 | 三十四

剩下的區域是我們不知道的區域,無論是硬幣還是背景。分水嶺算法應該找到它。這些區域通常位於前景和背景相遇(甚至兩個不同的硬幣相遇)的硬幣邊界附近。我們稱之為邊界。可以通過從sure_bg區域中減去sure_fg區域來獲得。

<code># 噪聲去除kernel = np.ones((3,3),np.uint8)opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)# 確定背景區域sure_bg = cv.dilate(opening,kernel,iterations=3)# 尋找前景區域dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)# 找到未知區域sure_fg = np.uint8(sure_fg)unknown = cv.subtract(sure_bg,sure_fg)/<code>

查看結果。在閾值圖像中,我們得到了一些硬幣區域,我們確定它們是硬幣,並且現在已分離它們。(在某些情況下,你可能只對前景分割感興趣,而不對分離相互接觸的對象感興趣。在那種情況下,你無需使用距離變換,只需侵蝕就足夠了。侵蝕只是提取確定前景區域的另一種方法。)

OpenCV-Python 圖像分割與Watershed算法 | 三十四

現在我們可以確定哪些是硬幣的區域,哪些是背景。因此,我們創建了標記(它的大小與原始圖像的大小相同,但具有int32數據類型),並標記其中的區域。我們肯定知道的區域(無論是前景還是背景)都標有任何正整數,但是帶有不同的整數,而我們不確定的區域則保留為零。為此,我們使用cv.connectedComponents()。它用0標記圖像的背景,然後其他對象用從1開始的整數標記。

但是我們知道,如果背景標記為0,則分水嶺會將其視為未知區域。所以我們想用不同的整數來標記它。相反,我們將未知定義的未知區域標記為0。

<code># 類別標記ret, markers = cv.connectedComponents(sure_fg)# 為所有的標記加1,保證背景是0而不是1markers = markers+1# 現在讓所有的未知區域為0markers[unknown==255] = 0/<code>

參見JET colormap中顯示的結果。深藍色區域顯示未知區域。當然,硬幣的顏色不同。剩下,肯定為背景的區域顯示在較淺的藍色,跟未知區域相比。

OpenCV-Python 圖像分割與Watershed算法 | 三十四

現在我們的標記已準備就緒。現在是最後一步的時候了,使用分水嶺算法。然後標記圖像將被修改。邊界區域將標記為-1。

<code>markers = cv.watershed(img,markers) img[markers == -1] = [255,0,0]/<code>

請參閱下面的結果。對某些硬幣,它們接觸的區域被正確地分割,而對於某些硬幣,卻不是。

OpenCV-Python 圖像分割與Watershed算法 | 三十四

附加資源

  1. CMM page on http://cmm.ensmp.fr/~beucher/wtshed.html

練習

  1. OpenCV samples has an interactive sample on watershed segmentation, watershed.py. Run it, Enjoy it, then learn it.


分享到:


相關文章: