close

分水嶺方法是基於拓撲理論的一種分割方法, 簡單講是將圖像每一點像素視為海拔高度(elevation), 每一個局部極小值及其鄰近區域為集水盆地(catchment basin), 當水面逐漸上升時, 下圖中的兩個集水盆地會變成同一個新的大集水盆地, 如果在兩個集水盆地匯合處建構一個水壩, 即得到所謂分水嶺(watershed)

 

1-D Watershed示意圖[1]

image

3-D Watershed示意圖[2]

image

  • Minimum(最小值)[3]:這裡最小值定義為connected component analysis中像素值較鄰近像素值低的聯通區塊
  •  
  • Markers(物件標籤)[4]: 如同connected component analysis 的作法, 會將相連通區域的像素標記相同的號碼, 而每一塊標籤可以表示為前景物件或背景物件
  • Gradient image(梯度影像): 輸入和原始影像一樣大小的梯度影像, 資料型態為灰階(8-bit, 0~255)。爲得到圖像的邊緣資訊,通常會進行下列運算,g(x,y)= grad( f(x,y) ),其中f(x,y)表示原始圖像,grad{.}表示梯度運算。
  • Marker image(標籤影像): 使用者可以在一張灰階影像中任意繪製一段曲線, 用來標記(marker)這是某一個前景物或背景物內的一條曲線,一般會設定該曲線所在位置的值為一個負數常數
  •  
  •  
  •  
  •  
  •  
  •  
  • 載入一張測試圖sample.jpg

image

在兩個想要切割的物件(object 1object 2)內分別用滑鼠拖曳出兩條曲線當marker

image

圖為原始彩色影像+兩條markers

image

下圖為兩個物件的markers

image

下面一張動畫也可以幫助理解分水嶺分割的精神[5], 紅色區域為三個物件的markers(第1張圖),圖2為flooding迭代過程 迭代完成後可以找到watershed(第3張圖)

=======================================================================

以下參考C:\OpenCV-2.4.7\opencv\sources\samples\cpp\watershed.cpp及部分修改, 附上註解筆記

操作說明help()

   1: // Win32ConsoleVS2010OpenCV247.cpp : 定義主控台應用程式的進入點。
   2: //
   3: #include "stdafx.h"
   4: #include <opencv2/core/core.hpp>
   5: #include "opencv2/imgproc/imgproc.hpp"
   6: #include <opencv2/highgui/highgui.hpp>
   7: #include <cstdio>
   8: #include <iostream>
   9: //#define DEBUG
  10: using namespace cv;
  11: using namespace std;
  12:  
  13: static void help()
  14: {
  15:     cout << "\nThis program demonstrates the famous watershed segmentation algorithm in OpenCV: watershed()\n"
  16:         "Usage:\n"
  17:         "./watershed [image_name -- default is fruits.jpg]\n" << endl;
  18:  
  19:  
  20:     cout << "Hot keys: \n"
  21:         "\tESC - quit the program\n"
  22:         "\tr - restore the original image\n"
  23:         "\tw or SPACE - run watershed segmentation algorithm\n"
  24:         "\t\t(before running it, *roughly* mark the areas to segment on the image)\n"
  25:         "\t  (before that, roughly outline several markers on the image)\n";
  26: }
  27: Mat markerMask, img;                                              
  28: Point prevPt(-1, -1);
  •  
  •  
  • 滑鼠事件偵測onMouse()
   1: static void onMouse( int event, int x, int y, int flags, void* )
   2: {
   3:     if( x < 0 || x >= img.cols || y < 0 || y >= img.rows )                 // 若x<0或x>=影寬或y<0或y>=影高, 離開
   4:         return;
   5:     if( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) )  // 左鍵up 或 非左鍵, prevPt重置(-1,-1)
   6:         prevPt = Point(-1,-1);
   7:     else if( event == CV_EVENT_LBUTTONDOWN )                               // 按下左鍵(down), 取得目前滑鼠座標(x,y)
   8:         prevPt = Point(x,y);
   9:     else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON) ) // 按下左鍵同時滑鼠移動
  10:     {
  11:         Point pt(x, y);
  12:         if( prevPt.x < 0 )                                                  // 若prevPt.x座標<0, 則更新prevPt成目前座標
  13:             prevPt = pt;
  14:         line( markerMask, prevPt, pt, Scalar::all(255), 5, 8, 0 );          // 在markerMask上畫線(從prevPt到pt)  
  15:         line( img, prevPt, pt, Scalar::all(255), 5, 8, 0 );                 // 在img上畫線(從prevPt到pt)
  16:         prevPt = pt;
  17:  
  18:         imshow("markerMask", markerMask);                                   // 顯示markerMask影像
  19:         imshow("image", img);                                               // 顯示img影像
  20:     }
  21: }

