OpenCV---分水嶺算法

推文:

OpenCV學習(7) 分水嶺算法(1)(原理簡介簡單明瞭)

OpenCV-Python教程:31.分水嶺算法對圖像進行分割(步驟講解不錯)

使用分水嶺算法進行圖像分割

(一)獲取灰度圖像,二值化圖像,進行形態學操做,消除噪點

def watershed_demo(image):
    blur = cv.pyrMeanShiftFiltering(image,10,100)
    gray = cv.cvtColor(blur,cv.COLOR_BGR2GRAY)  #獲取灰度圖像
    ret,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)  #將圖像轉爲黑色和白色部分
    cv.imshow("binary",binary)  #獲取二值化圖像

    #形態學操做,進一步消除圖像中噪點
    kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
    mb = cv.morphologyEx(binary,cv.MORPH_OPEN,kernel,iterations=2)  #iterations連續兩次開操做,消除圖像的噪點

(二)在距離變換前加上一步操做:經過對上面形態學去噪點後的圖像,進行膨脹操做,能夠獲得大部分都是背景的區域(原黑色不是咱們須要的部分是背景)

    sure_bg = cv.dilate(mb,kernel,iterations=3) #3次膨脹,能夠獲取到大部分都是背景的區域

(三)使用距離變換distanceTransform獲取肯定的前景色

根據distanceTransform獲取距離背景最小距離的結果(詳細看下面相關知識補充) 根據distanceTransform操做的結果,設置一個閾值,使用threshold決定哪些區域是前景,這樣獲得正確結果的機率很高
    dist = cv.distanceTransform(mb,cv.DIST_L2,5)  #獲取距離數據結果
    ret, sure_fg = cv.threshold(dist,dist.max()*0.6,255,cv.THRESH_BINARY)  #獲取前景色


相關知識補充(重點)

(1)距離變換原理html

推文:圖像識別中距離變換的原理及做用詳解,並附用OpenCV中的distanceTransform實現距離變換的代碼!(距離變換的定義講得不錯)算法

距離變換的處理圖像一般都是二值圖像,而二值圖像其實就是把圖像分爲兩部分,即背景和物體兩部分,物體一般又稱爲前景目標!
一般咱們把前景目標的灰度值設爲255,即白色
背景的灰度值設爲0,即黑色。
因此定義中的非零像素點即爲前景目標,零像素點即爲背景。
因此圖像中前景目標中的像素點距離背景越遠那麼距離就越大,若是咱們用這個距離值替換像素值,那麼新生成的圖像中這個點越亮

再經過設定合理的閾值對距離變換後的圖像進行二值化處理,則可獲得去除手指的圖像(以下圖「bidist」窗口圖像所示),手掌重心即爲該圖像的幾何中心。

(2)distanceTransform函數數組

主要用於計算非零像素到最近零像素點的最短距離。通常用於求解圖像的骨骼
def distanceTransform(src, distanceType, maskSize, dst=None, dstType=None): # real signature unknown; restored from __doc__
src:輸入的圖像,通常爲二值圖像
distanceType:所用的求解距離的類型,有CV_DIST_L1, CV_DIST_L2 , or CV_DIST_C
mask_size:距離變換掩模的大小,能夠是 35. 對 CV_DIST_L1 或 CV_DIST_C 的狀況,參數值被強制設定爲 3, 由於 3×3 mask 給出 5×5 mask 同樣的結果,並且速度還更快。

(3)如果想骨骼顯示(對咱們的分水嶺流程無影響),咱們須要對distanceTransform返回的結果進行歸一化處理,使用normalize函數

由於distanceTransform返回的圖像數據是浮點數值,要想在浮點數表示的顏色空間中,數值範圍必須是0-1.0,因此要將其中的數值進行歸一化處理
(重點)在整數表示的顏色空間中,數值範圍是0-255,但在浮點數表示的顏色空間中,數值範圍是0-1.0,因此要把0-255歸一化。
順便補充:如果不作歸一化處理,數值大於1的都會變爲1.0處理
mb = cv.morphologyEx(binary,cv.MORPH_OPEN,kernel,iterations=2)  #iterations連續兩次開操做
    cv.imshow("mb", mb)  #這是咱們形態學開操做過濾噪點後的圖像,暫時能夠看作源圖像
    #距離變換
    dist = cv.distanceTransform(mb,cv.DIST_L2,5)  #這是咱們獲取的字段距離數值,對應每一個像素都有,因此數組結構和圖像數組一致
    cv.imshow("dist",dist)
    dist_output = cv.normalize(dist,0,1.0,cv.NORM_MINMAX)  #歸一化的距離圖像數組
  cv.imshow("distinct-t",dist_output*50)

