1. 程式人生 > >OpenCV學習三十四:watershed 分水嶺演算法

OpenCV學習三十四:watershed 分水嶺演算法

1. watershed 

void watershed( InputArray image, InputOutputArray markers );

第一個引數 image,必須是一個8bit 3通道彩色影象矩陣序列,第一個引數沒什麼要說的。

關鍵是第二個引數 markers:在執行分水嶺函式watershed之前,必須對第二個引數markers進行處理,它應該包含不同區域的輪廓,每個輪廓有一個自己唯一的編號,輪廓的定位可以通過Opencv中findContours方法實現,這個是執行分水嶺之前的要求。

接下來執行分水嶺會發生什麼呢?演算法會根據markers傳入的輪廓作為種子(也就是所謂的注水點),對影象上其他的畫素點根據分水嶺演算法規則進行判斷,並對每個畫素點的區域歸屬進行劃定

,直到處理完影象上所有畫素點。而區域與區域之間的分界處的值被置為“-1”,以做區分。

簡單概括一下就是說第二個入參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;

}


原圖
black background.jpg
sharpen img.jpg
sharpen img2.jpg
binary.jpg
distance result normalize.jpg 備註:為了突出效果,這裡放大了1000倍
distance result threshold.jpg 備註:為了突出效果,這裡放大了1000倍
distance result erode.jpg 備註:為了突出效果,這裡放大了1000倍
maskers.jpg 備註:為了突出效果,這裡放大了1000倍
watershed.jpg 備註:這個圖片並不是全白,只是區分度比較小
dst.jpg