watershed—基于标记的分水岭算法

  • 算法介绍

原始的分水岭算法对于存在噪声以及梯度不规则的图像极易造成过度分割(存在大量分割区域),解决该问题的一种方案是通过融入预处理步骤来限制允许存在的区域数目,因此基于标记的分水岭算法应运而生。标记是属于一幅图像的一个连通分量,与感兴趣物体相联系的标记称为内部标记,与背景相关联的标记称为外部标记。

  • Opencv函数介绍

void watershed( 
                 InputArray image,         //Input 8-bit 3-channel image
                 InputOutputArray markers  //Input/output 32-bit single-channel 
              );

第一个参数image是CV_8UC3格式的原始图像;

第二个参数markers是CV_32S格式的标记图像,即该图像必须包含注水点(即种子点)。注水点的获取有以下三种方式:

        第一种:使用findContours函数自动标记。

        第二种:connectedComponents函数标记连通域。

        第三种:用鼠标划线标记(opencv官方例程即使用该方法),该方法需要手动操作。

算法执行后,区域间的边界像素会设为-1,所有0值像素会被设置为给定标记中的一个。

  • Opencv实例

  • 效果图

                                                                                                   原图

                                                                                                  结果图

  • 代码

//////////////////////////////////
//opencv4.1.0
//////////////////////////////////

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void findContours_watershed(Mat& dist_binary, Mat& src);        //第一种方式使用findContours函数标记
void connectedComponents_watershed(Mat& dist_binary, Mat& src); //第二种方式:使用connectedComponents函数标记
void DisplayRegion(int num, Mat& src, Mat& markers, const cv::String& winname);//使用不同的颜色显示分割后的各个区域,便于观察结果

int main() {
	Mat src, gray, binary, open_img, dilate_img, dist_img, dist_binary;
	src = imread("12.png");
	cvtColor(src, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

	//形态学操作
	Mat k = getStructuringElement(MORPH_RECT, Size(3, 3));
	morphologyEx(binary, open_img, MORPH_OPEN, k, Point(-1,-1), 2);
	dilate(open_img, dilate_img, k, Point(-1, -1), 3);

	//距离变换
	distanceTransform(open_img, dist_img, DIST_L2, 5);

	//归一化方法一
	//normalize(dist_img, dist_img, 0, 1,NORM_MINMAX);
	
	//归一化方法二
	double minVal,maxVal;
	minMaxLoc(dist_img, &minVal, &maxVal,0,0);
	Mat dst;
	dist_img.convertTo(dst, CV_8UC1, 1 / (maxVal-minVal)*255);

	//阈值分割提取每个区域的标记图像
	threshold(dst, dist_binary, 180, 255, 0);	

	//方式一
	findContours_watershed(dist_binary, src);
	//方式二
	connectedComponents_watershed(dist_binary, src);

	imshow("原图", src);
	waitKey(0);
	return 0;
}

void connectedComponents_watershed(Mat& dist_binary, Mat& src) {

	//定义一个标记图像
	Mat markers(src.size(), CV_32S);
	
	//计算连通域
	int num = connectedComponents(dist_binary, markers);

	//基于标记的分水岭分割
	watershed(src, markers);

	//使用不同的颜色显示各个区域
	DisplayRegion(num, src, markers, "result2");
}

void findContours_watershed(Mat& dist_binary, Mat& src) {

	//定义一个标记图像,并初始化为0
	Mat markers(src.size(), CV_32S);
	markers = Scalar::all(0);

	//寻找轮廓
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(dist_binary, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);

	//绘制轮廓
	for (int index = 0; index < contours.size(); index++)
		drawContours(markers, contours, index, Scalar::all(index), -1, 8, hierarchy, INT_MAX);

	//基于标记的分水岭分割
	watershed(src, markers);

	//使用不同的颜色显示各个区域
	int num = contours.size();
	DisplayRegion(num, src, markers, "result1");
}

void DisplayRegion(int num,Mat& src, Mat& markers,const cv::String& winname) {
	RNG rng(123456);
	vector<Vec3b> color(num);
	//color[0] = Vec3b(0, 0, 0);                                                           //设置背景颜色
	for (size_t i = 1; i < num; i++) {
		color[i] = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)); //设置目标颜色
	}

	Mat markersColor = Mat::zeros(src.size(), src.type());
	int w = src.cols;
	int h = src.rows;
	for (size_t row = 0; row < h; row++) {
		for (size_t col = 0; col < w; col++) {
			int label = markers.at<int>(row, col);
			if (label == -1) continue;
			markersColor.at<Vec3b>(row, col) = color[label];
		}
	}
	imshow(winname, markersColor);
}

 

 

 

猜你喜欢

转载自blog.csdn.net/jgj123321/article/details/94389566