發現了彷佛distanceTransform返回的圖像和源圖像同樣,彷佛出錯了
緣由:由於distanceTransform返回的是浮點型色彩空間,而dist中存放的數距離0值的最小距離,大可能是大於1.0的數值,
而上面提到浮點型色彩空間數值範圍0-1.0,當數值大於1.0都會被設置爲1.0,顯示白色,因此和原來的二值化圖像一致,
咱們要想顯示骨骼,必須先進行歸一化處理
下面是從二值化圖像源,distanceTransform距離數組,和歸一化距離數組中獲取的一段像素數組
print(mb[150][120:140])   print(dist[150][120:140]) print(dist_output[150][120:140])
整數型色彩空間二值化圖像
[ 0 0 0 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255 255 255]

浮點型色彩空間最小距離數組,因爲數值大於1.0都會被設置爲1.0,因此和上面二值化圖像一致
[ 0.        0.        0.        0.        0.        0.        0.
  0.        0.        1.        1.4       2.1969    3.1969    4.1969
  5.1969    6.1969    7.1969    8.196899  9.196899 10.187599]

浮點型色彩空間歸一化數組圖像,顯示骨骼
[0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.00047065 0.0006589  0.00103396
 0.00150461 0.00197525 0.00244589 0.00291654 0.00338719 0.00385783
 0.00432847 0.00479474]

(四)在獲取了背景區域和前景區域(其實前景區域是咱們的種子,咱們將從這裏進行灌水,向四周漲水,可是這個須要在markers中表示)後,這兩個區域中有未重合部分(注1)怎麼辦?首先肯定這些區域(尋找種子)

注1:
這裏是求取硬幣偏白色,使用THRESH_BINARY,因此咱們獲取對象是白色區域,是獲取未重合部分
如果咱們求取樹葉等偏黑,須要使用THRESH_BINARY_INV,此時咱們獲取的對象是黑色區域,就變爲了獲取重合部分了

 開始獲取未知區域unknown(柵欄會建立在這一區域),爲下一步獲取種子作準備

    surface_fg = np.uint8(sure_fg)  #保持色彩空間一致才能進行運算,如今是背景空間爲整型空間,前景爲浮點型空間,因此進行轉換
    unknown = cv.subtract(sure_bg,surface_fg)
    cv.imshow("unkown",unknown)
使用print查看背景前景色彩空間不一樣
    print(sure_fg[150][120:140])
    print(sure_bg[150][120:140])
---------------------------------------
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[  0   0   0   0   0   0 255 255 255 255 255 255 255 255 255 255 255 255
 255 255]

 (五)獲取了這些區域,咱們能夠獲取種子,這是經過connectedComponents實現,獲取masker標籤,肯定的前景區域會在其中顯示爲以1開始的數據,這就是咱們的種子,會從這裏開始漫水


推文:http://m.imooc.com/article/32675

推文:基於矩陣實現的Connected Components算法post

利用connectedComponents求圖中的連通圖

重點:

