溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

發(fā)布時(shí)間:2020-10-13 11:49:27 來源:腳本之家 閱讀:374 作者:iracer 欄目:編程語(yǔ)言

圖像分割是按照一定的原則,將一幅圖像分為若干個(gè)互不相交的小局域的過程,它是圖像處理中最為基礎(chǔ)的研究領(lǐng)域之一。目前有很多圖像分割方法,其中分水嶺算法是一種基于區(qū)域的圖像分割算法,分水嶺算法因?qū)崿F(xiàn)方便,已經(jīng)在醫(yī)療圖像,模式識(shí)別等領(lǐng)域得到了廣泛的應(yīng)用。

1.傳統(tǒng)分水嶺算法基本原理

分水嶺比較經(jīng)典的計(jì)算方法是L.Vincent于1991年在PAMI上提出的[1]。傳統(tǒng)的分水嶺分割方法,是一種基于拓?fù)淅碚摰臄?shù)學(xué)形態(tài)學(xué)的分割方法,其基本思想是把圖像看作是測(cè)地學(xué)上的拓?fù)涞孛玻瑘D像中每一像素的灰度值表示該點(diǎn)的海拔高度,每一個(gè)局部極小值及其影響區(qū)域稱為集水盆地,而集水盆地的邊界則形成分水嶺。分水嶺的概念和形成可以通過模擬浸入過程來說明。在每一個(gè)局部極小值表面,刺穿一個(gè)小孔,然后把整個(gè)模型慢慢浸人水中,隨著浸入的加深,每一個(gè)局部極小值的影響域慢慢向外擴(kuò)展,在兩個(gè)集水盆匯合處構(gòu)筑大壩如下圖所示,即形成分水嶺。

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

傳統(tǒng)分水嶺算法示意圖

然而基于梯度圖像的直接分水嶺算法容易導(dǎo)致圖像的過分割,產(chǎn)生這一現(xiàn)象的原因主要是由于輸入的圖像存在過多的極小區(qū)域而產(chǎn)生許多小的集水盆地,從而導(dǎo)致分割后的圖像不能將圖像中有意義的區(qū)域表示出來。所以必須對(duì)分割結(jié)果的相似區(qū)域進(jìn)行合并。
[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.改進(jìn)的分水嶺算法基本原理

因?yàn)閭鹘y(tǒng)分水嶺算法存在過分割的不足,OpenCV提供了一種改進(jìn)的分水嶺算法,使用一系列預(yù)定義標(biāo)記來引導(dǎo)圖像分割的定義方式。使用OpenCV的分水嶺算法cv::wathershed,需要輸入一個(gè)標(biāo)記圖像,圖像的像素值為32位有符號(hào)正數(shù)(CV_32S類型),每個(gè)非零像素代表一個(gè)標(biāo)簽。它的原理是對(duì)圖像中部分像素做標(biāo)記,表明它的所屬區(qū)域是已知的。分水嶺算法可以根據(jù)這個(gè)初始標(biāo)簽確定其他像素所屬的區(qū)域。傳統(tǒng)的基于梯度的分水嶺算法和改進(jìn)后基于標(biāo)記的分水嶺算法示意圖如下圖所示。

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

傳統(tǒng)基于梯度的分水嶺算法和基于標(biāo)記的分水嶺算法原理圖

從上圖可以看出,傳統(tǒng)基于梯度的分水嶺算法由于局部最小值過多造成分割后的分水嶺較多。而基于標(biāo)記的分水嶺算法,水淹過程從預(yù)先定義好的標(biāo)記圖像(像素)開始,較好的克服了過度分割的不足。本質(zhì)上講,基于標(biāo)記點(diǎn)的改進(jìn)算法是利用先驗(yàn)知識(shí)來幫助分割的一種方法。因此,改進(jìn)算法的關(guān)鍵在于如何獲得準(zhǔn)確的標(biāo)記圖像,即如何將前景物體與背景準(zhǔn)確的標(biāo)記出來。

3.基于標(biāo)記點(diǎn)的分水嶺算法應(yīng)用

基于標(biāo)記點(diǎn)的分水嶺算法應(yīng)用步驟

● 封裝分水嶺算法類

● 獲取標(biāo)記圖像

獲取前景像素,并用255標(biāo)記前景

獲取背景像素,并用128標(biāo)記背景,未知像素,使用0標(biāo)記

合成標(biāo)記圖像

● 將原圖和標(biāo)記圖像輸入分水嶺算法

● 顯示結(jié)果

(1)封裝分水嶺算法類

將分水嶺算法cv::watershed(image,markers)封裝進(jìn)類WatershedSegmenter,并保存為頭文件以便于操作。(本段封裝代碼參考《OpenCV計(jì)算機(jī)視覺編程攻略(第二版)》)

#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; 
    //在變換前,把每個(gè)像素p轉(zhuǎn)換為255p+255(在conertTo中實(shí)現(xiàn)) 
    markers.convertTo(tmp,CV_8U,255,255); 
 
    return tmp; 
   } 
}; 
#endif 

(2)獲取標(biāo)記圖像

標(biāo)記前景

讀取原圖

// 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); 

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

原圖

以下代碼目的是獲取前景物體的像素,并用255標(biāo)記。這里使用閾值分割初步分割前景和背景,接著使用形態(tài)學(xué)閉運(yùn)算連接二值圖像中前景的各個(gè)部分,并平滑邊緣。如何更好的獲取前景像素,需要根據(jù)實(shí)際圖像的情況靈活處理。

