分水嶺演算法可以將映像中的邊緣轉化成“山脈”,將均勻地區轉化為“山穀”。首先,計算灰階映像的梯度,然後開始從使用者指定點(或演算法得到的點) 開始持續“灌注”盆地直到這些地區連在一起。基於這樣產生的標記就可以吧地區合并在一起,合并後的地區又通過聚集的方式進行分割,好像映像被“填充”起來一樣。通過這種方式,與指示點相連的盆地就為指示點“所擁有”。最終我們把映像分割成相應的標記地區。
例1:
#include"cv.h"#include"highgui.h"using namespace cv;Mat markerMask, img;Point prePt(-1,-1);void onMouse(int event ,int x,int y,int flags,void* param){if (event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON)) prePt = Point(-1,-1);if (event == CV_EVENT_LBUTTONDOWN)prePt = Point(x,y);if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON)){Point pt(x,y);if (prePt.x < 0) prePt = pt;line(markerMask,prePt,pt,Scalar(255,255,255),5);line(img, prePt, pt, Scalar(255, 255, 255), 5);prePt = pt;imshow("原圖", img);}}int main(){Mat img0 = imread("D:\\tongliya.jpg",1);img0.copyTo(img);namedWindow("原圖",1);imshow("原圖",img);Mat imgGray;//最後顯示watershed效果圖用cvtColor(img0,markerMask,CV_BGR2GRAY);cvtColor(markerMask,imgGray,CV_GRAY2BGR);markerMask = Scalar::all(0);setMouseCallback("原圖",onMouse,NULL);while (1){int c = waitKey(10);if ((char)c == 'q' || (char)c == 27) return 0;if ((char)c == 'r'){img0.copyTo(img);markerMask = Scalar::all(0);imshow("原圖",img);}if (char(c) == 'p'){vector<vector<Point>> contours;vector<Vec4i> hierarchy;int count=0;findContours(markerMask,contours,hierarchy,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE);<span style="color:#33CC00;">//輸入映像image必須為一個2值單通道映像</span>if (contours.empty()) continue;Mat markers(markerMask.size(),CV_32S);markers = Scalar::all(0);for (int idx = 0; idx >= 0; idx = hierarchy[idx][0], count++)<span style="color:#33CC00;">//hierarchy[idx][0]表示後一個輪廓</span>drawContours(markers,contours,idx,Scalar::all(count+1),-1,8,hierarchy,INT_MAX);if (count == 0) continue;watershed(img0,markers);vector<Vec3b> colorTab;<span style="color:#33CC00;">//隨機設定顏色</span> for (int i = 0; i < count; i++){int b = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int r = theRNG().uniform(0, 255);colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));}Mat wshed(markers.size(),CV_8UC3);for (int i = 0; i < markers.rows;i++)for (int j = 0; j < markers.cols; j++){int tmp = markers.at<int>(i,j);if (tmp == -1) wshed.at<Vec3b>(i, j) = Vec3b(255,255,255);//邊界值為-1else if (tmp <= 0 || tmp>count) wshed.at<Vec3b>(i, j) = Vec3b(0,0,0);else wshed.at<Vec3b>(i, j) = colorTab[tmp-1];}imshow("分水嶺變換1", wshed);wshed = 0.5*wshed + 0.5*imgGray;imshow("分水嶺變換",wshed);}}return 0;}
個人理解:
1.watershed()函數接受兩個參數,void watershed(InputArray image, InputOutputArraymarkers)
輸入映像image為 8位3通道,markers為帶有標記映像(or map),結果儲存在markers中。
2.markers根據標記把像素 設定為1,2,3.。。。。
3.findContours contours參數為檢測的輪廓數組,count記錄輪廓組數,markers的結果也為1,2,....count,所以顏色也隨機設為count種。
4.根據markers值,不同地區繪製不同顏色,邊界值為-1,繪製為白色。
5.結果截圖
例2:
#include "cv.h"#include "highgui.h"using namespace cv;int main(){ Mat img = imread("D:\\tongliya.jpg"); imshow("original image",img); Mat imgGray; cvtColor(img,imgGray,CV_BGR2GRAY); imshow("gray image", imgGray); Mat binary; //adaptiveThreshold(imgGray,binary,255,ADAPTIVE_THRESH_MEAN_C,CV_THRESH_BINARY,5,0); threshold(imgGray,binary,100,255,CV_THRESH_BINARY_INV); imshow("binary image", binary); Mat fg; erode(binary,fg,Mat(),Point(-1,-1),1);//255標註前景,腐蝕使高亮地區隔離且縮小,可以擷取僅僅屬於重要物體的像素 imshow("foreground image",fg); Mat bg; dilate(binary,bg,Mat(),Point(-1,-1),1);//背景用128標註,膨脹使高亮地區得到擴充和聯通 threshold(bg,bg,1,128,THRESH_BINARY_INV); imshow("background image", bg); Mat markers(binary.size(),CV_8U,Scalar::all(0)); markers = fg + bg;//分水嶺演算法輸入參數 imshow("markers image", markers); markers.convertTo(markers,CV_32S); watershed(img,markers);//更新標記映像 Mat tmp = markers.clone(); markers.convertTo(tmp,CV_8U);//返回標記映像,分水嶺位於值0 imshow("Segmentation",tmp); markers.convertTo(tmp,CV_8U,255,255); imshow("Watersheds邊界", tmp); waitKey(); return 0;}