如今知道了那些是背景那些是硬幣(肯定的前景區域)了。
那咱們就能夠建立標籤(一個與原圖像大小相同,數據類型爲 in32 的數組,並標記其中的區域了。
對咱們已經肯定分類的區域(不管是前景仍是背景)使用不一樣的正整數標記,對咱們不肯定的區域(unknown區域)使用 0 標記
咱們可使用函數 cv2.connectedComponents()來作這件事。
它會把對標籤進行操做,將背景標記爲 0,其餘的對象使用從 1 開始的正整數標記(其實這就是咱們的種子,水漫時會從這裏漫出)。而後將這個標籤返回給咱們markers 可是,咱們知道若是背景標記爲 0,那分水嶺算法就會把它當成未知區域了。(咱們要將未知區域標記爲0,因此咱們要將背景區域變爲其餘整數,例如+1
因此咱們想使用不一樣的整數標記它們。
而對不肯定的區域(函數cv2.connectedComponents 輸出的結果中使用 unknown 定義未知區域)標記爲 0

#獲取mask
ret,markers
= cv.connectedComponents(surface_fg)  

函數原型:學習

def connectedComponents(image, labels=None, connectivity=None, ltype=None): # real signature unknown; restored from __doc__

參數:

參數image是須要進行連通域處理的二值圖像,其餘的這裏用不到

返回值:ui

ret是連通域處理的邊緣條數,是上面提到的肯定區域(出去背景外的其餘肯定區域:就是前景),就是種子數,咱們會從種子開始向外漲水 markers是咱們建立的一個標籤(一個與原圖像大小相同,數據類型爲 in32 的數組),其中包含有咱們原圖像的確認區域的數據(前景區域)

查看部分markers:(0表明的是背景色,)

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  #0是咱們的背景區域
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2  #像這些以1開始的整數就是咱們肯定的前景區域,就是咱們要找的種子
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

(六)根據未知區域unknown在markers中設置柵欄,並將背景區域加入種子區域,一塊兒漫水

注意:

watershed漫水算法須要咱們將柵欄區域設置爲0,因此咱們須要將markers中背景區域(原來爲0,會干擾算法)設置爲其餘整數。
解決方法將markers總體加一  #此時種子區域不止咱們原來的前景區域,有增長了一個背景區域,咱們將從這些區域一塊兒灌水
    markers = markers + 1
    markers[unknown==255] = 0

(七)根據種子開始漫水,讓水漫起來找到最後的漫出點(柵欄邊界),越過這個點後各個山谷中水開始合併。注意watershed會將找到的柵欄在markers中設置爲-1

    markers = cv.watershed(image,markers=markers)  #獲取柵欄
    image[markers==-1] = [0,0,255]  #根據柵欄,咱們對原圖像進行操做,對柵欄區域設置爲紅色

 markers再次查看

[-1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1 -1 -1 -1 -1 -1 -1 -1  #漫水算法會將找到的柵欄設置爲-1
 -1 -1 -1 -1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1 -1  2  2  2  2  2  2  2  2  2  2
  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2 -1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1 -1]

(八)結果查看

(九)所有代碼

import cv2 as cv
import numpy as np

def watershed_demo(image):
    blur = cv.pyrMeanShiftFiltering(image,10,100)
    gray = cv.cvtColor(blur,cv.COLOR_BGR2GRAY)  #獲取灰度圖像

    ret,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)
    #形態學操做,進一步消除圖像中噪點
    kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
    mb = cv.morphologyEx(binary,cv.MORPH_OPEN,kernel,iterations=2)  #iterations連續兩次開操做
    sure_bg = cv.dilate(mb,kernel,iterations=3) #3次膨脹,能夠獲取到大部分都是背景的區域
    cv.imshow("sure_bg",sure_bg)
    #距離變換
    dist = cv.distanceTransform(mb,cv.DIST_L2,5)
    cv.imshow("dist",dist)
    dist_output = cv.normalize(dist,0,1.0,cv.NORM_MINMAX)
    # print(mb[150][120:140])
    # print(dist[150][120:140])
    # print(dist_output[150][120:140])
    cv.imshow("distinct-t",dist_output*50)
    ret, sure_fg = cv.threshold(dist,dist.max()*0.6,255,cv.THRESH_BINARY)
    cv.imshow("sure_fg",sure_fg)
    # print(sure_fg[150][120:140])
    # print(sure_bg[150][120:140])
    #獲取未知區域
    surface_fg = np.uint8(sure_fg)  #保持色彩空間一致才能進行運算,如今是背景空間爲整型空間,前景爲浮點型空間,因此進行轉換
    unknown = cv.subtract(sure_bg,surface_fg)
    cv.imshow("unkown",unknown)
    #獲取maskers,在markers中含有種子區域
    ret,markers = cv.connectedComponents(surface_fg)
    #print(ret)

    #分水嶺變換
    markers = markers + 1
    markers[unknown==255] = 0

    markers = cv.watershed(image,markers=markers)
    image[markers==-1] = [0,0,255]

    cv.imshow("result",image)

src = cv.imread("./c.png")  #讀取圖片
cv.namedWindow("input image",cv.WINDOW_AUTOSIZE)    #建立GUI窗口,形式爲自適應
cv.imshow("input image",src)    #經過名字將圖像和窗口聯繫

watershed_demo(src)

cv.waitKey(0)   #等待用戶操做,裏面等待參數是毫秒,咱們填寫0,表明是永遠,等待用戶操做
cv.destroyAllWindows()  #銷燬全部窗口
相關文章
相關標籤/搜索