基于OpenCv的分水岭算法分割棉签C++实践

理论

核心流程:
找出每个分割物体的前景:cv::distanceTransform
对图片上每个像素分类:cv::connectedComponents
分水岭算法:cv::watershed

代码

void SplitImage(cv::Mat& image, std::vector<cv::Mat>& result)
{
	//输入
	cv::Mat localMat = image.clone();
	cv::Mat gray, binary, shifted;
	//cv::imshow("image", image);
	//cv::waitKey(0);

	//滤波后的二值化
	cv::cvtColor(localMat, gray, cv::COLOR_BGR2GRAY);
	cv::GaussianBlur(gray, gray, cv::Size(5, 5), 2);   //高斯滤波
	threshold(gray, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
	cv::imshow("binary", binary);
	//cv::waitKey(0);

	//去噪
	cv::Mat dist, sureFg, sureBg, unknown;
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));
	cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel);
	cv::imshow("morphologyEx", binary);

	cv::dilate(binary, sureBg, kernel, cv::Point(-1, -1), 2);//膨胀


	// 距离变换 + 提取前景 
	cv::distanceTransform(binary, dist, cv::DistanceTypes::DIST_L2, 5);
	//normalize(dist, dist, 0, 1, cv::NORM_MINMAX);
	//此处不用百分比的原因是,最好根据分割物体的中心到边缘的像素距离来选定threshhold
	threshold(dist, sureFg,  30, 255, cv::THRESH_BINARY);
	//cv::erode(sureFg, sureFg, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
	sureFg.convertTo(sureFg, CV_8UC1);
	cv::imshow("sureFg  result", sureFg);
	cv::subtract(sureBg, sureFg, unknown);//分割不清的地方
	//cv::imshow("unknown", unknown);


	// markers
	cv::Mat markers = cv::Mat::zeros(localMat.size(), CV_32S);// 如果使用 CV_8UC1 ,watershed 函数会报错
	//connectedComponents 获取联通件,markers中每个像素值0:背景,1,2,3...:代表每个连通件的序号 
	int num_labels = cv::connectedComponents(sureFg, markers, 8, CV_32S);


	// 为所有的标记加1,因为经过connectedComponents背景已经置0,+1后原背景也成为类别(1),unknown(0)则代表要不确定的区域(待算法确认属于1,2,3,4...)
	markers += 1;
	cv::Mat mask = (unknown == 255);
	markers.setTo(0, mask);

	//分水岭算法watershed,masker中的值表示 -1:未知区域,0表示背景区域(前景和背景之间的过渡区域),结果标签图将会被修改,边界区域的标记将变为-1
	cv::watershed(localMat, markers);

	展示markers到底获取了什么东西
	cv::Mat afterWatershed;
	convertScaleAbs(markers, afterWatershed);
	cv::applyColorMap(afterWatershed, afterWatershed, cv::COLORMAP_JET);
	imshow("After Watershed", afterWatershed);

	//背景(1)置0,
	cv::Mat  maskNegativeOne = (markers == -1);
	cv::Mat maskOne = (markers == 1);
	markers.setTo(0, maskNegativeOne | maskOne);

	std::vector<std::vector<cv::Point>> contours;
	for (int i = 2; i < num_labels; i++)
	{
		std::vector<std::vector<cv::Point>> temp;
		cv::Mat  maskGreaterThanOne = (markers == i);
		cv::findContours(maskGreaterThanOne, temp, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
		//imshow("maskGreaterThanOne result", maskGreaterThanOne);
		//cv::waitKey(0);
		contours.insert(contours.end(), temp.begin(), temp.end());

	}



	markers.convertTo(markers, CV_8UC1);
	//imshow("markers result", markers);




	//查找轮廓
	std::vector<std::vector<cv::Point>> contours;
	cv::findContours(markers, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);


	// 画图
	cv::Mat imageContours = cv::Mat::zeros(localMat.size(), CV_8UC1);
	srand(unsigned(time(0)));
	cv::RNG rng(12345);
	std::vector<cv::Scalar> colors;

	for (size_t i = 0; i < contours.size(); ++i) {
		// 计算轮廓的质心
		cv::Moments M = cv::moments(contours[i]);
		int cx = cvRound(M.m10 / M.m00);
		int cy = cvRound(M.m01 / M.m00);

		// 随机选择颜色
		cv::Scalar color;
		color[0] = rng.uniform(0, 256);
		color[1] = rng.uniform(0, 256);
		color[2] = rng.uniform(0, 256);

		// 绘制轮廓
		cv::drawContours(localMat, contours, static_cast<int>(i), color, 2);

		// 绘制标记
		cv::drawMarker(localMat, cv::Point(cx, cy), cv::Scalar(0, 255, 0), cv::MARKER_STAR, 8, 2);
	}

	cv::imshow("Contours", localMat);
	cv::waitKey(0);
	return;

}

结果:
在这里插入图片描述在这里插入图片描述 在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值