OpenCV學習三十四:watershed 分水嶺演算法
阿新 • • 發佈:2018-12-14
1. watershed
void watershed( InputArray image, InputOutputArray markers );
第一個引數 image,必須是一個8bit 3通道彩色影象矩陣序列,第一個引數沒什麼要說的。
關鍵是第二個引數 markers:在執行分水嶺函式watershed之前,必須對第二個引數markers進行處理,它應該包含不同區域的輪廓,每個輪廓有一個自己唯一的編號,輪廓的定位可以通過Opencv中findContours方法實現,這個是執行分水嶺之前的要求。
接下來執行分水嶺會發生什麼呢?演算法會根據markers傳入的輪廓作為種子(也就是所謂的注水點),對影象上其他的畫素點根據分水嶺演算法規則進行判斷,並對每個畫素點的區域歸屬進行劃定
簡單概括一下就是說第二個入參markers必須包含了種子點資訊。Opencv官方例程中使用滑鼠劃線標記,其實就是在定義種子,只不過需要手動操作,而使用findContours可以自動標記種子點。而分水嶺方法完成之後並不會直接生成分割後的影象,還需要進一步的顯示處理,如此看來,只有兩個引數的watershed其實並不簡單。
2. 步驟
- 1. 將白色背景程式設計黑色背景 - 目的是為了後面變的變換做準備
- 2. 使用filter2D與拉普拉斯運算元實現影象對比度的提高 - sharp
- 3. 轉為二值影象通過threshold
- 4. 距離變換
- 5. 對距離變換結果進行歸一化[0-1]之間
- 6. 使用閾值,在此二值化,得到標記
- 7. 腐蝕每個peak erode
- 8. 發現輪廓 findContours
- 9. 繪製輪廓 drawContours
- 10.分水嶺變換 watershed
- 11.對每個分割區域著色輸出結果
我覺得教程的思路有點過於囉嗦~
3. 程式碼及結果
#include<stdio.h> #include<opencv2/opencv.hpp> using namespace cv; using namespace std; int main(int argc, char* argv[]){ char input_win[] = "input image"; char watershed_win[] = "watershed segementation demo"; Mat src = imread("10.jpg", -1); resize(src, src, Size(), 0.25, 0.25, 1); if (src.empty()){ puts("could not load images"); return -1; } namedWindow(input_win, CV_WINDOW_AUTOSIZE); imshow(input_win, src); //1. 將白色背景程式設計黑色背景 - 目的是為了後面變的變換做準備 for (int row=0; row<src.rows; row++){ for (int col=0; col<src.cols; col++){ if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)){ //我這裡和視訊教程圖片不一樣,所以這一步不同 // src.at<Vec3b>(row, col)[0] = 0; // src.at<Vec3b>(row, col)[1] = 0; // src.at<Vec3b>(row, col)[2] = 0; } } } namedWindow("black background", CV_WINDOW_AUTOSIZE); imshow("black background", src); imwrite("black background.jpg", src); //2. 使用filter2D與拉普拉斯運算元實現影象對比度的提高 - sharp Mat kernel1 = (Mat_<float>(3, 3)<<1,1,1, 1,-8, 1, 1,1,1) ; Mat imgLaplance; Mat imgSharpen; filter2D(src, imgLaplance, CV_32F, kernel1, Point(-1,-1), 0, BORDER_DEFAULT); src.convertTo(imgSharpen, CV_32F); Mat imgResult = imgSharpen - imgLaplance; imgResult.convertTo(imgResult, CV_8UC3); imgLaplance.convertTo(imgLaplance, CV_8UC3); imshow("sharpen img", imgResult); imwrite("sharpen img.jpg", imgResult); //3. 轉為二值影象通過threshold Mat imgBinary; cvtColor(imgResult, imgResult, CV_BGR2GRAY); threshold(imgResult, imgBinary, 40, 255, THRESH_BINARY | THRESH_OTSU); Mat temp; imgBinary.copyTo(temp, Mat()); Mat kernel2 = getStructuringElement(MORPH_RECT, Size(2,2), Point(-1, -1)); morphologyEx(temp, temp, CV_MOP_TOPHAT, kernel2, Point(-1, -1), 1); for (int row=0; row<src.rows; row++){ for (int col=0; col<src.cols; col++){ imgBinary.at<uchar>(row, col) = saturate_cast<uchar>(imgBinary.at<uchar>(row, col) - temp.at<uchar>(row, col)); } } imshow("sharpen img", imgResult); imshow("binary img", imgBinary); imwrite("sharpen img2.jpg", imgResult); imwrite("binary.jpg", imgBinary); //4. 距離變換 Mat imgDist; distanceTransform(imgBinary, imgDist, CV_DIST_L1, 3); //5. 對距離變換結果進行歸一化[0-1]之間 normalize(imgDist, imgDist, 0, 1, NORM_MINMAX); imshow("distance result normalize", imgDist); imwrite("distance result normalize.jpg", imgDist); //6. 使用閾值,在此二值化,得到標記 threshold(imgDist, imgDist, 0.5, 1, CV_THRESH_BINARY); imshow("distance result threshold", imgDist); imwrite("distance result threshold.jpg", imgDist); //7. 腐蝕每個peak erode Mat kernel3 = Mat::zeros(15, 15, CV_8UC1); erode(imgDist, imgDist, kernel3, Point(-1,-1), 2); imshow("distance result erode", imgDist); imwrite("distance result erode.jpg", imgDist); //8. 發現輪廓 findContours Mat imgDist8U; imgDist.convertTo(imgDist8U, CV_8U); vector<vector<Point>> contour; findContours(imgDist8U, contour, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //9. 繪製輪廓 drawContours Mat maskers = Mat::zeros(imgDist8U.size(), CV_32SC1); for (size_t i=0; i<contour.size(); i++){ drawContours(maskers, contour, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1)); } imshow("maskers", maskers); imwrite("maskers.jpg", maskers); //10.分水嶺變換 watershed watershed(src, maskers); Mat mark = Mat::zeros(maskers.size(), CV_8UC1); maskers.convertTo(mark, CV_8UC1); bitwise_not(mark, mark, Mat()); imshow("watershed", mark); imwrite("watershed.jpg", mark); //11.對每個分割區域著色輸出結果 vector<Vec3b> colors; for (size_t i=0; i<contour.size(); i++) { int r = theRNG().uniform(0, 255); int g = theRNG().uniform(0, 255); int b = theRNG().uniform(0, 255); colors.push_back(Vec3b((uchar)r, (uchar)g, (uchar)b)); } Mat dst = Mat::zeros(maskers.size(), CV_8UC3); for (int row=0; row<src.rows; row++){ for (int col=0; col<src.cols; col++){ int index = maskers.at<int>(row, col); if (index>0 && index <= static_cast<int>(contour.size())){ dst.at<Vec3b>(row,col) = colors[index-1]; } else { dst.at<Vec3b>(row,col) = Vec3b(0,0,0); } } } imshow("dst", dst); imwrite("dst.jpg", dst); waitKey(); return 0; }