OpenCV分水嶺watershed的應用注意

在VS2010,OpenCV進行分水嶺的實現時。我遇到了一個問題:
在做好種子圖和背景圖後,也無法分隔開同一個背景框內的多個種子點。網上給的方法都是將背景點設置爲灰度128,種子點設置爲255,其他爲0。然後前景背景疊加作爲mark圖。再調用watershed(原圖,蒙版)。
但是,按照這個結果做出來的話,同一個128灰度的區域包圍的多個255灰度的種子區域無法被區分開。通過閱讀OpenCV源碼,我發現了原因。

/*-----劃分:如果其4鄰域只有一個集水區那就歸入其中,如果有多個那就標記爲分水嶺------*/  
        if( q[active_queue].first == 0 )    //當前層級處理完或無像素入隊,換下一層  
        {  
            for( i = active_queue+1; i < NQ; i++ )  
                if( q[i].first )    //換這層  
                    break;  
            if( i == NQ )   //退出大循環  
                break;  
            active_queue = i;   //下面給這一層的像素分配標號  
        }  
        //取一個點  
        ws_pop( active_queue, mofs, iofs );   

        m = mask + mofs;    //mask的值  
        ptr = img + iofs;   //img的值  
        //4鄰域只有一個區域,確定區域歸屬,要是處在多個集水區,成爲分水嶺  
        t = m[-1];  
        if( t > 0 )
            lab = t;  
        t = m[1];  
        if( t > 0 )  
        {  
            if( lab == 0 )
                lab = t;  
            else if( t != lab )
                lab = WSHED;  
        }  
        t = m[-mstep];  
        if( t > 0 )  
        {  
            if( lab == 0 )
                lab = t;  
            else if( t != lab )
                lab = WSHED;  
        }  
        t = m[mstep];  
        if( t > 0 )  
        {  
            if( lab == 0 )
                lab = t;  
            else if( t != lab )
                lab = WSHED;  
        }  
        assert( lab != 0 );  
        m[0] = lab;  
        if( lab == WSHED )  
            continue;   //繼續  

在這一部分可以看出,在設置分水嶺位置時,是通過比較上下左右四個點,是否有不爲0且不同的兩個灰度點,如果有,那麼設置爲WSHED(-1),也就是分水嶺位置。
引用一個比較易懂專業的描述:如果mask圖像中該像素的四鄰域中存在兩個不同的非0值,表示該點爲兩個注水盆地的邊緣,即分水嶺線,在mark圖像中標記該點爲-1;否則在mask圖像中標記該點爲四鄰域中大於0的那個值(如果有多個大於0的值,則按照左右上下的順序取)。
同理,對於同一個128灰度區域包圍的幾個不同的種子點區域,他們的灰度值要設置的不同,那樣在擴張不同水區域接觸的時候才能夠分開。
其實,什麼前景背景對於watershed函數來說根本就沒有區別,就是不同灰度值能夠表示不同的注水點。以這些注水點爲起點開始分水嶺算法的實現,而不是之前所想的128的背景圖部分是不動的。

同樣需要注意的還有,opencv自帶的分水嶺算法的漲水方式和傳統的漲水方式不同,在一片論文上我看到有對OpenCV分水嶺算法進行改進,指出OpenCV分水嶺算法在判斷下一步對何種像素點進行漲水時,依據的是已經被標記爲-2,也就是mark邊緣和mark邊緣的像素差的絕對值的大小。將其放入q隊列(0-255個位置分別記錄對應灰度值大小的點的位置)中,再從小到大搜尋q隊列,對找到的第一個灰度進行漲水。這個就會導致一些過漫水的問題。
那篇論文名稱爲:《OpenCV分水嶺算法的改進及其在細胞分割中的應用_張羽》,我按照這篇論文中提出的方法進行對源碼的修改了,效果也不盡人意,如果哪位看到這篇文章併成功修改,效果也不錯,請務必聯繫我。(如果這篇論文的作者看到了,也請幫助一下我這個初學者,具體應該如何修改。)
以下附上不同灰度種子改前改後的結果

這裏寫圖片描述

這裏寫圖片描述
其實最後的結果也不是很好,很有可能是我種子點的給出以及原圖像有點太差。不過意思到位了,大家看懂就行。
錯誤定義mark圖展示
這裏寫圖片描述
這裏就是把所有的種子點的像素都設置成了255,就會出現上面第一幅圖的結果。也就是沒分開。