// 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結(jié)構(gòu)元素 
  cv::Mat fg1; 
  cv::morphologyEx(binary, fg1,cv::MORPH_CLOSE,element5,Point(-1,-1),1);// 閉運(yùn)算填充物體內(nèi)細(xì)小空洞、連接鄰近物體 
 
  // Display the foreground image 
  cv::namedWindow("Foreground Image"); 
  cv::imshow("Foreground Image",fg1); 
  waitKey(); 

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

閾值分割原圖像的灰度圖

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

閉運(yùn)算獲取前景

標(biāo)記背景和未知區(qū)域

在上面閾值分割得到的二值圖像binary的基礎(chǔ)上,通過對(duì)白色前景的深度膨脹運(yùn)算獲得一個(gè)超過前景實(shí)際大小的物體,緊接著用反向閾值將深度膨脹后的圖像中的黑色部分轉(zhuǎn)換成128,即完成了對(duì)背景像素的標(biāo)記。實(shí)際上,在0~255范圍內(nèi),任意不為0或255的值均可作為背景的標(biāo)記。當(dāng)然如果有其他類型的物體,可以使用另外一個(gè)數(shù)值作為其標(biāo)記。也就是說,多個(gè)目標(biāo)可以有多個(gè)標(biāo)記來幫助分水嶺算法正確分割圖像。

// Identify image pixels without objects 
   
  cv::Mat bg1; 
  cv::dilate(binary,bg1,cv::Mat(),cv::Point(-1,-1),4);//膨脹4次,錨點(diǎn)為結(jié)構(gòu)元素中心點(diǎn) 
  cv::threshold(bg1,bg1,1,128,cv::THRESH_BINARY_INV);//>=1的像素設(shè)置為128(即背景) 
  // Display the background image 
  cv::namedWindow("Background Image"); 
  cv::imshow("Background Image",bg1); 
  waitKey(); 

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

將背景設(shè)置為128,未知區(qū)域設(shè)置為0

合成標(biāo)記圖像

將前景、背景及未知區(qū)域合成為一個(gè)標(biāo)記圖像。則標(biāo)記圖像中通過255標(biāo)記前景物體,通過128標(biāo)記背景,通過0標(biāo)記未知區(qū)域。

//Get markers image 
 
  Mat markers1 = fg1 + bg1; //使用Mat類的重載運(yùn)算符+來合并圖像。 
  cv::namedWindow("markers Image"); 
  cv::imshow("markers Image",markers1); 
  waitKey(); 

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

標(biāo)記圖像

(3)分水嶺算法分割圖像

將標(biāo)記圖像和原圖輸入分水嶺算法封裝的類WatershedSegmenter,執(zhí)行分水嶺算法,并顯示算法運(yùn)行的結(jié)果。

// Apply watershed segmentation 
 
  WatershedSegmenter segmenter1; //實(shí)例化一個(gè)分水嶺分割方法的對(duì)象 
  segmenter1.setMarkers(markers1);//設(shè)置算法的標(biāo)記圖像,使得水淹過程從這組預(yù)先定義好的標(biāo)記像素開始 
  segmenter1.process(image1);   //傳入待分割原圖 
    
  // Display segmentation result 
  cv::namedWindow("Segmentation1"); 
  cv::imshow("Segmentation1",segmenter1.getSegmentation());//將修改后的標(biāo)記圖markers轉(zhuǎn)換為可顯示的8位灰度圖并返回分割結(jié)果(白色為前景,灰色為背景,0為邊緣) 
  waitKey(); 
    // Display watersheds 
  cv::namedWindow("Watersheds1"); 
  cv::imshow("Watersheds1",segmenter1.getWatersheds());//以圖像的形式返回分水嶺(分割線條) 
  waitKey(); 

代碼segmenter1.process(image)將修改標(biāo)記圖像markers,每個(gè)值為0的像素都會(huì)被賦予一個(gè)輸入標(biāo)簽,而邊緣處的像素賦值為-1,得到的標(biāo)簽圖像如下圖所示。

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

顯示分水嶺分割圖像

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

分水嶺分割線顯示

(4)顯示結(jié)果圖像

本步驟的目的是將前景物體的分割結(jié)果在黑/白底色中顯示出來。背景顏色由黑轉(zhuǎn)白時(shí)使用了Mat矩陣掃描的.ptr方法與指針運(yùn)算。

// 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型指針則移動(dòng)1個(gè)字節(jié),即移動(dòng)到下1列 
    } 
   } 
  cv::namedWindow("result"); 
  cv::imshow("result",maskimage); 
  waitKey(); 

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

原圖的前景分割圖(黑色背景)

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

原圖的前景分割圖(白色背景)

從上圖的分割結(jié)果可以看出,基于標(biāo)記圖像的分水嶺算法較好的實(shí)現(xiàn)了復(fù)雜背景下前景目標(biāo)分割。算法應(yīng)用的關(guān)鍵步驟為標(biāo)記圖像的獲取,目前很多文獻(xiàn)提出了各類獲取標(biāo)記圖像的方法,如何使用還需要根據(jù)所處理的圖像來量身確定。

OpenCV圖像分割中的分水嶺算法原理與應(yīng)用詳解

貼出實(shí)驗(yàn)原始圖像:)

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI