OpenCV圖像分割中的分水嶺算法原理與應用詳解
圖像分割是按照一定的原則,將一幅圖像分為若干個互不相交的小局域的過程,它是圖像處理中最為基礎的研究領域之一。目前有很多圖像分割方法,其中分水嶺算法是一種基于區(qū)域的圖像分割算法,分水嶺算法因實現(xiàn)方便,已經(jīng)在醫(yī)療圖像,模式識別等領域得到了廣泛的應用。
1.傳統(tǒng)分水嶺算法基本原理
分水嶺比較經(jīng)典的計算方法是L.Vincent于1991年在PAMI上提出的[1]。傳統(tǒng)的分水嶺分割方法,是一種基于拓撲理論的數(shù)學形態(tài)學的分割方法,其基本思想是把圖像看作是測地學上的拓撲地貌,圖像中每一像素的灰度值表示該點的海拔高度,每一個局部極小值及其影響區(qū)域稱為集水盆地,而集水盆地的邊界則形成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明。在每一個局部極小值表面,刺穿一個小孔,然后把整個模型慢慢浸人水中,隨著浸入的加深,每一個局部極小值的影響域慢慢向外擴展,在兩個集水盆匯合處構筑大壩如下圖所示,即形成分水嶺。
傳統(tǒng)分水嶺算法示意圖
然而基于梯度圖像的直接分水嶺算法容易導致圖像的過分割,產(chǎn)生這一現(xiàn)象的原因主要是由于輸入的圖像存在過多的極小區(qū)域而產(chǎn)生許多小的集水盆地,從而導致分割后的圖像不能將圖像中有意義的區(qū)域表示出來。所以必須對分割結果的相似區(qū)域進行合并。
[1]L.Vincent, P Soille. Watersheds in digital space: An efficientalgorithms based on immersion simulation[J]. IEEE Trans. on Pattern Analysisand Machine Intelligence, 1991, 13(6): 583-598.
2.改進的分水嶺算法基本原理
因為傳統(tǒng)分水嶺算法存在過分割的不足,OpenCV提供了一種改進的分水嶺算法,使用一系列預定義標記來引導圖像分割的定義方式。使用OpenCV的分水嶺算法cv::wathershed,需要輸入一個標記圖像,圖像的像素值為32位有符號正數(shù)(CV_32S類型),每個非零像素代表一個標簽。它的原理是對圖像中部分像素做標記,表明它的所屬區(qū)域是已知的。分水嶺算法可以根據(jù)這個初始標簽確定其他像素所屬的區(qū)域。傳統(tǒng)的基于梯度的分水嶺算法和改進后基于標記的分水嶺算法示意圖如下圖所示。
傳統(tǒng)基于梯度的分水嶺算法和基于標記的分水嶺算法原理圖
從上圖可以看出,傳統(tǒng)基于梯度的分水嶺算法由于局部最小值過多造成分割后的分水嶺較多。而基于標記的分水嶺算法,水淹過程從預先定義好的標記圖像(像素)開始,較好的克服了過度分割的不足。本質上講,基于標記點的改進算法是利用先驗知識來幫助分割的一種方法。因此,改進算法的關鍵在于如何獲得準確的標記圖像,即如何將前景物體與背景準確的標記出來。
3.基于標記點的分水嶺算法應用
基于標記點的分水嶺算法應用步驟
● 封裝分水嶺算法類
● 獲取標記圖像
獲取前景像素,并用255標記前景
獲取背景像素,并用128標記背景,未知像素,使用0標記
合成標記圖像
● 將原圖和標記圖像輸入分水嶺算法
● 顯示結果
(1)封裝分水嶺算法類
將分水嶺算法cv::watershed(image,markers)封裝進類WatershedSegmenter,并保存為頭文件以便于操作。(本段封裝代碼參考《OpenCV計算機視覺編程攻略(第二版)》)
#if !defined WATERSHS #define WATERSHS #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> class WatershedSegmenter { private: cv::Mat markers; public: void setMarkers(const cv::Mat& markerImage) { // Convert to image of ints markerImage.convertTo(markers,CV_32S); } cv::Mat process(const cv::Mat &image) { // Apply watershed cv::watershed(image,markers); return markers; } // Return result in the form of an image cv::Mat getSegmentation() { cv::Mat tmp; // all segment with label higher than 255 // will be assigned value 255 markers.convertTo(tmp,CV_8U); return tmp; } // Return watershed in the form of an image以圖像的形式返回分水嶺 cv::Mat getWatersheds() { cv::Mat tmp; //在變換前,把每個像素p轉換為255p+255(在conertTo中實現(xiàn)) markers.convertTo(tmp,CV_8U,255,255); return tmp; } }; #endif
(2)獲取標記圖像
標記前景
讀取原圖
// Read input image cv::Mat image1= cv::imread("image.jpg"); if (!image1.data) return 0; // Display the color image cv::resize(image1, image1, cv::Size(), 0.7, 0.7); cv::namedWindow("Original Image1"); cv::imshow("Original Image1",image1);
原圖
以下代碼目的是獲取前景物體的像素,并用255標記。這里使用閾值分割初步分割前景和背景,接著使用形態(tài)學閉運算連接二值圖像中前景的各個部分,并平滑邊緣。如何更好的獲取前景像素,需要根據(jù)實際圖像的情況靈活處理。
// Identify image pixels with object Mat binary; cv::cvtColor(image1,binary,COLOR_BGRA2GRAY); cv::threshold(binary,binary,30,255,THRESH_BINARY_INV);//閾值分割原圖的灰度圖,獲得二值圖像 // Display the binary image cv::namedWindow("binary Image1"); cv::imshow("binary Image1",binary); waitKey(); // CLOSE operation cv::Mat element5(5,5,CV_8U,cv::Scalar(1));//5*5正方形,8位uchar型,全1結構元素 cv::Mat fg1; cv::morphologyEx(binary, fg1,cv::MORPH_CLOSE,element5,Point(-1,-1),1);// 閉運算填充物體內(nèi)細小空洞、連接鄰近物體 // Display the foreground image cv::namedWindow("Foreground Image"); cv::imshow("Foreground Image",fg1); waitKey();
閾值分割原圖像的灰度圖
閉運算獲取前景
標記背景和未知區(qū)域
在上面閾值分割得到的二值圖像binary的基礎上,通過對白色前景的深度膨脹運算獲得一個超過前景實際大小的物體,緊接著用反向閾值將深度膨脹后的圖像中的黑色部分轉換成128,即完成了對背景像素的標記。實際上,在0~255范圍內(nèi),任意不為0或255的值均可作為背景的標記。當然如果有其他類型的物體,可以使用另外一個數(shù)值作為其標記。也就是說,多個目標可以有多個標記來幫助分水嶺算法正確分割圖像。
// Identify image pixels without objects cv::Mat bg1; cv::dilate(binary,bg1,cv::Mat(),cv::Point(-1,-1),4);//膨脹4次,錨點為結構元素中心點 cv::threshold(bg1,bg1,1,128,cv::THRESH_BINARY_INV);//>=1的像素設置為128(即背景) // Display the background image cv::namedWindow("Background Image"); cv::imshow("Background Image",bg1); waitKey();
將背景設置為128,未知區(qū)域設置為0
合成標記圖像
將前景、背景及未知區(qū)域合成為一個標記圖像。則標記圖像中通過255標記前景物體,通過128標記背景,通過0標記未知區(qū)域。
//Get markers image Mat markers1 = fg1 + bg1; //使用Mat類的重載運算符+來合并圖像。 cv::namedWindow("markers Image"); cv::imshow("markers Image",markers1); waitKey();
標記圖像
(3)分水嶺算法分割圖像
將標記圖像和原圖輸入分水嶺算法封裝的類WatershedSegmenter,執(zhí)行分水嶺算法,并顯示算法運行的結果。
// Apply watershed segmentation WatershedSegmenter segmenter1; //實例化一個分水嶺分割方法的對象 segmenter1.setMarkers(markers1);//設置算法的標記圖像,使得水淹過程從這組預先定義好的標記像素開始 segmenter1.process(image1); //傳入待分割原圖 // Display segmentation result cv::namedWindow("Segmentation1"); cv::imshow("Segmentation1",segmenter1.getSegmentation());//將修改后的標記圖markers轉換為可顯示的8位灰度圖并返回分割結果(白色為前景,灰色為背景,0為邊緣) waitKey(); // Display watersheds cv::namedWindow("Watersheds1"); cv::imshow("Watersheds1",segmenter1.getWatersheds());//以圖像的形式返回分水嶺(分割線條) waitKey();
代碼segmenter1.process(image)將修改標記圖像markers,每個值為0的像素都會被賦予一個輸入標簽,而邊緣處的像素賦值為-1,得到的標簽圖像如下圖所示。
顯示分水嶺分割圖像
分水嶺分割線顯示
(4)顯示結果圖像
本步驟的目的是將前景物體的分割結果在黑/白底色中顯示出來。背景顏色由黑轉白時使用了Mat矩陣掃描的.ptr方法與指針運算。
// Get the masked image Mat maskimage = segmenter1.getSegmentation(); cv::threshold(maskimage,maskimage,250,1,THRESH_BINARY); cv::cvtColor(maskimage,maskimage,COLOR_GRAY2BGR); maskimage = image1.mul(maskimage); cv::namedWindow("maskimage"); cv::imshow("maskimage",maskimage); waitKey(); // Turn background (0) to white (255) int nl= maskimage.rows; // number of lines int nc= maskimage.cols * maskimage.channels(); // total number of elements per line for (int j=0; j<nl; j++) { uchar* data= maskimage.ptr<uchar>(j); for (int i=0; i<nc; i++) { // process each pixel --------------------- if (*data==0) //將背景由黑色改為白色顯示 *data=255; data++;//指針操作:如為uchar型指針則移動1個字節(jié),即移動到下1列 } } cv::namedWindow("result"); cv::imshow("result",maskimage); waitKey();
原圖的前景分割圖(黑色背景)
原圖的前景分割圖(白色背景)
從上圖的分割結果可以看出,基于標記圖像的分水嶺算法較好的實現(xiàn)了復雜背景下前景目標分割。算法應用的關鍵步驟為標記圖像的獲取,目前很多文獻提出了各類獲取標記圖像的方法,如何使用還需要根據(jù)所處理的圖像來量身確定。
貼出實驗原始圖像:)
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持我們。
上一篇:C語言實現(xiàn)學生信息管理系統(tǒng)(單鏈表)
欄 目:C語言
下一篇:C++實現(xiàn)企業(yè)職工工資管理系統(tǒng)
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/939.html
您可能感興趣的文章
- 01-10實現(xiàn)opencv圖像裁剪分屏顯示示例
- 01-10使用opencv拉伸圖像擴大分辨率示例
- 01-10C++基于Directx MMX實現(xiàn)的圖像灰度轉換代碼
- 01-10C++常用字符串分割方法實例匯總
- 01-10VC實現(xiàn)對話框窗口任意分割
- 01-10C++將CBitmap類中的圖像保存到文件的方法
- 01-10C語言中計算字符串長度與分割字符串的方法
- 01-10VC++中圖像處理類CBitmap的用法
- 01-10基于C++實現(xiàn)kinect+opencv 獲取深度及彩色數(shù)據(jù)
- 01-10淺談C語言的字符串分割


閱讀排行
本欄相關
- 04-02c語言函數(shù)調用后清空內(nèi)存 c語言調用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調用函數(shù)求fibo C語言調用函數(shù)求
隨機閱讀
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10C#中split用法實例總結
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 08-05織夢dedecms什么時候用欄目交叉功能?