主程式main()

   1: int _tmain(int argc, _TCHAR* argv[])
   2: {
   3:     char* filename = "sample.jpg";                     
   4:     Mat img0 = imread(filename, 1), imgGray;
   5:  
   6:     if( img0.empty() )
   7:     {
   8:         cout << "無法開啟影像 " << filename << "\n";
   9:         return 0;
  10:     }
  11:     help();                                       // 操作說明
  12:     namedWindow( "image", 1 );
  13:  
  14:     img0.copyTo(img);                             // 複製一份給img, img0為原始圖(不更動)
  15:     cvtColor(img, markerMask, CV_BGR2GRAY);       // img彩色轉灰階markerMask
  16: #ifdef DEBUG
  17:     imshow("markerMask",markerMask);
  18: #endif
  19:     cvtColor(markerMask, imgGray, CV_GRAY2BGR);   // 灰階轉彩色      
  20: #ifdef DEBUG    
  21:     imshow("Gray2BGR",imgGray);
  22: #endif
  23:     markerMask = Scalar::all(0);
  24:     imshow( "image", img );
  25:     setMouseCallback( "image", onMouse, 0 );      // 設定callback function :onMouse( int event, int x, int y, int flags, void* )
  26:                                                   //                                     事件種類,  x,y 座標, flags種類, void*
  27:     for(;;)
  28:     {
  29:         int c = waitKey(0);
  30:  
  31:         if( (char)c == 27 )                        // Escape鍵
  32:             break;
  33:  
  34:         if( (char)c == 'r' )
  35:         {
  36:             markerMask = Scalar::all(0);           // 初始化為零值
  37:             img0.copyTo(img);
  38:             imshow( "image", img );
  39:         }
  40:  
  41:         if( (char)c == 'w' || (char)c == ' ' )     // 按下w鍵或空白鍵
  42:         {
  43:             int i, j, compCount = 0;
  44:             vector<vector<Point> > contours;
  45:             vector<Vec4i> hierarchy;
  46:             // 對mmarkerMask進行輪廓搜尋
  47:             findContours(markerMask, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
  48:  
  49:             if( contours.empty() )                 
  50:                 continue;
  51:             Mat markers(markerMask.size(), CV_32S);   //初始化markers( 32 bit single ) 
  52:             markers = Scalar::all(0);
  53:             int idx = 0;
  54:             for( ; idx >= 0; idx = hierarchy[idx][0], compCount++ )
  55:                 drawContours(markers, contours, idx, Scalar::all(compCount+1), -1, 8, hierarchy, INT_MAX);
  56:  
  57:             
  58:             if( compCount == 0 )
  59:                 continue;
  60:  
  61:             vector<Vec3b> colorTab;                   // compCount x 3 隨機色彩表
  62:             for( i = 0; i < compCount; i++ )
  63:             {
  64:                 int b = theRNG().uniform(0, 255);
  65:                 int g = theRNG().uniform(0, 255);
  66:                 int r = theRNG().uniform(0, 255);
  67:  
  68:                 colorTab.push_back( Vec3b((uchar)b, (uchar)g, (uchar)r) );// Vec3b(b, g, r)
  69:             }
  70:  
  71:             double t = (double)getTickCount();                // tic
  72:             watershed( img0, markers );                       // 參數1:原始彩色影像, 參數2: markers(黑白影像), 白色為marker
  73:             t = (double)getTickCount() - t;                   // toc  
  74:             printf( "execution time = %gms\n", t*1000./getTickFrequency() );  // 顯示花費時間
  75:  
  76:             Mat wshed(markers.size(), CV_8UC3);               // wshed(彩色影像)
  77:  
  78:             // paint the watershed image
  79:             for( i = 0; i < markers.rows; i++ )
  80:                 for( j = 0; j < markers.cols; j++ )
  81:                 {
  82:                     int index = markers.at<int>(i,j);
  83:                     if( index == -1 )                                // (1) index == -1
  84:                         wshed.at<Vec3b>(i,j) = Vec3b(255,255,255);
  85:                     else if( index <= 0 || index > compCount )       // (2) 超過標記範圍塗黑 index <=0 或 index > compCount
  86:                         wshed.at<Vec3b>(i,j) = Vec3b(0,0,0);
  87:                     else
  88:                         wshed.at<Vec3b>(i,j) = colorTab[index - 1];  // (3) 根據隨機色彩表 第 (index-1) 筆
  89:                 }
  90:  
  91:                 wshed = wshed*0.5 + imgGray*0.5;                     // 原始彩色影像(imgGray) + marker隨機塗色
  92:                 imshow( "watershed transform", wshed );
  93:         }
  94:     }
  95:  
  96:     return 0;
  97: }

根據marker進行隨機塗色,下圖為隨機色彩表

image

===========================================================================================================

debug環境設定檔案

http://www.mediafire.com/download/l2kf42zva55u23q/VS2010OpenCV249X86Debug.props

release環境設定檔案

http://www.mediafire.com/download/3y9uer5ni6e0a0t/VS2010OpenCV249X86Release.props

 

[1]鄭 育 昕, ”分水嶺法的重疊腕骨分割以擷取骨齡特徵”, 國立中央學生物醫學工程研究所碩士論文

[2]Hai Gaoa, Weisi Linb,, Ping Xuea, Wan-Chi Siu, Marker-based image segmentation relying on disjoint set union, Signal Processing: Image Communication 21 (2006) 100–112

[3]Vincent, L. Watersheds in Digital Spaces: An Efficient Algorithm Based on Immersion Simulations, IEEE TRANSACTIONS ON PATTERN ANALYSIS AND MACHINE INTELLIGENCE, VOL. 13, NO. 6, JUNE 1991

[4]J. Serra, L. Vincent, An overview of morphological filtering, Circuits Systems Signal Process. 11 (1) (1992) 47–108

  1. [5]http://cmm.ensmp.fr/~beucher/wtshed.html
全站熱搜
創作者介紹
創作者 me1237guy 的頭像
me1237guy

天天向上

me1237guy 發表在 痞客邦 留言(0) 人氣()