生活随笔
收集整理的這篇文章主要介紹了
图像分割之(六)交叉视觉皮质模型(ICM)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我以前是不知道這個圖像分割方法的。之前有個朋友看到我之前圖像分割系列的博文,然后就和我說有這么一個東西。所以當時我就稍微看了下。主要還是參考下面這篇論文的,然后按照論文所說的算法自己實現了一部分的代碼(沒有實現熵的那部分)。
? ? ? ?牛建偉等,《基于修正交叉視覺皮質模型的圖像分割方法》,北京郵電大學學報,2010
? ? ? ?上述論文中提到,交叉視覺皮質模型(ICM)主要基于Eckhorn模型和Rybak模型演化而來,同時吸收了其他視覺模型的優點,是多種腦皮層模型交叉綜合的產物。ICM是一種簡化的脈沖耦合神經網絡(PCNN)模型。PCNN稱為第3代人工神經網絡。ICM由于保留了PCNN脈沖耦合、變閾值、同步脈沖發放等特性,十分適合圖像處理。哈哈,看不太懂,看似很先進的樣子。
?
一、ICM模型
? ? ? ?ICM是單層神經網絡,其單個神經元模型由接收、調制和脈沖產生三大部分組成,如圖所示:
???????直接拿圖像分割和公式來說明會好一點。下圖的公式(1~3)就構成了ICM模型。圖像中的每一個像素就是ICM模型的一個神經元,所以每個神經元的標號是兩維的,例如第(i, j)個神經元。這里有點意思的是,平時俺們的神經網絡都是把輸入圖像拉成一列當成神經網絡的輸入的,可以說是一維的,但ICM模型卻是2-D的神經元排布,這點當時好像可以給我點什么啟發的,但到現在,這點啟發對我來說還沒辦法讓我腦袋的某個神經元興奮。呵呵,扯遠了。
(1)接收部分:
? ? ? ?包括線性連接輸入和反饋輸入2部分,線性連接輸入接收局部相鄰神經元的輸入;反饋輸入接收來自外部的刺激輸入。對于一張圖像來說,每個像素就是一個神經元,表示為對應圖像上的位置(i,j),它的反饋輸入Sij實際上就是每個像素的像素值。像素的灰度值越大,那么這個神經元的反饋輸入越大。而對于圖像分割,我們還得考慮到鄰域像素的影響,一般來說,屬于同一個物體,它的顏色就很像,不同物體相接的地方,就是邊緣,相鄰像素的顏色一般差別蠻大的,所以這個信息對圖像分割來說異常重要。在這里,我們怎么用到這個信息呢?就是線性連接輸入W{Y}ij了。它反映的是這個神經元與相鄰神經元的連接強度,他們的顏色相似性越小,那么他們是同一個物體的可能性就越大,那么他們的連接強度就越大,線性連接輸入也就越大。如公式(6)所示,這里考慮的是3x3的鄰域。兩個神經元或者像素(i, j)和(k, l)距離越近,相似度越大,屬于同一個物體的可能性就越大,那么這個值越大。所以神經元(i, j)和(k, l)的連接權值Wij-kl=負灰度差異/兩點歐式距離。
(2)調制部分:
? ? ? ?調制部分將神經元的線性連接輸入和反饋輸入進行調制得到神經元的內部活動狀態。如公式(1)所示,Fij[n]表示第n個時間(也就是第n次迭代)的時候ICM模型的內部活動狀態值,這個值隨著迭代的進行不斷更新,它有個遺忘因子f。
(3)脈沖產生部分:
? ? ? ?脈沖產生部分比較內部活動狀態和動態閾值,若內部活動狀態超過動態閾值Eij[n],則產生脈沖。大則輸出1,小的輸出0,這樣輸入一幅圖像,就會輸出一幅同樣大小的二值圖,這個二值圖就是每一次迭代的分割結果,最好的情況,當然是目標全是1,背景像素全是0了。如公式(2)所示。每個神經元(每個像素)都會保存各自的內部活動狀態和各自的動態閾值。而且動態閾值是以公式(3)隨著迭代的進行而更新的。當神經元內部狀態值超過動態閾值時,輸出1個脈沖,此時神經元的閾值因反饋迅速提高,閾值高于內部狀態值,輸出為0。之后閾值開始衰減下降,當閾值低于內部狀態值,神經元再次點火而輸出脈沖,如此反復循環。顯然,點火的神經元又通過連接輸入而影響其他神經元的點火狀態。
? ? ? 上面說到,應用ICM進行圖像分割時,每個像素點對應1個神經元,每個神經元與鄰域神經元連接(一般領域取3×3),構成單層2維局部連接網絡。歸一化的像素值作為神經元的外部輸入,因而亮度大的像素點對應的神經元首先點火。有些首先點火神經元的鄰域神經元由于接收連接輸入,提高內部狀態值而提前點火,這種脈沖傳遞使亮度相似空間相鄰的神經元發出同步脈沖。同步發放脈沖的神經元稱為神經元集群,該神經元集群對應圖像上不同的區域。放脈沖輸出包含圖像的區域、紋理和邊緣等特征信息,可實現圖像分割。
?
二、代碼實現
? ? ? ?我的代碼是基于VS2010+ OpenCV 2.4.2的。代碼實現主要參考論文中的算法描述。但步驟5后面(通過計算原圖像與分割后圖像間的互信息量客觀評價圖像的分割效果)就沒有實現。具體代碼如下:
ICM4Segmentation.cpp
[cpp]?view plaincopy
?? ?? ?? ?? ?? ?? ?? ?? ?? #include?"opencv2/opencv.hpp"?? #include?<iostream>?? ?? using?namespace?std;?? using?namespace?cv;?? ?? int?main(?int?argc,?char**?argv?)?? {?? ?????? ????Mat?img?=?imread("hand.jpg");?? ????if?(img.empty())?? ????{?? ????????cout<<"Read?image?fail!"<<endl;?? ????????return?1;?? ????}?? ????imshow("input",?img);?? ?? ?????? ????const?int?MAX_ITERATION?=?20;?? ????float?h?=?20;?? ????float?delta?=?1?/?MAX_ITERATION;?? ????Mat?net?=?Mat::zeros(img.rows,?img.cols,?CV_32FC1);??????? ????Mat?output=?Mat::zeros(img.rows,?img.cols,?CV_32FC1);????? ????Mat?threshold?=?Mat::ones(img.rows,?img.cols,?CV_32FC1);?? ????Mat?image?=?Mat::zeros(img.rows,?img.cols,?CV_32FC1);????? ????vector<Mat>?iteraOutput;?? ?? ?????? ????Mat?temp;?? ????cvtColor(img,?temp,?CV_RGB2GRAY);?? ????double?maxP,?minP;?? ????minMaxLoc(temp,?&minP,?&maxP);?? ????temp.convertTo(image,?CV_32FC1,?1.0?/?(maxP?-?minP),?-?minP?/?(maxP?-?minP));?? ?? ?????? ????for?(int?k?=?1;?k?<=?MAX_ITERATION;?k++)?? ????{?? ????????cout<<"?Iteration:?"<<?k?<<endl;?? ?????????? ????????float?lamda?=?1.0?/?(MAX_ITERATION?+?k);?? ????????for?(int?i?=?1;?i?<?image.rows?-?1;?i++)?? ????????{?? ????????????for?(int?j?=?1;?j?<?image.cols?-?1;?j++)?? ????????????{?? ?????????????????? ????????????????float?weight?=?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?-?1,?j)))?+?? ????????????????????????????????(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?+?1,?j)))?+?? ????????????????????????????????(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i,?j?-?1)))?+?? ????????????????????????????????(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i,?j?+?1)))?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?-?1,?j?-?1)))?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?-?1,?j?+?1)))?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?+?1,?j?-?1)))?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?+?1,?j?+?1)));?? ?????????????????? ????????????????net.at<float>(i,?j)?=?net.at<float>(i,?j)?-?lamda?+?image.at<float>(i,?j)?+?weight;?? ?????????????????? ????????????????threshold.at<float>(i,?j)?=?threshold.at<float>(i,?j)?-?delta?+?h?*?output.at<float>(i,?j);?? ?????????????????? ????????????????output.at<float>(i,?j)?=?net.at<float>(i,?j)?>?threshold.at<float>(i,?j)???1.0?:?0.0;?? ????????????}?? ????????}?? ????????iteraOutput.push_back(255?*?output);?? ????}?? ?? ?????? ????Mat?showImg?=?Mat::zeros(img.rows?*?4,?img.cols?*?5,?CV_32FC1);??? ????for?(int?i?=?0;?i?<?4;?i++)?? ????{?? ????????for?(int?j?=?0;?j?<?5;?j++)?? ????????{?? ????????????Rect?roi(img.cols?*?j,?img.rows?*?i,?img.cols,?img.rows);?? ????????????resize(iteraOutput[i?*?4?+?j],?showImg(roi),?roi.size());?? ????????}?? ????}?? ????imshow("output",?showImg);?? ????waitKey(-1);?? ?? ????return?0;?? }??
三、結果
? ? ? 輸入的待分割圖像:
? ? 迭代20次,每次的迭代結果:
?
四、分析
? ? ? ?要分割的圖像挺簡單的,但分割出來的效果感覺不咋地(當然了,如果我的代碼有錯,還望大家指出,謝謝)。我在想,這個迭代其實是不是一個能量最小化的過程呢?如果不是能量最小化的過程,那么迭代的意義在哪呢?難道就像是在窮舉先驗分割結果,然后挑出一個好的嗎?那可不可以將其納入到某種能量最小化的框架里面,然后進行迭代優化呢?每次的迭代,能量都會變小,能量最小的時候,對應的就是最有的分割結果。就像一些圖割算法一樣。
? ? ? ?了解有限,學識有限,還望大家不吝指點。謝謝。
五、后話
? ? ? ?有位朋友發現了上面代碼的一個錯誤(感謝這位朋友),也就是論文的公式(4),計算weight的時候,我少乘了Y(n-1),然后修改后感覺分割效果更不咋的,哈哈。修改后代碼如下:
[cpp]?view plaincopy
?? ?? ?? ?? ?? ?? ?? ?? ?? #include?"opencv2/opencv.hpp"?? #include?<iostream>?? ?? using?namespace?std;?? using?namespace?cv;?? ?? int?main(?int?argc,?char**?argv?)?? {?? ?????? ????Mat?img?=?imread("hand.jpg");?? ????if?(img.empty())?? ????{?? ????????cout<<"Read?image?fail!"<<endl;?? ????????return?1;?? ????}?? ????imshow("input",?img);?? ?? ?????? ????const?int?MAX_ITERATION?=?20;?? ????float?h?=?20;?? ????float?delta?=?1?/?MAX_ITERATION;?? ????Mat?net?=?Mat::zeros(img.rows,?img.cols,?CV_32FC1);??????? ????Mat?output=?Mat::zeros(img.rows,?img.cols,?CV_32FC1);????? ????Mat?threshold?=?Mat::ones(img.rows,?img.cols,?CV_32FC1);?? ????Mat?image?=?Mat::zeros(img.rows,?img.cols,?CV_32FC1);????? ????vector<Mat>?iteraOutput;?? ?? ?????? ????Mat?temp;?? ????cvtColor(img,?temp,?CV_RGB2GRAY);?? ????double?maxP,?minP;?? ????minMaxLoc(temp,?&minP,?&maxP);?? ????temp.convertTo(image,?CV_32FC1,?1.0?/?(maxP?-?minP),?-?minP?/?(maxP?-?minP));?? ?? ?????? ????for?(int?k?=?1;?k?<=?MAX_ITERATION;?k++)?? ????{?? ????????cout<<"?Iteration:?"<<?k?<<endl;?? ?????????? ????????float?lamda?=?1.0?/?(MAX_ITERATION?+?k);?? ?? ????????Mat?preOutput;?? ????????if?(k?==?1)?? ????????????output.copyTo(preOutput);?? ????????else?? ????????????preOutput?=?iteraOutput[iteraOutput.size()-1];?? ?? ????????for?(int?i?=?1;?i?<?image.rows?-?1;?i++)?? ????????{?? ????????????for?(int?j?=?1;?j?<?image.cols?-?1;?j++)?? ????????????{?? ?????????????????? ????????????????float?weight?=?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?-?1,?j)))?*?preOutput.at<float>(i?-?1,?j)?+?? ????????????????????????????????(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?+?1,?j)))?*?preOutput.at<float>(i?+?1,?j)?+?? ????????????????????????????????(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i,?j?-?1)))?*?preOutput.at<float>(i,?j?-?1)?+?? ????????????????????????????????(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i,?j?+?1)))?*?preOutput.at<float>(i,?j?+?1)?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?-?1,?j?-?1)))?*?preOutput.at<float>(i?-?1,?j?-?1)?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?-?1,?j?+?1)))?*?preOutput.at<float>(i?-?1,?j?+?1)?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?+?1,?j?-?1)))?*?preOutput.at<float>(i?+?1,?j?-?1)?+?? ????????????????????????????????0.5?*?(1?-?abs(image.at<float>(i,?j)?-?image.at<float>(i?+?1,?j?+?1)))?*?preOutput.at<float>(i?+?1,?j?+?1);?? ?????????????????? ????????????????net.at<float>(i,?j)?=?net.at<float>(i,?j)?-?lamda?+?image.at<float>(i,?j)?+?weight;?? ?????????????????? ????????????????threshold.at<float>(i,?j)?=?threshold.at<float>(i,?j)?-?delta?+?h?*?output.at<float>(i,?j);?? ?????????????????? ????????????????output.at<float>(i,?j)?=?net.at<float>(i,?j)?>?threshold.at<float>(i,?j)???1.0?:?0.0;?? ????????????}?? ????????}?? ????????iteraOutput.push_back(255?*?output);?? ????}?? ?? ?????? ????Mat?showImg?=?Mat::zeros(img.rows?*?4,?img.cols?*?5,?CV_32FC1);??? ????for?(int?i?=?0;?i?<?4;?i++)?? ????{?? ????????for?(int?j?=?0;?j?<?5;?j++)?? ????????{?? ????????????Rect?roi(img.cols?*?j,?img.rows?*?i,?img.cols,?img.rows);?? ????????????resize(iteraOutput[i?*?4?+?j],?showImg(roi),?roi.size());?? ????????}?? ????}?? ????imshow("output",?showImg);?? ????waitKey(-1);?? ?? ????return?0;?? }??
效果圖如下:
總結
以上是生活随笔為你收集整理的图像分割之(六)交叉视觉皮质模型(ICM)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。