2018.8.21更新
我突然發現在我2017.5.31的實驗室組內彙報的時候,我的總結裏有一個根據別人的方法那裏學來的新的方法進行改進的想法,就是使用距離變換與分水嶺結合的方法,在分水嶺出來一部分結果之後,給出局部最小,再加上些新的東西使它能夠一定程度上區分開連通域。
在此聲明這個方法是我在http://www.it165.net/pro/html/201312/8297.html看到的。我講的肯定沒有人家的好,我在這簡單闡述一下我的改進過程和結果。

OpenCV分水嶺算法描述如下
初始化mark矩陣,生成最初的注水區域。
1. 設置mark圖像的邊框值爲-1
2. 標記每個mark區域的邊界爲-2
3. 對於mark圖像一個像素值,如果它本身值爲0,但上下左右四鄰域有一個像素值大於0,則把img圖像中該點按照RGB高度值(實際上是梯度值)放入優先隊列q的對應位置。
其中q是這樣的一個數據結構:它是一個0到255的數組,每個元素分別是一個隊列的頭。當往q的同一個位置添加高度值時,就是找到它的位置,再往對應隊列的後面添加元素。
之前的OpenCV分水嶺結果在上面可以看到。
而Matlab:
Watershed在Matlab中直接輸入圖片,進行分水嶺計算,原理是自動尋找像素值局部最低的地方作爲注水口,然後開始注水,基於分水嶺算法原理直接輸出結果。
讀入一張圖
filename=’4-HF開運算後.bmp’;
bw = imread(filename);
這裏寫圖片描述
距離變換反變換
D = -bwdist(~bw);
這裏寫圖片描述
直接分水嶺的話
Ld = watershed(D);
下圖爲原圖和分水嶺結果疊加圖
這裏寫圖片描述
這樣的結果中包含了不少過分割。原因和上面提到的一樣,局部最小太多。對於這種情況,通用的技巧是,在基於watershed的圖像分割中使用imextendedmin這個函數過濾掉一些特別小(指區域小)的局部最小。然後,我們修改距離變換的結果,讓濾波後的區域不會出現局部最小值,這個操作叫做“強制最小(minima imposition)”,可以用imimposemin這個函數實現。

使用imextendedmin將會只在我們希望分割的區塊中間產生小點。然後我們使用imshowpair來將模板疊加到原圖上。

mask = imextendedmin(D,2);
imshowpair(bw,mask,’blend’)

imextendedmin函數的介紹:
功能:擴展極小值變換。
用法:BW = imextendedmin(I,h)
D爲制定矩陣,2爲保留最小的幾個值的點,以下分別爲2,3,4,6的結果
這裏寫圖片描述
計算擴展的極小值變換,這是在H -極小區域極小變換。H是一個非負標量。
區域極小是以恆定強度值連通的像素,其外部邊界的像素都具有較高的價值。
默認情況下,imextendedmin使用二維圖像8鄰域,和三維圖像26鄰域
對於高維,imextendedmin使用conndef(ndims(I),’maximal’).
BW = imextendedmin(I,h,conn)
計算擴展極小值變換,在conn指定連接,conn可以有以下任何標值。
值 含義
二維的連接
4 4鄰域
8 8鄰域
三維的連接
6 6鄰域
18 18鄰域
26 26鄰域
連接可以被定義爲更普遍方式的任何維度通過了conn 3-by-3-by-…-by-3的0’s 和 1’s 矩陣。1值元素定義相對位置附近的conn的中心內容,注意,conn必須和它的中心元素對稱。
這裏寫圖片描述
最後,我們我們修改距離變換的結果,讓其只在想要的位置具有局部最小。然後進行watershed。這就是我們想要的結果了。
D2 = imimposemin(D,mask);
Ld2 = watershed(D2);
bw3 = bw;
bw3(Ld2 == 0) = 0;
imshow(bw3)
imimposemin
功能:施加極小值
用法:I2 = imimposemin(I,BW)
使用形態重構修改強度圖像I,使得它在BW非零的地方只有區域極小值。
BW是一個二值圖像,和I的尺寸相同。(一般選用剛纔imextendedmin的輸出)
默認情況下,imimposemin對二維圖像使用8連通域,對三維圖像使用26連通域。
對於更高維數的情況,imimposemin使用conndef( ndims (I), ’minimum’)。
I2 = imimposemin(I,BW,conn)

D2 = imimposemin(D,mask);運算前後D,D2的輸出結果
這裏寫圖片描述
效果已經算是理想,和我能想到的最好結果一樣

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