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)距離變換原理

推文:圖像識別中距離變換的原理及作用詳解,並附用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算法

利用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是需要進行連通域處理的二值圖像,其他的這里用不到

返回值:

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()  #銷毀所有窗口

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM