20180917【圖像分水嶺算法原理和代碼】

原理的理解上:

       灰度圖像類比成三維地勢圖:我們向每個山谷中灌入不同顏色的水,隨着水位的升高,不同山谷的水就會相遇並且匯合,爲了防止不同山谷的水匯合,我們需要在水即將匯合的部分建起堤壩。不停地灌水,就要不停的構築堤壩,直到所有的山峯都被水淹沒。這個時候,我們構築的堤壩就是對圖像的分割!

代碼分析:

                                                 

先從找到硬幣的近似估計開始。使用Otsy‘s二值化:

# -*- coding: utf-8 -*-
"""
Created on Sun Jan 19 12:10:49 2014
@author: duan
"""
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  將圖像從彩色變換爲灰度圖像

ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

cv2.THRESH_BINARY_INV:低於該閾值的像素值,轉換爲255

圖像閾值在P66)使用的方法是閾值處理,將灰度圖變換爲二值圖

cv2.threshold(灰度圖像,閾值,參數3,使用的不同觀點閾值確定方法)

其中參數3是:當像素值高於(有時候是低於)閾值時候,應該被賦予的新的像素值

那麼在斷碼段中,使用閾值確定方法爲otsu方法(多閾值),因此要將初始的閾值設置爲0。

函數的返回值裏,第一個ret是最優閾值,明顯在其他方法中並不需要使用這個值,thresh是結果圖像。

結果圖像如下:

              

現在要去掉圖像中的白噪聲:開運算=腐蝕+膨脹

所以我們現在知道靠近對象中心的區域肯定是前景,而遠離對象中心的區域肯定是背景。如圖:

              

# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
# sure background area
sure_bg = cv2.dilate(opening,kernel,iterations=3)
# Finding sure foreground area
# 距離變換的基本含義是計算一個圖像中非零像素點到最近的零像素點的距離,也就是到零像素點的最短距離
# 個最常見的距離變換算法就是通過連續的腐蝕操作來實現,腐蝕操作的停止條件是所有前景像素都被完全
# 腐蝕。這樣根據腐蝕的先後順序,我們就得到各個前景像素點到前景中心􅑗􂅥像素點的
# 距離。根據各個像素點的距離值,設置爲不同的灰度值。這樣就完成了二值圖像的距離變換
#cv2.distanceTransform(src, distanceType, maskSize)
# 第二個參數0,1,2 分別表示CV_DIST_L1, CV_DIST_L2 , CV_DIST_C
dist_transform = cv2.distanceTransform(opening,1,5)
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)

有關距離變換的講解,參考:https://blog.csdn.net/qq_18234121/article/details/82754187

得到下面經過距離變換以後的圖:

                       

現在知道了那些是背景那些是硬幣了。那我們就可以創建標籤(一個與原圖像大小相同,數據類型爲in32 的數組),並標記其中的區域了。對我們已經確定分類的區域(無論是前景還是背景)使用不同的正整數標記,對我們不確定的區域使用0 標記。我們可以使用函數cv2.connectedComponents()來做這件事。它會把將背景標記爲0,其他的對象使用從1 開始的正整數標記。但是,我們知道如果背景標記爲0,那分水嶺算法就會把它當成未知區域了。所以我們想使用不同的整數標記它們。而對不確定的區域(函數cv2.connectedComponents 輸出的結果中使用unknown 定義未知區域)標記爲0。

# Marker labelling
ret, markers1 = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers1+1
# Now, mark the region of unknown with zero
markers[unknown==255] = 0

結果使用JET 顏色地圖表示。深藍色區域爲未知區域。肯定是硬幣的區域使用不同的顏色標記。其餘區域就是用淺藍色標記的背景了。現在標籤準備好了。到最後一步:實施分水嶺算法了。標籤圖像將會被修改,邊界區域的標記將變爲-1.

markers3 = cv2.watershed(img,markers)
img[markers3 == -1] = [255,0,0]

結果如下。有些硬幣的邊界被分割的很好,也有一些硬幣之間的邊界分割的不好。

            

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章