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操做的結果,設置一個閾值,使用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:距離變換掩模的大小,能夠是 3 或 5. 對 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]
注1:
這裏是求取硬幣偏白色,使用THRESH_BINARY,因此咱們獲取對象是白色區域,是獲取未重合部分
如果咱們求取樹葉等偏黑,須要使用THRESH_BINARY_INV,此時咱們獲取的對象是黑色區域,就變爲了獲取重合部分了
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]
推文:基於矩陣實現的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 的數組),其中包含有咱們原圖像的確認區域的數據(前景區域)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 #0是咱們的背景區域 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 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]
watershed漫水算法須要咱們將柵欄區域設置爲0,因此咱們須要將markers中背景區域(原來爲0,會干擾算法)設置爲其餘整數。
解決方法將markers總體加一 #此時種子區域不止咱們原來的前景區域,有增長了一個背景區域,咱們將從這些區域一塊兒灌水
markers = markers + 1 markers[unknown==255] = 0
markers = cv.watershed(image,markers=markers) #獲取柵欄 image[markers==-1] = [0,0,255] #根據柵欄,咱們對原圖像進行操做,對柵欄區域設置爲紅色
[-1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 #漫水算法會將找到的柵欄設置爲-1 -1 -1 -1 -1 1 1 1 1 1 1 1 1 1 1 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() #銷燬全部窗口