https://www.cnblogs.com/zyly/p/9392881.htmlhtml
所謂圖像分割指的是根據灰度、顏色、紋理和形狀等特徵把圖像劃分紅若干互不交迭的區域,並使這些特徵在同一區域內呈現出類似性,而在不一樣區域間呈現出明顯的差別性。咱們先對目前主要的圖像分割方法作個概述,後面再對個別方法作詳細的瞭解和學習。算法
1、圖像分割算法概述
一、基於閾值的分割方法數組
閾值法的基本思想是基於圖像的灰度特徵來計算一個或多個灰度閾值,並將圖像中每一個像素的灰度值與閾值相比較,最後將像素根據比較結果分到合適的類別中。所以,該類方法最爲關鍵的一步就是按照某個準則函數來求解最佳灰度閾值。框架
二、基於邊緣的分割方法dom
所謂邊緣是指圖像中兩個不一樣區域的邊界線上連續的像素點的集合,是圖像局部特徵不連續性的反映,體現了灰度、顏色、紋理等圖像特性的突變。一般狀況下,基於邊緣的分割方法指的是基於灰度值的邊緣檢測,它是創建在邊緣灰度值會呈現出階躍型或屋頂型變化這一觀測基礎上的方法。函數
階躍型邊緣兩邊像素點的灰度值存在着明顯的差別,而屋頂型邊緣則位於灰度值上升或降低的轉折處。正是基於這一特性,可使用微分算子進行邊緣檢測,即便用一階導數的極值與二階導數的過零點來肯定邊緣,具體實現時可使用圖像與模板進行卷積來完成。學習
三、基於區域的分割方法ui
此類方法是將圖像按照類似性準則分紅不一樣的區域,主要包括種子區域生長法、區域分裂合併法和分水嶺法等幾種類型。spa
種子區域生長法是從一組表明不一樣生長區域的種子像素開始,接下來將種子像素鄰域裏符合條件的像素合併到種子像素所表明的生長區域中,並將新添加的像素做爲新的種子像素繼續合併過程,直到找不到符合條件的新像素爲止。該方法的關鍵是選擇合適的初始種子像素以及合理的生長準則。.net
區域分裂合併法(Gonzalez,2002)的基本思想是首先將圖像任意分紅若干互不相交的區域,而後再按照相關準則對這些區域進行分裂或者合併從而完成分割任務,該方法既適用於灰度圖像分割也適用於紋理圖像分割。
分水嶺法(Meyer,1990)是一種基於拓撲理論的數學形態學的分割方法,其基本思想是把圖像看做是測地學上的拓撲地貌,圖像中每一點像素的灰度值表示該點的海拔高度,每個局部極小值及其影響區域稱爲集水盆,而集水盆的邊界則造成分水嶺。該算法的實現能夠模擬成洪水淹沒的過程,圖像的最低點首先被淹沒,而後水逐漸淹沒整個山谷。當水位到達必定高度的時候將會溢出,這時在水溢出的地方修建堤壩,重複這個過程直到整個圖像上的點所有被淹沒,這時所創建的一系列堤壩就成爲分開各個盆地的分水嶺。分水嶺算法對微弱的邊緣有着良好的響應,但圖像中的噪聲會使分水嶺算法產生過度割的現象。
四、基於圖論的分割方法
此類方法把圖像分割問題與圖的最小割(min cut)問題相關聯。首先將圖像映射爲帶權無向圖G=<V,E>,圖中每一個節點N∈V對應於圖像中的每一個像素,每條邊∈E鏈接着一對相鄰的像素,邊的權值表示了相鄰像素之間在灰度、顏色或紋理方面的非負類似度。而對圖像的一個分割s就是對圖的一個剪切,被分割的每一個區域C∈S對應着圖中的一個子圖。而分割的最優原則就是使劃分後的子圖在內部保持類似度最大,而子圖之間的類似度保持最小。基於圖論的分割方法的本質就是移除特定的邊,將圖劃分爲若干子圖從而實現分割。目前所瞭解到的基於圖論的方法有GraphCut,GrabCut和Random Walk等。
五、基於能量泛函的分割方法
該類方法主要指的是活動輪廓模型(active contour model)以及在其基礎上發展出來的算法,其基本思想是使用連續曲線來表達目標邊緣,並定義一個能量泛函使得其自變量包括邊緣曲線,所以分割過程就轉變爲求解能量泛函的最小值的過程,通常可經過求解函數對應的歐拉(Euler.Lagrange)方程來實現,能量達到最小時的曲線位置就是目標的輪廓所在。按照模型中曲線表達形式的不一樣,活動輪廓模型能夠分爲兩大類:參數活動輪廓模型(parametric active contour model)和幾何活動輪廓模型(geometric active contour model)。
參數活動輪廓模型是基於Lagrange框架,直接以曲線的參數化形式來表達曲線,最具表明性的是由Kasset a1(1987)所提出的Snake模型。該類模型在早期的生物圖像分割領域獲得了成功的應用,但其存在着分割結果受初始輪廓的設置影響較大以及難以處理曲線拓撲結構變化等缺點,此外其能量泛函只依賴於曲線參數的選擇,與物體的幾何形狀無關,這也限制了其進一步的應用。
幾何活動輪廓模型的曲線運動過程是基於曲線的幾何度量參數而非曲線的表達參數,所以能夠較好地處理拓撲結構的變化,並能夠解決參數活動輪廓模型難以解決的問題。而水平集(Level Set)方法(Osher,1988)的引入,則極大地推進了幾何活動輪廓模型的發展,所以幾何活動輪廓模型通常也可被稱爲水平集方法。
2、圖像分割之GrabCut算法
這裏不去介紹GrabCut算法的原理,感興趣的童鞋去參考博客後面的文章。該算法主要基於如下知識:
-
k均值聚類
- 高斯混合模型建模(GMM)
- max flow/min cut
這裏介紹一些GrabCut算法的實現步驟:
- 在圖片中定義(一個或者多個)包含物體的矩形。
- 矩形外的區域被自動認爲是背景。
- 對於用戶定義的矩形區域,可用背景中的數據來區分它裏面的前景和背景區域。
- 用高斯混合模型(GMM)來對背景和前景建模,並將未定義的像素標記爲可能的前景或者背景。
- 圖像中的每個像素都被看作經過虛擬邊與周圍像素相鏈接,而每條邊都有一個屬於前景或者背景的機率,這是基於它與周邊像素顏色上的類似性。
- 每個像素(即算法中的節點)會與一個前景或背景節點鏈接。
- 在節點完成鏈接後(可能與背景或前景鏈接),若節點之間的邊屬於不一樣終端(即一個節點屬於前景,另外一個節點屬於背景),則會切斷他們之間的邊,這就能將圖像各部分分割出來。下圖能很好的說明該算法:
OpenCV提供了GrabCut算法相關的函數,grabCut函數:
grabCut(img,mask,rect,bgdModel,fgdModel,iterCount,mode )
輸入:圖像、被標記好的前景、背景
輸出:分割圖像
其中輸入的前景、背景指的是一種機率,若是你已經明確某一塊區域是背景,那麼它屬於背景的機率爲1;固然若是你以爲它有可能背景,可是沒有百分百的確定,這個時候你就要用到高斯模型,對其進行建模,而後估算機率。如今我如下圖爲例,用戶經過交互輸入框選區域,前景位於框選區域內,也就是說矩形區域外的所有屬於背景,且機率爲百分百。而後方框內可能屬於前景,機率須要用高斯混合建模求解。
參數說明:
- img——待分割的源圖像,必須是8位3通道,在處理的過程當中不會被修改
- mask——掩碼圖像,若是使用掩碼進行初始化,那麼mask保存初始化掩碼信息;在執行分割的時候,也能夠將用戶交互所設定的前景與背景保存到mask中,而後再傳入grabCut函數;在處理結束以後,mask中會保存結果。mask只能取如下四種值:
GCD_BGD(=0),背景;
GCD_FGD(=1),前景;
GCD_PR_BGD(=2),可能的背景;
GCD_PR_FGD(=3),可能的前景。
若是沒有手工標記GCD_BGD或者GCD_FGD,那麼結果只會有GCD_PR_BGD或GCD_PR_FGD;
- rect——用於限定須要進行分割的圖像範圍,只有該矩形窗口內的圖像部分才被處理;
- bgdModel——背景模型,若是爲None,函數內部會自動建立一個bgdModel;bgdModel必須是單通道浮點型圖像,且行數只能爲1,列數只能爲13x5;
- fgdModel——前景模型,若是爲None,函數內部會自動建立一個fgdModel;fgdModel必須是單通道浮點型圖像,且行數只能爲1,列數只能爲13x5;
- iterCount——迭代次數,必須大於0;
- mode——用於指示grabCut函數進行什麼操做,可選的值有:
GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
GC_INIT_WITH_MASK(=1),用掩碼圖像初始化GrabCut;
GC_EVAL(=2),執行分割。
接下來,咱們就演示上圖哪一個例子,把字符從圖片中摳出來:
# -*- coding: utf-8 -*- """ Created on Mon Jul 30 15:35:41 2018 @author: lenovo """ ''' 基於圖論的分割方法-GraphCut 【圖像處理】圖像分割之(一~四)GraphCut,GrabCut函數使用和源碼解讀(OpenCV) https://blog.csdn.net/kyjl888/article/details/78253829 ''' import numpy as np import cv2 #鼠標事件的回調函數 def on_mouse(event,x,y,flag,param): global rect global leftButtonDowm global leftButtonUp #鼠標左鍵按下 if event == cv2.EVENT_LBUTTONDOWN: rect[0] = x rect[2] = x rect[1] = y rect[3] = y leftButtonDowm = True leftButtonUp = False #移動鼠標事件 if event == cv2.EVENT_MOUSEMOVE: if leftButtonDowm and not leftButtonUp: rect[2] = x rect[3] = y #鼠標左鍵鬆開 if event == cv2.EVENT_LBUTTONUP: if leftButtonDowm and not leftButtonUp: x_min = min(rect[0],rect[2]) y_min = min(rect[1],rect[3]) x_max = max(rect[0],rect[2]) y_max = max(rect[1],rect[3]) rect[0] = x_min rect[1] = y_min rect[2] = x_max rect[3] = y_max leftButtonDowm = False leftButtonUp = True #讀入圖片 img = cv2.imread('image/img21.jpg') #掩碼圖像,若是使用掩碼進行初始化,那麼mask保存初始化掩碼信息;在執行分割的時候,也能夠將用戶交互所設定的前景與背景保存到mask中,而後再傳入grabCut函數;在處理結束以後,mask中會保存結果 mask = np.zeros(img.shape[:2],np.uint8) #背景模型,若是爲None,函數內部會自動建立一個bgdModel;bgdModel必須是單通道浮點型圖像,且行數只能爲1,列數只能爲13x5; bgdModel = np.zeros((1,65),np.float64) #fgdModel——前景模型,若是爲None,函數內部會自動建立一個fgdModel;fgdModel必須是單通道浮點型圖像,且行數只能爲1,列數只能爲13x5; fgdModel = np.zeros((1,65),np.float64) #用於限定須要進行分割的圖像範圍,只有該矩形窗口內的圖像部分才被處理; rect = [0,0,0,0] #鼠標左鍵按下 leftButtonDowm = False #鼠標左鍵鬆開 leftButtonUp = True #指定窗口名來建立窗口 cv2.namedWindow('img') #設置鼠標事件回調函數 來獲取鼠標輸入 cv2.setMouseCallback('img',on_mouse) #顯示圖片 cv2.imshow('img',img) while cv2.waitKey(2) == -1: #左鍵按下,畫矩陣 if leftButtonDowm and not leftButtonUp: img_copy = img.copy() #在img圖像上,繪製矩形 線條顏色爲green 線寬爲2 cv2.rectangle(img_copy,(rect[0],rect[1]),(rect[2],rect[3]),(0,255,0),2) #顯示圖片 cv2.imshow('img',img_copy) #左鍵鬆開,矩形畫好 elif not leftButtonDowm and leftButtonUp and rect[2] - rect[0] != 0 and rect[3] - rect[1] != 0: #轉換爲寬度高度 rect[2] = rect[2]-rect[0] rect[3] = rect[3]-rect[1] rect_copy = tuple(rect.copy()) rect = [0,0,0,0] #物體分割 cv2.grabCut(img,mask,rect_copy,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT) mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8') img_show = img*mask2[:,:,np.newaxis] #顯示圖片分割後結果 cv2.imshow('grabcut',img_show) #顯示原圖 cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()
一、上面代碼比較簡單,首先加載圖片,並建立一個與所加載圖像同形狀的掩模,並用0填充。
#讀入圖片 img = cv2.imread('image/img21.jpg') #掩碼圖像,若是使用掩碼進行初始化,那麼mask保存初始化掩碼信息;在執行分割的時候,也能夠將用戶交互所設定的前景與背景保存到mask中,而後再傳入grabCut函數;在處理結束以後,mask中會保存結果 mask = np.zeros(img.shape[:2],np.uint8)
二、建立以0填充的前景和背景模型。
#背景模型,若是爲None,函數內部會自動建立一個bgdModel;bgdModel必須是單通道浮點型圖像,且行數只能爲1,列數只能爲13x5; bgdModel = np.zeros((1,65),np.float64) #fgdModel——前景模型,若是爲None,函數內部會自動建立一個fgdModel;fgdModel必須是單通道浮點型圖像,且行數只能爲1,列數只能爲13x5; fgdModel = np.zeros((1,65),np.float64)
三、可使用數據填充這些模型,可是這裏準備使用一個標識出想要隔離的對象的矩形來初始化grabCut算法。因此背景和前景模型都要基於這個初始化矩形所留下來的區域來進行,這個矩形用下面代碼來定義:
#用於限定須要進行分割的圖像範圍,只有該矩形窗口內的圖像部分才被處理; rect = [0,0,0,0]
後面咱們使用鼠標回調事件來更新矩形框的帶下,當咱們鼠標左鍵按下的時候、開始在原始圖片上繪製矩形、當鼠標左鍵鬆開、矩形繪製完畢。
四、定義兩個表示位、表示鼠標左鍵的狀態
#鼠標左鍵按下 leftButtonDowm = False #鼠標左鍵鬆開 leftButtonUp = True
五、建立窗體、並設置鼠標回調函數、而後顯示源圖像
#指定窗口名來建立窗口 cv2.namedWindow('img') #設置鼠標事件回調函數 來獲取鼠標輸入 cv2.setMouseCallback('img',on_mouse) #顯示圖片 cv2.imshow('img',img)
六、鼠標回調事件代碼以下
#鼠標事件的回調函數 def on_mouse(event,x,y,flag,param): global rect global leftButtonDowm global leftButtonUp #鼠標左鍵按下 if event == cv2.EVENT_LBUTTONDOWN: rect[0] = x rect[2] = x rect[1] = y rect[3] = y leftButtonDowm = True leftButtonUp = False #移動鼠標事件 if event == cv2.EVENT_MOUSEMOVE: if leftButtonDowm and not leftButtonUp: rect[2] = x rect[3] = y #鼠標左鍵鬆開 if event == cv2.EVENT_LBUTTONUP: if leftButtonDowm and not leftButtonUp: x_min = min(rect[0],rect[2]) y_min = min(rect[1],rect[3]) x_max = max(rect[0],rect[2]) y_max = max(rect[1],rect[3]) rect[0] = x_min rect[1] = y_min rect[2] = x_max rect[3] = y_max leftButtonDowm = False leftButtonUp = True
七、循環部分,當鼠標左鍵按下、沒有鬆開則實時繪製矩形框。當左鍵鬆開、對圖像進行分割。
while cv2.waitKey(2) == -1: #左鍵按下,畫矩陣 if leftButtonDowm and not leftButtonUp: img_copy = img.copy() #在img圖像上,繪製矩形 線條顏色爲green 線寬爲2 cv2.rectangle(img_copy,(rect[0],rect[1]),(rect[0]+rect[2],rect[1]+rect[3]),(0,255,0),2) #顯示圖片 cv2.imshow('img',img_copy) #左鍵鬆開,矩形畫好 elif not leftButtonDowm and leftButtonUp and rect[0] != 0 and rect[1] != 0: rect_copy = tuple(rect.copy()) print(rect_copy) rect = [0,0,0,0] #物體分割 cv2.grabCut(img,mask,rect_copy,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT) mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8') img_show = img*mask2[:,:,np.newaxis] #顯示圖片分割後結果 cv2.imshow('grabcut',img_show) #顯示原圖 cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()
調用完grabCut函數以後,掩模圖像mask元素值已經變成了0~3之間的值。值爲0和2的將轉爲0,值爲1和3的將轉爲1,而後保存在mask2中,這樣就能夠用mask2過濾出全部的0值像素(理論上會保存全部的前景像素)。
3、圖像分割之分水嶺算法
分水嶺算法是在分割的過程當中,它會把跟臨近像素間的類似性做爲重要的參考依據,從而將在空間位置上相近而且灰度值相近(求梯度)的像素點互相鏈接起來構成一個封閉的輪廓。分水嶺算法經常使用的操做步驟:彩色圖像灰度化,而後再求梯度圖,最後在梯度圖的基礎上進行分水嶺算法,求得分段圖像的邊緣線。
下面左邊的灰度圖,能夠描述爲右邊的地形圖,地形的高度是有灰度圖的灰度值決定,灰度爲0對應地形圖的地面,灰度值最大的像素對應地形圖的最高點。
對灰度圖的地形圖的解釋,咱們考慮三類點:
- 局部最小點值,該點對應一個盆地的最低點,當咱們在盆地裏滴一滴水的時候,因爲重力做用,誰最終會匯聚到該點。注意:可能存在一個最小值面,該平面內的都是最小值點。
- 盆地的其餘位置點,該位置滴的水會匯聚到局部最小點。
- 盆地的邊緣點,是盆地和其餘盆地交界點,在該點滴一滴水,會等機率的流向任何一個盆地。
假設咱們在盆地的最小值點,打一個洞,而後往盆地裏面注水,並阻止兩個盆地的水匯聚,咱們會在兩個盆地的水聚集的時刻,在交界的邊緣上(即分水嶺線),建一個大壩,來阻止兩個盆地的水匯聚成一片水域。這樣圖像就被分紅2個像素集,一個是注水盆地像素集,一個是分水嶺線像素集。
在真實圖像中,因爲噪聲點或者其它干擾因素的存在,使得分水嶺算法經常出現過分分割的現象,這主要是由於圖像中可能存在不少很小的局部極小點的存在,對這些局部盆地進行分割會致使過度割。爲了解決過度割的問題,學者們提出了基於標記(mark)圖像的分水嶺算法,就是經過先驗知識,來指導分水嶺算法,以便得到更好的圖像分割效果。一般的mark圖像,都是在某個區域定義了一些灰度層級,在這個區域的洪水淹沒過程當中,水平面都是從定義的高度開始的,這樣能夠避免一些很小的噪聲極小值區域的分割。
下面咱們來學習一下OpenCV中提供的watershed函數:
watershed(image,markers)
參數說明:
- image:必須是一個8位 3通道彩色圖像
- markers:在執行分水嶺函數watershed以前,必須對參數markers進行處理,它應該包含不一樣區域的輪廓,每一個輪廓有一個本身惟一的編號,輪廓的定位能夠經過Opencv中findContours方法實現,這個是執行分水嶺以前的要求。
接下來執行分水嶺會發生什麼呢?算法會根據markers傳入的輪廓做爲種子(咱們把注水點由盆地的最小值點轉爲圖像的輪廓),對圖像上其餘的像素點根據分水嶺算法規則進行判斷,並對每一個像素點的區域歸屬進行劃定,直處處理完圖像上全部像素點。而區域與區域之間的分界處的值被置爲「-1」,以作區分。
簡單歸納一下就是說第二個入參markers必須包含了種子點信息。Opencv官方例程中使用鼠標劃線標記,其實就是在定義種子,只不過須要手動操做,而使用findContours能夠自動標記種子點。而分水嶺方法完成以後並不會直接生成分割後的圖像,還須要進一步的顯示處理,如此看來,只有兩個參數的watershed其實並不簡單。
分水嶺算法實現圖像自動分割的步驟:
- 圖像灰度化、Canny邊緣檢測
- 查找輪廓,而且把輪廓信息按照不一樣的編號繪製到watershed的第二個參數markers上,至關於標記注水點。
- watershed分水嶺算法
- 繪製分割出來的區域,而後使用隨機顏色填充,再跟源圖像融合,以獲得更好的顯示效果。
代碼以下:
# -*- coding: utf-8 -*- """ Created on Mon Jul 30 21:38:41 2018 @author: lenovo """ import numpy as np import cv2 #讀入圖片 img = cv2.imread('image/img22.jpg') #轉換爲灰度圖片 gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #canny邊緣檢測 函數返回一副二值圖,其中包含檢測出的邊緣。 canny = cv2.Canny(gray_img,80,150) cv2.imshow('Canny',canny) #尋找圖像輪廓 返回修改後的圖像 圖像的輪廓 以及它們的層次 canny,contours,hierarchy = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #32位有符號整數類型, marks = np.zeros(img.shape[:2],np.int32) #findContours檢測到的輪廓 imageContours = np.zeros(img.shape[:2],np.uint8) #輪廓顏色 compCount = 0 index = 0 #繪製每個輪廓 for index in range(len(contours)): #對marks進行標記,對不一樣區域的輪廓使用不一樣的亮度繪製,至關於設置注水點,有多少個輪廓,就有多少個輪廓 #圖像上不一樣線條的灰度值是不一樣的,底部略暗,越往上灰度越高 marks = cv2.drawContours(marks,contours,index,(index,index,index),1,8,hierarchy) #繪製輪廓,亮度同樣 imageContours = cv2.drawContours(imageContours,contours,index,(255,255,255),1,8,hierarchy) #查看 使用線性變換轉換輸入數組元素成8位無符號整型。 markerShows = cv2.convertScaleAbs(marks) cv2.imshow('markerShows',markerShows) #cv2.imshow('imageContours',imageContours) #使用分水嶺算法 marks = cv2.watershed(img,marks) afterWatershed = cv2.convertScaleAbs(marks) cv2.imshow('afterWatershed',afterWatershed) #生成隨機顏色 colorTab = np.zeros((np.max(marks)+1,3)) #生成0~255之間的隨機數 for i in range(len(colorTab)): aa = np.random.uniform(0,255) bb = np.random.uniform(0,255) cc = np.random.uniform(0,255) colorTab[i] = np.array([aa,bb,cc],np.uint8) bgrImage = np.zeros(img.shape,np.uint8) #遍歷marks每個元素值,對每個區域進行顏色填充 for i in range(marks.shape[0]): for j in range(marks.shape[1]): #index值同樣的像素表示在一個區域 index = marks[i][j] #判斷是否是區域與區域之間的分界,若是是邊界(-1),則使用白色顯示 if index == -1: bgrImage[i][j] = np.array([255,255,255]) else: bgrImage[i][j] = colorTab[index] cv2.imshow('After ColorFill',bgrImage) #填充後與原始圖像融合 result = cv2.addWeighted(img,0.6,bgrImage,0.4,0) cv2.imshow('addWeighted',result) cv2.waitKey(0) cv2.destroyAllWindows()
咱們對下面的圖像採用分水嶺算法進行分割:
而後咱們分析代碼:
左側是使用Canny邊緣檢測後獲得的二值化圖像,而後咱們對二值化圖像進行查找輪廓,並進行處理獲得符合要求的marks:
#尋找圖像輪廓 返回修改後的圖像 圖像的輪廓 以及它們的層次 canny,contours,hierarchy = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #32位有符號整數類型, marks = np.zeros(img.shape[:2],np.int32) #findContours檢測到的輪廓 imageContours = np.zeros(img.shape[:2],np.uint8) #輪廓顏色 compCount = 0 index = 0 #繪製每個輪廓 for index in range(len(contours)): #對marks進行標記,對不一樣區域的輪廓使用不一樣的亮度繪製,至關於設置注水點,有多少個輪廓,就有多少個輪廓 #圖像上不一樣線條的灰度值是不一樣的,底部略暗,越往上灰度越高 marks = cv2.drawContours(marks,contours,index,(index,index,index),1,8,hierarchy) #繪製輪廓,亮度同樣 imageContours = cv2.drawContours(imageContours,contours,index,(255,255,255),1,8,hierarchy)
而後咱們把marks轉換爲8位單通道灰度圖顯示,獲得上面的右圖,能夠看到圖像上不一樣輪廓的灰度值是不一樣的,底部略暗,越往上灰度越高。這些輪廓和不一樣的灰度值說明了什麼?
每個輪廓表明一個種子,輪廓的不一樣灰度值其實表明了對不一樣注水種子的編號,有多少不一樣灰度值的輪廓,就有多少個種子,圖像最後分割後就有多少個區域。
#查看 使用線性變換轉換輸入數組元素成8位無符號整型。 markerShows = cv2.convertScaleAbs(marks) cv2.imshow('markerShows',markerShows) #cv2.imshow('imageContours',imageContours)
再來看一下執行完分水嶺算法以後的marks(下面左圖)。
上面左圖爲分割出來的區域,咱們能夠看到,源圖像空間上臨近而且灰度值上相近的區域被劃分爲一個區域(同一區域的灰度值是同樣的),不一樣區域間被劃開。
#使用分水嶺算法 marks = cv2.watershed(img,marks) afterWatershed = cv2.convertScaleAbs(marks) cv2.imshow('afterWatershed',afterWatershed)
而後咱們使用顏色填充分割出來的區域,獲得上圖右邊的效果。
#生成隨機顏色 colorTab = np.zeros((np.max(marks)+1,3)) #生成0~255之間的隨機數 for i in range(len(colorTab)): aa = np.random.uniform(0,255) bb = np.random.uniform(0,255) cc = np.random.uniform(0,255) colorTab[i] = np.array([aa,bb,cc],np.uint8) bgrImage = np.zeros(img.shape,np.uint8) #遍歷marks每個元素值,對每個區域進行顏色填充 for i in range(marks.shape[0]): for j in range(marks.shape[1]): #index值同樣的像素表示在一個區域 index = marks[i][j] #判斷是否是區域與區域之間的分界,若是是邊界(-1),則使用白色顯示 if index == -1: bgrImage[i][j] = np.array([255,255,255]) else: bgrImage[i][j] = colorTab[index] cv2.imshow('After ColorFill',bgrImage)
咱們再把填充後的圖像與源圖像進行融合,獲得下面的效果:
#填充後與原始圖像融合 result = cv2.addWeighted(img,0.6,bgrImage,0.4,0) cv2.imshow('addWeighted',result) cv2.waitKey(0) cv2.destroyAllWindows()
[1]【圖像處理】圖像分割之(一~四)GraphCut,GrabCut函數使用和源碼解讀(OpenCV)
[2]圖像處理(十四)圖像分割(4)grab cut的圖割實現-Siggraph 2004
[4]圖像處理——分水嶺算法
[5]OpenCV庫中watershed函數(分水嶺算法)的詳細使用例程
[6]分水嶺算法及案例